三、线性表
3.1 线型表的概念和抽象数据类型
3.1.1概念
包括顺序表和链接表,从使用的角度,需要的提供的操作有哪些呢?
1、表的创建以及元素的序列问题
2、表的宏观检查:是否为空、长度
3、表的增删改查
4、多表操作,交集、并集、差集等
5、表元素的遍历操作(apply 函数)
6、表的销毁:这一部分和编程语言有关,C语言需要手动销毁释放资源、python会自动处理。
3.1.2 表的抽象数据类型
汇总:增删改查与遍历操作。有些操作进一步细化了。
3.1.3 线性表实现的基本考虑
两种基本的实现模型:顺序表和链接表
3.2 顺序表的实现
元素存储在连续的存储区域里面。
3.2.1 基本实现方式
基本的方式是同类型元素的连续等距存储,存取操作在O(1)时间内实现。当然也有可能存储的数据类型不一致,那么就存在两种存储方案:存数据、存地址。如果是存地址的话,在访问的时候需要通过地址间接访问。时间复杂度仍可为O(1)。
问题1:建立一个表时,应该安排多大的存储?
首先表有两种:固定表(python中的tuple)和变动表,对于变动表,合理的方式是预留一些空位。C语言中的数组需要定义数组的长度,那么为什么python中的List不需要分配空间呢?
3.2.2 顺序表基本操作的实现
顺序表及其操作的性质
着重讨论下定点增删的时间复杂度:设长度为n,在位置i增删,当考虑保序的情况,对于增加需要移动的元素是n-i,对于删除需要移动的元素是n-i-1.现在求平均时间复杂度。(所以时间复杂度为O(n))
3.2.3 顺序表的结构
按照前面的讨论,一个顺序表有两部分的信息,一是元素的信息,另外是关于整体的信息。所以需要一个对象把两部分封住起来。
两种基本的实现方式
一体式:优点是整体性强,易于管理,缺点是不同表对应的对象不同。
分离式:优点是表宏观部分等长,缺点是数据部分间接访问,不易管理。
替换存储区
分离式的优点在于可以在对象不变的情况下申请新的内存换一个更大的车箱,其过程如下:
后端插入和存储区扩展
3.2.4 python中的LIST
Tuple是固定的表,不支持任何非访问操作,下面集中关注list。
List的基本实现技术:
Python的官方文档中,List是采用分离式技术实现的动态顺序表。
几个操作
lst.clear()
Lst.reverse() # 前后对称的对调
Lst.sort() # 排序 等价于内嵌函数sorted(Lst)
3.3 链接表
3.3.2 单链表
单链表 = 二元组
从引用链表的变量可以找到链表的首节点,紧接着就可以访问表中任一节点。
链表的基本操作:
1、创建空链表:表头变量设置为空链接
2、删除链表:python中在将表指针赋值为None
3、判断是否为空:检查表头变量是否为空链接
加入元素:无论加入到那个位置,都是简单的链接指向修改
删除元素:
删除表头:head = head.next
删除其他位置:pre.next = pre.next.next
丢弃不用的节点将会被python解释器自动收回。
扫描、定位、遍历
此部分的很多是基础代码的编写,就不做详细的记录了。代码中重点关注下遍历操作,迭代器、生成器和谓词操作。
class LNode():
def __init__(self, elem, next=None):
self._elem = elem
self._next = next
class LinkedListUnderflow(ValueError):
pass
class LLIST():
def __init__(self):
self._head = None
self._len = 0
def is_empty(self):
return self._head is None
def prepend(self, elem):
node = Node(elem, self._head)
self._head = node
self._len += 1
def append(self, elem):
node = Node(elem)
p = self._head
if self._head is None:
self._head = node # 以下等价
# self.prepend(elem)
return
while p._next is not None:
p = p._next
p._next = node
def pop(self): # 弹出头部
if self._head is None:
raise LinkedListUnderflow("in pop")
e = self._head._elem
self._head = self._head._next
self._len -= 1
return e
def pop_last(self):
if self._head is None:
raise LinkedListUnderflow("IN POP_LAST")
if self._head._next is None: # 长度为1
e = self._head._elem
self._head = None
return e
p = self._head
while p._next._next is not None:
p = p._next
e = p._next._elem
p._next = None
return e
def for_each(self, op):
p = self._head
while p is not None:
p._elem = op(p._elem)
p = p._next
def elements(self):
p = self._head
while p is not None:
yield p._elem
p = p._next
def find_elems(self, op):
p = self._head
while p is not None:
if op(p._elem):
yield p._elem
p = p._next
def print_all(self):
infos = ''
for info in self.elements():
infos += info + '->'
infos = infos[:-2]
print(infos)
def reverse(self):
p = None # 临时变量
while self._head is not None:
q = self._head
self._head = q._next
q._next = p
p = q
self._head = p
llist = LLIST()
llist.append("1")
llist.append("加工")
llist.append("2")
llist.append("销售")
llist.print_all()
llist.reverse()
llist.print_all()
输出:
1->加工->2->销售
销售->2->加工->1
3.4 链表的变形和操作
3.4.1略
3.4.2 循环单链表
这种表对象只需一个数据域_real,它在逻辑上始终引着表的尾节点,因为这样支持时间复杂度为O(1)的表头增删和表尾增加。
class LCList(): # 注意空表和长度为1的表
def __init__(self):
"""循环单链表"""
self._rear = None
def is_empty(self):
return self._rear is None
def prepend(self, elem): # 前端增加
node = Node(elem)
if self._rear is None:
node._next = node
self._rear = node
else:
node._next = self._rear._next
self._rear._next = node
def append(self, elem): # 后端增加
self.prepend(elem)
self._rear = self._rear._next
def pop(self): # 前端删除
if self._rear is None:
raise LinkedListUnderflow
elif self._rear == self._rear._next:
self._rear = None
else:
self._rear._next = self._rear._next._next
def pop_last(self):
if self._rear is None:
raise LinkedListUnderflow
elif self._rear == self._rear._next:
self._rear = None
else:
p = self._rear._next
while p is not self._rear:
if p._next == self._rear:
self._rear = p
self.pop()
break
p = p._next
def elements(self):
p = self._rear._next
while p is not self._rear:
yield p._elem
p = p._next
lclist = LCList()
for i in range(1, 6):
lclist.append(i)
for ele in lclist.elements():
print(ele, end=' ')
print('')
lclist.prepend(0)
lclist.append(5)
for ele in lclist.elements():
print(ele, end=' ')
print('')
lclist.pop()
lclist.pop_last()
for ele in lclist.elements():
print(ele, end=' ')
输出:
1 2 3 4
0 1 2 3 4 5
1 2 3 4
3.4.3 双链表
每个节点视为三元组(elem, prev,next).首节点的prev和尾结点的next=None,这也是遍历的终止条件。
节点操作
节点删除:p.prev.next = p.next; p.next.prev = p.prev
class DLNODE():
def __init__(self, elem, prev=None, next=None):
"""双链表"""
self._prev = prev
self._next = next
self._elem = elem
class DLnodeFlow(ValueError):
pass
class DLLIST():
def __init__(self):
self._head = None
self._rear = None
def prepend(self, elem): # 头结点的prev是None,尾结点的next是None
node = DLNODE(elem, next=self._head,) # 直连头结点
if self._head is None: #
self._rear = node
else:
self._head._prev = node
self._head = node
def append(self, elem):
node = DLNODE(elem, prev=self._rear)
if self._head is None:
self._head = node
else:
self._rear._next = node
self._rear = node
def elements(self):
p = self._head
while p is not None:
yield p._elem
p = p._next
def re_elements(self):
p = self._rear
while p is not None:
yield p._elem
p = p._prev
dllist = DLLIST()
for i in range(1, 5):
dllist.append(i)
for elem in dllist.elements():
print(elem, end=' ')
print('')
dllist.prepend(0)
for elem in dllist.re_elements():
print(elem, end=' ')
输出:
1 2 3 4
4 3 2 1 0
3.4.4 两个链表的操作
链表反转:双链表可以直接互抛,单链表的话,可以逐个的将A链表的pop值prepend到一个新的链表。
链表排序:后面会有排序的专题