標籤:
- 7 Linked Lists
- 7.1 singly linked list
- 7.2 circular linked lists
- 7.4 the positional list ADT
- 7.5 sorting a positional list
- 7.6 鏈表與數組實現的序列比較
7 Linked Lists7.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): passclass 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__兩個方法。
為了方便判斷一個postion是否屬於一個list,在position中維護一個包含這個position的list的引用,還有 這個position指向的那個結點的引用。_validate函數接受一個position參數,判斷該position是否屬於當前list, 屬於則返回對應的結點node.
_make_position函數則通過一個結點得到對應的position
class Empty(Exception): passclass _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 elementclass 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)時間的插入刪除操作,只要給出相對位置的引用。而數組刪除某個位置的元素則比較慢,需要將後面的元素都向前移動一個位置。
python資料結構學習筆記(七)