換湯不換藥——Python實現雙向鏈表與單向循環鏈表

前言

菜雞大學生記錄和分享自己的學習歷程,不是在談新技術,求求各位嘴下留情
前面我們講到了Python中如何在沒有指針的情況下來實現一個單鏈表這種數據結構,面向對象程序很高明啊,用一個類實現結點,有了結點就可以串聯起來形成鏈表。既然大家都是鏈表,那麼雙向鏈表和單向循環鏈表跟單向鏈表不就是爸爸和兒子的關係嘛!那麼我們就可以使用繼承來實現雙向鏈表和單向循環鏈表了,使得其中的很多代碼都可以重用,提高了代碼編寫的效率。下面我就來着重介紹一下Python鏈表家族的其他成員的方法特有實現方式。

雙向鏈表

什麼是雙向鏈表?雙向鏈表指的是每個節點有兩個鏈接:一個指向前一個節點,當此節點爲第一個節點時,指向None;而另一個指向下一個節點,當此節點爲最後一個節點時,指向None。如下圖所示就是一個雙向鏈表的大概表現形式。
在這裏插入圖片描述
其實雙向鏈表就是在單鏈表的基礎上有指向前一個節點的鏈接,使得我們在訪問結點的時候能快速找到前一個節點的信息。

雙向鏈表結點實現

相比於單鏈表新增加了一個指向先一個節點的“指針”(鏈接)。

class Node(object):
    """雙向鏈表節點"""
    def __init__(self, item):
        self.item = item
        self.next = None
        self.prev = None

雙向鏈表的操作

  • is_empty() 鏈表是否爲空
  • length() 鏈表長度
  • travel() 遍歷鏈表
  • search(item) 查找節點是否存在
  • add(item) 鏈表頭部添加
  • append(item) 鏈表尾部添加
  • insert(pos, item) 指定位置添加
  • remove(item) 刪除節點
    分析下來可以發現,雙向鏈表的判空操作、計算鏈表長度操作、遍歷操作、查找操作跟單鏈表的代碼是一樣的,這裏我們就可以使用繼承的方法讓雙向鏈表來繼承單鏈表類的代碼。
class DoubleLinkList(SingleLinkList):
    """雙鏈表"""
    pass

雙向鏈表的實現

add(item) 鏈表頭部添加

雙向鏈表頭部添加元素跟單鏈表沒啥區別,就是要把原先的第一個元素指向新添加的元素。

    def add(self, item):
        """鏈表頭部添加元素,頭插法"""
        node = Node(item)
        node.next = self.__head
        self.__head = node
        node.next.prev = node

append(item) 鏈表尾部添加

先考慮一般情況,雙向鏈表尾部添加跟單鏈表一樣,只需要添加指向前一個的鏈接,如果列表爲空,直接將頭結點指向新添加的結點即可。

    def append(self, item):
        """鏈表尾部添加元素, 尾插法"""
        node = Node(item)
        if self.is_empty():
            self.__head = node
        else:
            cur = self.__head
            while cur.next != None:
                cur = cur.next
            cur.next = node
            node.prev = cur

insert(pos, item) 指定位置添加

在這裏插入圖片描述
將原有的鏈接打開,然後重新鏈接正確,鏈接步驟如上, node.next = cur, node.prev = cur.prev, cur.prev.next = node,cur.prev = node。需要注意的是,此處while循環的退出條件是count<pos而不是單鏈表中的count<pos-1,原因是我的遊標cur可以直接運動到pos位置處,而不需要到前一個就停住,因爲有prev指向前一個結點。其他特殊情況的操作與單鏈表相同。

    def insert(self, pos, item):
        """指定位置添加元素
        :param  pos 從0開始
        """
        if pos <= 0:
            self.add(item)
        elif pos > (self.length()-1):
            self.append(item)
        else:
            cur = self.__head
            count = 0
            while count < pos:
                count += 1
                cur = cur.next
            # 當循環退出後,cur指向pos位置
            node = Node(item)
            node.next = cur
            node.prev = cur.prev
            cur.prev.next = node
            cur.prev = node

remove(item) 刪除節點

雙向鏈表刪除結點不需要像單鏈表那樣要兩個遊標,因爲有prev可以指向前一個結點,只用一個cur就可以了。鏈表中間刪除且有多個結點:cur移到要刪除的結點位置,然後將其前一個結點與其後一個結點相連。如果是首結點:直接把頭結點指向原先的第二個結點,需要考慮只有一個節點的情況。如果是尾結點有的操作是不需要的,這裏就要做個判斷。具體代碼如下:

    def remove(self, item):
        """刪除節點"""
        cur = self.__head
        while cur != None:
            if cur.elem == item:
                # 先判斷此結點是否是頭節點
                # 頭節點
                if cur == self.__head:
                    self.__head = cur.next
                    if cur.next:
                        # 判斷鏈表是否只有一個結點
                        cur.next.prev = None
                else:
                    cur.prev.next = cur.next
                    if cur.next:
                    # 判斷是否是尾結點
                        cur.next.prev = cur.prev
                break
            else:
                cur = cur.next

測試:

以下是測試代碼:

