資料結構與演算法(二) -- 線性表之單向迴圈連結串列

Glen_發表於2020-04-02

一、什麼是連結串列

連結串列是一種線性表, 也是一種儲存資料的資料結構.

資料結構與演算法(二) -- 線性表之單向迴圈連結串列
這種的一個節點中包含自身資料以及指向下一個節點的位置,一個巢狀著下一個. 這中結構就稱之為連結串列.

二、連結串列的型別

資料結構與演算法(二) -- 線性表之單向迴圈連結串列

開頭就是最基本的連結串列型別 -- 單項鍊表. 除此之外,大致還有:

  • 單向迴圈連結串列
  • 雙向連結串列
  • 雙向迴圈連結串列

還有其他種類連結串列不再闡述. 這裡主要來探討一下單項迴圈連結串列.

三、單項迴圈連結串列的設計

從圖中可以知道, 每一個節點需要包含兩個東西: 資料與指向下一個節點的地址. 那麼來設計一個這樣的結構體:

typedef int Status;
typedef int LBData;
typedef struct Node {
    LBData data;//用來儲存一個int資料(具體資料型別根據開發實際情況而定,此處使用int)
    struct Node *next;//指向下一個節點的指標
} Node;
typedef struct Node * LBList;

複製程式碼

這樣連結串列中的一個節點就構建好來. 結下來我們要將這些節點來進行建立.

四、單項迴圈連結串列的建立

(因為本人主要使用語言是物件導向的語言, 所以後面的實現均以物件的思想.) 宣告一個東西首先要對它進行初始化, 一般我們再初始化的時候會將資料在初始化的時候扔進去.

LBList LBListInit(const int *data, int length) {
    LBList lb = (LBList)malloc(sizeof(Node));//定義一個節點並開闢記憶體(此節點為當前節點)
    if (!lb) return NULL;//開闢記憶體失敗返回
    
    LBList headNode = lb;//記錄連結串列的首節點,用於後面結尾指向首節點
    for (int i = 0; i < length; i++) {
        lb->data = data[i];//將引數放入進當前節點
        if (i == length - 1) {//判斷資料是否結尾
            return headNode;//返回連結串列
        }
        LBList temp = (LBList)malloc(sizeof(Node));//開闢一個節點用來當作 當前節點的下一個節點
        if (!temp) return NULL;//開闢記憶體失敗返回
        temp->next = headNode;//最後的節點始終指向首節點
        lb->next = temp;//當前節點的下一個節點為建立的節點
        lb = temp;//將當前節點改成下一個節點
    }
    return headNode;
}
複製程式碼

這樣就可以將一個一個的節點首尾相連形成一種鎖鏈的形式. 執行一下看看結果:

資料結構與演算法(二) -- 線性表之單向迴圈連結串列
初始化一個單項迴圈連結串列成功.

五、單項迴圈連結串列的插入

得到一個連結串列後, 在使用時當然需要對這個連結串列來進行增刪改查來.

首先來分析一下 “增” 是如何實現的.

  1. 首先需要得到將要插入的位置的節點A
  2. 新建一個節點B, 將值修改為需要的資料
  3. 將B的next變成A的next
  4. 將A的next指向B
  5. 特殊情況: 插入到第一個位置, 最後到一個節點需要修改next

資料結構與演算法(二) -- 線性表之單向迴圈連結串列

進入程式碼模式:

//list: 傳進來的連結串列‘物件’指標  data: 需要插入的資料  index: 插入到第幾個位置(從1開始)
Status LBListInsert(LBList *list, LBData data, unsigned int index) {
    if (index <= 0) return 0;//不合法的位置直接返回錯誤
    
    LBList tempList = *list;//當前節點
    LBList insertNode = (LBList)malloc(sizeof(Node));//建立一個即將插入到節點
    if (!insertNode) return 0;//建立失敗
    
    
    if (index == 1) {
        //特殊情況: 插入到第一個位置
        
        //遍歷找到最後一個節點
        while (*list != tempList->next) {
            tempList = tempList->next;
        }
        insertNode->data = data;//插入的節點賦值
        insertNode->next = *list;//插入的節點的下一個即為傳遞過來的list指標地址
        tempList->next = insertNode;//當前節點(最後一個節點)的下一個即為插入的節點
        *list = insertNode;//將list地址修改為插入的節點地址
        return 1;
        
    } else {
    
        for (int i = 1; i < index - 1; i++) {//根據需要插入的位置來對連結串列進行遍歷
            if (tempList->next == *list) return 0;//已經到達最後一個節點, index錯誤
            tempList = tempList->next;//將當前節點變成下一個節點
        }
        
        insertNode->data = data;//插入的節點賦值
        insertNode->next = tempList->next;//插入的節點的下一個即為當前節點的下一個
        tempList->next = insertNode;//當前節點的下一個變為插入的節點
        return 1;
        
    }
}
複製程式碼

執行除錯:

資料結構與演算法(二) -- 線性表之單向迴圈連結串列

六、單項迴圈連結串列的刪除

分析一下如何進行刪除操作:

  1. 找到需要刪除的節點
  2. 保留需要刪除的上一個節點與下一個節點
  3. 將被刪除的上一個節點的next指向被刪除的下一個節點
  4. 釋放被刪除的節點的記憶體空間
  5. 特殊情況: 刪除第一個節點, 同時需要修改最後一個節點

資料結構與演算法(二) -- 線性表之單向迴圈連結串列
進入程式碼模式:


Status LBListDelete(LBList *list, unsigned int index) {

    LBList tempList = *list;//當前節點
    LBList delList = (LBList)malloc(sizeof(Node));//準備要刪除的節點

    if (index <= 0) return 0;//非法引數
    
    if (index == 1) {
        //特殊情況: 刪除第一個位置
        
        //找到最後一個節點
        while (*list != tempList->next) {
            tempList = tempList->next;
        }
        tempList->next = (*list)->next;//將最後一個節點的下一個設定為首節點的下一個
        *list = (*list)->next;//首節點替換為首節點的下一個
        return 1;
    } else {
        
        for (int i = 1; i < index - 1; i++) {
            tempList = tempList->next;//切換節點
            if (tempList->next == (*list)) return 0;//節點遍歷結束, 非法引數
        }
        delList = tempList->next;//找到需要刪除的節點
        tempList->next = delList->next;//將當前節點的下一個替換為即將刪除節點的下一個
        free(delList);//釋放節點
        return 1;
    }
}
複製程式碼

執行程式碼除錯:

資料結構與演算法(二) -- 線性表之單向迴圈連結串列

七、總結

連結串列故名思意就是像鏈條一樣一個巢狀一個的資料結構, 上面提到如何對連結串列進行了‘增’ ‘刪’操作, ‘改’ ‘查’也在‘增’‘刪’中有所體現.

通過對它進行操作可以發現它有個缺點,即當資料量過於龐大的時候對它進行操作會耗費大量時間來進行節點的查詢.但是好處就是在記憶體足夠的情況下可以無限延伸. 實際上也可以通過記錄一些特殊節點來保證它的查詢效率.

例如:

typedef struct Node {
    LBData data;//用來儲存一個int資料(具體資料型別根據開發實際情況而定,此處使用int)
    struct Node *next;//指向下一個節點的指標
} Node;

typedef struct MyLB {
    struct Node *lastNode;
    struct Node *list;
} LB;
複製程式碼

使用結構體巢狀來新增一個節點時刻記錄連結串列的最後一個節點. 但是相對應的操作方法也要做一下改變, 這裡就不做演示.

相關文章