第3章 順序表的鏈式存儲


第3章 順序表的鏈式存儲

目錄

數據結構與算法_師大完整教程目錄(更有python、go、pytorch、tensorflow、爬蟲、人工智能教學等着你):https://www.cnblogs.com/nickchen121/p/13768298.html

一、鏈式存儲

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

二、單鏈表

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. 如果在單鏈表的最前面插入新結點,讓單鏈表的首指針指向新插入的結點

p->next = head;
head = p;
6. 如果在單鏈表中間插入新結點:

p->next = q->next;
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. 如果刪除結點為第一個結點

head = head->next;
free(p)
6. 如果刪除結點為其他結點

pre->next = q->next;
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.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. 如果在非空的帶頭結點的單鏈表最前面插入一個新結點

p->next = q->next;
q->next = p;
6. 如果在非空的帶頭結點的單鏈表的內部插入一個新結點

p->next = q->next;
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. 刪除帶頭結點的單鏈表的第一個實際結點:

pre->next = q->next;
free(q)
6. 刪除帶頭結點的單鏈表的內部結點:

pre->next = q->next;
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.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.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.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.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.1 求單鏈表中結點個數(算法)

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

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 求帶頭結點的單鏈表中的結點個數(算法)

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

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 逆轉一個單鏈表(算法)

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

  1. 核心思想:通過 head->next 保留上一個 \(q\) 的狀態

  2. 算法步驟:

    1. \(p\) 指向實際的第一個結點
    2. 循環以下步驟:

\(p\) 一直循環下去,直到走完整個鏈表,\(p\) 循環的時候,\(q\) 跟着 \(p\) 一起刷新
\(q\)\(next\) 指針域始終指向 head->next;
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;
    }
}

九、錯題集

  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\)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM