面相物件(三):模擬連結串列

小丑与锁鸟發表於2024-04-11

物件導向的基本原理是對物件建模,讓抽象的邏輯封裝成具象的行為,更方便人們理解和使用。在前面的文章中我寫了關於繼承的一些理解,一般來說這裡應該討論與繼承同為物件導向三個主要特徵的多型與封裝了。但是我想多型與封裝是一種伴隨著類的定義自然而然形成的現象,只有先接觸了一定數量的類物件,我們才能更好地理解多型;也只有對一個類有了切實的應用,才能更好體會封裝的精妙。因此,這些內容不會直接出現在這裡。今天我希望分享的是如何去運用類建立一個連結串列。

連結串列由一系列結點(連結串列中每一個元素稱為結點)組成,每個結點包括兩個部分:一個是儲存資料元素的資料域,另一個是儲存下一個結點地址的指標域

以上是百度百科中關於連結串列的描述。需要說明的是,在本文中,我們只討論最基礎的單向無迴圈連結串列,實際業務中,連結串列可能會比基礎形態更加複雜。連結串列的每個結點,都只有一個前驅結點和一個後繼結點,因此會形成一個結點串。
連結串列的優點是,由於只需要更改結點的地址域就可以實現元素的插入和刪除,不需要像列表一樣對更改之後的全部元素索引進行整體調整,因此在進行這兩個操作時較為高效。與普通列表相比,連結串列利用了記憶體中的零碎空間,將整個表格分成一個個的結點,分別存到記憶體空著的地方,因此在處理記憶體空間時比較靈活。但是缺點是由於連結串列不儲存索引,當需要取出固定位置的元素值時,連結串列必須從頭開始按圖索驥,來找到目標,這意味著連結串列的查改效率相對不高。實踐中,我們會根據業務需求進行使用。

現在讓我們來建模一個最基礎的單向無迴圈連結串列。

  1. 定義結點
    顯然,連結串列中的結點可以被建模成一個類。這個類應該有兩個屬性,即數值域和地址域。單個結點在建立時應該輸入數值域,而地址域則應該賦值為None,因為他還沒有被加入到連結串列中。
點選檢視程式碼
class SingleNode(object):
    def __init__(self, data):
        self.item = data
        self.next = None
  1. 定義整條連結串列
    有了結點,我們就有了形成連結串列的基本單位。連結串列物件應該擁有一些行為可以讓我們來操作這些結點。但是首先,我們必須要有結點。所在在建立連結串列物件時,先應該傳入第一個結點物件,並將其設定為我們的頭結點(即連結串列開始的地方)。頭結點會在每次連結串列被訪問時第一個被訪問到,在單向無迴圈連結串列中,沒有結點的地址域會指向頭結點。
點選檢視程式碼
class SingleLinkedList(object):
    def __init__(self,node=None):
        self.head = node  # 將傳入的節點作為頭節點
  1. 為連結串列新增功能-頭插法與尾插法
    頭插法是指在連結串列頭部新增結點,而尾插法恰恰相反。注意我們每次插入一個值,都是要將其先例項化為一個結點,之後才可以進行操作。
點選檢視程式碼
    def add(self,item):              # 頭插法
        new_node = SingleNode(item)  # 建立新節點
        new_node.next = self.head    # 將新節點的next指標指向原本的頭節點
        self.head = new_node         # 並將新節點作為頭節點
    def append(self,item):           # 尾插法
        new_node = SingleNode(item)
        cur = self.head              # 將當前結點定位到頭結點
        while cur:                   # 遍歷連結串列,找到最後一個節點
            if cur.next ==None:
                cur.next = new_node  # 將新節點新增到連結串列尾部
                break
            else:
                cur = cur.next
        else:
            self.head = new_node    # 如果沒有進入while迴圈,說明頭結點為空,則直接將新節點作為頭節點
  1. 為連結串列新增功能-遍歷與檢視連結串列長度
    遍歷連結串列只需要按順序輸出每個結點的數值域,並順著地址域找到下一個結點即可。
    檢視長度在此基礎上進行統計即可。
點選檢視程式碼
    def length(self):      # 計算連結串列長度
        cur =self.head
        count = 0
        while cur:         # 由於初始條件為count=0,每次拿到一個非空結點之後count+1,並將cur指向下一個結點即可 
            count += 1    
            cur = cur.next
        return count
    def travel(self):        # 遍歷連結串列
        cur = self.head
        while cur:
            print(cur.item)
            cur = cur.next
  1. 為連結串列新增功能-在指定位置插入值
    正常情況下在指定位置插入值只需要將原本位於此處的結點地址域開啟,並將新的結點接入即可。
點選檢視程式碼
    def insert(self,pos,item):
        if pos <= 0:
            self.add(item)
        elif pos > self.length():
            self.append(item)
        else:
            count =0
            cur = self.head
            while count < pos-1:        # 透過迴圈找到插入位置的前一個節點
                count += 1
                cur = cur.next
            new_node = SingleNode(item)
            new_node.next = cur.next    # 將新節點的next指標指向原本的cur節點的下一個節點
            cur.next = new_node         # 將原本的cur節點的next指標指向新節點
  1. 為連結串列新增功能-刪除指定值
    刪除值的操作一般不需要指定位置,而是透過值來判斷。最基礎的情況下,我們只刪除找到的第一個結點,並將他前一個結點的地址域直接指向它的下一個結點,以此來讓這個結點處於無法被找到的狀態(但他並未被從記憶體中刪除)
點選檢視程式碼
    def remove(self,item):
        cur = self.head
        if cur ==None:             # 如果連結串列為空,則直接返回
            print(f'連結串列為空,不存在{item}')
            return
        elif cur.item == item:     # 如果頭節點就是要刪除的節點,則直接將頭節點指向下一個節點
            self.head = cur.next
            return
        count =0
        while cur:
            count +=1
            pre = cur              # 用pre記錄前一個節點
            cur = cur.next
            if cur.item ==item:
                pre.next =cur.next  # 將pre節點的next指標指向cur節點的下一個節點
                return
        print(f'未找到{item}')       # 如果沒有找到要刪除的節點,則列印提示資訊
  1. 為連結串列新增功能-搜尋某個值是否在連結串列中
    此功能較為簡單,就不再補充程式碼。

以上,就是對於連結串列的建模,實現了連結串列的基本功能。

相關文章