大话数据结构笔记_线性表

线性表的定义 :

  简而言之 : 0 个 或 多个元素(类型相同)的有限序列( 有顺序 ) , 第一个元素无前驱 , 最后一个元素无后继 , 其他元素 与有唯一的前驱 和 唯一的后继
  数学语言定义 : 若将线性表记为 ( a1 , a2 , ..... , ai - 1 , ai , ai+1 , ... , an) , 则表中 ai-1 领先于 ai , ai 领先于 ai+1
           称 ai-1 是 ai 的前驱 , ai+1 是 ai 的后继元素 . 当 i = 1 , 2 ... , n - 1 时候 , ai 有且仅有 一个 直接后
         继 , 当 i = 2 ,......., n 时候 , ai 有且仅有一个直接的前驱。
  线性表的长度 : 线性表的元素的个数 n( n>=0 ) , 当 n = 0 时候 我们称之为空表


 

线性表的ADT :

  注意 :  这只是 抽象 层面 来说 , 也就是说实现方式尽管 有 顺序表连表等实现方式 , ADT仅仅抽取他们的相同
      处 且 基本具有的操作 , 具体实现时候 , 可以添加Data 数据 , 可以 扩充 操作 或者添加 辅助方法( 抽取
      相同代码 )。 对于实际问题中更复杂的操作 , 可以使用ADT中的基本操作来组合完成。

       定义如下:
    

ADT 线形表 ( List ) 
Data : 下一层数据类型 + 描述
线性表的数据对象的序列 {a1,a2,a3,......, an} , 每个元素均为DataType , 其中 , 除了元素 a1 外 , 每一个    
元素都只有唯一的前驱元素 , 除了an外 , 每个元素都只有唯一的后继元素 。 元素之间的关系是一对一的关 
系。
Operation: 操作 + 描述
(规定 : 提前说明 , 我们使用的编号 均从 0 开始 , 操作返回 0 代表成功 , -1 代表失败)

InitList(*L) : 初始化操作 , 给L 指针指向的List类型开辟能容纳序列的空间
ListEmpty(L) : 若线性表为空 , 返回true else false
ClearList(*L) : 将线性能表清空 
GetElem(L , i ,*e) : 将线性表L中的第i个元素通过传出参数e取出 , 返回值 0 代表的 成功 , -1 代表错误
LocateElem(L,e ) : 将线形表L 中查找 第一个匹配到的e , 成功返回 编号位置 , 失败返回-1
ListInsert(*L , i , e) : 在线性表第 i 个元素 的前面 插入一个元素 , 成功返回 0 失败返回 -1 
ListDelete(*L , i ,*e ): 删除线性表中的第i个位置的元素 , 并用e返回其值
ListLength(L) : 返回线性表中的元素的个数 : 
endADT

 


线形表的顺序存储结构:

  ADT -> 物理层面 -> 语言实现( C 语言实现):

  指的是使用一段连续的存储单元依次存储线性表的数据元素。

   

sqList 结构体 定义在头文件当中:

#ifndef __SEQ_LIST_H
#define __SEQ_LIST_H

#define MAXSIZE 20     /*假设顺序表的容量为20*/
#define TRUE     0     /*我们只使用TRUE 和 FALSE 两个量表示成功和失败就行了 , 但是注意TRUE是0*/
#define FALSE   -1   
 
typedef int ElemType;  /*假设ElemType 类型为int*/
typedef int BOOL;      /*BOOL 表示两个量 : TRUE | FALSE */
 
/*结构体的声明放在*/
typedef struct
{
     ElemType data[MAXSIZE];
     int length;        /*扩充了ATD的DATA,其表示指向序列最后一个元素的下一个位置*/
}sqList;
  
/*函数的声明放在这个位置 */
extern void InitList(sqList * L);
extern BOOL ListEmpty(sqList L);
extern void ClearList(sqList * L);
extern int LocateElem(sqList L , ElemType e);
extern int  ListLength(sqList L);
extern BOOL ListInsert(sqList * L , int i , ElemType e);
extern BOOL ListDelete(sqList * L , int i , ElemType *e);
extern int GetElem(sqList L , int i  , ElemType * e); 

#endif
sqList 结构体定义 以及 操作接口声明

