数据结构与算法4:链式其他形式的表示和实现——循环链表、双链表

2.5-II、链式其他形式的表示和实现——循环链表、双链表

2.5.1、循环链表

循环链表:是一种头尾相接的链表(即:表中最后i一个结点的指针域指向头结点,整个链表形成一个环)。
在这里插入图片描述

注解:单链表中最后一个结点之后再无其他结点自然最后一个结点的指针域中为空,但在循环链表中,最后一个结点的指针域不为空,它存储的是头结点的地址。这样有什么好处呢?相比单链表,在循环链表中,到最后一个结点后还可以向下继续寻找头结点,继而其他结点。循环结点的空表怎么表示?其头结点存储的是自身的地址(或者头结点的指针域指向自身)。那循环链表中怎么知道到最后一个一个结点an呢?
优点:从表中任一结点出发均可找到表中其他结点。

————————那循环链表中怎么知道到最后一个一个结点an呢?
最后结点an的确认:由于循环链表中没有NULL指针,故涉及遍历操作时,其终止条件就不再像非循环链表那样判断p或p->next是否为空,而是判断他们是否等于头指针
在这里插入图片描述

经常首尾操作,循环链表的选择:————为什么要选带尾指针的循环链表?
在这里插入图片描述

2.5.2、带尾指针的循环链表的合并

尾指针循环链表的合并(将Tb合并再Ta之后)

注解:整个链表是怎么表示的呢?带头指针的链表表示时只需要一个头指针的地址就能代表整个链表,当然也就暗示默认知道这个头指针的地址了,当然带尾指针的链表也是同理,即Ta就知道an的地址,知道这个原理就能理解下面分析过程了。

算法步骤:
在这里插入图片描述

分析有哪些操作?

  1. p存表头结点 p = Ta -> next;
  2. Tb表头(首元结点,而非头结点)连接头Ta的表尾 Ta-> next = Tb -> next -> next;
  3. 释放Tb表头结点 delete Tb -> next;
  4. 修改指针 Tb -> next = p;
    算法描述
    在这里插入图片描述

2.5.3、双向链表

为什么要讨论双向链表?——因为单链表的某些缺陷,使得人们开始设计最优化的方案。
在这里插入图片描述

单链表中的结点——>有指示后继的指针域——>找后继结点方便;即:查找某结点的后继结点的执行时间尾O(1)。——>无指示前趋的指针域——>找前趋结点难:从表头出发查找,即:查找某结点的前趋结点的执行时间为O(n)。可用双向链表来克服这种单链表的这种缺点。

注解:简单来说,因为单链表的属性,一个结点=数据域+下一个结点的地址,这样的结构使得查找一个结点下一个结点容易,但是查找该结点上一个结点就很难,得从头结点开始查起。双向链表企图解决基于容易查找后继结点的优点上,又容易查找前驱结点的方法。

双向链表:在单链表的每个结点里面再增加一个指向其直接前趋的指针域prior,这样链表中就形成了有两个方向不同的链,故称为双向链表
双向链表的结构定义
在这里插入图片描述

按照研究但单链表的方法,双链表也可以有循环链表,如下:
双向循环链表
和单链的循环链表类似,双向链表也可以有循环表
● 让头结点的前驱指针指向链表的最后一个结点
● 让最后一个结点的后继指针指向头结点。
在这里插入图片描述

那么问题来了,我们知道单链表中是一个数据域+指针域,这个指针域指向下一个结点(也是存储着下一个结点的地址),但在双向链表中,这种 “指针域+ 数据域 + 指针域” 结构中,怎么规范一个结点两个指针域的问题?(实现功能,简单易懂)?———————————即一个结点的后继结点是下一个结点的前驱结点
双向链表结构的对称性(设指针p指向某一结点):
p -> prior ->next = p = p -> next -> prior
在双链表中有哪些操作(如:ListLength 、GetElem等),因仅涉及一个方向的指针,故它们的算法与线性链表的相同。但在插入、删除时,则需同时修改两个方向上的指针,两者的操作的时间复杂度均为O(n)。
在这里插入图片描述

2.5.4、算法1:双向链表的插入

算法步骤:
在这里插入图片描述

算法描述:
在这里插入图片描述

