数据结构线性表 | 青训营笔记

205 阅读9分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 15 天

线性表

定义

线性表(List ):零个或多个数据元素的有限序列。

有序 有限

image-20220409122219124

第i个数据元素,称i为数据元素旬在线性表中的位序

抽象数据类型

方法

image-20220409123813432

image-20220409123953922

例题

image-20220409124813349

image-20220409124935283

image-20220409124958358

顺序存储结构

顺序存储定义

线性表的顺序存储结构,指的是用一段地址连续的存储单元依次 存储线性表的数据元素。

image-20220409125330012

顺序存储方式

所以可以用C语言 (其他语言也相同)的一维数组来实现顺序存储结构,即把第一个数据元素存到数组下 标为。的位置中,接着把线性表

相邻的元素存储在数组中相邻的位置。

数据长度与线性表长度区别

线性表的长度是线性表中数据元素的个数,随着线性表插入和删除操作的进行, 这个量是变化的。

在任意时刻,线性表的长度应该小于等于数组的长度。

地址计算方法

image-20220409141919533

分配的数组空间要大于等于当前线性表的长度

存储器中的每个存储单元都有自己的编号,这个编号称为地址。

假设占用的是c个存储单元 LOC表示获得存储位置的函数

image-20220409142055447

不管它是第一个还是最后一个,都是相同的时间。那么我们对每个线性表位置的存入或者取出数据,对于计算机来说都是相等的时间,也就是一个常数,

它的存取时间性能为O(1)。我们通常把具有这一特点的存储结构称为随机存取结构。

顺序存储结构的插入与删除

image-20220409142715084

返回值类型Status是一个整型,返回OK代表1, ERROR代表0。之后 代码中出现就不再详述。

获得元素操作

GetElem操作

image-20220409143020297

插入操作

插入算法的思路:

■ 如果插入位置不合理,抛出异常;

■ 如果线性表长度大于等于数组长度,则抛出异常或动态增加容量;

■ 从最后一个元素开始向前遍历到第i个位置,分别将它们都向后移动一个位 置;

■ 将要插入元素填入位置i处;

■ 表长加1。

image-20220409143816527

删除操作

删除算法的思路:

■ 如果删除位置不合理,抛岀异常;

■ 取出删除元素;

■ 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一 个位置;

■ 表长减1。

image-20220409145452763

image-20220409145900757

性表的顺序存储结构,在存、读数据时,不管是哪个位置,时间 复杂度都是0(1);而插入或删除时,时间复杂度都是0(n)。

线性表顺序存储结构的优缺点

image-20220409152243196

链式存储结构

定义

image-20220409154659108

image-20220409155109493

image-20220409155352761

头结点的数据域可以不存储任何信息 也可以存储如线性表的长度等附加信息,头结点的指针域存储指向第一个 结点的指针

image-20220409155700355

头指针与头结点的异同

image-20220409161237724

线性表链式存储结构

image-20220409162729770

image-20220409163335404

image-20220409163359968

image-20220409163411662

代码描述

image-20220409164212417

结点由存放数据元素的数据域存放后继结点 地址的指针域组成。假设P是指向线性表第i个元素的指针,则该结点at的数据域 我们可以用p-

data来表示,p->data的值是一个数据元素,结点句的指针域可以用 p->next来表示,p->next的值是一个指针。p->next指向谁呢?当然是指向第i+1个 元素,即指向ai+1的指针。也就是说,如果p->data=aj,那么p->next>data=aj+1

image-20220409164642617

单链表的读取

获得链表第i个数据的算法思路:

  1. 声明一个结点P指向链表第一个结点,初始化j从1开始;
  2. 当j<i时,就遍历链表,让P的指针向后移动,不断指向下一结点,j累加1;
  3. 若到链表末尾P为空,则说明第i个元素不存在;
  4. 否则查找成功,返回结点P的数据。

image-20220409180340174

image-20220409180351991

头开始找,直到第i个元素为止。由于这个算法的时间复杂度取 决于i的位置,当时,则不需遍历,第一个就取出数据了,而当i=n时则遍历

n-1 次才可以。因此最坏情况的时间复杂度是0(n)

主要核心思想:“工作指针后移” 由于单链表的结构中没有定义表长,所以不能事先知道要循环多少次,因此也就 不方便使用for来控制

循环。

单链表的插入与删除

image-20220409181532637

image-20220409181550762

切记 切记 顺序不能反

单链表第i个数据插入结点的算法思路:

  1. 声明一结点P指向链表第一个结点,初始化j从1开始;
  2. 当何时,就遍历链表,让P的指针向后移动,不断指向下一结点,j累加1;
  3. 若到链表末尾P为空,则说明第i个元素不存在;
  4. 否则查找成功,在系统中生成一个空结点s;
  5. 将数据元素e赋值给s->data;
  6. 单链表的插入标准语句s->next=p->next; p->next=s;
  7. 返回成功。

image-20220409182439964

我们用到了 C语言的malfoc标准函数,它的作用就是生成一 个新的结点,其类型与Node是一样的,其实质就是在内存中找了一小块空

地,准备用来存放e数据s结点。

解释 L=(Linklist)malloc(sizeof(Node))含义

image-20220409183846026

单链表的删除