sqList 操作定义在 .c 文件当中 :

/*************************************************
Function: InitList 
Description: 初始化顺序表的长度为 0 
Output: *L : 线性表发生变动
Return: 空 TRUE | 非空FALSE (TRUE :0  FALSE:-1)
*************************************************/
void InitList(sqList * L)
{
     L->length = 0;
}

/*************************************************
Function: LisEempty
Description: 返回顺序表是否为空 , O(1)
              
              判断 L->length 是否为0即可
 
Input:       L : 线性表结构 
Return: 空 TRUE | 非空FALSE (TRUE :0  FALSE:-1)
*************************************************/
BOOL ListEmpty(sqList L)
{   
    return L.length ? FALSE : TRUE;
}

/*************************************************
Function: ClearList 
Description:  将顺序表清空 O(1) 
              
              将L->length 置为0即可
Output: *L : 线性能表发生变动
Return: void
*************************************************/
void ClearList(sqList * L)
{
    L->length = 0;
}

/*************************************************
Function: LocateElem
Description: 获取第一次出现指定元素的位置 O(n)
            
             开始遍历判断是否相等 
             返回下下标位置
Input:  
        L : 顺序表结构
        e : 指定的元素 
Return: int 具体位置 , 没找到返回-1 
*************************************************/
int LocateElem(sqList L , ElemType e)
{
    int i = 0;
    for(;i < L.length ; ++i)
    {
        if(L.data[i] == e)
            return i;
    }
    return -1;
}

/*************************************************
Function: ListLength 
Description:  获取顺序表的长度 O(1)
                
              直接返回 L.length即可
Input:  L 表结构
Return: int 顺序表的长度
*************************************************/
int ListLength(sqList L)
{
    return L.length;
}

/*************************************************
Function: ListInsert
Description:  
            在顺序表的指定位置插入元素 : 时间复杂度O(n) : 时间浪费在移动上面 : 

            插入位置不合理  , return FALSE
            如果 length + 1 > MAXSIZE  return FALSE
            需要将 i 到 length-1的元素向后移动一个位置
            将元素插入指定位置后 , 表的长度 + 1
Input: 
            i : i位置元素的前面进行插入
            e : 被插入的元素
Output:     
            *L: 传出参数,表示线性表会被修改
Return: 成功 TRUE | 失败 FALSE
*************************************************/
BOOL ListInsert(sqList * L , int i  , ElemType  e)
{
    int j = 0;
    if( 0>i || i > L->length || L->length + 1 > MAXSIZE)
        return FALSE;

    for(j = i+1 ; j <=L->length ; ++j)
        L->data[j] = L->data[j-1];

    L->data[i] = e;
    L->length+=1;

    return TRUE;
}

/*************************************************
Function: ListDelete
Description: 删除指定位置的元素 : O(n) 时间也是浪费在移动数据上面 :

             如果删除的位置不合适: return FALSE
             取出删除的元素
             将 i 位置之后的元素都向前移动一个元素:
             表的长度需要减去 1 
Input:  
        i : 删除元素的位置 :  0<=i<=L->length-1
Output:
        L : 表会发生变动
        e : 输出被删除的元素
Return: 删除成功 TRUE | 失败 FALSE
*************************************************/
BOOL ListDelete(sqList * L , int i , ElemType * e)
{
    int j = 0;
    if(0 == L->length || 0 > i || i >= L->length)
            return FALSE;

    *e = L->data[i];

    for(j = i+1 ; j <=L->length ; ++j)
    {
        L->data[j-1] = L->data[j];
    }

    L->length-=1;
    return TRUE;
}


/*************************************************
Function: GetElem
Description: 获取顺序表中指定位置的元素 , 时间复杂度O(n) , 时间浪费在遍历上面:
            
             i位置不合理 return FALSE
             取得i位置的元素
        
Input: 
      L  : 顺序表结构
      i  : 获取元素的位置 ,  0<= i <= L.length-1 
Output: 
      *e : 输出类型参数 , 获取 i 指向的元素  
Return: 成功TRUE | 失败 FALSE
*************************************************/
BOOL GetElem(sqList L , int i  , ElemType * e)
{
    /* i 是从0 开始计算的*/
    if(0 == L.length || i < 0 || i > L.length-1)
        return FALSE;
    *e = L.data[i];
    return TRUE;
}
sqList 操作的实现

