玩转《数据结构》线性表的链式表示

107 阅读6分钟

1.单链表的定义

1)定义

  • 线性表的链式存储又称单链表,它是指通过一组任意的存储单元来存储线性表中的数据元素。为了建立数据元素之间的线性关系,对每个链表节点,除存放元素自身的信息(data数据域)外,还需要存放一个指向其后继的指针(next指针域)

2)特性

  • 单链表可以解决顺序表需要大量连续存储单元的缺点,但单链表的元素离散地分布在存储空间中,所以单链表是非随机存取的存储结构

3)结点类型

typedef struct LNode{
       ElemType data;
       struct LNode *next;
}LNode,*LinkList;

4)带头结点

  • 单链表第一个结点是数据域不设任何信息

5)不带头结点

  • 单链表第一个结点是就是数据结点

6)带头结点优点

  • 1.由于第一个数据结点的位置被存放在头结点的指针域中,因此在链表的第一个位置上的操作和在表的其他位置的操作一致 2.无论链表是否为空,其头指针都是指向头结点的非空指针,因此空表和非空表的处理也就得到了统一

2.单链表上基本操作的实现

1)头插法建立单链表

  • 描述

    • 该方法从一个空表开始,生成新结点,并将读取到的数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头,即头结点之后
  • 代码

    LinkList List_HeadInsert(LinkList &L){
                   LNode *s; int x;
                   L=(LinkList)malloc(sizeof(LNode));
                   L->next=NULL;
                   scanf("%d",&x);
                   while(x!=9999){
                         s=(LNode*)malloc(sizeof(LNode));
                         s->data=x;
                         s->next=L->next;
                         L->next=s;
                         scanf("%d",&x);
                    }
                    return L;
              }
  • 时间复杂度

    • O(n)

2)尾插法建立单链表

  • 描述

    • 该方法将新结点插入到当前链表的表尾,为此必须增加一个尾指针r,使其始终指向当前链表的尾结点
  • 特性

    • 生成的链表结点次序和输入数据的顺序一致
  • 代码

      LinkList List_TailInsert(LinkList &L){
                   int x;
                   L=(LinkList)malloc(sizeof(LNode));
                   LNode *s;,*r=L;
                   scanf("%d",&x);
                   while(x!=9999){
                         s=(LNode*)malloc(sizeof(LNode));
                         s->data=x;
                         r->next=s;
                         r=s;
                         scanf("%d",&x);
                    }
                    r->next=NULL;
                    return L;
              }
  • 时间复杂度

    • O(n)

3)按序号查找结点值

  • 描述

    • 从第一个结点出发,顺指针next域逐个往下搜索,直到找到第i个结点为止,否则返回最后一个结点指针域NULL
  • 代码

	LNode *GetElem(LinkList L,int i){
          int j=1;
          if(i==0)
               return L;
          if(i<1)
             return NULL;
          while(p&&j<i){
             p=p->next;
             j++;
         }
         return p;
 }
  • 时间复杂度

    • O(n)

4)按值查找表结点

  • 描述

    • 从第一个结点开始,依次往后比较表中各结点数据域的值,等于e则返回结点指针;若整个链表没有,返回NULL
  • 代码

	 LNode *LocateElem(LinkList L,ElemType e){
             LNode *p = L->next;
             while(p!=NULL&&p->data!=e)
                 p=p->next;
              return p;
     }
  • 时间复杂度

    • O(n)

5)插入结点操作

  • 描述

    • 首先调用按序号查找算法GetElem(L,i-1),查找第i-1个结点。假设返回的第i-1个结点为p,然后令新结点s的指针域执行p的后继结点,再令结点p的指针域指向新插入的结点*s
  • 代码

P=GetElem(L,i-1);
s->next=p->next;
p->next=s;
  • 时间复杂度

    • O(n)
  • 扩展

    • 已知s结点做前插,我们将s插入到*p的后面,然后将p->data与s->data交换,这样既满足了逻辑关系,又能使得时间复杂度为O(1)

6)删除结点操作

  • 描述

    • 将单链表的第i个结点删除。先检查删除位置合法性,后查找表中第i-1个结点,即被删结点的前驱结点,再将其删除
  • 代码

P=GetElem(L,i-1);      //获取删除位置的前驱结点
q=p->next;               //令q指向被删除结点
p->next=q->next;    //将*q结点从链中“断开”
free(q);                      //释放结点的存储空间
  • 时间复杂度

    • O(n)
  • 扩展

    • 删除结点p的操作可用删除p的后继结点操作来实现,实质就是将其后继结点的值赋予本身,然后删除后继结点,时间复杂度为O(1)

7)求表长操作

  • 描述

    • 从第一个结点开始顺序依次访问表中的每个结点,为此设置一个计数器变量,每访问一个节点,计数器加1,直到访问到空为止。对不带头结点的单链表,当表为空时,需单独处理

    • 时间复杂度

      • O(n)

3.静态链表

1)定义

  • 借助数组来描述线性表的链式存储结构,结点也有数据域data和指针域next,这里的指针是结点的相对地址(数组下标),又称游标。和顺序表一样,静态链表也要预先分配一块连续的内存空间

2)结构类型

#define MaxSize 50
typedef struct{
       ElemType data; 
       int next;
} SLinkList[MaxSize]

3)特性

  • 以next==-1作为其结束的标志。静态链表的插入、删除操作和动态链表的相同,只需要修改指针,而不需要移动元素

4.循环链表

1)循环单链表

  • 定义

    • 循环单链表和单链表的区别在于,表中最后一个结点的指针不是NULL,而改为指向头结点从而整个链表形成一个环
  • 特性

    • 循环单链表的插入、删除算法与单链表的几乎一样,所不同的是若操作是在表尾进行,则操作不同,以让单链表继续保持循环的性质
  • 头尾指针

    • 有时对单链表常做的操作是在表头和表尾进行的,仅设尾指针而不设头指针,若设的是头指针,对表尾操作需要O(n)的时间复杂度,而若设尾指针r,r->next即为头指针

2)循环双链表

  • 定义

    • 在循环单链表基础上增加prior指针
  • 特性

    • 某结点*p为尾结点时,p->next==L;当为空表时,其头结点的prior域和next域都等于L

5.双链表

1)定义

  • 双链表在单链表的结点中增加了一个指向其前驱的prior指针,因此双链表中的按值查找和按位查找的操作与单链表相同

2)特性

  • 双链表在插入和删除操作的实现上,与单链表有较大的不同。这是因为“链”变化时也需要对prior指针做出修改,其关键是保证在修改的过程中不断链。此外,双链表可以很方便找到其前驱结点,因此,插入、删除操作的时间复杂度仅为O(1)

3)双链表的插入操作

  • 描述

    • 双链表中p所指的结点之后插入结点*s
  • 代码

s->next=p->next;
p->next->prior=s;
s->prior=p;
p->next=s;
  • 时间复杂度

    • O(1)

4)双链表的删除操作

  • 描述

    • 删除双链表中结点p的后继结点q
  • 代码

p->next=q->next;
p->next->prior=p;
free(p);
  • 时间复杂度

    • O(1)