单链表第i个数据删除结点的算法思路:

  1. 声明一结点P指向链表第一个结点,初始化j从1开始;
  2. 当Ki时,就遍历链表,让P的指针向后移动,不断指向下一个结点,j累加 1;
  3. 若到链表末尾P为空,则说明第i个元素不存在;
  4. 否则查找成功,将欲删除的结点p->next赋值给q;
  5. 单链表的删除标准语句p->next=q->next;
  6. 将q结点中的数据赋值给e,作为返回;
  7. 释放q结点;
  8. 返回成功。

image-20220409191921262

image-20220409192021928

代码

image-20220409192353588

这段算法代码里,我们又用到了另一个C语言的标准函数free。它的作用就是让 系统回收一个Node结点,释放内存。

它们是由两部分组成:第一部分就是遍历查找第i个元素;第二部分就是插入和删除元素。

对于插入或删除数据越频 繁的操作,单链表的效率优势就越是明显。

单链表的整表创建

单链表整表创建的算法思路:

  1. 声明一结点P和计数器变量i;
  2. 初始化一空链表L;
  3. 让L的头结点的指针指向NULL,即建立一个带头结点的单链表;
  4. 循环:

♦ 生成一新结点赋值给P;

♦ 随机生成一数字赋值给P的数据域p->data;

♦ 将p插入到头结点与前一新结点之间。

头插法

image-20220409193330216

image-20220409193515138

尾插法

image-20220409195006255

image-20220409195032882

L与r的关系,L是指整个单链表,而r是指向尾结点的变量,r会随着循环 不断地变化结点,而L则是随着循环增长为一个多结点的链表。

理解

r->next=p;的意思,其实就是将刚才的表尾终端结点r的指针指 向新结点P, 如图

image-20220409195455416

r=p;就是本来r是在ai-1元素的结点,可现在它已经不是最后的结点了,现 在最后的结点是ai所以应该要让将p结点这个最后的结点赋值

给r。此时r又是最终的尾结点了。

单链表的整表删除

单链表整表删除的算法思路如下:

  1. 声明一结点P和q;
  2. 将第一个结点赋值给P;
  3. 循环:

♦ 将下一结点赋值给q;

♦ 释放p;

♦ 将q赋值给p。

image-20220409200523044

单链表结构与顺序存储结构优缺点

image-20220409200711408

若线性表需要频繁查找,很少进行插入和删除操作时,宜釆用顺序存储结构。

若需要频繁插入和删除时,宜采用单链表结构。

当线性表中的元素个数变化较大或者根本不知道有多大时,最好用单链表结构

而如果事先知道线性表的大致长度,这种用顺序存储结构效率会高很多。比如一年12个月,一周就是星期一至星期日共七天,

静态链表(游标实现法)

数组来代替指针,来描述单链表。

数组的元素都是由两个数据域组成,data和cur。也就是说,数组的每 个下标都对应一个data和一个cur0数据域data,用来存放数据元

素,游标cur相当于单链表中的next指针,存放该元素的后继在数组 中的下标。

image-20220409201936537

image-20220409201951743

image-20220409204745682

通常把未被使用的数组元素称为备用链表。

image-20220409202434331

image-20220409204656175

image-20220409204710584

静态链表的插入操作

image-20220409211915641

image-20220409211847045

image-20220409212619492

静态链表的删除操作

image-20220409213802628

image-20220409213819688

image-20220409213834509

前面代码都一样,for循环因为i=l 而不操作,j=k[999].cur=l, L[k].cur=L[j].cur 也就是 L[999].cur=L[l].cur=2 。

“甲”现在要走,这个位置就空出来了,也就是,未来如果有新人来, 最优先考虑这里,所以原来的第一个空位分量,即下标是8的分量,

它降级了,把8 给"甲”所在下标为1的分量的cur,也就是space[l].cur=space[0].cur=8,而 space[0].cur=k=l其实就是让这个删除的位置成为

第一个优先空位,把它存入第一个 元素的cur中

image-20220409214035748

静态链表的ListLength

image-20220409214117109

静态链表优缺点

image-20220409214225168

循环链表

定义

单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一 个环,这种头尾相接的单链表称为单循环链表,简称循环链表 (circular linked list)

image-20220411112728364

image-20220411112806134

尾指针 rear

image-20220411162704061

终端结点用尾指针rear指示,则查找终端结点是0(1),而开 始结点,其实就是rear->next->next,其时间复杂也为0(1)。

合并两个单循环链表

image-20220411162503351

image-20220411162441114

image-20220411162423414

image-20220411162357392

双向链表

定义

双向链表 (double linked list) 是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。

所以在双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接 前驱。

image-20220411165145398

image-20220411165251021

结构

image-20220411165413971

image-20220411165901831

对于链表中的某一个结点P,它的后继的前驱是谁?当 然还是它自己。它的前驱的后继自然也是它自己,即:

image-20220411170652458

插入

双向链表既然是比单链 表多了如可以反向遍历査找等数据结构,那么也就需要付出一些小的代价:在插入和 删除时,需要更改两个指针变量。

image-20220411172156982

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

删除

image-20220411172742057

image-20220411172804977

它由于每个结点都需要记录两份 指针,所以在空间上是要占用略多一些的。

说白了,就是 用空间来换时间。

总结

线性表的这两种结构其实是后面其他数据结构的基础

image-20220411173126349

栈与队列