(Python3)數據結構——06.雙向鏈表的原理及實現

前言

  • 有Python基礎
  • 學過數據結構那自然是最好的

原理

雙向鏈表

在這裏插入圖片描述
雙向鏈表和單鏈表的差別在哪?雙向鏈表的節點和單鏈表的節點是不一樣的。單鏈表的節點有item和next,而雙向鏈表的節點還必須有一個指向前一個節點的prior(別的命名也行,反正必須有個東西指向前一個節點)。prior存在的意義也是爲了更好地查找節點的前驅節點。因此必須對Node進行改造。

實現

節點的定義

class Node(object):
    # 初始化時它就只是個孤家寡人,前不着村後不着店,prior和next都是空
    def __init__(self, item):
        self.item = item
        self.prior = None
        self.next = None

初始化

  • 雙向鏈表的構造函數和單鏈表一樣,只需要設置空的頭節點即可。
class DeLink(object):
    def __init__(self):
        self.__head = None
  • 事實上,雙向鏈表比單鏈表的節點多了一個屬性prior,其他的方法照樣可以沿用。
  • 參照博客
https://blog.csdn.net/sf9898/article/details/104946291  
  • 將單鏈表的代碼拷貝下來,在單鏈表的class Node下按照上面說的那樣子改造,加一個prior屬性,其他的別改,直接運行並沒有毛病。其原因在於增加了一個並沒有使用到的prior屬性,運行的實際上還是單鏈表。
  • 正如前面講的,prior存在的意義是爲了查找前一個節點,這也是雙向鏈表的優點。那麼就可以有利用prior屬性查找節點的方法。
  • 接下來按照雙向鏈表的原理重新構造一下class DeLink
  • size 方法不需要講究,利用原來的單鏈表方法即可,畢竟只是求個數。當然,判空和遍歷也可以沿用。
 	def isEmpty(self):
        return self.__head is None
        
 	def size(self):
        count = 0
        cur = self.__head
        while cur:
            count += 1
            cur = cur.next
        return count

    def travel(self):
        cur = self.__head
        while cur:
            print(cur.item, end=' ')
            cur = cur.next
        print('')

插入

  • 查找,刪除數值等函數亦是可以沿用,加上了prior屬性之後變化的主要是插入和刪除,其他的沿用即可。接下來重點看看插入和刪除。
  • 頭部插入

在這裏插入圖片描述

 # 頭部插入
    def addFront(self, item):
        node = Node(item)       # 新的節點只有item,next和prior都是None
        node.next = self.__head     # 新的節點的next指向當前的第一個節點
        if self.__head:
        	self.__head.prior = node        # 不爲空時設置當前第一個節點的prior指向新來的node
        self.__head = node      # 新來的node成爲新的頭結點
  • 尾部插入:尾部插入需要將原來的尾部節點last的next指向新的node(原來的last的next是指向None的),node的prior指向last(這一步要有,這樣通過node的prior就可以找到last了)。爲了減少工作量,直接在原有的單鏈表上面改了。

在這裏插入圖片描述

# 尾部插入
    def addBack(self, item):
        node = Node(item)
        if self.__head is None:
            self.__head = node
            return
        cur = self.__head
        pre = None
        while cur:
            pre = cur
            cur = cur.next
            # 當cur 爲最後一個節點時帶入,pre更新爲最後一個節點,cur更新爲最後一個節點的下一個節點即爲空,
            # 下一次while cur 時會退出循環,此時的pre表示的就是最後一個節點,將node掛到pre的後面即可
        pre.next = node     # 這裏的pre相當於例子中的last
        # 僅加了下面這句
        node.prior = pre
  • 中間插入:需要注意的是要找到插入位置的前一個節點。如圖,找到pre,之後:
  1. pre.next = node
  2. node.prior = pre
  3. node.next = nd
  4. nd.prior = node

在這裏插入圖片描述

	# 插入節點, 節點在插入後是第 pos 個節點,當然這個函數也可以實現頭部插入和尾部插入的功能
    # 故pos取值1時,即頭部插入,取值size+1時是尾部插入。因此取值的合法範圍是[1,size + 1]
    def insert(self, pos, item):
        if pos > (self.size() + 1) or pos < 1:
            return
        if pos == 1:
            self.addFront(item)
            return
        node = Node(item)
        cur = self.__head
        pre = None
        for i in range(pos - 1):
            pre = cur
            cur = cur.next
        # 1) pre.next = node
        # 2) node.prior = pre
        # 3) node.next = nd
        # 4) nd.prior = node
        pre.next = node
        node.prior = pre
        node.next = cur
        if cur:
            cur.prior = node

刪除

  • 頭部刪除:更新頭結點爲頭結點的下一個節點,原頭結點的next指向None,現頭結點的prior指向None。

在這裏插入圖片描述

# 刪除頭部結點
    def removeFront(self):
        cur = self.__head
        self.__head = self.__head.next
        cur.next = None
        self.__head.prior = None
  • 刪除尾部結點

在這裏插入圖片描述

# 刪除尾部節點
    def removeBack(self):
        # 空節點時
        if self.__head is None:
            return
        # 只有一個節點
        if self.__head and self.__head.next is None:
            self.__head = None
            return
        # 鏈表節點有兩個及以上
        cur = self.__head  # 當前節點
        pre = None  # 前一個節點
        cn = cur.next  # 後一個節點
        # 剛開始cur取到的是第一個節點,cn是第二個
        while cn:
            pre = cur
            cur = cur.next
            cn = cur.next
        # 僅修改此處
        pre.next = None
        cur.prior = None
  • 中間刪除

在這裏插入圖片描述