注解:if(!(p = GetElemP_DuL(L,i))) return ERROR;//在链表L中找到i赋值给p,即p指向i个结点,如果这是时候给到i的位置不合理,是非法的(体现在!上),那就返回错误。

2.5.5、算法2:双向链表的删除

算法步骤
在这里插入图片描述

注解:已知p结点位置,用p来操作,
● 1、需将a结点(p->prior)的next域存储上c结点的地址(c的地址存在于指针p的next域中);
● 2、需将c结点(p->next)的前驱prior存储上a结点的地址(a的地址存在于指针p的前驱中);。

算法描述
在这里插入图片描述

2.5.6、单链表、循环链表和双链表的时间效率比较

在这里插入图片描述

2.6、顺序表的和链式表的比较

先总结链式表的优缺点

链式存储结构的优点
结点空间可以动态申请和释放
○ 数据元素的逻辑次序依靠结点的指针来指示,插入和删除时不需要移动数据元素
链式存储结构的缺点
存储密度小,每个结点的指针域需额外占用存储空间。当每个结点的数据域所占字节不多时,指针域所占存储空间的比重显得很大。
○ 链式存储结构是非随机存取结构。对任一结点的操作都要从头指针依指针链查找到该结点,这增加了算法的复杂度。
存储密度:是指结点数据本身所占的存储量和整个结点结构中所占的存储量之比,
即: 存储密度 = 结点数据本身占用的空间 结点占用的空间总量 存储密度 = \frac{结点数据本身占用的空间}{结点占用的空间总量} 存储密度=结点占用的空间总量结点数据本身占用的空间
在这里插入图片描述

一般地,存储密度越大,存储空间的利用率就越高。显然,顺序表的存储密度为1(100%),而链表的存储密度小于1。

接着是顺序表和链表的比较

在这里插入图片描述

————————2.7介绍线性表的典型应用,2.8介绍2.2案例引入 中案例如何解决

2.7、线性表的应用

2.7.1、线性表、有序表的合并————表示

线性表的合并
问题描述:假设利用两个线性表La和Lb分别表示两个集合A和集合B,现要求一个新的集合A=A∪B。
在这里插入图片描述

算法步骤:依此取出b中的每个元素,执行以下操作:
○ 1、在La中查找每个元素
○ 2、如果找不到,则将其插入La的最后
● 算法描述
在这里插入图片描述

算法的时间复杂度是O(ListLength(La)*ListLength(Lb))
有序表的合并
问题描述:已知线性表La和Lb中的数据元素按值非递减(不是严格递减,有的值相等)有序排列,现要求将La和Lb归并为一个新的线性表Lc,且Lc中的数据元素仍然按值非递减有序排列。
在这里插入图片描述

算法步骤
○ 1、创建一个空表Lc
○ 2、依此从La或Lb中“摘取”,元素较小的结点插入到Lc表的最后,直到其中一个表变空为止
○ 3、继续将La或Lb其中一个表的剩余结点插入在Lc表的最后

2.7.2、线性表、有序表的合并————实现

【算法2.16】有序表合并——顺序表实现
在这里插入图片描述

算法描述
在这里插入图片描述

todo没有理解
在这里插入图片描述

算法的时间复杂度是O(ListLength(La)+ListLength(Lb))
算法的空间复杂度是O(ListLength(La)+ListLength(Lb))
【算法2.17】有序表合并——用链表表实现
用La的头结点作为Lc的头结点
在这里插入图片描述

注解:用La的头结点作为Lc的头结点,Lc=pc=La;将La的头结点赋值给Lc,再指定三个指针变量来操作列表中的结点,La列表中用pa操作,Lb列表中用pb操作,Lc列表中用pc操作。

在这里插入图片描述

注解:先看一下当前结点pa和pb所指的那个比较小,因为pa的数据域data小于pb的数据域,则把pa接在Lc列表中,即pc -> next = pa;然后pc的指针指向1所在的结点(图示如下),pa又指向La列表中下一个结点(图示如下)。

在这里插入图片描述

注解:接着是La列表中pa指针所指的第二结点(7所在地方),和Lb列表中pb所指的首元结点(2所在的地方)进行比较,因为7大于2,所以1后面接下来的是比较小2,作为Lc列表中的第二个结点,随后Lb中首元结点已经接上使用完了,pb指针指向下一个结点(示图如下),依此类推。

