文章目录
链表
使用list类有一些缺点:
1.一个动态数组的长度可能超过实际存储数组元素所需的长度。
2.在实时系统中对操作的摊销边界时不可接受的。
3.在一个数组内部执行插入和删除操作的代价太高。
单向链表
多个节点的集合共同构成的一个线性序列。
每个节点有两个部分组成:元素存储地址与下一个节点的存储地址。
链表的第一个和最后一个节点称为列表的头节点和尾节点。
从头节点开始,通过每个节点的next引用,可以从一个节点移动到另一个节点。
遍历链表、链接跳跃、指针跳跃。
一个链表需要包括:head、node、tail
head:对头部节点的引用;
node:element:对外部对象的引用;next:对下一节点的引用;
tail:对尾节点的引用。
另外还需要size来记录链表的长度。
在单向链表的头部插入一个元素
流程:
1.创建一个新节点newest;
2.更改头部指针。
在单向链表的尾部插入一个元素
流程:
1.创建一个新节点newest;
2.更改当前tail节点的next引用;
3.更改tail指向。
从单向链表的头部删除一个元素
更改head指向即可。
一个节点不被引用会被收回。
需要注意的是,我们难以删除尾节点,我们需要找到倒数第二个节点,而这需要从头遍历。
用单向链表实现栈
注:链表的头部设置为栈顶。
这是因为尾节点难以删除。
class LinkedStack(object):
class _Node(object):
__slots__ = '_element','_next'
def __init__(self,element,next):
self._element = element
self._next = next
def __init__(self):
self._head = None
#没有用到tail。
self._size = 0
def __len__(self):
return self._size
def is_empty(self):
return self._size == 0
def push(self,e):
self._head = self._Node(e,self._head) #指向Node的实例即可。同时完成创建与连接。head记录了旧的实例。内部实例,加self。
self._size += 1
def top(self):
if self.is_empty(): #内部函数,加self。
raise Empty('Stack is empty!')
return self._head._element #返回的是栈顶的元素。
def pop(self):
if self.is_empty():
raise Empty('Stack is empty!')
answer = self._head._element
self._head = self._head._next #next存储的是实例。每一个节点,都是一个实例。
self._size -= 1
return answer
时间复杂度分析
操作 | 时间复杂度 |
---|---|
S.push(e) | O(1) |
S.pop() | O(1) |
S.top() | O(1) |
len(S) | O(1) |
S.is_empty | O(1) |
用单向链表实现队列
class LinkedQueue():
class _Node():
__slots__ = '_element','_next'
def __init__(self,element,next): #2个属性
self._element = element
self._next = next
def __init__(self): #3个属性
self._head = None
self._tail = None
self._size = 0
def __len__(self):
return self._size
def is_empty(self):
return self._size == 0
def first(self):
if self.is_empty():
raise Empty('Queue is empty!')
return self._head._element
def enqueue(self,e):
new_node = self._Node(e,None)
if self.is_empty(): #空与非空需要讨论
self._head = new_node
else:
self._tail._next = new_node
self._tail = new_node
self._size += 1
def dequeue(self):
if self.is_empty():
raise Empty('Queue is empty!')
answer = self._head._element
self._head = self._head._next
self._size -= 1
if self.is_empty(): #需要更新尾部
self._tail = None
return answer
循环链表
用循环链表实现队列
class CircularQueue():
class _Node():
__slots__ = '_element','_next'
def __init__(self,element,next):
self._element = element
self._next = next
def __init__(self):
self._tail = None
self._size = 0
def __len__(self):
return self._size
def is_empty(self):
return self._size == 0
def first(self):
if self.is_empty():
raise Empty('Queue is empty')
elif self._size == 1:
return self._tail._element
else:
return self._tail._next._element
def enqueue(self,e):#单独元素指向本身
new_node = self._Node(e,None)
if self.is_empty():
new_node._next = new_node
else:
new_node._next = self._tail._next
self._tail._next = new_node
self._tail = new_node #_tail这些实例变量最后处理。
self._size += 1
def dequeue(self):#讨论0、1、其他三种情况
if self.is_empty():
raise Empty('Queue is empty!')
oldhead = self._tail._next
elif self._size == 1:
self._tail = None
else :
self._tail._next = oldhead._next
self._size -= 1
return oldhead._element
双向链表
一个节点由三个部分组成:前驱节点的存储地址,元素的存储地址以及后继节点的存储地址。
双向链表需要包括:header、node和tailer。
每一个部分都是一个node,由prev、element、next组成。
哨兵
header和tailer称为头哨兵和尾哨兵。
初始化:
header.prev = None
header.next = tailer
tailer.prev = header
tailer.next = None
优点
头节点和尾节点从来不改变,这样可以用统一的方式处理所有节点的操作。
在之前的实现中,都需要引入is_empty。
双端链表的插入和删除
1.创建一个新节点,设置其前驱节点和后继节点;
2.更改前驱节点和后继节点的next和prev。
更改前驱节点和后继节点的引用即可。
双向两表的基本实现
class _DoublyLinkedBase():
class _Node():
__slots__ = '_element','_next','_prev'
def __init__(self,element,next,prev):
self._element = element
self._next = next
self._rev = prev
def __init__(self):
self._header = self._Node(None,None,None)
self._trailer = self._Node(None,None,None)
self._header._next = self._trailer
self._trailer._prev = self._header
self._size = 0
def __len__(self):
return self._size
def is_empty(self):
return self._size == 0
def _insert_between(self,e,predecessor,successor): #在任何地方插入
newest = self._Node(e,predecessor,successor)
predecessor._next = newest
successor.prev = newest
self._size += 1
return newest
def _delete_node(self,node): #在任何地方删除
predecessor = node._prev
successor = node._next
predecessor._next = successor
successor._prev = predecessor
self._size -= 1
element = node._element
node._prev = node._next = node._element = None
return element
用双向链表实现双端队列
class LinkedDeque(_DoublyLinkedBase):
def first(self):
if self.is_empty():
raise Empty('Deque is empty!')
return self._header._next._element
def last(self):
if self.is_empty():
raise Empty('Deque is empty!')
return self._trailer._prev._element
def insert_first(self,e):
self._insert_between(e,self._header,self._header._next)
def insert_last(self,e):
self._insert_between(e,self._trailer._prev,self._trailer)
def delete_first(self):
if self.is_empty():
raise Empty('Deque is empty!')
return self._delete._node(self._header._next)
def delete_last(self):
if self.is_empty():
raise Empty('Deque is empty!')
return self._delete._node(self._trailer._prev)
位置列表的抽象数据类型
我们目前讨论的抽象数据类型包括了栈、队列和双向队列。
缘由:以上数据类型是有局限的,无法实现插队操作,因为无法便利地获得其位置。
处理基于数组的序列时,整数索引提供了一种很好的方式来描述一个元素的位置。然而数字索引并不适合描述一个链表内部的位置。同时索引并非好的抽象,序列中不停地发生插入或删除操作,这会导致条目的索引值不断发生变化。
节点的引用表示位置
关于位置列表这一数据结构的实现,我们计划采用链表结构,好处是:只要给出列表相关节点的引用,他可以实现在列表的任意位置执行插入和删除的时间复杂度都是O(1)。因此,我们决定采用以一个节点的引用实现描述位置的机制。
含位置信息的列表抽象数据类型
操作 | 作用 |
---|---|
p.element() | 返回存储在位置p的元素 |
L.first() | 返回L中第一个元素的位置,若为空,返回None |
L.last() | 返回L中最后一个元素的位置,若为空,返回None |
L.before(p) | 返回L中p紧邻的前面的元素的位置,若为第一个位置,返回None |
L.after(p) | 返回L中p紧邻的后面的元素的位置,若为最后一个位置,返回None |
L.is_empty() | 如果L列表不包含任何元素,返回True |
len(L) | 返回列表的元素个数 |
iter(L) | 返回列表元素的前向迭代器 |
L.add_first(e) | 在L的前面插入新元素e,并返回新元素的位置 |
L.add_last(e) | 在L的后面插入新元素e,并返回新元素的位置 |
L.add_before(p,e) | 在L中位置p之前插入一个新元素e,并返回新元素的位置 |
L.add_after(p,e) | 在L中位置p之后插入一个新元素e,并返回新元素的位置 |
L.replace(p,e) | 用元素e取代位置p处的元素,返回之前p位置处的元素 |
L.delete(p) | 删除并返回L中位置p处的元素,取消该位置 |
双向链表实现位置列表
命题:当使用双向链表实现时,位置列表ADT每个方法的运行时间的为最坏情况O(1).
class _DoublyLinkedBase:
__slots__ = '_element','_next','_prev'
class _Node:
def __init__(self,element,next,prev):
self._element = element
self._next = next
self._prev = prev
def __init__(self):
self._header = self._Node(None,None,None)
self._tailer = self._Node(None,None,None)
self._header._next = self._tailer
self._tailer._prev = self._header
self._size = 0
def __len__(self):
return self._size
def is_empty(self):
return self._size == 0
def _insert_between(self,e,predecessor,successor):
newest = self._Node(e,predecessor,successor)
predecessor._next = newest
successor._prev = newest
self._size += 2
return newest
def _delete_node(self,node):
predecessor = node._prev
successor = node._next
predecessor._next = successor
successor._prev = predecessor
node._prev = None
node._next = None
self._size -= 1
return node._element
class PositionalList(_DoublyLinkedBase):
class Position:
def __init__(self,container,node):
self._container = container #存储实例的位置,该实例是self,也就是PositionalList
self._node = node
def element(self):
return self._node._element
def __eq__(self,other): #位置是否一致
return type(other) is type(self) and other._node is self._node
def __ne__(self,other): #位置是否不一致
return not (self == other)
#-----------------------utility method-------------------------
#self指的是实例对象本身,返回的是对象的地址。
def _validate(self,p): #单下划线意味着仅供内部使用。 输入的是位置实例
if not isinstance(p,self.Position): #p是否是Position的实例或者是Position的子类的实例
raise TypeError('p must be proper Position type')
if p._container is not self: #p这个节点属于的链表是本身。
raise ValueError('p does not belong to this container')
if p._node._next is None: #确保p没有被删除,因为delete_node操作会让next和prev变为None
raise ValueError('p is no longer valid')
return p._node #返回p处的底层节点
def _make_positon(self,node): #返回位置节点的位置
if node is self._header or node is self._tailer:
return None
else:
return self.Position(self,node)
#-----------------------accessors------------------------------
def first(self):#返回第一个位置节点
return self._make_positon(self._header._next)
def last(self):#返回最后一个位置节点
return self._make_positon(self,_tailer._prev)
def before(self,p):#返回某个位置节点前的位置节点
node = self._validate(p)
return self._make_positon(node._prev)
def after(self,p):#返回某个位置节点后的位置节点
node = self._validate(p)
return self._make_positon(node._next)
def __iter__(self): #返回列表中每一个对象元素。
cursor = self.first()
while cursor is not None:
yield cursor.element()
cursor = self.after(cursor)
#-----------------------mutators--------------------------------
def _insert_between(self,e,predecessor,successor):
node = super()._insert_between(e,predecessor,successor)
return self._make_positon(node) #插入新的节点需要返回其位置节点的位置,目的是方便后续其他操作。
def add_first(self,e):
return self._insert_between(e,self._header,self._header._next)
def add_last(self,e):
return self._insert_between(e,self._tailer_prev,self._tailer)
def add_before(self,p,e):
original = self._validate(p)
return self._insert_between(e,original._prev,original)
def add_after(self,p,e):
original = self._validate(p)
return self._insert_between(e,original,original._next)
def delete(self,p):
original = self._validate(p)
return self._delete_node(original)
def replace(self,p,e):
original = self._validate(p)
old_value = original._element
original._element = e
return old_value
位置列表的排序
插入排序
marker:列表当前排序部分最右边的位置;
walk:向marker左侧移动;
pivot:列表当前微排序部分最左侧的位置。
# 升序
def insertion_sort(L):
if Len(L)>1:
marker = L.first()
while marker != L.last():
pivot = L.after(marker)
value = pivot.element()
if value > marker.element():
marker = pivot
else:
walk = marker
while walk != L.first() and L.before(walk).element()>value:
walk = L.before(walk)
L.delete(pivot)
L.add_before(walk,value)
首先我们需要保证这个位置列表是值得排序的。
marker一直在向右移动。
pivot一直在marker右侧。
在判定pivot的value比marker小之后,我们引入walk,使walk向左移动。
共需要两个判断两个循环。
基于链接的序列与基于数组的序列
基于数组的序列的优点
- 数组提供时间复杂度为O(1)的基于整数索引的访问元素的方法。
- 具有等效边界的操作使用基于数组结构运行一个常数因子比链表结构更有效率。
- 相较于链式结构,基于数组的表示使用存储的比例更少。
n个元素最坏的情况,系统已经为数组分配2n个空间,而链表本身就需要2n个空间。
基于链表的序列的优点
1.基于链表的结构为他们的操作提供最坏情况的时间界限。
2.基于链表的结构支持在任意位置进行时间复杂度为O(1)的插入和删除操作。