5.雙鏈表
(1)remove操作要從頭到尾遍歷,時間復雜度是O(n)
(2)只能單向遍歷,不能反向遍歷
2.使用雙鏈表可以克服以上兩個缺點
雙鏈表相對於單鏈表來說,雙鏈表的節點(Node)多了一個指針:
這樣一來就能指向前一個節點,而且也可以指向後一個節點。
同樣root節點也有一個prev和next,root節點的next指向head節點,head節點的prev指向root節點,這樣就能實現一個雙端鏈表。
循環雙端鏈表:
比如要反向遍歷的時候,都是從root節點作為一個入口,把root節點的prev反過來指向tail節點,這樣就能實現從頭向尾節點遍歷,然後從root節點的prev反過來指向上一個節點,對比正向遍歷從next指向下一個節點,這樣就實現循環雙端鏈表。
雙鏈表的屬性:
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
5.雙鏈表