1. 程式人生 > 實用技巧 >資料結構筆記 — 連結串列

資料結構筆記 — 連結串列

連結串列


引言

  • 資料型別:一組性質相同的值得集合及定義在此集合上的一些操作的總稱

  • 抽象資料型別 (Abstract Data Type, ADT) :是指一個數學模型及定義在該模型上的一組操作

  • 抽象資料型別的標準格式(虛擬碼)

1 ADT 抽象資料型別名
2 Data
3     資料元素之間邏輯關係的定義
4 Operation
5     操作
6 endADT

1 線性表

1.1 線性表的概念
  • 定義:由零個或者多個數據元素組成的 有限序列

    • 若元素存在多個,則第一個元素無前驅,而最後一個元素無後繼,其他元素都有且只有 一個前驅一個後繼

    • 線性表元素的個數 n(n>=0)

      定義為線性表的長度,當 n=0 時,為空表

    • 線性表的抽象資料型別定義

      ADT 線性表(List)
      Data
          線性表的資料物件集合為{a1, a2, ..., an},每個元素的型別均為 DataType .其中,除第一個元素 a1 外,每一個元素有且只有一個直接前驅元素,除了最後一個元素 an 外,
        每一個元素有且只有一個直接後繼元素, 且資料元素之間的關係是一對一的關係 Opreation InitList(
      *L):初始化操作,建立一個空的線性表 ListEmpty(L):判斷線性表是否為空表,為空表返回 true,否則返回 false ClearList(
      *L):清空線性表 GetElem(L, i, *e):將線性表 L 中的第 i 個位置的元素值返回給 e LocateElem(L, e):線上性表 L 中查詢與給定值 e 相等的元素,如果查詢成功,返回該元素在表中的序號;否則,返回 0 表示失敗 ListInsert(*L, i, e):線上性表 L 中第 i 個位置插入新元素 e ListDelete(*L, i, *e):刪除線性表 L 中第 i 個位置的元素,並用 e 返回其值 endADT

      [例] 若存在兩個集合 A 、B,求出兩個集合 A 、B 的並集 A ∪ B

      [思路分析]

      迴圈遍歷集合 B 中的每個元素,判斷當前元素是否存在於 A 中,若不存在,將當前元素插入集合 A 中,最後得到的集合 A 即為 A ∪ B 的結果

      //虛擬碼如下
      void unionList(List *La, List Lb){
          int La_len, Lb_len, i;
          
          ElemType e;
          La_len = ListLength(*La);
          Lb_len = ListLength(*Lb);
          
          for (i = 1; i <= Lb_len; i++) {
              GetElem(Lb, i, *e);
              if (!LocateElem(*La, e)){
                  ListInsert(La, **La_len, e);
              }
          }
      }
