#你的考试笔记之数据结构——三,线性表的链式存储结构

你的考试笔记之数据结构——三,线性表的链式存储结构

精简到只有考试会用到的知识

线性表的链式存储结构

解决了线性表的顺序存储结构插入和删除时需要移动大量元素,耗费大量时间的问题。

一.单链表

1.单链表结构

单链表结构实现代码如下:

/*线性表的单链表存储结构*/
typedef struct Node
{
   ElemType data;
   struct Node *next;
}Node;
typedef struct Node *LinkList;   /*定义 LinkList*/

做题时要注意单链表是否带头结点!
(1)不带头结点的单链表:
不带头结点的单链表
(2)带头结点的单链表:
带头结点的单链表(3)空链表:
空链表

2.单链表主要的两种操作
(1)插入操作

假设存储元素e的结点为s,要实现结点p、p->next和s之间逻辑关系的变化,只需将结点s插入到结点p和p->next之间即可。

s->next-p->next;
p->next=s;

解读这两句代码,也就是说让p的后继结点改成s的后继结点,再把结点s变成p的后继结点。如图所示:

插入操作
注意,如果先p->next=s;再s->next=p->next;此时第一句会使得将p->next给覆盖成s的地址了,那么s->next=p->next,其实就等于s->next=s,这样真正的拥有ai+1数据元素的结点就没了上级。这样的插入操作就是失败的。
对于单链表的表头和表尾的特殊情况,操作是相同的,如图所示:
在这里插入图片描述
单链表的创建过程即从“空表”的初始状态起,依次建立各元素结点,并逐个插入链表。

头插法:

算法思路:

  1. 声明一结点p和计数器变量i;
  2. 初始化一空链表L;
  3. 让L的头结点的指针指向NULL,即建立一个带头结点的单链表;
  4. 循环:生成一新结点赋值给p;随机生成一数字赋值给p的数据域p->data;将p插入到头结点与前一新结点之间。
    头插法

实现代码算法如下:

/*随机产生n个元素的值,建立带表头结点的单链线性表L(头插法)*/
voidCreateListHead(LinkList *L,int n)
{
   LinkList p;
   int i;
   srand (time (0));   /*初始化随机数种子,这里链表的数据随机生成,也可以用scanf()函数自己输入数据插入链表*/
   *L=(LinkList)malloc(sizeof(Node));
   (*L)->next=NULL/*先建立一个带头结点的单链表*/
   for(i=0;i<n;i++)
   {
      P=(LinkList)malloc(sizeof(Node));  /*生成新结点*/
      p->data=rand()%100+1;   /*随机生成100以内的数字*/
      P->next=(*L)->Next;
      (*L)->next=p;    /*插入到表头*/
   }
}
尾插法:

头插法建立单链表的算法虽然简单,但生成的链表中结点的次序和输入数据的顺序不一致。若希望两者次序一致,则可采用尾插法。该方法将新结点插入到当前链表的表尾,为此必须增加一个尾指针r,使其始终指向当前链表的尾结点,如图所示:尾插法实现代码算法如下:

/*随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法)*/voidCreateListTail(LinkList *L,int n)
{
   LinkList p,r;
   int i;
   srand (time (0));   /*初始化随机数种子*/
   *L=(LinkList)malloc(sizeof(Node));
   r=*L;   /*r为指向尾部的结点*/
   for(i=0;i<n;i++)
   {
      p=(Node*)malloc(sizeof(Node));   /*生成新结点*/
      p->data=rand()%100+1;   /*随机生成100以内的数字*/
      r->Next=p;   /*将表尾终端结点的指针指向新结点*/
      r=p;   /*将当前的新结点定义为表尾终端结点*/
   }
   r->next=NULL;   /*表示当前链表结束,末尾置空以便以后遍历时可以确认其是尾部*/
}

(2)删除操作

设存储元素a的结点为q,要实现将结点q删除单链表的操作,其实就是将它的前继结点的指针绕过,指向它的后继结点即可,如图所示:
删除节点

我们所要做的,实际上就是一步,p->next=p->next->next,用q来取代p->next,即是

q=p->next;
p->next=q->next;
3.单链表结构与顺序存储结构对比
(1)存储分配方式

顺序存储结构用一段连续的存储单元依次存储线性表的数据元素。
单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素。

(2)时间性能

查找:
顺序存储结构O(1)
单链表O(n)
插入和删除:
顺序存储结构需要平均移动表长一半的元素,时间为O(n)
单链表在线出某位置的指针后,插入和删除时间仅为O(1)

(3)空间性能

顺序存储结构需要预分配存储空间,分大了,浪费,分小了易发生上溢。
单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制。

二、循环链表

将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表。
为了便空链表与非空链表处埋一致,找们通常设一个头结点,当然,这并不是说,循环链表一定要头结点。循环链表带有头结点的空链表如图所示:
循环链表带有头结点的空链表
对于非空的循环链表就如图所示:
非空的循环链表其实循环链表和单链表的主要差异就在于循环的判断条件上,原来是判断p->next是否为空,现在则是p->next不等于头结点,则循环未结束。

三、双链表

在单链表中,有了next指针,这就使得我们要查找下一结点的时间复杂度为O(1)。可是如果我们要查找的是上一结点的话,那最坏的时间复杂度就是O(n)了,因为我们每次都要从头开始遍历查找。
为了克服单向性这一缺点设计出了双向链表,双向链表是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。所以在双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱。
既然单链表也可以有循环链表,那么双向链表当然也可以是循环表。双向链表的循环带头结点的空链表如图所示:
双向链表的循环带头结点的空链表
非空的循环的带头结点的双向链表如图3-14-4所示.

非空的循环的带头结点的双向链表
重点是双向循环链表的插入和删除操作的顺序

(1)双向循环链表的插入操作

我们现在假设存储元素e的结点为s,要实现将结点s插入到结点p和p->next之间需要下面几步,如图所示:
双向循环链表的插入操作

s->prior=p;/*把p赋值给s的前驱,如图中①*/
s->next=p->next;/*把p->next赋值给s的后继,如图中②*/
p->next->prior=s;/*把s赋值给p->next的前驱,如图中③*/
p->next=s;/*把s赋值给p的后继,如图中④*/

关键在于它们的顺序,由于第2步和第3步都用到了p->next。如果第4步先执行,则会使得p->next 提前变成了s,使得插入的工作完不成。所以顺序是先搞定s的前驱和后继,再搞定后结点的前驱,最后解决前结点的后继。

(1)双向循环链表的删除操作

循环链表删除操作

p->prior->next=p->next;   /*把p->next赋值给p->prior的后继,如图中①*/
p->next->prior=p->prior;   /*把p->prior赋值给p->next的前驱,如图中②*/
free (p);   /*释放结点*/

四、静态链表(不太重要,了解即可)

静态链表借助数组来描述线性表的链式存储结构,结点也有数据域data和指针域next,与前面所讲的链表中的指针不同的是,这里的指针是结点的相对地址(数组下标),又称游标。和顺序表一样,静态链表也要预先分配一块连续的内存空间。
静态链表和单链表的对应关系如图所示:
对应关系

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

I'm一只耳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值