第3章 順序表的鏈式存儲

第3章 順序表的鏈式存儲

1、鏈式存儲

  1. 解決問題:對於線性結構,使用順序存儲,須要足夠大的連續存儲區域
  2. 鏈式存儲:結點除了存放信息,而且附設指針,用指針體現結點之間的邏輯關係
  3. 注:\(c\)語言的動態分配函數\(malloc()\)\(free()\)分別實現內存空間的動態分配和回收,因此沒必要知道某個結點的具體地址
  4. 注:鏈式存儲中,必須有一個指針指向第一個結點的存儲位置,通常爲\(head\)標示
  5. 順序存儲和鏈式存儲的區別:順序存儲更適合查詢量大的程序設計;鏈式存儲更適合須要頻繁插入和刪除的程序

2、單鏈表

2.1 單鏈表的基本概念及描述

  1. 單鏈表結點構造:兩個域,一個存放數據信息的\(info\)域;另外一個指向該結點的後繼結點的\(next\)

2.2 單鏈表的實現

  1. 單鏈表的經常使用操做:
    1. 創建一個空的單鏈表
    2. 輸出單鏈表中各個結點的值
    3. 在單鏈表中查找第\(i\)個結點

2.2.1 單鏈表的存儲結構

typedef int datatype;
typedef struct link_node {
    datatype info;
    struct link_node *next;
} node;

2.2.2 單鏈表的插入操做(算法)

  1. 算法步驟(插入結點爲\(p\),插入到結點\(q\)後面):
    1. 經過 find(head,i) 查找\(q\)結點,查不到打印報錯信息
    2. 給插入結點\(p\)分配空間,並設置信息
    3. 若是在單鏈表的最前面插入新結點,讓單鏈表的首指針指向新插入的結點
      1. p->next = head;
      2. head = p;
    4. 若是在單鏈表中間插入新結點:
      1. p->next = q->next;
      2. q->next=p;
typedef int datatype;
typedef struct link_node {
    datatype info;
    struct link_node *next;
} node;

node *insert(node *head, datatype x, int i) {
    node *p, *q;
    q = find(head, i); // 查找第i個結點
    if (!q && i != 0) {
        printf("\n找不到第%d個結點,不能插入%d!", i, x);
    } else {
        p = (node *) malloc(sizeof(node)); // 分配空間
        p->info = x; // 設置新結點
        if (i == 0) // 插入的結點做爲單鏈表的第一個結點
        {
            p->next = head;
            head = p;
        } else {
            p->next = q->next; // 後插
            q->next = p;
        }
    }
    return head;
}