# 刪除指定數值的節點
    def delete(self, item):
        if self.__head is None:
            return
        if self.__head.item == item:
            self.__head = None
            return
        cur = self.__head.next  # 取第二個節點
        pre = self.__head  # 第一個節點
        while cur:
            if cur.item == item:
            	# 僅修改此處
                pre.next = cur.next
                cur.prior = None
                cur.next.prior = pre
                cur.next = None
                break
            else:
                pre = cur
                cur = cur.next

完整代碼

參照之前單鏈表進行測試,下面是完整代碼,如有錯誤之處還望批評指出。

class Node(object):
    # 初始化時它就只是個孤家寡人,前不着村後不着店,prior和next都是空
    def __init__(self, item):
        self.item = item
        self.prior = None
        self.next = None


class DeLink(object):
    def __init__(self):
        self.__head = None

    def isEmpty(self):
        return self.__head is None

    # 頭部插入
    def addFront(self, item):
        node = Node(item)  # 新的節點只有item,next和prior都是None
        node.next = self.__head  # 新的節點的next指向當前的第一個節點
        if self.__head:
            self.__head.prior = node  # 設置當前第一個節點的prior指向新來的node
        self.__head = node  # 新來的node成爲新的頭結點

    # 尾部插入
    def addBack(self, item):
        node = Node(item)
        if self.__head is None:
            self.__head = node
            return
        cur = self.__head
        pre = None
        while cur:
            pre = cur
            cur = cur.next
            # 當cur 爲最後一個節點時帶入,pre更新爲最後一個節點,cur更新爲最後一個節點的下一個節點即爲空,
            # 下一次while cur 時會退出循環,此時的pre表示的就是最後一個節點,將node掛到pre的後面即可
        pre.next = node  # 這裏的pre相當於例子中的last
        node.prior = pre

    def size(self):
        count = 0
        cur = self.__head
        while cur:
            count += 1
            cur = cur.next
        return count

    def travel(self):
        cur = self.__head
        while cur:
            print(cur.item, end=' ')
            cur = cur.next
        print('')

    # 刪除頭部節點
    def removeFront(self):
        cur = self.__head
        self.__head = self.__head.next
        cur.next = None
        self.__head.prior = None

    # 刪除尾部節點
    def removeBack(self):
        # 空節點時
        if self.__head is None:
            return
        # 只有一個節點
        if self.__head and self.__head.next is None:
            self.__head = None
            return
        # 鏈表節點有兩個及以上
        cur = self.__head  # 當前節點
        pre = None  # 前一個節點
        cn = cur.next  # 後一個節點
        # 剛開始cur取到的是第一個節點,cn是第二個
        while cn:
            pre = cur
            cur = cur.next
            cn = cur.next
        # 僅修改此處
        pre.next = None
        cur.prior = None

    # 查找鏈表中有沒有item,有返回True,沒有則返回False
    def search(self, item):
        cur = self.__head
        res = False
        while cur:
            if cur.item == item:
                res = True
                break
            else:
                cur = cur.next
        return res

    # 查找某個數的下標
    # def searchIndex(self, item):
    #     if self.search(item):
    #         # 存在的話才進行
    #         cur = self.__head
    #         index = -1
    #         while cur:
    #             index += 1
    #             if cur.item == item:
    #                 break
    #             else:
    #                 cur = cur.next
    #         return index
    #     else:
    #         return -1

    # 刪除指定數值的節點
    def delete(self, item):
        if self.__head is None:
            return
        if self.__head.item == item:
            self.__head = None
            return
        cur = self.__head.next  # 取第二個節點
        pre = self.__head  # 第一個節點
        while cur:
            if cur.item == item:
                pre.next = cur.next
                cur.prior = None
                cur.next.prior = pre
                cur.next = None
                break
            else:
                pre = cur
                cur = cur.next

    # 插入節點, 節點在插入後是第 pos 個節點,當然這個函數也可以實現頭部插入和尾部插入的功能
    # 故pos取值1時,即頭部插入,取值size+1時是尾部插入。因此取值的合法範圍是[1,size + 1]
    def insert(self, pos, item):
        if pos > (self.size() + 1) or pos < 1:
            return
        if pos == 1:
            self.addFront(item)
            return
        node = Node(item)
        cur = self.__head
        pre = None
        for i in range(pos - 1):
            pre = cur
            cur = cur.next
        # 1) pre.next = node
        # 2) node.prior = pre
        # 3) node.next = nd
        # 4) nd.prior = node
        pre.next = node
        node.prior = pre
        node.next = cur
        if cur:
            cur.prior = node


ll = DeLink()
print(ll.isEmpty())     # 初始化時爲空,理應輸出True
for i in range(5):
    ll.addFront(i)
print('size:', ll.size())       # 5個數
ll.travel()     # 4 3 2 1 0 
for i in range(5):
    ll.addBack(i)
ll.travel()     # 4 3 2 1 0 0 1 2 3 4 
ll.removeFront()
ll.travel()     # 3 2 1 0 0 1 2 3 4 
print('----')
ll.removeBack()
ll.travel()     # 3 2 1 0 0 1 2 3 
ll.insert(9, 12)
ll.travel()     # 3 2 1 0 0 1 2 3 12 
print(ll.search(13))        # 沒有,False
print(ll.search(1))     # True
ll.delete(2)
ll.travel()     # 3 1 0 0 1 2 3 12 
print('------')
# print(ll.searchIndex(12))
ll.insert(8, 100)
ll.travel()     # 3 1 0 0 1 2 3 100 12 
  • 結果

在這裏插入圖片描述

可以發現寫起來代碼量會多一點點,事實上,很多東西用單鏈表就能解決了,雙向鏈表比單鏈表多了一個prior,這一點是雙向鏈表的優點,同時也是它的缺點,畢竟多一個元素就多佔一點空間,因此還是單鏈表用的比較多。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章