1.單鏈表的缺點:node
(1)remove操做要從頭至尾遍歷,時間複雜度是O(n)python
(2)只能單向遍歷,不能反向遍歷app
2.使用雙鏈表能夠克服以上兩個缺點ide
雙鏈表相對於單鏈表來講,雙鏈表的節點(Node)多了一個指針:單元測試
這樣一來就能指向前一個節點,並且也能夠指向後一個節點。測試
一樣root節點也有一個prev和next,root節點的next指向head節點,head節點的prev指向root節點,這樣就能實現一個雙端鏈表。spa
循環雙端鏈表:3d
好比要反向遍歷的時候,都是從root節點做爲一個入口,把root節點的prev反過來指向tail節點,這樣就能實現從頭向尾節點遍歷,而後從root節點的prev反過來指向上一個節點,對比正向遍歷從next指向下一個節點,這樣就實現循環雙端鏈表。指針
雙鏈表的屬性:blog
data { root 有個根節點
maxsize 控制它的最大長度
length 記錄長度的屬性
雙鏈表
method { headnode 獲取頭節點的方法
tailnode 獲取尾節點的方法
append 最後添加新節點
appendleft 在頭節點前面,根節點後面添加新節點
remove 刪除節點,時間複雜度爲O(1);
好比,有3個節點,要刪除中間節點,就可讓前面和後面節點互指,最後再del掉中間的節點。
iter_node 遍歷節點的操做
iter_node_reverse 反向遍歷節點的操做
實現方式:
class Node(object): def __init__(self, value=None, prev=None, next=None): self.value, self.prev, self.next = value, prev, next class CircualDoubleLinkedList(object): def __init__(self, maxsize=None): self.maxsize = maxsize node =Node() #這兩行代碼,用於構建一個根節點, node.next, node.prev = node, node #這個根節點是本身指向本身的默認是一個閉環。 self.root = node #把node賦值給根節點 self.length = 0 #長度屬性默認是0,root節點是不計算在鏈表長度裏面的 def __len__(self): return self.length #返回長度值 def headnode(self): #定義頭節點 return self.root.next #也就是root節點的下一個節點 def tailnode(self): #定義尾節點 return self.root.prev #也就是root節點的上一個節點 """ 假設有一條几個節點的鏈表,插入一個新的節點前,要先構造這個新的節點, 而後再讓鏈表原來尾節點的next指向新節點,而且新節點的prev指向原來的 尾節點,root節點的prev也要指向新節點,新節點的next指向root節點, 這樣就造成了一個閉環,實現了append新增節點。 """ def append(self, value): if self.maxsize is not None \ and len(self) > self.maxsize: #判斷是否已經超長,若是是就報異常。 raise Exception("The LinkedList is Full") node = Node(value=value) #構造新節點 tailnode = self.tailnode() #尾節點 tailnode.next = node #尾節點的next指向新節點 node.prev = tailnode #新節點的prev指向尾節點 node.next = self.root #新節點的next指向root節點 self.root.prev = node #root節點的prev指向新節點 self.length += 1 #最後將長度+1 def appendleft(self, vlaue): if self.maxsize is not None \ and len(self) > self.maxsize: #判斷是否已經超長,若是是就報異常。 raise Exception("The LinkedList is Full") node = Node(value=vlaue) if self.root.next is self.root: #判斷這個鏈表是空的狀況 node.next = self.root node.prev = self.root #新節點的next和prev都指向root節點,造成一個閉環。 self.root.next = node #同理,將root節點的next指向新節點 self.root.prev = node #將root節點的prev指向新節點 else: #不然,若是鏈表不是空的話 headnode = self.root.next #定義root節點的next節點是鏈表的頭節點 node.prev = self.root #將新節點的prev指向root節點 node.next = headnode #將新節點的next指向原頭節點 headnode.prev = node #最後將頭節點的prev指向新節點 self.length += 1 #鏈表長度加1 def remove(self, node): #node是要刪除的節點,是O(1)的時間複雜度,注意是node不是value if node is self.root: #若是隻有根節點,啥都不返回 return else: #不然是非根節點 node.prev.next = node.next #將要刪除節點的前一個節點的next指針指向要刪除節點的下一個節點 node.next.prev = node.prev #將要刪除節點的後一個節點的prev指針指向要刪除節點的上一個節點 self.length -= 1 #鏈表長度-1 return node #返回刪除的節點 def iter_node(self): #遍歷節點 if self.root.next is self.root: #防止鏈表是空的 return curnode = self.root.next #不然,不是空的,從頭開始遍歷 while curnode.next is not self.root: #當curnode不是尾節點 yield curnode #一直把curnode節點給yield出來 curnode = curnode.next #更新curnode節點,讓curnode一直往下一個節點走 yield curnode #最後別忘了把最後一個curnode給yield出來 #由於遍歷到最後一個節點,但並無去yield這個節點 #當while循環終止時,當前curnode已經到達了tailnode節點, #因此要把它yield出來才完整。 def iter_node_reverse(self): if self.root.prev is self.root: return curnode = self.root.prev #和正向遍歷相反,這個是tailnode節點 while curnode.prev is not self.root: yield curnode curnode = curnode.prev #前移 yield curnode #單元測試 def test_double_link_list(): dll = CircualDoubleLinkedList() assert len(dll) == 0 dll.append(0) dll.append(1) dll.append(2) assert list(dll) == [0, 1, 2] assert [node.value for node in dll.iter_node_reverse()] == [2, 1, 0] assert [node.value for node in dll.iter_node()] == [0, 1, 2] headnode = dll.headnode() #取頭節點 assert headnode.value == 0 #斷言頭節點的值爲0,由於0是第一個被添加的 dll.remove(headnode) #O(1) assert len(dll) == 2 assert [node.value for node in dll.iter_node()] == [1, 2] dll.appendleft(0) assert [node.value for node in dll.iter_node()] == [0, 1, 2]
執行測試:
# pytest double_link_list.py
平均時間複雜度:
循環雙端鏈表操做 | 平均時間複雜度 |
---|---|
cdll.append(value) | O(1) |
cdll.appendleft(value) | O(1) |
cdll.remove(node),注意這裏參數是 node | O(1) |
cdll.headnode() | O(1) |
cdll.tailnode() | O(1) |