宗上看出:  

  主要操作:
    插入 , 删除 O(n)
    搜寻 , 修改 O(1)

线性能表存储结构的优点和缺点:

  • 优点:
    • 无需为表中元素之间的逻辑关系增加额外空间(比如连表 , 需要增加一个指针域)
    • 可以快速的存取表中 任意元素的位置
  • 缺点:
    • 插入 和 删除操作需要移动大量元素
    • 当线性表的长度变化较大时 , 难以确定存储空间的容量
    • 造成存储空间的碎片 [ 可能开辟的空间大小 > 顺序表的大小造成 空间的浪费 ]

线性表的链式存储结构:

    为了解决链表的使用顺序结构存储的实现方式导致的插入 和 删除元素时候的效率问题 , 使 

  用链式来解决 , 但是链式存储结构的随机访问效率降低(不能根据第一个元素的位置直接得出指
  定位置元素的地址 , 只能通过遍历来实现)

 

  ADT-> 链式存储结构->语言层次实现( C语言 ):

  带上头结点 , 就能让在链表的头位置插入元素/删除元素变得统一

 

Node 结构体的定义: 放置在LinkList.h 的头文件当中

#ifndef _LINK_LIST_H
#define _LINK_LIST_H
#define TRUE 0
#define FALSE -1

typedef int ElemType;
typedef int BOOL;

typedef struct Node
{
    ElemType data;
    struct Node * next;
}Node;

typedef struct Node * LinkList;

/*基于上述链表结构体的操作的声明:*/
/*ADT 中的实现*/;
extern BOOL ListInit(LinkList *L);
extern BOOL ListEmpty(LinkList L);
extern void ClearList(LinkList *L);
extern BOOL GetElem(LinkList *L, int i, ElemType * e);
extern int LocateElem(LinkList *L,ElemType e);
extern BOOL ListInsert(LinkList *L, int i, ElemType e);
extern BOOL ListDelete(LinkList *L, int i, ElemType *e);
extern int ListLength(LinkList L);
/*ADT上的操作的扩展*/

#endif
LinkList.h

 

单链表的操作的实现 , LinkList.c 文件:

/**
 * File name: LinkList.c
 * Author:MusaGeek    Version: 1.0       Date: 2018-11-21
 * Description:  单链表的操作的实现的.c文件
 * Function List:  // 主要函数列表,每条记录应包括函数名及功能简要说明

    BOOL ListInit(LinkList *L);                     初始化链表
    BOOL ListEmpty(LinkList L);                     判断链表是否为空
    void ClearList(LinkList *L);                     清空链表
    BOOL GetElem(LinkList *L, int i, ElemType * e); 获取链表指定位置的元素
    int LocateElem(LinkList *L,ElemType e);         定位元素的位置
    BOOL ListInsert(LinkList *L, int i, ElemType e);插入元素
    BOOL ListDelete(LinkList *L, int i, ElemType *e);删除元素
    int ListLength(LinkList L);                         获取链表的长度
*/

#include <stdio.h>
#include <stdlib.h>
#include "LinkList.h"

/*************************************************
Function: ListInit
Description: 初始化单链表中的数据 
             1.为指向链表的头指针分配头结点空间
             2.将头结点的指针域设置为NULL
Input: 
      L  : 指向链表头结点的指针
Return: 成功TRUE | 失败 FALSE
*************************************************/
BOOL ListInit(LinkList *L)
{
    Node *head = (Node *)malloc(sizeof(Node));
    if(!head)
        return FALSE;
    *L = head;
    head->next = NULL;
    return TRUE;
}


/*************************************************
Function: ListInit
Description: 初始化单链表中的数据 
             1.为指向链表的头指针分配头结点空间
             2.将头结点的指针域设置为NULL
Input: 
      L  : 指向链表头结点的指针
Return: 成功TRUE | 失败 FALSE
*************************************************/
BOOL ListEmpty(LinkList L)
{
    return ( (L->next) ? FALSE : TRUE);
}


