Python 數據結構 --> 鏈表

類:節點NODEpython

  用鏈表實現的基本模塊是節點。每一個節點對象必須持有至少兩條信息。首先,節點必須包含列表元素自己。
咱們將這稱爲該節點的「數據區」(data field)。此外,每一個節點必須保持到下一個節點的引用
示例1 顯示了Python 的實現方法。須要指出,咱們將一般如下圖 所示的方式表明一個節點對象。
節點類還包括訪問和修改的經常使用方法:返回節點
數據和引用到下一項。數組

class Node:
    def __init__(self, initdata):
        self.data = initdata
        self.next = None

    def getData(self):
        return self.data

    def getNext(self):
        return self.next

    def setData(self, newdata):
        self.data = newdata

    def setNext(self, newnext):
        self.next = newnext

  

  節點類對象包含自己的項目數據和對下一個節點的引用app

咱們以常見的方式建立了節點類。測試

>>> temp = Node(66)
>>> temp.getData()
66
>>> 

 Python 的特殊值None 將在節點類和以後的鏈表類中發揮重要的做用。引用None 意味着沒有下一個節點。
在構造器中,一個節點的對下一節點引用的初始賦值是None。由於這有時被稱爲把節點「接地」(grounding)
咱們在圖中將使用標準化的「接地」標誌來表示一個值爲None 的引用。
None 來做爲你在初始化時對下一個節點的引用是一個極妙的主意。spa

 

 無序列表 UNORDERED LIST3d

  無序列表將由一個節點集合組成,每個節點採用顯式引用連接到下一個節點。
只要咱們知道第一個節點的位置(包含第一項),在這以後的每一個元素均可以經過如下鏈
接找到下一個節點。爲實現這個想法,UnorderedList 類必須保持一個對第一節點的引用。指針

代碼2 顯示了這種構造結構。注意每一個UnorderedList 對象將保持列表的頭一個節點的引用。orm

class UnorderedList:
    def __init__(self):
        self.head = None

實例化:對象

>>> mylist = UnorderedList()

 在最初當咱們創建一個列表時,其中沒有任何元素。這些賦值語句創建了一個鏈接好的鏈表。
正如咱們在定義節點類時討論到的,特殊引用——None 會再被用來講明列表的頭不指向任何東西。
最終,如圖所示,以前給出的示例列表將由一個鏈表來表示。列表的頭指向包含列表的第一項的第一節點。
以此類推,該節點有一個指針指向到下一個節點(的內容)。須要注意的一點是,
列表類自己不包含任何節點。取而代之的是,它包含對鏈式存儲結構的第一個節點的引用。

blog

 is_empty(判斷鏈表是否爲空)方法僅僅只是檢查了列表的頭是否指向None。其結果以布爾值表示,
若是鏈表中沒有節點,self.head==None 的結果只能是True。由於新鏈表爲空,因此構造器和
是否爲空的檢驗必須與另外一個保持一致。這個顯示了使用指向None 來指示鏈表結構的結束。在
Python 中,None 能夠與任何指向相提並論。若是兩個指向同一個物體,那麼他們是平等的。

    def isEmpyty(self):
        return self.head == None

鏈表頭部新增節點:

    def add(self, item):
        temp = Node(item)
        temp.setNext(self.head)
        self.head = temp

 列表中的每一個元素一定屬於一個節點。第二行建立了一個新的節點並將插入的元素做爲節點的數據。

如今咱們必須經過連接這個新的節點與原有的結構來完成插入元素的工做。

  •  第一個步驟(第3行)是把新插入節點的引用設爲原來列表的頭節點
  • 因爲列表中的其餘部分已經和這個新節點正確地鏈接了,咱們能夠把列表頭部head 指向這個新的節點。
  • 代碼第4行就是這一步驟,它肯定了列表的頭部。

 接下來咱們所要實現的方法——求元素個數(size)查找(search) 移除(remove),所有
是基於一個叫作鏈表的遍歷(traversal)的操做的。遍歷指的是有序地訪問每個節點的過程。爲了
作到這一點,咱們可使用一個外部引用,它最開始指向列表的第一個節點。每當咱們訪問一個節點時,
咱們經過「側向移動」(traversing)到下一個引用的方式,將外部引用移動到下一個節點。

 爲了實現「size 求元素個數」的方法,咱們須要遍歷鏈表,而且記錄出現過的節點個數。

    def size(self):
        current = self.head
        count = 0
        while current is not None:
            count += 1
            current = current.getNext()
        return count

  咱們把這個外部引用稱爲「當前」(current)

  • 在第二行中它被初始化,指向列表的頭部。最初咱們並無發現任何節點,因此計數的初值被設定爲0
  • 第四到第六行實際上實現了此次遍歷。
  • 只要這個外部引用沒有遇到列表的尾端(None),咱們就將current移動到下一個節點,正如第6 行所示。
  • 和前文相同,把引用和None 進行比較的操做很是有用。
  • 每當current 移動到了一個新的節點,咱們就把計數器加1(count)
  • 最終,咱們在循環結束後返回了計數值

  在一個無序表中查詢一個數值這一方法的實現一樣須要用到遍歷。每當咱們訪問一個鏈表中的節點時,
咱們會判斷儲存在此的數據是否就是咱們所要找的元素。
事實上,若是咱們的工做進行到了列表的底端,這意味着咱們所要尋找的元素在列表中並不存在。
一樣,若是咱們找到了那個元素,那麼就沒有必要繼續尋找了。

    def search(self, item):
        current = self.head
        found = False
        while current is not None and not found:
            if current.getData() == item:
                found = True
            else:
                current = current.getNext()
        return found
  • size 方法同樣,遍歷在列表的頭部被初始化(第2行)。
  • 咱們一樣使用一個叫作found 的布爾變量來表示咱們是否找到了咱們所要找尋的元素。
  • 考慮到咱們在遍歷開始時並無找到那個元素,found 被設爲假(False)(第3 行)。
  • 第4 行中的循環同時考慮了上述的兩種狀況。
  • 只要還有餘下的未訪問節點而且咱們尚未找到元素,咱們便繼續檢查下一個節點。
  • 第5 行中的條件語句判斷所尋的數據項是否在節點current 之中。若是是,那麼found 被設爲真(True)。

 

