1. 程式人生 > 其它 >資料結構中的線性離散儲存-連結串列

資料結構中的線性離散儲存-連結串列

在上節,我們已經瞭解到了線性儲存中的連續儲存,我們還把這種儲存結構叫做順序表,或者陣列。並且知道線性連續儲存存在以下優缺點:

  • 優點:能實現快速追加和存取元素
  • 缺點:插入元素或刪除元素都要移動大量的原有元素

在本節,我們將一起來了解《資料結構》中研究的另一種線性資料結構-離散儲存,我們也可以把線性的離散儲存叫做連結串列。連結串列的基本結構如下圖:

如果你沒有閱讀過本系列的前面部門文章,建議您通過以下連結先閱讀之前的內容:

一、連結串列的實現過程

1.定義連結串列節點

和順序表相比,連結串列的儲存結構在實現插入、刪除時,不需要移動大量的元素。但不容易實現隨機存取元素線性表中第i個元素的操作。所以連結串列適用於需要經常進行插入和刪除的操作的線性表,如飛機航班乘客表。

首先我們定義一個02-LinkList.cpp檔案,需要引入基本的c語言標頭檔案,並且定義連結串列節點的結構體

# include <stdio.h> // 標準io頭部,包含printf函式
# include <malloc.h> // 包含malloc函式,在mac電腦上,改為sys/malloc.h
# include <stdlib.h> // 包含exit函式

typedef struct Node
{
    int data;           // 資料域
    struct Node *pNext; // 指標域
} NODE, *PNODE;         // NODE 等價於 struct Node, *PNODE 等價於* Node

2.建立連結串列

接下來我們定義建立連結串列的函式

PNODE create_list(void)
{
    int len; // 存放節點的有效個數
    int val; //存放使用者輸入的臨時存入的節點的值

    // 分配一個不存在任何資料的頭節點
    PNODE pHead = (PNODE)malloc(sizeof(NODE));
    if (NULL == pHead)
    {
        printf("記憶體分配失敗!\n");
        exit(-1);
    }

    // 初始狀態下,連結串列尾節點和頭節點指向同一個記憶體(即頭節點就是尾節點),而指標域為NULL
    PNODE pTail = pHead;
    pTail->pNext = NULL;

    printf("請輸入您需要生成的連結串列節點的個數:len=");
    scanf("%d", &len);

    for (int i = 0; i < len; i++)
    {
        printf("請輸入第%d個節點的值", i + 1);
        scanf("%d", &val);

        PNODE pNew = (PNODE)malloc(sizeof(NODE));
        if (NULL == pNew)
        {
            printf("分配失敗,程式終止!\n");
            exit(-1);
        }

        // 分配成功,給新節點賦值
        pNew->data = val;
        // 讓連結串列尾節指標域點指向最新的節點,實現增加新節點
        pTail->pNext = pNew;
        // 新節點的指標域為NULL
        pNew->pNext = NULL;

        // 最後,再讓尾節點指向新節點。
        pTail = pNew;
    }

    // 連結串列建立完成後,返回頭節點
    return pHead;
}

3.遍歷連結串列元素

從頭節點開始,如果連結串列節點的指標域不為NULL,即輸出資料

void traverse_list(PNODE pHead)
{
    // 把第一個節點賦給變數p
    PNODE p = pHead->pNext;

    while (NULL != p)
    {
        // p 不為NULL,代表有資料,則輸出p的資料於
        printf("%d  ", p->data);
        // 輸出p的資料域之後,讓p指向下一個節點
        p = p->pNext;
    }
    printf("\n");
    return;
}

4.判斷連結串列是否為空和計算連結串列長度

如果連結串列頭節點的指標域為空,則連結串列是空連結串列。長度的計算則通過遍歷連結串列來計算,如下程式碼

// 判斷連結串列是否為空
bool is_empty(PNODE pHead)
{
    if (NULL == pHead->pNext)
    {
        return true;
    }
    else
    {
        return false;
    }
}

// 計算連結串列的長度
int length_list(PNODE pHead)
{
    PNODE p = pHead->pNext;
    int len = 0;
    while (NULL != 0)
    {
        ++len;
        p = p->pNext;
    }
    return len;
}

5.連結串列排序

接下來,我們根據從小到大的資料域值對連結串列節點進行排序。連結串列的排序和順序表類似,我們使用兩個節點變數用於臨時儲存對比中的兩個節點,如下程式碼

// 連結串列排序
void sort_list(PNODE pHead)
{
    int i, j, t;
    int len = length_list(pHead);
    // 定義p和q兩個節點變數,用於臨時存放交換節點
    PNODE p, q;

    // 讓p指向當前節點
    for (i = 0, p = pHead->pNext; i < len; i++, p = p->pNext)
    {
        // 讓q指向下一個節點
        for (j = i + 1, q = p->pNext; i < len; j++, q = q->pNext)
        {
            // 用當前節點和下一個節點進行對比,如果當前節點的資料域大於下一個節點,就將資料進行交換
            if (p->data > q->data)
            {
                t = p->data;
                p->data = q->data;
                q->data = t;
            }
        }
    }
}

