文章目录
单向链表
- 遍历列表:从头节点开始,使用
next
引用到达尾节点,这个过程叫做遍历链表。 - 链表跳跃/指针跳跃:遍历链表的过程。
- 每个节点被表示为唯一的对象。
- 链表实例至少必须包括一个指向链表头节点的引用。
- 显示的保存一个指向尾节点的引用,是避免为访问尾节点而进行链表遍历的常用方法
- 链表实例保存一定数量的节点总数(通常称为列表)可以避免为计算节点数量而需要遍历整个链表
- 单项链表没有预先确定的大小
在单项链表的头部插入一个元素
- 创建一个新的节点,将新节点的元素域设置为新元素
- 将该节点的“next”指针指向当前的头节点
- 设置列表的头指针指向新节点
注:要在为新节点分配头指针变量之前设置新节点的“next”指针。如果初始列表为空(即头指针为空),那么就将新节点的“next”指针指向空。
在单项链表的尾部插入一个元素
- 创建一个新的节点,将其“next”指针设置为空,并设置尾节点的“next”指针指向新节点
- 更新尾指针指向新节点
注:在设置尾指针指向新节点之前,设置尾节点的“next”指针指向原来的尾节点。
从单向链表中头部删除一个元素
- 设置列表的头指针指向下一个节点
- 删除当前节点
用单向链表实现栈
- 一个节点只有两个实例变量:
_elment
和_next
(元素引用和指向下一节点的引用) - 为了提高内存的利用率,定义
__slots__
来避免实例对象命名空间的辅助字典的创建,因为一个单项链表可能有多个节点实例
class Empty(Exception):
""" 重写异常 """
pass
class LinkedStack:
""" 单向链表实现栈ADT """
class _Node:
""" 存储单个链表节点的轻量级,私有类 """
__slots__ = '_element', '_next_'
def __init__(self, element, next):
self._element = element
self._next = next
def __init__(self):
""" 创建一个空栈 """
self._head = None
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)
self._size += 1
def top(self):
""" 显示栈顶元素 """
if self.is_empty():
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
self._size -= 1
return answer
用单向链表实现队列
- 在实现栈的时候只需要使用到链表的头部节点,当时队列需要使用到链表的头部和尾部节点。
- 使用嵌套类
_Node
来定义节点 - 当只有队列中只有一个元素的时候,在执行出队操作时,要删除列表的尾部,还要设置尾指针为空
- 在入队时,新节点将成为新的链表的尾部,当这个节点是唯一节点时,该节点也将成为新的链表的头部;否则,新的节点必须被立即链接到现有的尾部节点之后。
class LinkedQueue:
""" 用单向链表实现队列的ADT """
class _Node:
""" 存储单个节点轻量级的私有类 """
def __ini__(self):
""" 创建一个空的队列 """
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 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
def enqueue(self, e):
""" 进入队尾 """
newest = self._Node(e, None)
if self.is_empty():
self._head = newest
else:
self._tail._next = newest
self._tail = newest
self._size += 1
循环链表
- 使链表的尾部节点的
“next”
指针指向链表的头部 - 必须为一个特定的节点维护一个引用才能使用循环链表
轮转调度
-
以循环的方式迭代的遍历一个元素的集合,并通过执行一个给定的动作为集合中的每个元素进行服务。
- e = Q.dequeue()
- Service element e
- Q.enqueue(e)
用循环链表实现队列
- 使用循环链表实现队列不需要同时保存头指针和尾指针,只需要保存尾指针,就能通过尾部的
“next”
引用找到头部 - 循环链表实现的队列除了队列的传统操作外,还支持一个循环的方法:设
self._tail=self._tail._next
,以使原来的头部变成新的尾部(原来头部的后继节点成为新的头部)。该方法可以更有效的实现删除队首的元素以及将该元素插入队尾这两个操作的合并处理。
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')
head = self._tail._next
return head._element
def dequeue(self):
""" 弹出队首元素 """
if self.is_empty():
raise Empty('Queue is empty')
oldhead = self._tail._next
if self._size == 1:
self._tail = None
else:
self._tail._next = oldhead._next
self._size -= 1
return oldhead._element
def dequeue(self, e):
""" 进入队尾 """
newest = self._Node(e, None)
if self.is_empty():
newest._next = newest
else:
newest._next = self._tail._next
self._tail._next = newest
self._tail = newest
self._size += 1
def rotate(self):
""" 使原来的头部变成新的尾部 """
if self._size > 0:
self._tail = self._tail._next
双向链表
- 每个节点都维护了指向其先驱节点以及后继节点的引用
头哨兵和尾哨兵
- 在链表的两端都追加节点,这些特定的节点被称为哨兵,并且不存储主序列的元素
- 初始化一个空链表,使头节点的“next”域指向尾节点,并令尾节点的“prev”域指向头节点;对于一个非空的列表,头节点的“next”域将指向一个序列中第一个真正包含元素的节点,对应的尾节点的“prev”域指向这个序列中最后一个包含元素的节点
使用哨兵的优点
- 哨兵只占用很小的额外空间就能极大地简化操作的逻辑
- 头和尾节点从来不改变
- 可以用统一的方式处理所有插入节点的操作
双向链表的插入和删除
- 向双向链表插入节点的每个操作都将发生在两个已有节点之间
- 被删除节点的两个邻居直接相互连接起来,从而绕过被删除节点
双向链表的基本实现
-
当处理一个链表时,描述一个操作的位置最直接的方法是找到与这个列表相关联的节点
-
双向链表的版本和单向链表比多了
_prev
属性 -
构造函数实例化两个哨兵节点并将这两个节点直接链接
-
删除节点时,设置被删除节点的
_prev、_next、_element
域为空,利用了python的垃圾回收机制class _DoubleLinkedBase: """ 管理双向链表的基类 """ class _Node: """ 用于双向链表的Python_Node类 """ __slots__ = 'element', '_prev', '_next' def __init__(self, element, prev, next): self._elment = element self._prev = prev self._next = next 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 def __len__(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._elment node._prev = node._next = node._elment = None return element
用双向链表实现双端队列
-
继承前边的基本类来初始化一个新的实例
-
双端队列前端插入一个元素,需要将这个元素立即插入头节点和其后的一个节点之间
-
在末尾插入节点,可直接将节点置于尾节点之前
class LinkedDequeue(_DoubleLinkedBase):
""" 从继承双向链基类而实现的链式双端队列类 """
def first(self):
""" 显示队头元素 """
if self.is_empty():
raise Empty('Dequeue is empty')
return self._header._next._elment
def last(self):
""" 显示队尾元素 """
if self.is_empty():
raise Empty('Dequeue is empty')
return self._trailer._prev._elment
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('Dequeue is empty')
return self._delete_node(self._header._next)
def delete_last(self):
""" 弹出队尾元素 """
if self.is_empty():
raise('Dequeue is empty')
return self._delete_node(self._trailer._prev)
位置列表的抽象数据类型
希望一个抽象数据类型来为用户提供一种可以定位到序列中任何元素的方法,并且能够执行任意的插入和删除操作。
节点的引用表示位置
以一个节点引用实现描述位置的机制,使用这种数据结构的原因如下:
- 对于用户来说希望不被数据结构的实现中那些低级操作等不必要的细节所干扰
- 不允许用户直接访问或操作节点
- 通过更好地封装实施的内部细节,可以获得更大的灵活性来重新设计数据结构以及改善性能
含位置信息的列表抽象数据类型
- 将一个位置作为更广泛的位置列表中的一个标记或标志
- 改变列表的其他位置不会影响位置P
- 使一个位置变得无效的唯一方法就是直接显式地发出一个命令来删除它
- 含位置信息列表ADT中方法的返回值是相关位置,不是元素,通过调用位置的方法来获取元素
- 位置实例是一个简单的对象,只支持以下的方法:
p.element()
: 返回存储在位置p的元素L.first()
: 返回L中第一个元素的位置,如果L为空,返回NoneL.last()
: 返回L中最后一个元素的位置,如果L为空,返回NoneL.is_empty()
: 如果列表不包含任何元素,返回truelen(L)
: 返回列表元素的个数iter(L)
: 返回列表元素的前向迭代器L.add_first(e)
: 在L的前面插入新元素e,返回新元素的位置L.add_last(e)
: 在L的后面插入新元素e,返回新元素的位置L.replce(p, e)
: 用元素e 取代位置p处的元素, 返回之前p位置处的元素L.delete(p)
: 删除并且返回L中位置p处的元素,取消该位置
双向链表实现
确认位置
当方法以参数形式接受一个位置信息时,要确认这个位置是有效的,以确定与这个位置关联的底层的节点。
访问和更新方法
_validate
工具“解包”发送的任何位置- 依赖
_make_position
工具来“包装”节点作为Postion实例返回给用户,确保不要返回一个引用哨兵的位置。 - 重载了继承的实用程序方法中的
_insert_between
方法,这样可以返回一个相对应的新创建节点的位置。
class PositionalList(_DoubleLinkedBase):
""" 允许位置访问的顺序元素容器 """
class Position:
""" 单个元素位置的抽象表示 """
def __init__(self, container, node):
""" 初始化构造函数 """
self._container = container
self._node = node
def element(self):
""" 返回当前位置的元素 """
return self._node._elment
def __eq__(self, other):
""" 当两个位置引用是同一个节点时返回真 """
return type(other) is type(self) and other._node is self._node
def __ne__(self, other):
""" 当两个位置引用不是同一个节点时返回真 """
return not (self == other)
#_______________________工具方法————————————————————————————————————
def _validate(self, p):
""" 判断位置是否合法,合法返回当前位置的节点,否则抛出异常 """
if not isinstance(p, self.Position):
raise TypeError('P must be proper position type')
if p._container is not self:
raise ValueError('P does not belong to this container')
if p._node._next is None:
raise ValueError('P is no longer valid')
return p._node
def _make_position(self, node):
""" 返回给定节点的位置实例 """
if node is self._header or node is self._trailer:
return None
else:
return self.Position(self, node)
#_______________________访问器——————————————————————————————————————————
def first(self):
""" 返回列表的第一个位置对象 """
return self._make_position(self._header._next)
def last(self):
""" 返回列表的最后一个位置对象 """
return self._make_position(self._trailer._prev)
def before(self, p):
""" 返回给定位置的前一个位置对象 """
node = self._validate(p)
return self._make_position(node._prev)
def after(self, p):
""" 返回给定位置的下一个位置对象 """
node = self._validate(p)
return self._make_position(node._next)
def __iter__(self):
""" 成为迭代器 """
cursor = self.first()
while cursor is not None:
yield cursor.element()
cursor = self.after(cursor)
#____________________________________调整器————————————————————————————————————————
# 重写继承的插入方法使返回的为位置对象而不是节点
def _insert_between(self, e, predecessor, successor):
node = super()._insert_between(e, predecessor, successor)
return self._make_position(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._trailer._prev, self._trailer)
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._elment
original._elment = e
return old_value
位置列表的排序
使用位置列表来实现插入排序:
- 维护一个名为marker的变量,这个变量表示一个列表当前排序部分最右边的位置。
- 使用pivot标记刚好超过marker的位置并考虑pivot元素相对于排序部分的位置
- 使用另一个被命名为walk的变量,从marker向左移动,只要还有一个前驱元素的值大于pivot元素的值,就一直移动
插入排序中一个步骤的示意图:
def insertion_sort(L):
""" 在位置列表中执行插入排序 """
if len(L) > 1:
marker = L.first()
while marker != L.last():
pivot = L.after(marker)
value = pivot.elemnet()
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)
基于链表的序列与基于数组的序列的对比
基于数组的序列的优点
- 数组提供时间复杂度为O(1)的基于整数索引的访问一个元素的方法
- 通常,具有等效边界的操作使用基于数组的结构运行一个常数因子比基于链表的结构运行更有效率
- 相较于链式结果,基于数组的表示使用存储的比例更少
基于链表的序列的优点
- 基于链表的结构为操作提供最坏情况的时间界限
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)
## 基于链表的序列与基于数组的序列的对比
**基于数组的序列的优点**
- 数组提供时间复杂度为O(1)的基于整数索引的访问一个元素的方法
- 通常,具有等效边界的操作使用基于数组的结构运行一个常数因子比基于链表的结构运行更有效率
- 相较于链式结果,基于数组的表示使用存储的比例更少
**基于链表的序列的优点**
- 基于链表的结构为操作提供最坏情况的时间界限
- 基于链表的结构支持在任意位置进行时间复杂度为O(1)的插入和删除操作