线性表是具有
相同
数据类型的n (n>=0)个数据元素的有限序列,其中n为表长,当n = 0时线性表是一个空表。
若用L命名线性表,则其一般表示为 L = ( a 1 , a 2 , . . . , a i , a i + 1 , . . . , a n ) L= (a1, a2,... , a_i, a_{i+1}, ... , an) L=(a1,a2,...,ai,ai+1,...,an)
a i a_i ai是线性表中的“第i个”元素线性表中的
位序
注意:位序从1开始,数组下标从0开始。
a 1 a_1 a1是表头元素
;
a n a_n an是表尾元素
。
除第一个元素外,每个元素有且仅有一个直接前驱
;
除最后一个元素外,每个元素有且仅有一个直接后继
.
“&”符号的作用:对参数的修改结果带回主函数
。
①InitList(&L):
初始化
表。构造一个空的线性表L,分配内存空间
。
②DestroyList(&L):销毁
操作。销毁线性表,并释放线性表L所占用的内存空间。
③ListInsert(&L,i,e):插入
操作。在表L中的第i个位置上插入指定元素e.
④ListDelete(&L,i,&e):删除
操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
⑤LocateElem(L,e):按值查找
操作。在表L中查找具有给定关键字值的元素。
⑥GetElem(L,i):按位查找
操作。获取表L中第i个位置的元素的值。
⑦Length(L):求表长。返回线性表L的长度,即L中数据元素的个数。
⑧PrintList(L):输出操作。按前后顺序输出线性表L的所有元素值。
⑨Empty(L):判空操作。若L为空表,则返回true,否则返回false。
顺序表:用
顺序存储
的方式实现线性表。
顺序存储:把逻辑上相邻的元素存储在物理位置上也相邻
的存储单元中,元素之间的关系由存储单元的邻接关系来体现。
顺序表的实现方式:静态分配和动态分配。
存储空间是静态的。
使用“静态数组”实现,大小一旦确定就无法改变。
①
随机访问
,即可以在(O1)时间内找到第i个元素。
②存储密度高,每个节点只存储数据元素。
③拓展容量不方便(即便采用动态分配的方式实现,拓展长度的时间复杂度也比较高)
④插入、删除操作不方便,需要移动大量元素。
①最好情况:新元素插入到表尾,不需要移动元素。
i = n + 1 i = n+1 i=n+1,循环0次;最好时间复杂度
=O(1)
②最坏情况:新元素插入到表头,需要将原有的n个元素全都向后移动。
i = 1 i = 1 i=1,循环n次;最坏时间复杂度
= O(n);
③平均情况:假设新元素插入到任何一个位置的概率相同,
即 i = 1 , 2 , 3 , . . , l e n g t h + 1 的概率都是 p = 1 n + 1 i=1,2,3,.. , length+1的概率都是p=\frac{1}{n+1} i=1,2,3,..,length+1的概率都是p=n+11
平均循环次数= n ( n + 1 ) 2 1 n + 1 = n 2 \frac{n(n+1)}{2}\frac{1}{n+1}=\frac{n}{2} 2n(n+1)n+11=2n,平均时间复杂度
= O(n)
①最好情况:删除表尾元素,不需要移动其他元素。
i = n,循环0次;最好时间复杂度
=O(1)
②最坏情况:删除表头元素,需要将后续的n-1个元素全都向前移动。
i = 1,循环n-1 次;最坏时间复杂度
= O(n);
③平均情况:假设删除任何一个元素的概率相同,即 i = 1 , 2 , 3 , . . . , l e n g t h i= 1,2,3,... , length i=1,2,3,...,length的概率都是 p = 1 n p =\frac{1}{n} p=n1
平均循环次数 = n ( n − 1 ) 2 1 n = n − 1 2 \frac{n(n-1)}{2}\frac{1}{n}=\frac{n-1}{2} 2n(n−1)n1=2n−1,平均时间复杂度
= O(n)
GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。
由于顺序表的各个数据元素在内存中连续存放,因此可以根据起始地址和数据元素大小立即找到第i个元素:“随机存取
”特性。
时间复杂度为O(1)
LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。
①最好情况
:目标元素在表头,循环1次;最好时间复杂度=O(1)
②最坏情况
:目标元素在表尾,循环n 次;最坏时间复杂度= O(n);
③平均情况
:假设目标元素出现在任何一个位置的概率相同,都是 1 n \frac{1}{n} n1,平均循环次数= n ( n + 1 ) 2 1 n = n + 1 2 \frac{n(n+1)}{2}\frac{1}{n}=\frac{n+1}{2} 2n(n+1)n1=2n+1
平均时间复杂度= O(n)
单链表由一个一个结点组成,每个结点除了存放数据元素外,还要存储指向下一个节点的指针。
优点
:不要求大片连续空间,改变容量方便。
缺点
:不可随机存取,要耗费一定空间存放指针。
强调这是一个单链表:使用 L i n k L i s t LinkList LinkList
强调这是一个结点:使用 L N o d e ∗ LNode * LNode∗
头指针本身不存储数据,数据域为空,只有他指向的下一个结点开始存储数据。
不带头结点,写代码更麻烦。
对第一个数据结点和后续数据结点的处理需要用不同的代码逻辑,对空表和非空表的处理需要用不同的代码逻辑。
Listlnsert(&L,i,e):插入操作。在表L中的
第i个位置
上插入指定元素e。
①带头结点
平均时间复杂度:O(n)
②不带头结点
①使用带
头指针
的链表,循环查找元素的前驱
,再对元素前驱进行后插操作。
时间复杂度为O(n)
②将新增结点复制为需要进行前插操作
的结点,再将待插入的数据的数据域复制给当前结点,连接两个结点即可。
时间复杂度为O(1)
ListDelete(&L,i,&e):删除操作。删除表L中
第i个位置
的元素,并用e返回删除元素的值。
找到第i-1个结点,将其指针指向第i+1个结点,并释放第i个结点。
删除结点p,需要修改其前驱结点的next指针,
方法1:传入头指针,循环寻找p的前驱结点
方法2:偷天换日(类似于结点前插的实现)
时间复杂度为O(1),但如果p为最后一个结点时会存在空指针问题
。
平均时间复杂度为O(n).
时间复杂度:O(n)
要设置一个指向表尾结点的指针。
双链表
不可随机存取
,按位查找,按值查找操作都只能用遍历的方式实现。
时间复杂度O(n)
表尾结点的next指针指向头结点。
从一个结点出发可以找到其他任何一个结点。
①从头结点找到尾部,时间复杂度为O(n)
②从尾部找到头部,时间复杂度为O(1)
③很多时候对链表的操作都是在头部或尾部,可以让L指向表尾元素(插入、删除时可能需要修改L)。
静态链表:分配一整片连续的内存空间,各个结点集中安置。
静态链表:用数组
的方式实现的链表。
优点:增、删操作不需要大量移动元素
缺点:不能随机存取,只能从头结点开始依次往后查找。
容量固定不可变
。
适用场景:
①不支持指针的低级语言;
②数据元素数量固定不变的场景(如操作系统的文件分配表FAT)
①查找
从头结点出发挨个往后遍历结点。时间复杂度为O(n)
②插入
找到一个空的结点,存入数据元素
从头结点出发找到位序为i-1的结点
修改新结点的next
修改i-1号结点的next
③删除
从头结点出发找到前驱结点
修改前驱结点的游标
被删除结点next设为-2
都属于线性表,都是线性结构
①顺序表(顺序存储)
优点:支持随机存取、存储密度高。
缺点:大片连续空间分配不方便,改变容量不方便。
②链表(链式存储)
优点:离散的小空间分配方便,改变容量方便。
缺点:不可随机存取,存储密度低。
①顺序表
需要预分配大片连续空间。
若分配空间过小,则之后不方便拓展容量;
若分配空间过大,则浪费内存资源。
②链表
只需分配一个头结点(也可以不要头结点,只声明一个头指针),之后方便拓展。
①顺序表
修改Length=0
系统自动回收空间
②链表
依次删除各个结点( free )
需要手动free
①顺序表
插入/删除元素要将后续元素都后移/前移
时间复杂度O(n)
时间开销主要来自移动元素
若数据元素很大,则移动的时间代价很高
②链表
插入/删除元素只需修改指针即可
时间复杂度o(n)
时间开销主要来自查找目标元素
查找元素的时间代价更低
①顺序表
按位查找:O(1)
按值查找:O(n)若表内元素有序,可在 O ( l o g 2 n ) O(log_2n) O(log2n)时间内找到
②链表
按位查找:O(n)
按值查找:O(n)
表长难以预估、经常要增加/删除元素—―链表。
表长可预估、查询(搜索)操作较多――顺序表。