/*************************************************
Function:  ClearList
Description: 因为 LinkList 中的结点均是malloc而来的 , 因此当整个链表不使用了时候 需要手动释放掉,避免消耗内存
             但是保留头结点
             1.创建 指针 p = (*L)->next , 指针 q;
             2.循环 :当 p 不为 NULL 时候 ; q = p->next;释放 p; p = q;
Input: 
      L  : 指向链表头结点的指针  
Return: void
*************************************************/
void ClearList(LinkList *L)

{
    Node *p = (*L) = (*L)->next, *q = NULL;
    while(p)
    {
        q = p->next;
        free(p);
        p = q;
    }
    (*L)->next = NULL; /*头结点不释放 ,指针域置为NULL*/
}


/*************************************************
Function: GetElem
Description: 获取顺序表中指定位置的元素 , 时间复杂度O(n) , 时间浪费在遍历上面:
            1.声明一个结点p指向链表的第一个结点 , 初始化 j 从1开始
            2.当 j < i 时 , 就遍历链表 , 让指针p不断的向后移动 , j+=1
            3.若最终 NULL == p 则 返回FALSE
            4.若 NULL != p 则 将data域数据传出 返回TRUE , 
Input: 
      L  : 指向链表头结点的指针
      i  : 获取元素的位置  
Output: 
      *e : 输出类型参数 , 获取 i 指向的元素  
Return: 成功TRUE | 失败 FALSE
*************************************************/
BOOL GetElem(LinkList *L, int i, ElemType * e)
{
    Node * p = (*L)->next;
    int j = 1;
    while(p && j < i)
    {
        p = p->next;
        j++;
    }
    *e = p->next->data;
    return (p ? TRUE : FALSE);  
}


/*************************************************
Function: LocateElem
Description: 定位e在链表中的位置, 时间复杂度O(n), 时间浪费在遍历上面:
             遍历找到第一个匹配的元素即可   
Input: 
      L: 指向链表头结点的指针的指针
      e: 需要定位的元素值
Return: int 元素在链表中的位置,没有匹配到为-1
*************************************************/
int LocateElem(LinkList *L,ElemType e)
{
    Node *p = (*L)->next;
    int j =  1;
    while(p && p->data != e)
    {
        p = p->next;
        j++;
    }
    return p ? j : -1;
}


/*************************************************
Function:  ListInsert
Description: 在指定 i 的位置插入元素 , O(n) , 浪费的时间在遍历上面 , 但是没有移动元素造成的复制开销
            
              1.声明一个结点 p 指向链表的头结结点 , 初始化 j 从 1 开始
              2.当 j < i 遍历链表 , 让p指针不断后移 , j+=1
              3.若 遍历结束 , NULL == p , 返回FALSE
              4.否则 当 j == i 时候 , p 已经指向的了 第i个元素之前的元素
            5.创建结点s(malloc) , s->next = p->next; p->next = s;
            6.返回TRUE 
Input: 
      L  : 指向链表头结点的指针
      i  : 获取元素的位置  
      e  : 插入的元素值  
Output: 
     
Return: 成功TRUE | 失败 FALSE
*************************************************/
BOOL ListInsert(LinkList *L, int i, ElemType e)
{
    Node *p = *L , *s = NULL;
    int j = 1;
    while(p && j<i)                           //如果 i 有效的话 ,那么p最终会指向第i个结点前面的那个结点
    {
        p = p->next;
        j++;
    }
    
    if(p)                                
    {
        s = (struct Node *)malloc(sizeof(Node));    //给新插入的结点分配空间
        s->data = e;
        s->next = p->next;                                   //下面两步骤不能反了
        p->next = s;
        return TRUE;
    }
    return FALSE;
}


/*************************************************
Function:  ListDelete
Description: 删除指定的第 i 个位置的元素 , O(n) 但是没有移动元素复制元素的开销
            
            1.定义指针p指向第一个结点 , 定义 j = 1;
            2.在 j < i 且 p !=NULL 的情况下 让指针p向后移动 , j++
            3.当 j >= i 时 若 NULL = p 那么 返回FALSE
            4.否则 临时指针变量 t 保存 p->next;p->next = p->next->next; 再释放t
            5.返回TRUE       
Input: 
      L  : 链表头指针
      i  : 获取元素的位置  
Output: 
      *e : 输出类型参数 , 获取 i 指向的元素  
Return: 成功TRUE | 失败 FALSE
*************************************************/
BOOL ListDelete(LinkList *L, int i, ElemType *e)
{
    Node *p = *L, *t = NULL;
    int j = 1;
    while(p && j < i)     //如果 i 有效的话 ,那么p最终会指向第i个结点前面的那个结点
    {
        p = p->next;
        j++;
    }
    if(p)
    {
        t = p->next;
        p->next = t->next;  //将要删除结点剔除链表
        *e = t->data;
        free(t);
        return TRUE;
    }
    return FALSE;
}


