【数据结构】· 线性表(下)

写在前面


        Hello大家好, 我是【麟-小白】,一位软件工程专业的学生,喜好计算机知识。希望大家能够一起学习进步呀!本人是一名在读大学生,专业水平有限,如发现错误不足之处,请多多指正!谢谢大家!!!

        如果小哥哥小姐姐们对我的文章感兴趣,请不要吝啬你们的小手,多多点赞加关注呀!❤❤❤ 爱你们!!!


目录

写在前面

  1. 线性表的链式表示和实现

1.1 链式存储结构

1.2 单链表、双链表和循环链表

1.3 头指针、头结点和首元结点

1.4 单链表的定义和表示

1.5 循环链表

1.6 双向链表

2. 顺序表和链表的比较

2.1 顺序表和链表的比较

4.2 存储密度

 3. 线性表的应用

4.1 线性表的合并

4.2 有序表的合并

 4. 案例分析与实现

4.1 一元多项式的运算:实现两个多项式加、减、乘运算

4.2 系数多项式的运算

结语


 【往期回顾】

【数据结构】· 第一章 · 线性表(上)


1. 线性表的链式表示和实现


1.1 链式存储结构

  • 结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定

相邻。

  • 线性表的链式表示又称为非顺序映像或链式映像。
  • 用一组物理位置任意的存储单元来存放线性表的数据元素。
  • 这组存储单元既可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上的。
  • 链表中元素的逻辑次序和物理次序不一定相同。

例:26个英文小写字母表的链式存储结构: 

 各结点由两个域组成:

  • 数据域:存储元素数值数据。
  • 指针域:存储直接后继结点的存储位置。

与链式存储有关的术语:

  • 结点:数据元素的存储映像。由数据域和指针域两部分组成。
  • 链表:n个结点由指针链组成一个链表,它是线性表的链式存储映像,称为线性表的链式存储结构。


1.2 单链表、双链表和循环链表

  • 结点只有一个指针域的链表,称为单链表或线性链表。
  • 结点由两个指针域的链表,称为双链表。
  • 首位相接的链表称为循环链表。


1.3 头指针、头结点和首元结点

  • 头指针:是指向链表中第一个结点的指针。
  • 首元结点:是指链表中存储第一个数据元素a1的结点。
  • 头结点:是在链表的首元结点之前附设的一个结点。 

前面的例子中的链表的存储结构示意图有以下两种形式:

如何表示空表呢?

  • 无头结点时,头指针为空时表示空表。
  • 有头结点时,当头结点的指针域为空时表示空表。

在链表中设置头结点有什么好处?

  • 便于首元结点的处理:首元结点的地址保存在头结点的指针域中,所以在链表的第一个位置上的操作和其他位置一致,无需进行特殊处理。
  • 便于空表和非空表的偶统一处理:无论链表是否为空头指针都是指向头结点的非空指针,因此空表和非空表的处理也就统一了。

头结点的数据域内装的是什么?

  • 头结点的数据域可以为空也可以存放线性表长度等附加信息,但此节点不能计入链表长度值。

 链表(链式存储结构)的特点:

  • 结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻。
  • 访问时只能通过头指针进入链表,并通过每个结点的指针域依次向后顺序扫描其余结点,所以寻找第一个结点和最后一个结点所花费的时间不等。这种存取元素的方法被称为顺序存储法。


1.4 单链表的定义和表示

带头结点的单链表:

单链表是由表头唯一确定,因此单链表可以用头指针的名字来命名。若头指针名是L,则把链表称为表L

  • 单链表的存储结构:

例如:存储学生学号、姓名、成绩的单链表结点类型定义如下: 


算法一:单链表的初始化(带头结点的单链表)

  • 即构造一个如图的空表

算法步骤:

  1. 生成新结点作头结点,用头指针L指向头结点。
  2. 将头结点的指针域置空。

算法描述:


补充:单链表的几个常用简单算法:

补充算法一:判断链表是否为空:

空表:链表中无元素,称为空链表(头指针和头结点仍然在)。

算法思路:判断头结点指针域是否为空。

补充算法二:单链表的销毁:链表销毁后不存在

算法思路:从头指针开始,依次释放所有结点。

算法:摧毁单链表L