2.2.3 單鏈表的刪除操做(算法)

  1. 算法步驟(被刪除結點\(q\),被刪除結點前一個結點\(pre\)
    1. 判斷鏈表是否爲空
    2. 循環查找被刪除結點\(q\),而且設置一個結點\(pre\)標示被刪除結點的前一個結點
    3. 若是刪除結點爲第一個結點
      1. head = head->next;
      2. free(p)
    4. 若是刪除結點爲其餘結點
      1. pre->next = q->next;
      2. free(p)
typedef int datatype;
typedef struct link_node {
    datatype info;
    struct link_node *next;
} node;

node *dele(node *head, datatype x) {
    node *pre = NULL, *p;
    if (!head) {
        printf("單鏈表是空的");
        return head;
    }
    p = head;
    while (p && p->info != x) // 尋找被刪除結點p
    {
        pre = p; // pre指向p的前驅結點
        p = p->next;
    }
    if (p) {
        if (!pre) // 被刪除結點沒有上一個結點,則是要刪除的是第一個結點
        {
            head = head->next;
        } else {
            pre->next = p->next;
        }
        free(p)
    }
    return head;
}

3、帶頭結點的單鏈表

3.1 帶頭結點的單鏈表的基本概念及描述

  1. 頭結點的做用:單鏈表的插入和刪除須要對空的單鏈表進行特殊處理,所以能夠設置 \(head\) 指針指向一個永遠不會被刪除的結點——頭結點
  2. 注:\(head\) 指示的是所謂的頭結點,它不是實際結點,第一個實際結點應該是 head->next 指示的

3.2 帶頭結點的單鏈表的實現

  1. 帶頭結點的單鏈表的經常使用操做:
    1. 創建一個空的帶頭結點的單鏈表
    2. 輸出帶頭結點的單鏈表中各個結點的值
    3. 在帶頭結點的單鏈表中查找第 \(i\) 個結點

3.2.1 帶頭結點的單鏈表的存儲結構

typedef int datatype;
typedef struct link_node {
    datatype info;
    struct link_node *next;
} node;

3.2.2 帶頭結點的單鏈表的插入(算法)

  1. 算法步驟( \(p\) 爲插入結點,\(q\) 爲插入前一個結點):
    1. 經過 find(head,i) 查找帶頭結點的單鏈表中的第 \(i\) 個結點( \(i=0\) 表示新結點插入在頭結點以後)
    2. 若是沒找到結點 \(q\),打印報錯信息
    3. 若是在非空的帶頭結點的單鏈表最前面插入一個新結點
      1. p->next = q->next;
      2. q->next = p;
    4. 若是在非空的帶頭結點的單鏈表的內部插入一個新結點
      1. p->next = q->next;
      2. q->next = p;
typedef int datatype;
typedef struct link_node {
    datatype info;
    struct link_node *next;
} node;

node *insert(node *head, datatype x, int i) {
    node *p, *q;

    q = find(head, i); // 查找帶頭結點的單鏈表中的第 i 個結點,i=0 時表示新結點插入在頭結點以後

    if (!q) // 沒有找到
    {
        printf("\n帶頭結點的單鏈表中不存在第%d個結點!不能插入%d!", i, x);
        return head;
    }

    p = (node *) malloc(sizeof(node)); // 爲準備插入的新結點分配空間
    p->info = x; // 爲新結點設置值
    p->next = q->next;
    q->next = q; // i=0 時,本語句等價於 head->next=p
    return head;
}

3.2.3 帶頭結點的單鏈表的刪除(算法)

  1. 算法步驟(被刪除結點爲 \(q\),被刪除結點的前一個結點爲 \(pre\)):
    1. 設置 \(pre\) 指向頭結點
    2. \(q\) 從帶頭結點的單鏈表的第一個實際結點開始循環尋找值爲 \(x\) 的結點
    3. 刪除帶頭結點的單鏈表的第一個實際結點:
      1. pre->next = q->next;
      2. free(q)
    4. 刪除帶頭結點的單鏈表的內部結點:
      1. pre->next = q->next;
      2. free(q)
typedef int datatype;
typedef struct link_node {
    datatype info;
    struct link_node *next;
} node;

node *dele(node *head, datatype x) {
    node *pre = head, *q; // pre 指向頭結點

    q = head->next; // q 從帶頭結點的單鏈表的第一個實際結點開始找值爲 x 的結點
    while (q && q->info != x) // 循環查找值爲 x 的結點
    {
        pre = q; // pre 指向 q 的前驅
        q = q->next;
    }

    if (q) {
        pre->next = q->next; // 刪除
        free(q); // 釋放內存空間
    }
    return head;
}

4、循環單鏈表

4.1 循環單鏈表的基本概念及描述

  1. 單鏈表存在的問題:從表中的某個結點開始,只能訪問該結點後面的結點
  2. 循環單鏈表解決的問題:從表中的任意一個結點開始,使其都能訪問到表中的全部的結點
  3. 循環單鏈表:在單鏈表的基礎上,設置表中最後一個結點的指針域指向表中的第一個結點

4.2 循環單鏈表的實現

  1. 循環單鏈表的經常使用操做:
    1. 創建一個空的循環單鏈表
    2. 得到循環單鏈表的最後一個結點的存儲地址
    3. 輸出循環單鏈表中各個結點的值
    4. 在循環單鏈表中查找一個值爲 \(x\) 的結點
    5. 循環單鏈表的插入操做
    6. 循環單鏈表的刪除操做
    7. 循環單鏈表的總體插入與刪除操做

4.2.1 循環單鏈表的存儲結構

typedef int datatype;
typedef struct link_node {
    datatype info;
    struct link_node *next;
} node;

5、雙鏈表

5.1 雙鏈表的基本概念及描述

  1. 雙鏈表解決的問題:設置一個 \(llink\) 指針域,經過這個指針域直接找到每個結點的前驅結點

5.2 雙鏈表的實現

  1. 雙鏈表的經常使用操做:
    1. 創建一個空的雙鏈表
    2. 輸出雙鏈表中各個結點的值
    3. 查找雙鏈表中第 \(i\) 個結點
    4. 雙鏈表的插入操做
    5. 雙鏈表的刪除操做

5.2.1 雙鏈表的存儲結構

typedef int datatype;
typedef struct dlink_node {
    datatype info;
    struct dlink_node *llink, *rlink;
} dnode;

6、鏈式棧

6.1 鏈式棧的基本概念及描述

  1. 鏈式棧:使用鏈式存儲的棧
  2. 注:鏈式棧的棧頂指針通常用 \(top\) 表示

6.2 鏈式棧的實現

  1. 鏈式棧的經常使用操做:
    1. 創建一個空的鏈式棧
    2. 判斷鏈式棧是否爲空
    3. 取得鏈式棧的棧頂結點值
    4. 輸出鏈式棧中各個結點的值
    5. 向鏈式棧中插入一個值爲 \(x\) 的結點
    6. 刪除鏈式棧的棧頂節點

6.2.1 鏈式棧的存儲結構

typedef int datatype;
typedef struct link_node {
    datatype info;
    struct link_node *next;
} node;

7、鏈式隊列

7.1 鏈式隊列的基本概念及描述

  1. 鏈式隊列:使用鏈式存儲的隊列
  2. 注:隊列必須有隊首和隊尾指針,所以增長一個結構類型,其中的兩個指針域分別爲隊首和隊尾指針

7.2 鏈式隊列的實現

  1. 鏈式隊列的經常使用操做:
    1. 創建一個空的鏈式隊列
    2. 判斷鏈式隊列是否爲空
    3. 輸出鏈式隊列中各個結點的值
    4. 取得鏈式隊列的隊首結點值
    5. 向鏈式隊列中插入一個值爲 \(x\) 的結點
    6. 刪除鏈式隊列中的隊首結點

7.2.1 鏈式隊列的存儲結構

typedef int datatype;
typedef struct link_node {
    datatype info;
    struct link_node *next;
} node;
typedef struct {
    node *front, *rear; // 定義隊首和隊尾指針
} queue;

8、算法設計題

8.1 求單鏈表中結點個數(算法)

設計一個算法,求一個單鏈表中的結點個數node

typedef struct node {
    int data;
    struct node *next;
} linknode;
typedef linknode *linklist;

int count(linklist head) {
    int c = 0;
    linklist p = head; // head爲實際的第一個結點

    while (p) // 計數
    {
        c++;
        p = p->next;
    }
    return c;
}

8.2 求帶頭結點的單鏈表中的結點個數(算法)

設計一個算法,求一個帶頭結點單鏈表中的結點個數c++

typedef struct node {
    int data;
    struct node *next;
} linknode;
typedef linknode *linklist;

int count(linlist head) {
    int c = 0;
    linklist = head->next; // head->next 爲實際的第一個結點

    while (p) // 計數
    {
        c++;
        p = p->next;
    }
    return c;
}

8.3 在單鏈表中的某個結點前插一個新結點(算法)

設計一個算法,在一個單鏈表中值爲 y 的結點前面插入一個值爲 x 的結點。即便值爲 x 的新結點成爲值爲 y 的結點的前驅結點算法

typedef struct node {
    int data;
    struct node *next;
} linknode;
typedef linknode *linklist;

void insert(linklist head, int y, int c) {
    linklist pre, p, s; // 假設單鏈錶帶頭結點
    pre = head;
    p = head->next;

    while (p && p->data != y) {
        pre = p;
        p = p->next;
    }

    if (p) // 找到了值爲 y 的結點,即 p == y
    {
        s = (linklist) malloc(sizeof(linknode));
        s->data = x;
        s->next = p;
        pre->next = s;
    }
}

8.4 判斷單鏈表的各個結點是否有序(算法)

設計一個算法,判斷一個單鏈表中各個結點值是否有序函數

typedef struct node {
    int data;
    struct node *next;
} linknode;
typedef linknode *linklist;

int issorted(linklist head, char c) // c='a' 時爲升序,c='d' 時爲降序
{
    int flag = 1;
    linklist p = head->next;

    switch (c) {
        case 'a': // 判斷帶頭結點的單鏈表 head 是否爲升序
            while (p && p->next && flag) {
                if (p->data <= p->next->data) p = p->next;
                else flag = 0;
            }
            break;
        case 'd': // 判斷帶頭結點的單鏈表 head 是否爲降序
            while (p && p->next && flag) {
                if (p->data >= p->next->data) p = p->next;
                else flag = 0
            }
            break;
    }
    return flag;
}

8.5 逆轉一個單鏈表(算法)

設計一個算法,利用單鏈表原來的結點空間將一個單鏈表就地轉置spa

  1. 核心思想:經過 head->next 保留上一個 \(q\) 的狀態
  2. 算法步驟:
    1. \(p\) 指向實際的第一個結點
    2. 循環如下步驟:
      1. \(p\) 一直循環下去,直到走完整個鏈表,\(p\) 循環的時候,\(q\) 跟着 \(p\) 一塊兒刷新
      2. \(q\)\(next\) 指針域始終指向 head->next;
      3. head->next; 始終指向上一個 \(q\)
typedef struct node {
    int data;
    struct node *next;
} linknode;
typedef linknode *linklist;

void verge(linklist head) {
    linlist p, q;
    p = head->next;
    head->next = NULL;

    while (p) {
        q = p;
        p = p->next;
        q->next = head->next; // 經過 head->next 保留上一個 q 的狀態
        head->next = q;
    }
}

8.6 拆分結點值爲天然數的單鏈表,原鏈表保留值爲偶數的結點,新鏈表存放值爲奇數的結點(算法)

設計一個算法,將一個結點值天然數的單鏈表拆分爲兩個單鏈表,原表中保留值爲偶數的結點,而值爲奇數的結點按它們在原表中的相對次序組成一個新的單鏈表設計

typedef struct node {
    int data;
    struct node *next;
} linknode;
typedef linknode *linklist;

linklist sprit(linklist head) {
    linklist L, pre, p, r;

    L = r = (linklist) malloc(sizeof(linknode));
    r->next = NULL;
    pre = head;
    p = head->next;

    while (p) {
        if (p->data % 2 == 1) // 刪除奇數值結點,並用 L 鏈表保存
        {
            pre->next = p->next;
            r->next = p;
            r = p; // 這樣使得 r 變成了 r->next
            p = pre->next; // 這樣使得 p 變成了 head->next->next
        } else // 保留偶數值結點
        {
            pre = p; // 書中的貌似多餘操做
            p = p->next;
        }
    }
    r->next = NULL; // 置返回的奇數鏈表結束標記
    return L;
}

8.7 在有序單鏈表中刪除值大於 x 而小於 y 的結點(算法)

設計一個算法,對一個有序的單鏈表,刪除全部值大於 x 而不大於 y 的結點指針

typedef struct node {
    int data;
    struct node *next;
} linknode;
typedef linknode *linklist;

void deletedata(linklist head, datatype x, datatype y) {
    linklist pre = head, p, q;

    p = head->next;

    // 找第 1 處大於 x 的結點位置
    while (p && p->data <= x) {
        pre = p;
        p = p->next;
    }

    // 找第 1 處小於 y 的位置
    while (p && p->data <= y) p = p->next;

    // 刪除大於 x 而小於 y 的結點
    q = pre->next;
    pre->next = p; // 小於 x 的第一個結點指向大於 y 的第一個結點
    pre = q->next;
  	// 釋放被刪除結點所佔用的空間
    while (pre != p) { // 此時 p 已經指向了大於 y 的第一個結點
        free(q);
        q = pre;
        pre = pre->next;
    }
}

9、錯題集

  1. 在頭結點的單鏈表中查找 \(x\) 應選擇的程序體是:node *p = head; while (p && p->info != x) p = p->next; return p;
    1. 注:未找到時須要返回頭結點 \(head\),而不是返回一個 \(NULL\)
  2. 用不帶頭結點的單鏈表存儲隊列時,其隊頭指針指向隊頭結點,其隊尾指針指向隊尾結點,則在進行刪除操做時隊頭隊尾指針均可能要修改
    1. 注:鏈式隊列中只有一個結點是會出現該狀況,插入時同理
  3. 若從鍵盤輸入 \(n\) 個元素,則創建一個有序單向鏈表的時間複雜度爲 \(O(n^2)\)
    1. 注:第 \(1\) 個數:\(0\) 次查找;第 \(2\) 個數:\(1\) 次查找 \(,\cdots,\)\(n\) 個數,\(n-1\) 次查找,總共 \(n(n-1)/2\)
相關文章
相關標籤/搜索