6.插入新節點

在接下來的插入和刪除操作中,我們記連結串列的索引為position,position從0開始。首先,在連結串列的position位置插入節點,該節點的值是val,程式碼如下

// 插入節點
bool insert_list(PNODE pHead, int position, int val)
{
    int len = length_list(pHead);
    if (len < 0 || position > len)
    {
        return false;
    }

    int i = 0;
    PNODE p = pHead;
    // 使用while迴圈,使p變數指向position節點的前一個節點
    while (NULL != p->pNext && i < position)
    {
        p = p->pNext;
        ++i;
    }

    // 程式執行到這裡,p已經指向position節點的前一個節點,position節點是否為空無所謂

    // 插入過程1:分配新節點
    PNODE pNew = (PNODE)malloc(sizeof(NODE));
    if (NULL == pNew)
    {
        printf("動態分配記憶體失敗失敗");
        exit(-1);
    }
    // 插入過程2:將傳入的值賦給新節點的資料域
    pNew->data = val;

    // 插入過程3:用變數q臨時儲存position節點
    PNODE q = p->pNext;
    // 插入過程4:將position節點的前一個節點指向新節點
    p->pNext = pNew;
    // 插入過程5:再將新節點指向posion節點
    pNew->pNext = q;

    return true;
}

7.刪除節點

刪除節點和插入節點操作類似。區別在於,插入節操作在找到position節點後,動態分配新空間並插入到原連結串列的position位置,刪除節點操作則在找到position節點之後,釋放position節點的空間,再把原position旁邊兩個不相連的節點連線起來。

// 刪除節點
bool delete_list(PNODE pHead, int position, int *pVal)
{
    int len = length_list(pHead);
    if (len < 0 || position > len)
    {
        return false;
    }

    int i = 0;
    PNODE p = pHead;

    // 使用while迴圈,使p變數指向position節點的前一個節點
    while (NULL != p->pNext && i < position)
    {
        p = p->pNext;
        ++i;
    }

    // 如果position節點為NULL,返回false
    if (NULL == p->pNext)
    {
        return false;
    }

    // 程式執行到這裡,p已經指向position節點的前一個節點,並且position節點是存在的
    // 刪除過程1,讓q變數指向position節點
    PNODE q = p->pNext;
    // 刪除過程2,將position節點的資料賦給pVal
    *pVal = q->data;

    // 刪除過程3,讓position節點的前一個節點指向position節點的下一個
    p->pNext = p->pNext->pNext;

    // 刪除過程4,釋放position節點指向的記憶體,並讓q變數指向NULL
    free(q);
    q = NULL;

    return true;
}

二、測試與驗證

1.測試

所有測試程式碼如下

#include <stdio.h>
#include <sys/malloc.h>
#include <stdlib.h>

// 定義連結串列節點
typedef struct Node
{
    int data;           // 資料域
    struct Node *pNext; // 指標域
} NODE, *PNODE;         // NODE 等價於 struct Node, *PNODE 等價於* Node

// 建立一個非迴圈的單鏈表
PNODE create_list(void)
{
    int len; // 存放節點的有效個數
    int val; //存放使用者輸入的臨時存入的節點的值

    // 分配一個不存在任何資料的頭節點
    PNODE pHead = (PNODE)malloc(sizeof(NODE));
    if (NULL == pHead)
    {
        printf("記憶體分配失敗!\n");
        exit(-1);
    }

    // 初始狀態下,連結串列尾節點和頭節點指向同一個記憶體(即頭節點就是尾節點),而指標域為NULL
    PNODE pTail = pHead;
    pTail->pNext = NULL;

    printf("請輸入您需要生成的連結串列節點的個數:len=");
    scanf("%d", &len);

    for (int i = 0; i < len; i++)
    {
        printf("請輸入第%d個節點的值:", i + 1);
        scanf("%d", &val);

        PNODE pNew = (PNODE)malloc(sizeof(NODE));
        if (NULL == pNew)
        {
            printf("分配失敗,程式終止!\n");
            exit(-1);
        }

        // 分配成功,給新節點賦值
        pNew->data = val;
        // 讓連結串列尾節指標域點指向最新的節點,實現增加新節點
        pTail->pNext = pNew;
        // 新節點的指標域為NULL
        pNew->pNext = NULL;

        // 最後,再讓尾節點指向新節點。
        pTail = pNew;
    }

    // 連結串列建立完成後,返回頭節點
    return pHead;
}

// 遍歷連結串列
void traverse_list(PNODE pHead)
{
    // 把第一個節點賦給變數p
    PNODE p = pHead->pNext;

    while (NULL != p)
    {
        // p 不為NULL,代表有資料,則輸出p的資料於
        printf("%d  ", p->data);
        // 輸出p的資料域之後,讓p指向下一個節點
        p = p->pNext;
    }
    printf("\n");
    return;
}