/*************************************************
Function:  ListLength
Description: 获取链表的长度
             直接遍历链表获得
Input: 
      L  : 链表头指针
Return: int 链表的长度
*************************************************/
int ListLength(LinkList L)
{
    Node *p = L->next;
    int len = 0;
    while(p)
    {
        len++;
        p = p->next;
    }
    return len;
}
LinkList.c

 

 

    单链表虽然在 插入 , 删除元素上面的操作也是O(n) , 但是只是时间花费在了遍历的开销上面了 , 而不是元素移动上 , 且如果元素的类型越复杂 , 移动上面造成复制的开销将更大 , 因此单链表 比 顺序表更适合频繁的元素插入的和删除。

  单链表的创建又分为 头插法 和 尾插法 两种创建方式 

 

  顺序表 和 链表的对比 :

    • 频繁查找 , 很少进行删除 和 插入时 适用顺序表
    • 线性表的元素变化较大 , 频繁的进行插入和删除 ,使用链表
    • 线性表不知道元素的个数的多少的时候 , 就使用链表

     


 静态链表:

  对于早期没有指针的编程语言来说, 实现单链表可以使用静态链表方式来实现线性链表;

       使用定长的数组来模仿一个链表 , 且数组中的每个元素 包含 一个 data 和 一个指向其他元素的数组下标的cur , 这样就能实现在一个连续存储空间上面的链表了。

  细节注意:

     将整个数组分为两个部分 : 备用链表 和 已经使用的链表 。 第一个元素的cur指向的是备用 链表的开始位置。最后一个元素的cur指向的是 已经使用的链表的第一个元素下标 [为 空时候指向的是 0 ]

     

  静态链表的优缺点:

    • 优点:
      • 在插入 和 删除操作的时候只需要修改游标 , 不需要移动元素。
    • 缺点:
      • 表长度不能确定。
      • 虽然物理内存空间连续 , 但是失去了像顺序表那样的随机访问的能力。

循环链表

  将单链表的中的尾结点的指针域指向头结点即可 , 头尾相接的链表的即为循环链表。

  解决问题 :
    从其中某个结点出发依然能够遍历整个链表。

    改进方式 :  

      将指向链表的指针指向循环的链表的尾巴元素 , 这样 不光 访问最后一个元素可以由O(n) 优化到O(1) , 并且对于两个链表的合并 也可以省去遍历到最后一个元素的操作 ,由O(n) 优化到O(1)

 


 

双向链表:

    一个的Node中包含两个指针域 , 一个指向前驱元素(prior) , 一个指向后继元素即可(next)。初始化时候的头结点的 prior 指向自己 , next 也指向自己。
    注意 : 删除元素 和 插入元素时候 涉及元素的 指针值修改的顺序需要特别的注意。

  双向链表 的 插入 和 删除 :


总结:

  • 线性表 : 0个或多个具有相同的类型的数据元素构成有限序列
  • 顺序结构 :
    • 顺序表 : 适合频繁随机访问 , 不适合频繁插入和删除
    • 静态链表 : 使用顺序结构模仿 链表 : 失去了随机访问能力 , 且空间不能自动增长 , 但是改进了顺序表的移动开销
  • 链式结构 :
    • 单链表 : 适合 频繁 插入 和 删除 , 不适合 频繁的随机访问
    • 循环链表 : 能从任意结点开始遍历整个链表 , 尾巴针的方式 改进了访问直接访问最后一个元素的时间复杂度 , 同时也改进了访问两个链表合并的时间复杂度要特别的注意。
    • 双向链表 : 多增加了一个指向 前驱元素的指针域 , 插入/删除操作的顺序需要注意

 

posted @ 2018-11-07 20:32  MusaGeek  阅读(284)  评论(0编辑  收藏  举报