「移除(remove)」這個方法須要兩個步驟。

  • 首先咱們須要遍歷這個列表,來找尋咱們想要移除的元素。
  • 只要找到了這個元素(假設它存在),就必須移除它。第一步同查詢(search)十分接近。
  • 咱們使用一個外部引用,讓它開始時指向鏈表的頭部,順着鏈表遍歷,直到找到要移除的元素爲止。
  • 因爲咱們假設待移除的元素必定存在,那麼循環將會在遍歷到列表底部前終止。
  • 因此,咱們這時只須要再使用一個布爾變量found

當found 爲真(True)時,current 將會是對包含了要移除元素的一個引用。但咱們要如何移除它?

 解決這個難題的方法是,在遍歷鏈表時使用兩個外部引用。current 不變,仍然標記當前遍歷到的位置。
新加入的引用——咱們叫「前一個」(previous)——在遍歷過程當中老是落後於current 一個節點
這樣,當current 停在待刪除節點時,previous 即停在鏈表中須要修改的節點處。

    def remove(self, item):
        current = self.head
        previous = None
        found = False
        while not found:
            if current.getData() == item:
                found = True
            else:
                previous = current
                current = current.getNext()

        if previous is None:
            self.head = current.getNext()
        else:
            previous.setNext(current.getNext())

 完整的移除(remove)過程:

  • 第二第三行給兩個外部引用賦了初始值。注意到current 如同其餘的「遍歷」實例同樣,從列表的頭部開始。
  • 然而,咱們設定previous 老是訪問current 的前一個節點。所以,previous 的初值設爲None,由於頭部以前沒有節點
  • 布爾變量found 將會再次被用於控制此次循環。
  • 在第六到第七行咱們區分了儲存在節點中的單元是不是咱們想要移除的元素。
  • 若是是,found 將會變成真(True)。若是咱們沒有找到元素,previous current 必須同時向後移動一個節點。
  • 一樣,這兩個操做的順序相當重要,首先previous 要向後移動一個節點,到current 的位置,
  • 而後current 才能移動。這一過程經常被稱爲「一寸寸蠕動」(inch-worming),由於previous 先要跟上current
    current 才能向前移動。

移除(remove)工做中的查詢步驟一旦完成,咱們須要從鏈表中移除那個節點。圖3.21顯示了一個必須進行改動的鏈接。

然而這裏又有一點須要特別說明。

若是要移除的那個元素剛好是列表中的第一個,那麼current會引用鏈表第一個節點。這也就意味着previous的引用會是None。
咱們以前提到,previous要指向那個引用要發生變化的節點。在這種狀況下,須要改動的不是previous,而是鏈表的頭節點(如圖3.22)。

代碼7的第12行讓咱們能夠檢查咱們所要處理的狀況是不是上述的特殊狀況。

  • 若是previous沒有移動,那麼當布爾變量found已經爲真時,previous仍然是None
  • 這種狀況下,鏈表頭部head要發生變化,引用緊跟current的那個節點,實際效果就等於從列表中移除第一個節點。
  • 而當previous不是None時,要移除的節點必定在鏈表中表頭後方的某處。
  • 這時previous將會讓咱們找到所要移除的節點的前一個節點。
  • 第15行調用了previoussetNext方法來完成此次移除。
  • 注意到在兩種狀況中,須要改動的節點或表頭最終都指向了current的後一個節點。

鏈表反轉

方法一:

 對於一個長度爲n的單鏈表list,用一個大小爲n的數組arr儲存從單鏈表從頭至尾遍歷的全部元素,

在從arr尾到頭讀取元素簡歷一個新的單鏈表; 時間消耗O(n),空間消耗O(n)

class Node:
    def __init__(self, initdata):
        self.data = initdata
        self.next = None


def reverse_linkedlist1(head):
    if head == None or head.next == None: # 邊界條件
        return head
    arr = []      # 空間消耗爲n, n 爲單鏈表的長度
    while head:
        arr.append(head.data)
        head = head.next

    newhead = Node(0)
    tmp = newhead
    for i in arr[::-1]:
        tmp.next = Node(i)
        tmp = tmp.next

    return newhead.next

 

方法二:

 開始以單鏈表的第二個元素爲循環變量,用2個變量循環向前操做,

並設置1個輔助變量tmp,保存數據; 時間消耗O(n),空間消耗O(1);

def reverse_linkedlist2(head):
    if head == None or head.next == None: # 邊界條件
        return head
    p1 = head              # 循環變量1
    p2 = head.next         # 循環變量2
    tmp = None             # 保存數據的臨時變量
    while p2:
        tmp = p2.next
        p2.next = p1
        p1 = p2
        p2 = tmp
    head.next = None
    return p1

測試:

def create_all(arr):
    pre = Node(0)
    tmp = pre
    for i in arr:
        tmp.next = Node(i)
        tmp = tmp.next
    return pre.next


def print_all(head):
    tmp = head
    result = []
    while tmp:
        result.append(tmp.data)
        tmp = tmp.next
    print(result)


a = create_all(range(5))
print_all(a)

b = reverse_linkedlist1(a)
print_all(b)

c = reverse_linkedlist2(a)
print_all(c)
相關文章
相關標籤/搜索