// 判斷連結串列是否為空
bool is_empty(PNODE pHead)
{
    if (NULL == pHead->pNext)
    {
        return true;
    }
    else
    {
        return false;
    }
}

// 計算連結串列的長度
int length_list(PNODE pHead)
{
    PNODE p = pHead->pNext;
    int len = 0;
    while (NULL != p)
    {
        ++len;
        p = p->pNext;
    }
    return len;
}

// 連結串列排序
void sort_list(PNODE pHead)
{
    int i, j, t;
    int len = length_list(pHead);
    // 定義p和q兩個節點變數,用於臨時存放交換節點
    PNODE p, q;

    // 讓p指向當前節點
    for (i = 0, p = pHead->pNext; i < len - 1; i++, p = p->pNext)
    {
        // 讓q指向當前節點的下一個節點
        for (j = i + 1, q = p->pNext; j < len; j++, q = q->pNext)
        {
            // 用當前節點和下一個節點進行對比,如果當前節點的資料域大於下一個節點,就將資料進行交換
            if (p->data > q->data)
            {
                t = p->data;
                p->data = q->data;
                q->data = t;
            }
        }
    }
}

// 插入節點
bool insert_list(PNODE pHead, int position, int val)
{
    int len = length_list(pHead);
    if (len < 0 || position > len)
    {
        return false;
    }

    int i = 0;
    PNODE p = pHead;
    // 使用while迴圈,使p變數指向position節點的前一個節點
    while (NULL != p->pNext && i < position)
    {
        p = p->pNext;
        ++i;
    }

    // 程式執行到這裡,p已經指向position節點的前一個節點,position節點是否為空無所謂

    // 插入過程1:分配新節點
    PNODE pNew = (PNODE)malloc(sizeof(NODE));
    if (NULL == pNew)
    {
        printf("動態分配記憶體失敗失敗");
        exit(-1);
    }
    // 插入過程2:將傳入的值賦給新節點的資料域
    pNew->data = val;

    // 插入過程3:用變數q臨時儲存position節點
    PNODE q = p->pNext;
    // 插入過程4:將position節點的前一個節點指向新節點
    p->pNext = pNew;
    // 插入過程5:再將新節點指向posion節點
    pNew->pNext = q;

    return true;
}

// 刪除節點
bool delete_list(PNODE pHead, int position, int *pVal)
{
    int len = length_list(pHead);
    if (len < 0 || position > len)
    {
        return false;
    }

    int i = 0;
    PNODE p = pHead;

    // 使用while迴圈,使p變數指向position節點的前一個節點
    while (NULL != p->pNext && i < position)
    {
        p = p->pNext;
        ++i;
    }

    // 如果position節點為NULL,返回false
    if (NULL == p->pNext)
    {
        return false;
    }

    // 程式執行到這裡,p已經指向position節點的前一個節點,並且position節點是存在的
    // 刪除過程1,讓q變數指向position節點
    PNODE q = p->pNext;
    // 刪除過程2,將position節點的資料賦給pVal
    *pVal = q->data;

    // 刪除過程3,讓position節點的前一個節點指向position節點的下一個
    p->pNext = p->pNext->pNext;

    // 刪除過程4,釋放position節點指向的記憶體,並讓q變數指向NULL
    free(q);
    q = NULL;

    return true;
}

int main()
{
    // 建立連結串列,定義長度為6,輸入1、5、6、4、3、2
    PNODE pHead = create_list();

    // 遍歷元素:輸出 1 5 6 4 3 2
    traverse_list(pHead);

    // 連結串列排序
    sort_list(pHead);
    // 遍歷元素:輸出 1 2 3 4 5 6
    traverse_list(pHead);

    // 在索引為0的位置新增元素7
    insert_list(pHead, 0, 7);
    // 遍歷元素:輸出 7 1 2 3 4 5 6
    traverse_list(pHead);

    // 在索引為5的位置刪除元素,並輸出刪除的元素
    int val;
    delete_list(pHead, 5, &val);
    // 遍歷元素,輸出:
    traverse_list(pHead);
    // 列印刪除的元素
    printf("被刪除的元素資料域是%d\n", val);

    return 0;
}

2.結果

程式編譯與執行的結果如下:

pan@pandeMBP ds % g++ 02-LinkList.cpp
pan@pandeMBP ds % ./a.out 
請輸入您需要生成的連結串列節點的個數:len=6
請輸入第1個節點的值:1
請輸入第2個節點的值:5
請輸入第3個節點的值:6
請輸入第4個節點的值:4
請輸入第5個節點的值:3
請輸入第6個節點的值:2
1  5  6  4  3  2  
1  2  3  4  5  6  
7  1  2  3  4  5  6  
7  1  2  3  4  6  
被刪除的元素資料域是5

本文原創首發自wx訂閱號:極客開發中up,禁止轉載