7 Linked Lists
7.1 singly linked list
单向链表,就是一系列的结点组成一个线性集合。里面每个结点存储有一个元素的引用,还会指向链表的下一个元素. 最后一个元素指向None.
链表的第一个和最后一个结点称为链表的头部和尾部。从头部开始不断访问下一个next可以遍历这个链表。为了能访问 链表,必须保证有头部的引用。
链表的一个特点使可以很方便的在头部和尾部增加新的结点。add_first在头部增加一个新结点,并更新头部指针。
add_first(L, e): newest = Node(e) newest.next = L.head L.head = newest L.size = L.size + 1
在尾部增加结点并更新tail
add_last(L, e): newest = Node(e) newest.next = None L.tail.next = newest L.tail = newest L.size = L.size + 1
之前使用list实现的stack和queue在插入和删除操作上有摊还O(1)的时间,但是在个别插入上可能时间比较长。 使用链表来实现则可以所有操作都达到O(1)的时间。
class Empty(Exception): pass class LinkedStack: class _Node: """lightweight, nonpublic class for storing a singly linked 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
链表实现队列的情况类似,需要注意的是队列需要维护多一个成员_tail,因为队列可以在末尾插入新的元素。 在空的队列插入第一个元素的时候要注意同时还要修改_head来指向这第一个元素。同时如果删除一个元素刚好 队列为空的时候也需要注意,修改_tail来指向None.
7.2 circular linked lists
将链表最后一个元素的next指向head,就可以形成一个环。
有时候在队列中我们取出第一个元素,进行操作后又要放回队列的末尾,实现不断循环的重复操作,这时候 就可以使用循环链表来实现队列。
队列只需要维护一个tail引用,需要head的时候访问tail.next即可。而需要取出队列头部的一个元素放到尾部的时候, 不需要真正的改变数据结构,只要将tail = tail.next就等于将第一个元素放到尾部。
下面用循环链表模拟一个循环队列:
class CircularQueue: class _Node: """lightweight, nonpublic class for storing a singly linked 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') 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 enqueue(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
和普通队列一样,需要注意的是往空的队列中插入第一个元素和删除最后一个元素的情况。
7.4 the positional list ADT
前面的队列只是支持在头部或者尾部增删元素,但是不能随便在任意位置进行操作。对于链表来说,使用下标访问 并不是那么容易,因为很难通过一个下标来访问一个结点,只能从头部或者尾部开始逐个移动。
如果用一个结点的引用表示他的位置,则可以在特定位置前面或者后面插入元素,或者删除某个特定结点。
下面重新抽象出一种队列,可以根据引用来访问特定位置的结点。定义positional list ADT。
前面的队列中执行插入等是直接在提供给用户对结点的操作,如果用户给的参数结点出错则可能造成无法预测的结果。 所以现在要想办法将这些对结点的操作封装起来,然后提供给用户一些高层次的借口,比如在某个位置上的操作。
定义位置position为新的数据类型,p.element()返回这个位置上存储的元素。然后之前list的操作中直接返回元素的都 改为只是返回一个position位置。 对于一个list L:
- L.first(): 返回第一个元素的position,如果L为空则返回None.
- L.last()
- L.before(p): 返回position p前一个位置的position,如果p是第一个位置则返回None.
- L.after(p)
- L.is_empty()
- len(L)
- iter(L): 返回一个遍历elements的迭代器,如可以使用for e in L: process e等
- L.add_first(e): 参数e是要插入的element,返回新元素的position
- L.add_last(e)
- L.add_before(p, e): 同样返回position
- L.add_after(p, e)
- L.replace(p, e): 返回原来的element
- L.delete(p): 返回被删除的element
现在用position把元素封装起来,要访问第一个元素可以通过L.first().element()
遍历整个list的方法:
cursor = data.first() while cursor is not None: print cursor.element() cursor = data.after(cursor)
因为cursor等于最后一个position的时候,after(cursor)返回None,所以用来作为判断结束的条件。
为了判断两个position是否相等,给position加上__eq__
和__ne__
两个方法。
- 验证position是否合法
为了方便判断一个postion是否属于一个list,在position中维护一个包含这个position的list的引用,还有 这个position指向的那个结点的引用。_validate
函数接受一个position参数,判断该position是否属于当前list, 属于则返回对应的结点node.
_make_position
函数则通过一个结点得到对应的position
class Empty(Exception): pass class _DoublyLinkedBase: class _Node: __slots__ = '_element', '_prev', '_next' def __init__(self, element, prev, next): self._element = element self._prev = prev self._next = next 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 PositionalList(_DoublyLinkedBase): """a sequential container of elements allowing positional access.""" class Position: def __init__(self, container, node): self._container = container 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) 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 = _DoublyLinkedBase._insert_between(self, 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) # inherited method returns element def replace(self, p, e): """ :param p, e: replace the element at position p with e :return: return the element formerly at position p. """ original = self._validate(p) old_value = original._element original._element = e return old_value
7.5 sorting a positional list
下面要使用插入排序的算法来对positional list排序。回顾插入排序的算法,从左边第一个元素开始设置一个标志 marker,表示包括marker左边的都是已经有序的。然后每次将marker右边一个元素加进来,逐个和marker前面的元素 比较,找到合适的位置并将其插入,直到整个list都是有序的。每次取marker右边的一个元素为pivot,然后用一个变量 walk从marker开始向左遍历,直到walk的前一个元素的值小于等于pivot的值(从小到大排序),最后将pivot插入到 walk和前一个元素之间。
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(): # pivot is already sorted. 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)
7.6 链表与数组实现的序列比较
数组序列的优势
- 数组可以在O(1)的时间实现下标访问,相比这下链表就不能实现下标访问,而访问任意一个元素需要从头开始遍历查找。
- 实际操作中对某个位置的操作可能比链表要快,因为只要改变该位置存储内容就行,而链表通常还要更新相邻的指针
- 使用的内存更少,最坏的情况是数组刚刚扩展的时候内存利用率只有一半,但是链表由于每个元素要多存一个或者两个指针而需要两到三倍的空间。
链表序列优势
- 链表的增删查改执行时间是O(1),相比之下数组有些操作只是摊还时间为O(1)
- 支持任意位置O(1)时间的插入删除操作,只要给出相对位置的引用。而数组删除某个位置的元素则比较慢,需要将后面的元素都向前移动一个位置。