1. 线性表的概念和抽象数据类型
1.1 概念
- 线性表,通常是指某种数据元素的一个结合,并且记录着元素之间的一种顺序关系。
1.2 表的基本操作
ADT List: # 一个表的抽象数据类型
List(self) # 表构造操作,创建一个新表
is_empty(self) # 判断 self 是否为空表
len(self) # 获取表 self 的长度
prepend(self, elem) # 将元素 elem 插入表中作为第一个元素
append(self, elem) # 将元素 elem 插入表中最为最后一个元素
insert(self, elem, i) # 将元素 elem 插入表中作为第 i 个元素
del_first(self) # 删除表中的第一个元素
del_last(self) # 删除表中的最后一个元素
del(self, i) # 删除表中第 i 个元素
search(self, elem) # 查找元素 elem 在表中的位置,不存在返回 -1
forall(self, op) # 对表中的每个元素执行 op 操作
复制代码
1.3 线性表的实现
- 将表中元素顺序地存放在一大块连续的存储区里,这样实现的表叫做顺序表。在这种实现中,元素之间的顺序由它们的存储顺序自然表示。
- 将表元素放在通过链接构造起来的一系列存储块里,这样实现的表称为链表。
2. 顺序表
-
布局方案,如图所示
-
创建顺序表时,一般是创建可变的表,所以要考虑表中当前元素的个数以及元素存储区的容量,一个合理的方法是分配拟一块足以容纳当前需要记录的元素的存储块,还应该保留一些空位,以满足增加元素的需要。如图所示:
-
顺序表的基本实现方式
分离式的优点:在标识不变的情况下,为表对象换一块元素存储区。即表还是原来的表,里面内容不变,但是容量增加了。
如果一直不断地向表中添加元素,一定会填满元素存储区,如果采用一体式结构,就会添加失败。
采用分离式结构,可以在不改变对象的情况下换一块更大的元素存储区,使加入元素正常完成。具体步骤如下:①. 另外申请一块更大的元素存储区
②. 把表中已有的元素复制到新的存储区
③. 用新的元素存储区替换原来的元素存储区
④. 加入新元素 -
Python 中的 list
在 Python 的官方实现中, list 就是一种采用分离式技术实现的动态顺序表。
相关操作复杂度:-
len(): O(1)
-
元素访问和复制,尾端加入和尾端删除(包括尾端切片删除) 都是 O(1) 操作
-
一般位置元素的加入,切片替换,切片删除,表拼接(extend)都是 O(n) 操作
-
list.clear() 时间复杂度 O(1)
-
list.reverse() 时间复杂度 O(n)
a = [1, 2, 3, 4, 5, 6, 7, 8, 9] def reverse_list(): i = 0 j = len(a) - 1 while i < j: a[i], a[j] = a[j], a[i] i += 1 j -= 1 reverse_list() print(a) 复制代码
-
-
顺序表的不足
- 由于元素存储的集中方式和连续性,表的结构不灵活,如果要经常修改表结构,顺序表的实现就不太方便。
- 如果程序里需要巨大的线性表,采用顺序表就需要巨大的元素存储空间,会造成存储管理方面的困难。
2. 链表
-
采用链接方式实现线性表的基本思想:
- 把表中的元素份分别存储在一批独立的存储区块里,称为表的结点。
- 保证从组成表的任意一个结点可以找到与其相关的下一个结点。
- 在前一结点里用链接的方式记录与下一个结点之间的关联。
这样,只要能找到表的第一个结点,就能顺序的找到这个表的其他节点。
-
单链表
操作复杂度:
- 创建空表: O(1)
- 删除表: O(1)
- 判断空表: O(1)
- 加入元素:
- 首端加入元素:O(1)
- 尾端加入元素:O(n)
- 定位加入元素:O(n)
- 删除元素:
- 首端删除元素:O(1)
- 尾端删除元素:O(n)
- 定位删除元素:O(n)
-
带有尾结点引用的单链表
单链表有一个缺点: 尾端加入元素操作的效率低
在表对象增加一个表尾结点引用域,只需常亮时间就可以找到尾结点,表尾插入就能达到O(1)。
-
循环单链表
循环单链表与普通单链表的区别是最后一个结点的next不为None,而是指向表的第一个结点,如图 a) 所示。仔细考虑,发现在链表对象记录表尾结点更合适,如图 b)。
这样就支持 O(1) 时间的表头/表尾插入操作,o(1)时间的表头删除
循环单链表的部分代码实现:
-
双链表
对单链表加入另一个方向的链接,就得到了双链表,那么首尾两端插入和删除操作都能高效的完成。
双链表的部分代码实现:
-
循环双链表
让双链表的表尾结点的next指向表的首节点,表首节点的prev指向表的尾结点。这样的话,知道首节点或者尾结点其中一个,就可以高效实现首尾两端的元素加入或删除,时间复杂度为 O(1).
-
链表总结
- 基本单链表,首端插入删除 O(1)
- 增加尾结点引用域的单链表首端/尾端插入和首端删除 O(1)
- 循环单链表首端/尾端插入和首端删除 O(1)
- 双链表,如果有尾结点应用, 则两端插入和删除 O(1)
- 循环双链表,两端插入和删除 O(1)
- 对于单链表,遍历和数据检索只能从表头开始,需要 O(n) 时间。对于双链表,可以从表头或者表尾开始,复杂度也是 O(n)。与它们对应的两种循环链表,遍历和数据检索可以从表中任何一个地方开始。
-
链表优缺点
优点:
- 由于表结构是由一些链接起来的结点形成的,所以表的结构很容易修改。
- 只需要修改结点之间的链接,就能灵活的修改表的结构和数据排列方式。
缺点:
- 定位访问需要线性时间
- 单链表尾端操作需要线性时间,增加一个尾指针,可以将尾端插入变为O(1)操作,但是尾端删除仍是线性时间。只有双链表才能实现两端高效的插入和删除。
- 单链表查找当前元素的前一元素,必须从头开始扫描表结点。双链表可以解决这个问题,但是存储空间增大。
参考
数据结构与算法 Python语言描述(裘宗燕)