1. 程式人生 > >5.雙鏈表

5.雙鏈表

root 測試 ado value nbsp 判斷 刪除 del exception

1.單鏈表的缺點:

(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.雙鏈表