資料結構——線性表

Dictator丶發表於2019-01-10

1.  線性表的概念和抽象資料型別

 1.1  概念

  • 線性表,通常是指某種資料元素的一個結合,並且記錄著元素之間的一種順序關係。

 1.2  表的基本操作

ADT List:                       # 一個表的抽象資料型別
    List(self)                  # 表構造操作,建立一個新表
    is_empty(self)              # 判斷 self 是否為空表
    len(self)                   # 獲取表 self 的長度
    prepend(self, elem)         # 將元素 elem 插入表中作為第一個元素
    append(self, elem)          # 將元素 elem 插入表中最為最後一個元素
    insert(self, elem, i)       # 將元素 elem 插入表中作為第 i 個元素
    del_first(self)             # 刪除表中的第一個元素
    del_last(self)              # 刪除表中的最後一個元素
    del(self, i)                # 刪除表中第 i 個元素
    search(self, elem)          # 查詢元素 elem 在表中的位置,不存在返回 -1
    forall(self, op)            # 對錶中的每個元素執行 op 操作
複製程式碼

 1.3  線性表的實現

  • 將表中元素順序地存放在一大塊連續的儲存區裡,這樣實現的表叫做順序表。在這種實現中,元素之間的順序由它們的儲存順序自然表示。
  • 將表元素放在通過連結構造起來的一系列儲存塊裡,這樣實現的表稱為連結串列。

2.  順序表

  1. 佈局方案,如圖所示

    資料結構——線性表

  2. 建立順序表時,一般是建立可變的表,所以要考慮表中當前元素的個數以及元素儲存區的容量,一個合理的方法是分配擬一塊足以容納當前需要記錄的元素的儲存塊,還應該保留一些空位,以滿足增加元素的需要。如圖所示:

    資料結構——線性表

  3. 順序表的基本實現方式

    資料結構——線性表

          分離式的優點:在標識不變的情況下,為表物件換一塊元素儲存區。即表還是原來的表,裡面內容不變,但是容量增加了。
          如果一直不斷地向表中新增元素,一定會填滿元素儲存區,如果採用一體式結構,就會新增失敗。
          採用分離式結構,可以在不改變物件的情況下換一塊更大的元素儲存區,使加入元素正常完成。具體步驟如下:

          ①.   另外申請一塊更大的元素儲存區
          ②.   把表中已有的元素複製到新的儲存區
          ③.   用新的元素儲存區替換原來的元素儲存區
          ④.   加入新元素

  4. Python 中的 list

    在 Python 的官方實現中, list 就是一種採用分離式技術實現的動態順序表。
    相關操作複雜度:

    • len(): O(1)

    • 元素訪問和複製,尾端加入和尾端刪除(包括尾端切片刪除) 都是 O(1) 操作

    • 一般位置元素的加入,切片替換,切片刪除,表拼接(extend)都是 O(n) 操作

    • list.clear() 時間複雜度 O(1)

    • list.reverse() 時間複雜度 O(n)

        a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
        def reverse_list():
            i = 0
            j = len(a) - 1
            while i < j:
                a[i], a[j] = a[j], a[i]
                i += 1
                j -= 1
      
        reverse_list()
        print(a)
      複製程式碼
  5. 順序表的不足

    • 由於元素儲存的集中方式和連續性,表的結構不靈活,如果要經常修改表結構,順序表的實現就不太方便。
    • 如果程式裡需要巨大的線性表,採用順序表就需要巨大的元素儲存空間,會造成儲存管理方面的困難。

2.  連結串列

  1. 採用連結方式實現線性表的基本思想:

    • 把表中的元素份分別儲存在一批獨立的儲存區塊裡,稱為表的結點。
    • 保證從組成表的任意一個結點可以找到與其相關的下一個結點。
    • 在前一結點裡用連結的方式記錄與下一個結點之間的關聯。
      這樣,只要能找到表的第一個結點,就能順序的找到這個表的其他節點。
  2. 單連結串列

    資料結構——線性表

    操作複雜度:

    • 建立空表: O(1)
    • 刪除表: O(1)
    • 判斷空表: O(1)
    • 加入元素:
      • 首端加入元素:O(1)
      • 尾端加入元素:O(n)
      • 定位加入元素:O(n)
    • 刪除元素:
      • 首端刪除元素:O(1)
      • 尾端刪除元素:O(n)
      • 定位刪除元素:O(n)

    單連結串列實現程式碼

  3. 帶有尾結點引用的單連結串列

    單連結串列有一個缺點: 尾端加入元素操作的效率低

    在表物件增加一個表尾結點引用域,只需常亮時間就可以找到尾結點,表尾插入就能達到O(1)。

    資料結構——線性表

  4. 迴圈單連結串列

    迴圈單連結串列與普通單連結串列的區別是最後一個結點的next不為None,而是指向表的第一個結點,如圖 a) 所示。仔細考慮,發現在連結串列物件記錄表尾結點更合適,如圖 b)。

    這樣就支援 O(1) 時間的表頭/表尾插入操作,o(1)時間的表頭刪除

    資料結構——線性表

    迴圈單連結串列的部分程式碼實現:

    資料結構——線性表

  5. 雙連結串列

    對單連結串列加入另一個方向的連結,就得到了雙連結串列,那麼首尾兩端插入和刪除操作都能高效的完成。

    雙連結串列的部分程式碼實現:

    資料結構——線性表
    資料結構——線性表

  6. 迴圈雙連結串列

    讓雙連結串列的表尾結點的next指向表的首節點,表首節點的prev指向表的尾結點。這樣的話,知道首節點或者尾結點其中一個,就可以高效實現首尾兩端的元素加入或刪除,時間複雜度為 O(1).

  7. 連結串列總結

    • 基本單連結串列,首端插入刪除 O(1)
    • 增加尾結點引用域的單連結串列首端/尾端插入和首端刪除 O(1)
    • 迴圈單連結串列首端/尾端插入和首端刪除 O(1)
    • 雙連結串列,如果有尾結點應用, 則兩端插入和刪除 O(1)
    • 迴圈雙連結串列,兩端插入和刪除 O(1)
    • 對於單連結串列,遍歷和資料檢索只能從表頭開始,需要 O(n) 時間。對於雙連結串列,可以從表頭或者表尾開始,複雜度也是 O(n)。與它們對應的兩種迴圈連結串列,遍歷和資料檢索可以從表中任何一個地方開始。
  8. 連結串列優缺點

    優點:

    • 由於表結構是由一些連結起來的結點形成的,所以表的結構很容易修改。
    • 只需要修改結點之間的連結,就能靈活的修改表的結構和資料排列方式。

    缺點:

    • 定位訪問需要線性時間
    • 單連結串列尾端操作需要線性時間,增加一個尾指標,可以將尾端插入變為O(1)操作,但是尾端刪除仍是線性時間。只有雙連結串列才能實現兩端高效的插入和刪除。
    • 單連結串列查詢當前元素的前一元素,必須從頭開始掃描表結點。雙連結串列可以解決這個問題,但是儲存空間增大。

參考

資料結構與演算法 Python語言描述(裘宗燕)

相關文章