if __name__ == "__main__":
    dll = DoubleLinkList()
    print(dll.is_empty())
    print(dll.length())
    dll.append(1)
    print(dll.is_empty())
    print(dll.length())
    dll.append(2)
    dll.add(8)
    dll.append(3)
    dll.append(4)
    dll.append(5)
    dll.append(6)
    dll.insert(-1, 9) 
    dll.travel()
    dll.insert(3, 100) 
    dll.travel()
    dll.insert(10, 200) 
    dll.travel()
    dll.remove(100)
    dll.travel()
    dll.remove(9)
    dll.travel()
    dll.remove(200)
    dll.travel()

在這裏插入圖片描述

單向循環鏈表

在這裏插入圖片描述
單向循環鏈表就是在單鏈表的基礎上增加了一個從尾結點指向首結點的“指針”,使得整個鏈表成了一個圈,可以循環遍歷。單向循環鏈表的結點定義如下:

class Node(object):
    """節點"""
    def __init__(self, elem):
        self.elem = elem
        self.next = None

其初始化函數如下:

lass SingleCycleLinkList(object):
    """單向循環鏈表"""
    def __init__(self, node=None):
        self.__head = node
        if node:
            node.next = node

如果鏈表非空node.next = node,也就是有指向結點的結點。

單向循環鏈表的操作

  • is_empty() 判斷鏈表是否爲空
  • length() 返回鏈表的長度
  • travel() 遍歷
  • add(item) 在頭部添加一個節點
  • append(item) 在尾部添加一個節點
  • insert(pos, item) 在指定位置pos添加節點
  • remove(item) 刪除一個節點
  • search(item) 查找節點是否存在

直降與單鏈表操作不同的代碼段哈!

length() 返回鏈表的長度

這個跟單鏈表不同的地方在於這裏count是=1,單鏈表是0,原因在於單鏈表的條件是cur == None截止,而這裏是cur.next = =self.__head,cur指向的是最後一個結點,從1計數即可,單鏈表是None則要從0計數。

    def length(self):
        """鏈表長度"""
        if self.is_empty():
            return 0
        # cur遊標,用來移動遍歷節點
        cur = self.__head
        # count記錄數量
        count = 1
        while cur.next != self.__head:
            count += 1
            cur = cur.next
        return count

travel() 遍歷

    def travel(self):
        """遍歷整個鏈表"""
        if self.is_empty():
            return
        cur = self.__head
        while cur.next != self.__head:
            print(cur.elem, end=" ")
            cur = cur.next
        # 退出循環,cur指向尾節點,但尾節點的元素未打印
        print(cur.elem)

add(item) 在頭部添加一個節點

如果非空,操作跟單鏈表基本一致,注意的是有從尾結點到首結點的指向。如果是空列表,直接將頭結點指向添加的結點即可。

    def add(self, item):
        """鏈表頭部添加元素,頭插法"""
        node = Node(item)
        if self.is_empty():
            self.__head = node
            node.next = node
        else:
            cur = self.__head
            while cur.next != self.__head:
                cur = cur.next
            # 退出循環,cur指向尾節點
            node.next = self.__head
            self.__head = node
            # cur.next = node
            cur.next = self.__head

append(item) 在尾部添加一個節點

注意指向首結點。

    def append(self, item):
        """鏈表尾部添加元素, 尾插法"""
        node = Node(item)
        if self.is_empty():
            self.__head = node
            node.next = node
        else:
            cur = self.__head
            while cur.next != self.__head:
                cur = cur.next
            # node.next = cur.next
            node.next = self.__head
            cur.next = node

insert(pos, item) 在指定位置pos添加節點

pos<=0頭插,pos>表長尾插。在中間插入的話不涉及頭尾結點的關係,就完全是一個單鏈表的插入了。

    def insert(self, pos, item):
        """指定位置添加元素
        :param  pos 從0開始
        """
        if pos <= 0:
            self.add(item)
        elif pos > (self.length()-1):
            self.append(item)
        else:
            pre = self.__head
            count = 0
            while count < (pos-1):
                count += 1
                pre = pre.next
            # 當循環退出後,pre指向pos-1位置
            node = Node(item)
            node.next = pre.next
            pre.next = node

remove(item) 刪除一個節點

刪除這裏很有點麻煩,需要考慮很多的情況,提供一種另外的思路是先判斷鏈表的長度,再刪除。

    def remove(self, item):
        """刪除節點"""
        if self.is_empty():
            return
        cur = self.__head
        pre = None
        while cur.next != self.__head:
            if cur.elem == item:
                # 先判斷此結點是否是頭節點
                if cur == self.__head:
                    # 頭節點的情況
                    # 找尾節點
                    rear = self.__head
                    while rear.next != self.__head:
                        rear = rear.next
                    self.__head = cur.next
                    rear.next = self.__head
                else:
                    # 中間節點
                    pre.next = cur.next
                return
            else:
                pre = cur
                cur = cur.next
        # 退出循環,cur指向尾節點
        if cur.elem == item:
            if cur == self.__head:
                # 鏈表只有一個節點
                self.__head = None
            else:
                # pre.next = cur.next
                pre.next = self.__head

這裏的測試代碼和結果跟前面的都差不多。

後記

部分代碼和圖片來源於我學習的資料
鏈表部分就到此結束了,貌似漲了很多粉,哈哈。再次說明的是我在這裏寫博客不是說有啥高大上的東西,就是記錄和分享自己的學習歷程,畢竟也有很多新手來CSDN學習的,可能有些東西寫出來不符合您的胃口,不要噴我啦,再噴就自閉了!
如果您覺得我寫的東西有點用的話,歡迎點一波關注,大家一起加油叭!

新手上路,技術有限,不喜勿噴

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