补充算法三: 清空链表

链表仍存在,但链表中无元素,成为空链表(头结点和头指针仍然在)。

算法思路:依次释放所有结点,并将头结点指针域设置为空。

                                                                                                                

算法:清空链表L:

补充算法四: 求单链表的表长

算法思路:从首元结点开始,依次计数所有结点

算法:求单链表L的表长:


算法二:取值——取单链表中第i个元素的内容

算法思路:取出表中第3个元素   (i=3)

从链表的头指针出发,顺着链域next逐个结点往下搜索,知道搜索到第i个结点为止。因此,链表不是随机存取结构 。

算法步骤:

  1. 从第一个结点(L->nest)顺链扫描,用指针p指向当前扫描到的结点,p初值:p->next。
  2. j做计数器,累计当前扫描过的结点数,j初值为1。
  3. 当p指向扫描到的下一结点时,计数器j+1.
  4. 当j==i时,p所指的结点就是要找的第i个结点。

算法描述:


算法三:按值查找——根据指定数据获取该位置所在的位置(地址) 

查找值为30的元素   (e=30)

 

算法步骤:

  1. 从第一个结点起,依次和e相比较。
  2. 如果找到一个其值与e相等的数据元素,则返回其在链表中的位置或地址。
  3. 如果查遍整个链表都没有找到其值与e相等的元素,则返回0或NULL。

算法描述:

 

算法三变化:按值查找——根据指定数据获取该数据位置序号


算法四:插入——在第i个结点前插入值为e的点

算法步骤:

  1. 首先找到ai-1的存储位置p。
  2. 生成一个数据域为e的新结点s。
  3. 插入新节点:①新结点的指针域指向结点ai

                              ②结点ai-1的指针域指向新结点

 

思考:步骤1和2能互换吗?

答:不可以,会丢失ai的地址。

算法描述:


算法五: 删除——删除第i个结点

算法步骤:

  1. 首先找到ai-1的存储位置p ,保存要删除的ai的值。
  2. 令p->next指向ai+1。
  3. 释放结点ai的空间

 算法描述:


单链表的查找、插入、删除算法时间效率分析 :

一:查找:因线性链表只能顺序存取,即在查找时要从头指针找起,查找的时间复杂度为

O(n)。

 二:插入和删除:因线性表不需要移动元素,只要修改指针,一般情况下时间复杂度为O(1)。但是,如果要在单链表中进行前插或删除操作,由于要从头查找前驱结点,所消耗的时间复杂度为O(n)。


算法六:建立单链表:头插法——元素插入在链表头部,也叫前插法

算法步骤:         

  1. 从一个空表开始,重复读入数据。
  2. 生成新结点,将读入数据存放到新结点的数据域中。
  3. 从最后一个结点开始,依次将各结点插入到链表的前端。 

例如:建立链表L(a,b,c,d,e)

     算法描述:

 算法的时间复杂度是O(n)。


算法七:建立单链表:尾插法——元素插入在链表尾部,也叫后插法

算法步骤:   

  1. 从一个空表开始,将新节点逐个插入到链表的尾部,尾指针r指向链表的尾结点。
  2. 初始时,r同L均指向头结点。每读入一个数据元素则申请一个新结点,将新节点插入到尾结点后,r指向新结点。

 算法描述:

算法的时间复杂度是O(n)。


1.5 循环链表

循环链表:是一种头尾相接的链表(即表中最后一个结点的指针域指向头结点,整个链表形成一个环)。

优点:从表中任一结点出发均可找到表中其他结点。 

注意:由于循环链表中没有NULL指针,故涉及遍历操作时,其终止条件就不再像非循环链表那样判断p或p->next是否为空,而是判断它们是否等于头指针。

注意:表的操作往往是在表的首位位置上进行。

带尾指针循环链表的合并 

分析有哪些操作?

  • p存表头结点
  • Tb表头连接到Ta表尾 
  • 释放Tb表头结点
  • 修改指针

 算法描述:

 算法的时间复杂度是O(1)。


1.6 双向链表

为什么要讨论双向链表呢?

单链表的结点->有指示后继的指针域->找后继结点方便;

即:查找某结点的后继结点的执行时间为O(1)

