1. 线性表的定义
线性表(List):零个或多个数据元素的有限序列
线性表元素的个数 n(n>=0)定义为线性表的长度,当 n=0 时,称为空表
在较复杂的线性表中,一个数据元素可以由若干个数据项组成
2.线性表的抽象数据类型
ADT 线性表(List) Data 线性表的数据对象集合为{a1,a2,......,an},每个元素的类型均为DataType。其中,除第一个元素a1外,
每一个元素有且只有一个直接前驱元素,除了最后一个元素an外,每一个元素有且只有一个后继元素。数据元素之间的关系是一对一的关系。 Operation InitList ( *L ) : 初始化操作,建立一个空的线性表L。 ListEmpty(L): 若线性表为空,返回true,否则返回false。 ClearList(*L):将线性表清空。 GetElem(L,i,*e):将线性表L中的第i个位置元素值返回给e。 LocateElem(L,e):在线性表L中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号表示成功;否则,返回0表示失败。 ListInsert(*L,i,e):在线性表L中的第i个位置插入新元素e。 ListDelete(*L,i,*e):删除线性表L中第i个位置元素,并用e返回其值。 ListLength(L):返回线性表L的元素个数。 endADT
3. 线性表的顺序存储结构
线性表的顺序存储结构:指的是用一段地址连续的存储单元依次存储线性表的数据元
描述顺序存储结构需要的三个属性
- 存储空间的起始位置:数组 data,他的存储位置就是存储空间的存储位置
- 线性表的最大存储容量:数组长度 MaxSize
- 线性表的当前长度:length
数据长度与线性表长度的区别
- 数组的长度是存放线性表的存储空间的长度,存储分配后这个量一般是不变的
- 线性表的长度是线性表中数据元素的个数,随着线性表插入和删除操作的进行,这个量是变化的
- 在任意时刻,线性表的长度应该小于等于数组的长度
地址计算方法
-
存储器中的每个存储单元都有自己的编号,这个编号称为地址
-
对于第 i 个数据元素 ai 的存储位置可以由 a1 推算得出:LOC(ai) = LOC(a1) + (i-1) * c
- LOC 表示获得存储位置的函数
- c 表示每个元素占用的存储单元
4. 顺序存储结构的插入与删除
插入算法的思路
- 如果插入位置不合理,抛出异常
- 如果线性表长度大于等于数组长度,则抛出异常或动态增加容量
- 从最后一个元素开始向前遍历到第 i 个位置,分别将它们都向后移动一个位置
- 将要插入元素填入位置 i 处
- 表长加 1
删除算法的思路
- 如果删除位置不合理,抛出异常
- 取出删除元素
- 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置
- 表长减 1
存读数据时,时间复杂度都是 O(1);插入或删除时,时间复杂度都是 O(n)
顺序存储结构的优点
- 无须为表示表中元素之间的逻辑关系而增加额外的存储空间
- 可以快速地存取表中任一位置的元素
顺序存储结构的缺点
- 插入和删除操作需要移动大量元素
- 当线性表长度变化较大时,难以确定存储空间的容量
- 造成存储空间的“碎片”
5. 线性表的链式存储结构
定义
- 为了表示每个数据元素 ai 与其直接后继数据元素 ai+1 之间的逻辑关系,对数据元素 ai 来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)
- 我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域
- 指针域中存储的信息称做指针或链
- 这两部分信息组成数据元素 ai 的存储映像,称为结点(Node)
单链表
- n 个结点(ai 的存储映像)链结成一个链表,即为线性表(a1,a2,... ,an)的链式存储结构,因为此链表的每个结点中只包含一个指针域
线性链表的最后一个结点指针为“空”(通常用 NULL 或 “^” 符号表示)
结点由存放数据元素的数据域,和存放后继结点地址的指针域组成
若线性表为空表,则头结点的指针域为“空”
头指针
- 头指针是指链表指向第一个节点的指针,若链表有头结点,则是指向头结点的指针
- 头指针具有表示作用,所以常用头指针冠以链表的名字
- 无论链表是否为空,头指针均不为空。头指针是链表的必要元素
- 整个链表的存取就必须是从头指针开始进行
头结点
- 单链表的第一个结点前附设一个结点,称为头结点,其数据域可不存储信息
- 头结点的指针域存储指向第一个结点的指针
- 有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其它结点的操作就统一了
- 头结点不一定是链表的必须要素
6. 单链表的读取
获得链表第 i 个数据的算法思路
- 声明一个结点 p 指向链表第一个结点,初始化 j 从 1 开始
- 当 j<i 时,就遍历链表,让 p 的指针向后移动,不断指向下一结点,j 累加1
- 若到链表末尾 p 为空,则说明第 i 个元素不存在
- 否则查找成功,返回结点 p 的数据
7. 单链表的插入与删除
单链表第 i 个数据插入结点的算法思路
- 声明一结点 p 指向链表第一个结点,初始化 j 从 1 开始
- 当 j<i 时,就遍历链表,让 p 的指针向后移动,不断指向下一结点, j 累加 1
- 若到链表末尾 p 为空,则说明第 i 个元素不存在
- 否则查找成功,在系统中生成一个空结点 s
- 将数据元素 e 赋值给 s->data
- 单链表的插入标准语句 s->next = p->next;p->next = s
- 返回成功
单链表第 i 个数据删除结点的算法思路
- 声明一结点 p 指向链表第一个结点,初始化 j 从 1 开始
- 当 j<i 时,就遍历链表,让 p 的指针向后移动,不断指向下一结点, j 累加 1
- 若到链表末尾 p 为空,则说明第 i 个元素不存在
- 否则查找成功,将欲删除的结点 p->next 赋值给 q
- 单链表的删除标准语句 p->next = q->next
- 将 q 结点中的数据赋值给 e,作为返回
- 释放 q 结点
- 返回成功
时间复杂度都是 O(n),对于插入或删除数据越频繁的操作,单链表的效率优势就越是明显
8. 单链表的整表创建
算法思路
-
声明一结点 p 和计数器变量 i
-
初始化一空链表 L
-
让 L 的头结点的指针指向 NULL,即建立一个带头结点的单链表
-
循环
- 生成一新结点赋值给 p
- 随机生成一数字赋值给 p 的数据域 p->data
- 将 p 插入到头结点与前一新结点之间
9. 单链表的整表删除
算法思路
-
声明一结点 p 和 q
-
将第一个结点赋值给 p
-
循环
- 将下一结点赋值给 q
- 释放 p
- 将 q 赋值给 p
10. 单链表和顺序存储结构优缺点
存储分配方式
- 顺序存储结构用一段连续的存储单元依次存储线性表的数据元素
- 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素
时间性能
-
查找
- 顺序存储结构 O(1)
- 单链表 O(n)
-
插入和删除
- 顺序存储结构需要平均移动表长一半的元素,时间为 O(n)
- 单链表在找出某位置的指针后,插入和删除时间仅为 O(1)
空间性能
- 顺序存储结构需要与分配存储空间,分大了浪费,分小了易发生上溢
- 单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制
11. 静态链表
描述方法:游标实现法
- 让数组的元素都是两个数据域组成,data 和 cur
- 数组的每个下标对应一个 data 和一个 cur
- 数据域 data,用来存放数据元素,也就是通常我们要处理的数据
- 游标 cur 相当于单链表中的 next 指针,存放该元素的后继在数组中的下标
数组第一个和最后一个元素作为特殊元素处理,不存数据
-
我们通常把未被使用的数组元素称为备用链表
-
数组第一个元素,即下标为 0 的元素的 cur 就存放备用链表的第一个结点的下标
-
数组的最后一个元素的 cur 则存放第一个有数值的元素的下标,相当于单链表中头结点作用
- 当整个链表为空时,则 cur 为0
静态链表的插入操作
- 为了辨明数组中哪些分量未被使用,解决的办法是将所有未被使用过的及已被删除的分量用游标链成一个备用的链表,每当进行插入时,便可以从备用链表上取得第一个结点作为待插入的新结点
静态链表的插入操作
- 删除的位置成为第一个优先空位,把它存入第一个元素的 cur 中
静态链表的优缺点
-
优点
- 在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点
-
缺点
- 没有解决连续存储分配带来的表长难以确定的问题
- 失去了顺序存储结构随机存取的特性
12. 循环链表
定义
- 将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表
13. 双向链表
在单链表的每个结点中,在设置一个指向其前驱结点的指针域
双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱
可以反向遍历查找等数据结构
插入和删除时,需要更改两个指针变量