系列文章目录
文章目录
线性表
线性表的定义和基本操作
一个线性表是由0个或多个具有相同类型的结点组成的有序集合。
用[]表示线性表,n=0时,线性表为空表;n≥1时,
称为表头,
为表尾。n≥2时,
是
的前驱结点,
是
的后继结点。表头无前驱结点,表尾无后继结点。若线性表中的结点是按值(或按关键字值)由小到大(或者由大到小)排列的,则称线性表是有序的
- 线性表可以是单值的:人工智能学院某学科某学年期末考试成绩,按照学号的顺序成绩依次是: 85, 87, 89, 90, 85, ……, 87, 94, 88
- 线性表也可以是记录型的:某校2020级同学的基本情况:{(‘2020414101’,‘张里户’,‘男’,06/24/2003), (‘2020414102’,‘张化司’,‘男’,08/12/2004) …, (‘2020414102’,‘李利辣’,‘女’,08/12/2003) }
线性表的基本操作:
- 创建一个线性表
- 确定线性表的长度
- 判断线性表是否为空
- 在线性表中第k个结点后插入一个新结点(增)
- 删除线性表中第k个结点(删)
- 存取线性表中第k个结点的字段值(改)
- 查找指定字段值在线性表中的位置(查)
- 归并、分拆、复制、排序…...
线性表的顺序存储结构
顺序存储:按逻辑顺序将线性表的结点依次存放在一组地址连续的字节中
顺序存储的线性表也被称为顺序表,若其中元素有序,则称其为有序顺序表
顺序表特点:逻辑顺序和物理顺序相同
对于顺序结构,结点的物理地址可以由数学公式算出
b是表头地址,c为存储结点所需字节数
顺序表的插入操作
插入数据的基本操作
- 判断插入数据是否合法
- 将顺序表L中的第k个至第n个结点后移一个位置
- 将结点item插入到结点
后
- 顺序表长度加1
时间复杂度
- 插入操作的基本运算是元素移动
- 长度为n的线性表有n+1中插入可能
- 设插入合法且插入各位置的概率相同:1/(n+1)
则时间复杂度期望为:
顺序表的删除操作
删除数据的基本操作
- 判断删除结点是否合法
- 将顺序表L中的第k+1个至第n个结点向前移动一个位置
- 顺序表长度减一
时间复杂度
- 删除操作的基本运算是元素移动
- 长度为n的线性表有n中删除可能
- 设删除合法且插入各位置的概率相同:1/(n+1)
则时间复杂度期望为:
顺序表优缺点
优点:
- 空间利用率高
- 简单、易于实现
- 存取速度快
缺点:
- 插入和删除结点时间复杂度高
线性表的数目可以改变,定义数组时需定义足够大的静态数组(可能空间利用率低)或者使用动态数组
线性表的链接存储结构
链式存储:用一组任意的存储单元存储线性表中的数据元素。用这种方法存储的线性表称为线性链表
链表中结点的逻辑顺序和物理顺序不一定相同。存储链表中结点的一组任意的存储单元可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上的。
分类
- 单链表
- 循环链表
- 双链表
单链表
一个单链表结点由以下两个域构成:
- 链表的第一个结点被称为头节点(表头),指向头节点的指针被称为头指针(head)
- 链表的最后一个结点被称为尾结点(表尾),指向尾结点的指针(如果有)被称为尾指针(tail),验证一个结点是否为尾结点,只需考察该结点的next域是否为NULL
![](https://i-blog.csdnimg.cn/direct/5d8a0322897041ca8716258203d00831.png)
- 逻辑顺序与物理顺序可以相同也可不同,即逻辑上相邻的结点与物理上不必相邻
单链表插入结点
- 在单链表中,插入一个结点,只需改变一个或两个相关结点的指针域,不对其它结点产生影响
- 插入操作:在结点 p 之后插入结点 s,next(s) ← next(p) next(p) ← s
单链表删除结点
- 在单链表中,删除一个结点,只需改变一个或两个相关结点的指针域,不对其它结点产生影响
- 删除操作:在结点 p 之后删除结点 q,q ← next (p). next (p) ← next (q) .
哨兵结点
在插入和删除结点时,若我们的操作对象是表头,则需要修改头指针,令其指向新结顶,此时我们可以引入哨兵结点
哨兵结点与其他结点相同,也是由数据域和指针域构成,但数据域没有被赋值,指针域存储指向第一个真正结点的指针,即头节点
哨兵结点不被看作表中实际结点
单链表查找结点
- 在单链表中,在单链表中,查找一个结点,只能头结点开始比对,找到才返回
时间复杂度(增删查)
- 最好情况下时间复杂度为O(1)
- 最坏情况下时间复杂性度为O(n)
- 平均情况下是O(n)
单链表综述
单链表的特性:
- 利用链接域实现线性表元素的逻辑关系
- 单链表有头结点、尾结点、头指针
单链表的优点:
- 插入、删除方便
- 共享空间好,结点可以不连续存储,易于扩充
单链表的不足:
- 单链表虽然克服了顺序存储的缺点,但却不能进行随机访问
- 对单链表来说,只有从头结点开始才能扫描链表中的全部结点
- 从一个结点出发,只能访问到链接在它后面的结点,而无法访问位于它前面的结点
循环链表
- 把链表结构“循环化”,即让表尾结点的next域存放指向哨位结点的指针,而不是存放空指针NULL,这样的链表称为循环链表
- 循环链表的指针域接成一个环
- 循环链表使我们可以从链表任何位置开始,访问链表中的任一结点
- 判断空表的条件:next(head) = head
- 判断表尾的条件:next (p) = head
- 在循环链表中访问某结点p的前驱结点,需遍历整个链表,其时间复杂性为O(n).
循环链表本质就是将单链表进行改造,因此其增删查的操作与单链表相同
双向链表
所谓双向链表(Double-Linked List),指链表中任一结点P都是由data域、左指针域left和右指针域right构成的,左指针域和右指针域分别存放P的左右两边相邻结点的地址信息,链表中表头结点的left指针和表尾结点的right指针均为NULL. 指针head和tail分别指向双向链表的头结点和尾结点,双向链表也被称为双重链表
双向链表特点
- 每个结点有两个指针域left和right
- 左指针指向其前驱结点,右指针指向其后继结点
- 头结点和尾结点的指针域特殊
优点:
- 方便查找某个结点的前驱结点
双向链表插入结点
在s之后插入结点p
- left(right(s)) ← p
- right(p) ← right(s)
- left(p) ← s
- right(s) ← p
双向链表删除结点
删除s结点
- right(left(s)) ← right(s)
- left(right(s)) ← left(s)
各链表使用场景
- 链表的头结点和尾结点使用频繁:带尾指针的循环链表
- 经常查找结点的前驱和后继的场合:双向链表
- 结合循环链表和双向链表的特点,构造双向循环链表
ps:删除链表结点时,记得释放删除结点的存储空间,python等语言会自动释放,但C和C++不会