但是其没有指示前驱的指针域 ->找前驱结点难:从表头出发查找。

即:查找某结点的前驱结点的执行时间是O(n)

我们可以使用双向链表来克服单链表的这种缺点。

双向链表:在单链表的每个结点里再增加一个指向其直接前驱的指针域prior,这样链表中就形成了有两个方向不同的链,故称为双向链表。

双向链表的结构可定义如下:

 双向循环链表:

和单链表的循环表类似,双向链表也有循环表。

  • 让头结点的前驱指针指向链表的最后一个结点。
  • 让最后一个结点的后继指针指向头节点。

双向链表结构的对称性(设指针p指向某一结点):

 

在双向链表中有些操作(如ListLength、GetElem等) 仅涉及一个方向的指针,故它们的算法和线性表的相同,但在插入、删除时,则需同时修改两个方向上的指针两者的操作的时间复杂度均为O(n)。

算法八:双向链表的插入


算法九:双向链表的删除 

  算法的时间复杂度是O(n)。


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


a3b38f210a304bf391c697d680fc1cd4.png

2. 顺序表和链表的比较


2.1 顺序表和链表的比较

链式存储结构的优点:

  • 结点空间可以动态申请和释放。
  • 数据元素的逻辑次序考结点的指针来指示,插入和删除时不需要移动数据元素。

链式存储结构的缺点:

  • 存储密度小,每个结点的指针域需额外占用存储空间。当每个结点的数据域所占字节不多时,指针域所占空间的比重显得很大。
  • 链式存储结构是非随机存取结构,对任一结点的操作都要从头指针依指针链查找到该结点,这增加了算法的复杂度。


4.2 存储密度

存储密度是指结点数据本身所占的存储量和整个结点结构中所占的存储量之比,即:

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


a3b38f210a304bf391c697d680fc1cd4.png

 3. 线性表的应用


4.1 线性表的合并

假设利用两个线性表La和Lb分别表示两个集合A和B,现要求一个新的集合A=A∪B

算法步骤:

依次取出Lb中的每个元素,执行以下操作:

  1. 在La中查找该元素
  2. 如果找不到,则将其插入La的最后

算法的时间复杂度为:O(ListLength(La) * ListLength(Lb))


4.2 有序表的合并

已知线性表La和Lb中的数据元素按值非递减有序排列,现要求将La和Lb归并为一个新的线性表Lc,且Lc中的数据元素仍按值非递减有序排列。

算法步骤:

  1. 创建一个空表Lc
  2. 依次从La或Lb中“摘取”元素值较小的结点插入到Lc表的最后,直至其中一个表变空为止。
  3. 继续将La或Lb其中一个表的剩余结点插入在Lc表的最后。 

用顺序表实现:

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

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


用链表实现:

用La的头结点作为Lc的头结点

 

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

算法的空间复杂度是:O(1)


a3b38f210a304bf391c697d680fc1cd4.png

 4. 案例分析与实现


4.1 一元多项式的运算:实现两个多项式加、减、乘运算

实现两个多项式相加运算: 


4.2 系数多项式的运算

  • 创建一个新数组c
  • 分别从头遍历比较a和b的每一项
    • 指数相同,对应系数相加,若其和不为零,则在c中增加一个新项
    • 指数不相同,则将指数较小的项复制到c中
  • 一个多项式已经遍历完成时,将另一个剩余项依次复制到c中即可

多项式创建算法步骤:

①创建一个只有头结点的空链表

②根据多项式的项数n,循环n次执行以下操作:

  • 生成一个新结点*s
  • 输入多项式当前项的系数和指数赋给新结点*s的数据域
  • 设置一前驱指针pre,用于指向待找到的第一个大于输入项指数的结点的前驱,pre初值指向头结点
  • 指针q初始化,指向首元结点
  • 循链向下逐个比较表中当前结点与输入项指数,找到第一个大于输入项指数的结点*q
  • 将输入项结点*s插入到结点*q之前

算法描述:

多项式相加算法分析 :

 

多项式相加算法步骤:

 


结语


本人会持续更新文章的哦!希望大家一键三连,你们的鼓励就是作者不断更新的动力

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

麟-小白

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

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

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

打赏作者

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

抵扣说明:

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

余额充值