在这里插入图片描述

注解:La列表的长度小于Lb列表的长度,所以直到La表中完毕(pa为NULL),就会直接把Lb列表中剩余的元素接上,这样就形成了Lc列表了(图示如下)。

在这里插入图片描述

注解:最后把列表Lb的头结点释放掉就完成,完整的算法描述如下:

算法描述
在这里插入图片描述

算法的时间复杂度是O(ListLength(La)+ListLength(Lb))
算法的空间复杂度是O(1)

注解:在生成Lc列表中(即La列表和Lb列表合并),直接在列表La和列表Lb上操作,没有涉及额外的空间,所以空间复杂度为1(在原来的空间上就表示1)。

2.8、案例分析与实现

案例2.1】一元多项式的运算:实现两个多项式加、减、乘运算
在这里插入图片描述

实现两个多项式相加运算
在这里插入图片描述

案例2.2】:稀疏多项式的运算
多项式非零项的数组表示
在这里插入图片描述

多项式非零项的数组实现
线性表A=((7,0),(3,1),(9,8),(5,17))
线性表B=((8,1),(22,7),(-9,8))
算法步骤
● 创建一个新数组c
● 分别从头遍历比较a和b的每一项
指数相同,对应系数相加,若其和不为零,则在c中增加一个新项
指数不相同,则将指数较小的项复制到c中
● 一个多项式已遍历完毕时,将另一个剩余项依次复制到c中即可
c多大合适呢?——————————若a是n这么大,b为m大,那么c为m+n,最小为多少呢,为0。还得新建一个数组c存储,来一个新空间,空间上也比较浪费,如何解决?
在这里插入图片描述

多项式创建——【算法步骤】
(1)创建要一个只有头结点的空链表。
(2)根据多项式的项的个数,循环n次执行以下操作:
● 生成一个新结点s;
● 输入多项式当前项的系数和指数赋给新结点
s的数据域;
● 设置一前驱指针pre,用于指向待找到的第一个大于输入项指数的结点的前驱,pre初值指向头结点;
● 指针q初始化,指向首元结点;
● 循环链向下逐个比较链表中当前结点与输入项指数,找到第一个大于输入项指数的结点q;
● 将输入项结点
s插入到结点*q之前。
多项式创建——【算法描述】:
在这里插入图片描述

多项式相加————【算法分析】:
在这里插入图片描述

多项式相加————【算法步骤】
1、指针p1和p2初始化,分别指向pa和pb的首元结点。
2、p3指向和多项式的当前结点,初值为pa的头结点。
3、当指针p1和p2均未达到相应表尾时,则循环比较p1和p2所指结点对应的指数值(p1->expn与p2->expn),有下列3种情况:
● 当p1->expn==p2->expn时,则将两个结点中的系数相加
○ 若和不为0,则修改p1所指结点的系数值,同时删除p2所指结点
○ 若和为0,则删除p1和p2所指结点;
● 当p1->expnexpn时,则应摘取p1所指结点插入到“和多项式”l链表中去;
● 当p1->expn>p2->expn时,则应摘取p2所指结点插入到“和多项式”l链表中去。
4、将非空多项的剩余段插入p3所指结点之后。
5、释放pb的头结点。
【案例2.3 】:图书信息管理系统
在这里插入图片描述

注解:图书管理系统完成一些对图书的管理操作,比如说按照书名查询,有没有《Java入门到放弃》的书,比如说要删除一本叫做《使用数据结构》的书,还有修改书名,等等一系列增删改查操作,这些问题呢?就是从问题当中抽象出数据模型,是线性表,先后顺序的逻辑关系。此线性表中的每个元素呢都是复杂元素,每个数据元素都包含书号书名定价三个数据项,咱们这些问题怎么实现呢?即可以用顺序表也可以用链表实现,而其表中的数据域data存储着ISBN 书名 价格。
在这里插入图片描述

注解:用顺序表?还是链表?到底用那种呢?如果表中的书的变化不大,很少做删除,插入操作,而且经常需要通过序号去查找一本书籍,那么便用顺序表,因为顺序表的逻辑顺序和存储顺序一致。如果表中书的变化非常大,经常要删除,插入操作,那就用链式结构。接下来这两种表如何定义?

在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值