这是我参与「第五届青训营 」伴学笔记创作活动的第 15 天
线性表
定义
线性表(List ):零个或多个数据元素的有限序列。
有序 有限
第i个数据元素,称i为数据元素旬在线性表中的位序
抽象数据类型
方法
例题
顺序存储结构
顺序存储定义
线性表的顺序存储结构,指的是用一段地址连续的存储单元依次 存储线性表的数据元素。
顺序存储方式
所以可以用C语言 (其他语言也相同)的一维数组来实现顺序存储结构,即把第一个数据元素存到数组下 标为。的位置中,接着把线性表
相邻的元素存储在数组中相邻的位置。
数据长度与线性表长度区别
线性表的长度是线性表中数据元素的个数,随着线性表插入和删除操作的进行, 这个量是变化的。
在任意时刻,线性表的长度应该小于等于数组的长度。
地址计算方法
分配的数组空间要大于等于当前线性表的长度
存储器中的每个存储单元都有自己的编号,这个编号称为地址。
假设占用的是c个存储单元 LOC表示获得存储位置的函数
不管它是第一个还是最后一个,都是相同的时间。那么我们对每个线性表位置的存入或者取出数据,对于计算机来说都是相等的时间,也就是一个常数,
它的存取时间性能为O(1)。我们通常把具有这一特点的存储结构称为随机存取结构。
顺序存储结构的插入与删除
返回值类型Status是一个整型,返回OK代表1, ERROR代表0。之后 代码中出现就不再详述。
获得元素操作
GetElem操作
插入操作
插入算法的思路:
■ 如果插入位置不合理,抛出异常;
■ 如果线性表长度大于等于数组长度,则抛出异常或动态增加容量;
■ 从最后一个元素开始向前遍历到第i个位置,分别将它们都向后移动一个位 置;
■ 将要插入元素填入位置i处;
■ 表长加1。
删除操作
删除算法的思路:
■ 如果删除位置不合理,抛岀异常;
■ 取出删除元素;
■ 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一 个位置;
■ 表长减1。
性表的顺序存储结构,在存、读数据时,不管是哪个位置,时间 复杂度都是0(1);而插入或删除时,时间复杂度都是0(n)。
线性表顺序存储结构的优缺点
链式存储结构
定义
头结点的数据域可以不存储任何信息 也可以存储如线性表的长度等附加信息,头结点的指针域存储指向第一个 结点的指针
头指针与头结点的异同
线性表链式存储结构
代码描述
结点由存放数据元素的数据域存放后继结点 地址的指针域组成。假设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
单链表的读取
获得链表第i个数据的算法思路:
- 声明一个结点P指向链表第一个结点,初始化j从1开始;
- 当j<i时,就遍历链表,让P的指针向后移动,不断指向下一结点,j累加1;
- 若到链表末尾P为空,则说明第i个元素不存在;
- 否则查找成功,返回结点P的数据。
头开始找,直到第i个元素为止。由于这个算法的时间复杂度取 决于i的位置,当时,则不需遍历,第一个就取出数据了,而当i=n时则遍历
n-1 次才可以。因此最坏情况的时间复杂度是0(n)
主要核心思想:“工作指针后移” 由于单链表的结构中没有定义表长,所以不能事先知道要循环多少次,因此也就 不方便使用for来控制
循环。
单链表的插入与删除
切记 切记 顺序不能反
单链表第i个数据插入结点的算法思路:
- 声明一结点P指向链表第一个结点,初始化j从1开始;
- 当何时,就遍历链表,让P的指针向后移动,不断指向下一结点,j累加1;
- 若到链表末尾P为空,则说明第i个元素不存在;
- 否则查找成功,在系统中生成一个空结点s;
- 将数据元素e赋值给s->data;
- 单链表的插入标准语句s->next=p->next; p->next=s;
- 返回成功。
我们用到了 C语言的malfoc标准函数,它的作用就是生成一 个新的结点,其类型与Node是一样的,其实质就是在内存中找了一小块空
地,准备用来存放e数据s结点。
解释 L=(Linklist)malloc(sizeof(Node))含义
单链表的删除
单链表第i个数据删除结点的算法思路:
- 声明一结点P指向链表第一个结点,初始化j从1开始;
- 当Ki时,就遍历链表,让P的指针向后移动,不断指向下一个结点,j累加 1;
- 若到链表末尾P为空,则说明第i个元素不存在;
- 否则查找成功,将欲删除的结点p->next赋值给q;
- 单链表的删除标准语句p->next=q->next;
- 将q结点中的数据赋值给e,作为返回;
- 释放q结点;
- 返回成功。
代码
这段算法代码里,我们又用到了另一个C语言的标准函数free。它的作用就是让 系统回收一个Node结点,释放内存。
它们是由两部分组成:第一部分就是遍历查找第i个元素;第二部分就是插入和删除元素。
对于插入或删除数据越频 繁的操作,单链表的效率优势就越是明显。
单链表的整表创建
单链表整表创建的算法思路:
- 声明一结点P和计数器变量i;
- 初始化一空链表L;
- 让L的头结点的指针指向NULL,即建立一个带头结点的单链表;
- 循环:
♦ 生成一新结点赋值给P;
♦ 随机生成一数字赋值给P的数据域p->data;
♦ 将p插入到头结点与前一新结点之间。
头插法
尾插法
L与r的关系,L是指整个单链表,而r是指向尾结点的变量,r会随着循环 不断地变化结点,而L则是随着循环增长为一个多结点的链表。
理解
r->next=p;的意思,其实就是将刚才的表尾终端结点r的指针指 向新结点P, 如图
r=p;就是本来r是在ai-1元素的结点,可现在它已经不是最后的结点了,现 在最后的结点是ai所以应该要让将p结点这个最后的结点赋值
给r。此时r又是最终的尾结点了。
单链表的整表删除
单链表整表删除的算法思路如下:
- 声明一结点P和q;
- 将第一个结点赋值给P;
- 循环:
♦ 将下一结点赋值给q;
♦ 释放p;
♦ 将q赋值给p。
单链表结构与顺序存储结构优缺点
若线性表需要频繁查找,很少进行插入和删除操作时,宜釆用顺序存储结构。
若需要频繁插入和删除时,宜采用单链表结构。
当线性表中的元素个数变化较大或者根本不知道有多大时,最好用单链表结构
而如果事先知道线性表的大致长度,这种用顺序存储结构效率会高很多。比如一年12个月,一周就是星期一至星期日共七天,
静态链表(游标实现法)
数组来代替指针,来描述单链表。
数组的元素都是由两个数据域组成,data和cur。也就是说,数组的每 个下标都对应一个data和一个cur0数据域data,用来存放数据元
素,游标cur相当于单链表中的next指针,存放该元素的后继在数组 中的下标。
通常把未被使用的数组元素称为备用链表。
静态链表的插入操作
静态链表的删除操作
前面代码都一样,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中
静态链表的ListLength
静态链表优缺点
循环链表
定义
单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一 个环,这种头尾相接的单链表称为单循环链表,简称循环链表 (circular linked list)
尾指针 rear
终端结点用尾指针rear指示,则查找终端结点是0(1),而开 始结点,其实就是rear->next->next,其时间复杂也为0(1)。
合并两个单循环链表
双向链表
定义
双向链表 (double linked list) 是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。
所以在双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接 前驱。
结构
对于链表中的某一个结点P,它的后继的前驱是谁?当 然还是它自己。它的前驱的后继自然也是它自己,即:
插入
双向链表既然是比单链 表多了如可以反向遍历査找等数据结构,那么也就需要付出一些小的代价:在插入和 删除时,需要更改两个指针变量。
键在于它们的顺序,由于第2步和第3步都用到了 p->next。如果第4步先执 行,则会使得p->next提前变成了 s,使得插入的工作完不成。所以我们不妨把上面这 张图在理解的基础上记忆,顺序是先搞定s的前驱和后继,再搞定后结点的前驱,最 后解决前结点的后继。
删除
它由于每个结点都需要记录两份 指针,所以在空间上是要占用略多一些的。
说白了,就是 用空间来换时间。
总结
线性表的这两种结构其实是后面其他数据结构的基础