1. 程式人生 > >1.6 python資料結構之雙向連結串列/迴圈連結串列——以OrderedDict資料結構為例

1.6 python資料結構之雙向連結串列/迴圈連結串列——以OrderedDict資料結構為例

在連結串列這一部分的最後,我們以python中十分強大的collections包中的OrderedDict為例,看一下雙向迴圈列表的功能實現。 OrderedDict 它提供了有序的dict結構,因此他不是常規的雜湊雜湊表,為了保證儲存物件有序,它用連結串列實現了這一功能,map這一功能不去討論,我們來看雙向迴圈列表是怎麼建立和維護的。

本文轉載自另一位大佬的部落格,尊重智慧財產權哈!
python OrderedDict 詳解

先把原始碼貼一下

class OrderedDict(dict):
    '記住插入順序的字典'
    # 一個繼承自dict的鍵值對字典
# 繼承的字典提供 __getitem__, __len__, __contains__, get 方法 # 所有方法的O() 均與正常的字典一樣. # 內部的 self.__map 字典將key與雙向連結串列的指標關聯在一起 # 迴圈雙向連結串列是以一個哨兵元素作為開始和結束節點的 # 哨兵元素永遠不會被刪除 (這簡化了演算法). def __init__(*args, **kwds): '''初始化一個有序字典。 方法的簽名與常規字典一樣''' '''Initialize an ordered dictionary. The signature is the same as regular dictionaries, 但是並不建議使用關鍵字引數進行初始化,因為他們的插入順序是任意的,沒有保證的 '''
if not args: raise TypeError("descriptor '__init__' of 'OrderedDict' object " "needs an argument") self = args[0] args = args[1:] if len(args) > 1: raise TypeError('expected at most 1 arguments, got %d' % len(args)
) try: self.__root except AttributeError: self.__root = root = [] # sentinel node root[:] = [root, root, None] self.__map = {} self.__update(*args, **kwds) def __delitem__(self, key, dict_delitem=dict.__delitem__): 'od.__delitem__(y) <==> del od[y]' # 刪除已有的元素,使用 self.__map 來獲取key對應的連結串列 # 我們通過更新該節點的前繼節點和後續節點中的連結來刪除 dict_delitem(self, key) link_prev, link_next, _ = self.__map.pop(key) link_prev[1] = link_next # update link_prev[NEXT] link_next[0] = link_prev # update link_next[PREV] def __iter__(self): 'od.__iter__() <==> iter(od)' # 按順序遍歷連結串列 root = self.__root curr = root[1] # start at the first node while curr is not root: yield curr[2] # yield the curr[KEY] curr = curr[1] # move to next node def __reversed__(self): 'od.__reversed__() <==> reversed(od)' # 反向遍歷連結串列 root = self.__root curr = root[0] # start at the last node while curr is not root: yield curr[2] # yield the curr[KEY] curr = curr[0] # move to previous node def clear(self): 'od.clear() -> None. 刪除所有的連結串列中的元素' root = self.__root root[:] = [root, root, None] self.__map.clear() dict.clear(self)

然後看一下核心結構的記憶體示意圖:

image

這裡展示了遞迴多維陣列的記憶體結構

每個連結都儲存為長度為3的序列 : [PREV, NEXT, KEY]

od的有序實際上是由一個雙向連結串列實現的. 由於 Python 裡面的list是可變物件, 一個節點list裡的 PREV 和 NEXT 是對前驅和後繼節點list的引用

在初始化中, 前驅和後繼都指向本身節點,方便接下來實現環鏈

核心程式碼:

def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
    # 新增新元素的時候會在連結串列的末尾建立新連結串列, 並使用新的 鍵值對 更新被繼承的字典
    if key not in self:
        root = self.__root
        # 這個root 就是所謂的哨兵節點
        last = root[0]  # root 節點的前驅節點
        last[1] = root[0] = self.__map[key] = [last, root, key] 
        # 將root節點作為尾節點,建立新節點,並更新root節點的前驅節點的後繼節點和root節點的前驅節點的指向, 形象一點就是在root 節點前不斷的對新節點進行前插(基本連結串列演算法)
    return dict_setitem(self, key, value)

ok, 為什麼不簡單使用list來進行儲存, 而是要使用這種結構的雙向連結串列?這就涉及到了連結串列和陣列的主要用途. 兩者同樣是序列,陣列按照 index 取值, 對於固定的靜態序列資料的存取都是 O(1), 雙向連結串列 按照 pre, next 遍歷, 因為節點是可變物件, 可以被引用(對於 od來說就是 self.__map[key]的用途), 對於 動態 的序列存取也是 O(1)。

od顯然要維護一個動態序列, 所以連結串列就是一個非常好的選擇。你可能想到list可以del某個元素, 但是這其實破壞了陣列的規則, index已被改變, 無法按照原有的index進行存取。需要移動大量陣列元素。

summary:

陣列靜態分配記憶體,連結串列動態分配記憶體

陣列在記憶體中連續,連結串列不連續

陣列元素在棧區,連結串列元素在堆區

陣列利用下標定位,時間複雜度為O(1),連結串列定位元素時間複雜度O(n)

這裡使用__map來進行定位,所以複雜度也是O(1)

陣列插入或刪除元素的時間複雜度O(n),連結串列的時間複雜度O(1)。