1.2 線性表的順序儲存結構
  • 定義:指的是用一段地址連續的儲存單元一次儲存線性表的資料元素

  • 圖示:線性表 (a1, a2, a3, ..., an) 的順序儲存如下所示:

  • 線性表的順序儲存結構程式碼

    #define MAXSIZE 20
    typedef int ElemType;
    typedef struct{
        ElemType data[MAXSIZE];
        int length; //線性表當前長度
    } SqList;
  • 順序儲存結構封裝需要三個屬性

    • 儲存空間的起始位置 ,陣列 data ,它的儲存位置就是線性表儲存空間的儲存位置

    • 線性表的 最大儲存容量 :陣列的最大長度 MaxSize

    • 線性表的 當前長度length

  • 地址的計算方法

    • 假設 ElemType 佔用的儲存單元大小為 c 位元組,則對於線性表中第 i 個元素的儲存位置可由 a1 推算得出:LOC(ai) = LOC(a1) + (i-1)*c ,其中 LOC() 為獲取元素儲存地址的函式

    • 通過這個公式,可以隨時計算出線性表中任意元素的地址,且所用時間均相同,因此它的儲存時間效能就是 O(1) ,通常稱其為 隨機儲存結構    

  • 獲得元素 (GetElem) 操作

    • 將線性表 L 中第 i 個未知的元素值返回,即返回陣列中下標為 i-1 的值即可

      #define OK 1
      #define ERROR 0
      #define TRUE 1
      #define FALSE 0
      typedef int Status;
      // 初始條件:順序線性表 L 已經存在, 1 <= i <= ListLength(L)
      // 操作結果:用 e 返回 L 中第 i 個元素的值Status GetElem(Sqlist L, int i, ElemType *e) {
          if (L.length == 0 || i < 1 || i > L.length) {
              return ERROR;
          }
          *e = L.data[i-1];
          return OK;
      }
  • 插入元素 ListInsert(*L, i, e) 操作

    • 如果插入位置不合理,丟擲異常

    • 如果線性表長度大於等於陣列長度,則丟擲異常或者動態增加陣列容量

    • 從最後一個元素向前開始遍歷到第 i 個位置,分別後移一個位置

    • 將要插入的元素填入位置 i

    • 線性表長度 +1

      // 初始條件:順序線性表 L 已經存在, 1 <= i <= ListLength(L)
      // 操作結果:在 L 中第 i 個位置之前插入新的元素資料 e,L 長度 +1
      ​
      Status ListInsert(SqlList *L, int i, ElemType) {
          int k;
          
          if (L->length == MAXSIZE) { //順序線性表已滿
              return ERROE;
          }
          if (i < 1 || i > L->length) {   //當 i 不在合法範圍內
              return ERROR;
          }
          if (i <= L->length) {   //若資料插入位置不在表尾
              //將要插入位置以後的元素後移一位
              for (k = L->length-1; k >= i-1; k--) {
                  L->data[k+1] = L->data[k];
              }
          }
          
          L->data[i-1] = e;   //將新元素插入
          L->length++;
          
          return OK;
      }
  • 刪除元素 ElemDelete() 操作

    • 如果刪除的位置不合理,丟擲異常

    • 取出刪除元素

    • 從刪除的元素位置開始遍歷到最後一個元素位置,分別將他們都向前移動一個位置

    • 表長 -1

      // 初始條件:順序線性表 L 已經存在, 1 <= i <= ListLength(L)
      // 操作結果:刪除 L 中第 i 個位置的元素資料,並用 e 返回其值,L 長度 -1
      ​
      Status ListDelete(SqList *L, int i, ElemType *e) {
          int k;
          
          if (L->length == 0) {
              return ERROR;
          }
          if (i < 1 || i > L->length) {
              return ERROR;
          }
          
          *e = L->data[i-1];
          
          if (i < L->length) {
              for (k = i; k < L->length; k++) {
                  L->data[k-1] = L->data[k];
              }
          }
          
          L->length--;
          
          return OK;
      }
  • 線性表順訊儲存結構優缺點

    • 線性表的順序儲存結構,在存、讀取資料時,不管是哪個位置,時間複雜度都是 O(1) ,而插入和刪除操作,時間複雜度均為 O(n)

    • 順序儲存結構 比較適合元素個數比較穩定,不經常插入和刪除元素,更多是存取資料的應用

    • 優點

      • 無須為表示表中元素之間的邏輯關條而增加額外的儲存空間

      • 可以快速地存取表中任意位置的元素。

    • 缺點

      • 插入和刪除操作需要移動大量元素

      • 當線性表長度變化較大時,難以確定儲存空間的容量

      • 容易造成儲存空間的“碎片”

1.3 線性表的鏈式儲存結構
  • 定義:我們把儲存資料元素資訊的域稱為 資料域 ,把儲存直接後繼位置的域稱為 指標域 。指標域中儲存的資訊稱為 指標或鏈 。這兩部分資訊組成資料元素稱為 儲存映像 ,稱為 結點(Node)

  • 連結串列中的第一個結點的儲存位置叫做 頭指標 ,最後一個結點 指標為空 (NULL)

  • 頭指標與頭結點的異同

    • 頭指標

      • 頭指標是指連結串列指向第一個結點點的指標,若連結串列有頭結點,則是指向頭結點的指標

      • 頭指標具有標識作用,所以常用頭指標冠以連結串列的名字(指標變數的名字)

      • 無論連結串列是否為空,頭指標均不為空

      • 頭指標是連結串列的必要元素

    • 頭結點

      • 頭結點是為了操作的統一和方便而設立的,放在第一個元素的結點之前,其資料域一般無意義(但也可以用來存放連結串列的長度)

      • 有了頭結點,對在第一元素結點前插入結點和刪除第一結點的操作與其它結點的操作就統一了

      • 頭結點不一定是連結串列的必須要素

  • 單鏈表儲存結構

    typedef struct Node {
        ElemType data;      //資料域
        struct Node *Next;  //指標域
    } Node;
    typedef struct Node *LinkedList;
  • 單鏈表的讀取操作

    • 宣告一個結點 p 指向連結串列第一個結點,初始化 j1 開始

    • j<i 時,就遍歷連結串列,讓 p 的指標向後移動,不斷指向一下結點,j+1

    • 若到連結串列末尾 p 為空,則說明第 i 個元素不存在

    • 否則查詢成功,返回結點 p 的資料

      // 初始條件:順序線性表 L 已經存在, 1 <= i <= ListLength(L)
      // 操作結果:用 e 返回 L 中第 i 個元素的值
      Status GetElem(ListedList L,
      int i, ElemType *e) { int j; LinkedList p; p = L->next; ++j; while (p && j < i) { p = p->next; ++j; } if (!p || j > i) { return ERROR; } *e = p->data; return OK; }