資料結構與演算法(三) -- 線性表之雙向連結串列

Glen_發表於2020-04-04

一、雙向連結串列

之前講過關於單向連結串列的建立以及插入刪除操作.

雙向連結串列有一點不同於單向連結串列. 單向連結串列只能是一個順序方向進行查詢, 而雙向連結串列可以對下一個以及上一個進行查詢.

這樣在某些情況下可以提高計算機的工作效率.

1.1、雙向連結串列的結構

資料結構與演算法(三) -- 線性表之雙向連結串列

單向連結串列是包含節點資料以及下一個節點的指標.

雙向連結串列則是包含節點資料以及 下一個和上一個節點的指標. 那麼對於單向連結串列來講, 就應該是多一個指標指向上一個節點.

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

1.2、雙向連結串列的建立

這次我們使用雙向連結串列給它加一個頭節點, 頭節點僅提供第一個節點的指標.

LBList LBInit(LBData data[], unsigned int length) {
    LBList lb = (LBList)malloc(sizeof(Node));//開闢一個雙向連結串列的空間, 此時lb為頭節點
    if (!lb) return 0;//記憶體申請失敗
    lb->data = -1;
    lb->next = NULL;//初始化
    lb->prior = NULL;//初始化
    
    LBList returnLB = lb;//記錄一下頭節點的地址, 用於返回出去
    
    //根據資料長度進行節點的建立
    for (int i = 0; i < length; i++) {
        LBList tempList = (LBList)malloc(sizeof(Node));//開闢一個節點
        if (!tempList) return 0;//記憶體申請失敗
        tempList->data = data[i];//將資料存放進開闢的節點中
        tempList->next = NULL;//此時新增的節點是連結串列的最後一個, 最後一個沒有下一個節點
        tempList->prior = lb;//被新增的節點的上一個節點也就是當前的節點
        lb->next = tempList;//當前節點的下一個節點即為即將新增的節點
        lb = tempList;//將當前節點變成新增的節點
    }
    return returnLB;
}
複製程式碼

它與單向連結串列的不同之處是每次新增一個節點, 必須對新增的節點進行上一個指標與下一個指標的設定.

1.3、雙向連結串列的插入操作

資料結構與演算法(三) -- 線性表之雙向連結串列

依圖分析:

  1. 新建一個節點X
  2. 找到節點B
  3. 節點C的位置放進X的next
  4. 節點B的位置放進X的prior
  5. 節點X的位置放進C的prior與B的next
  6. 特殊情況: 當插入的位置為最後一個位置, 則不需要處理下一個節點

程式碼實現:

Status LBInsert(LBList *lb, LBData data, unsigned int index) {
    if (lb == NULL || (*lb)->next == NULL || index <= 0) return 0; //判斷非法引數情況
    
    LBList currentLB = (*lb);
    for (int i = 1; i < index && currentLB != NULL; i++, currentLB = currentLB->next);//通過for直接找到要存放的位置的前一個節點, 定位當前節點
    if (!currentLB) return 0;//一個非法的位置
    
    LBList insert = (LBList)malloc(sizeof(Node));//建立一個節點用於插入
    if (!insert) return 0;
    insert->data = data;
    insert->next = currentLB->next;//將此節點的下一個節點設定為當前節點的下一個節點
    insert->prior = currentLB;//將吃節點的上一個節點設定為當前節點
    if (currentLB->next != NULL) {//假如插入的位置為最後一個,即不需要設定它的下一個節點
        currentLB->next->prior = insert;//將當前節點的下一個節點的上一個節點設定為即將插入的節點(此時並未插入節點)
    }
    currentLB->next = insert;//將當前節點的下一個節點設定為即將插入的節點, 插入完成
    return 1;
}
複製程式碼

1.4、雙向連結串列的刪除操作

資料結構與演算法(三) -- 線性表之雙向連結串列

依圖分析:

  1. 找到即將刪除的節點B
  2. 將節點C的上一個節點改為A
  3. 將節點A的下一個節點改成C
  4. 釋放B節點
  5. 特殊情況: 當刪除當節點為最後一個節點, 則不需要處理被刪除的下一個節點

程式碼實現:

Status LBDelete(LBList *lb, unsigned int index, LBData *data) {// data用於返回被刪除的資料, 給外界一個記錄
    if (lb == NULL || (*lb)->next == NULL || index <= 0) return 0;//非法引數
    LBList currentLB = (*lb);//記錄當前節點
    for (int i = 1; i <= index && currentLB != NULL; i++, currentLB = currentLB->next);//通過for查詢到即將刪除的節點, 並設定為當前節點
    if (!currentLB) return 0;//非法位置
    
    currentLB->prior->next = currentLB->next;//將被刪除的節點的上一個節點的下一個節點設定為被刪除節點的下一個節點
    if (currentLB->next != NULL) {//是否刪除的是最後一個節點, 此時無需處理後面的節點
        currentLB->next->prior = currentLB->prior;//將被刪除節點的下一個節點的上一個節點設定為被刪除節點的上一個節點
    }
    data = &currentLB->data;//返回被刪除的資料給外界
    free(currentLB);//釋放掉被刪除的節點, 刪除完成
    return 1;
}
複製程式碼

二、雙向迴圈連結串列

上文主要是對雙向連結串列的操作, 雙向迴圈連結串列是將雙向連結串列的最後一個節點指向第一個節點(非頭節點), 達成一個迴圈.

2.1、雙向迴圈連結串列的結構

資料結構與演算法(三) -- 線性表之雙向連結串列
雙向迴圈連結串列的結尾不在是一個NULL了, 最終指向的是第一個節點變成一個迴圈.

2.2、雙向迴圈連結串列的建立

在雙向連結串列的基礎上, 是要對最後一個節點以及第一個節點進行改進.

程式碼實現:

LBList LBInit(LBData data[], unsigned int length) {
    LBList lb = (LBList)malloc(sizeof(Node));//開闢一個雙向連結串列的空間, 此時lb為頭節點
    if (!lb) return 0;//記憶體申請失敗
    lb->data = -1;
    lb->next = NULL;//初始化
    lb->prior = NULL;//初始化
    
    LBList returnLB = lb;//記錄一下頭節點的地址, 用於返回出去
    
    //根據資料長度進行節點的建立
    for (int i = 0; i < length; i++) {
        LBList tempList = (LBList)malloc(sizeof(Node));//開闢一個節點
        if (!tempList) return 0;//記憶體申請失敗
        tempList->data = data[i];//將資料存放進開闢的節點中
        tempList->next = NULL;//此時新增的節點是連結串列的最後一個, 最後一個沒有下一個節點
        tempList->prior = lb;//被新增的節點的上一個節點也就是當前的節點
        lb->next = tempList;//當前節點的下一個節點即為即將新增的節點
        lb = tempList;//將當前節點變成新增的節點
    }
    //此時lb為最後一個節點
    lb->next = returnLB->next;//將最後節點的下一個設定為第一個節點(頭節點的下一個節點)
    returnLB->next->prior = lb;//第一個節點的上一個就是最後一個節點
    
    return returnLB;
}
複製程式碼

2.3、雙向迴圈連結串列的插入

雙向迴圈連結串列的插入與雙向連結串列的插入幾乎一致, 唯一的改動之處就是當插入最後一個節點的時候是不需要特殊處理的

相對於雙向連結串列額外的分析:

  1. 當插入的位置為1的時候, 需要處理頭節點
  2. 因為是迴圈連結串列, 獲取插入位置的連結串列的時候需要考慮要插入的位置是否越界

程式碼實現:

Status LBInsert(LBList *lb, LBData data, unsigned int index) {
    if (lb == NULL || (*lb)->next == NULL || index <= 0) return 0; //判斷非法引數情況
    
    LBList currentLB = (*lb);
    int i;
    for (i = 1; i < index && currentLB != NULL && currentLB != (*lb)->next->prior; i++, currentLB = currentLB->next);//通過for直接找到要存放的位置的前一個節點, 定位當前節點, 需要考慮是否超過連結串列長度
    if (!currentLB || i != index) return 0;//一個非法的位置
    
    LBList insert = (LBList)malloc(sizeof(Node));//建立一個節點用於插入
    if (!insert) return 0;
    insert->data = data;
    insert->next = currentLB->next;//將此節點的下一個節點設定為當前節點的下一個節點
    if (index == 1) {//當插入位置為第一個的時候處理頭節點
        insert->prior = (*lb)->next->prior;//插入節點的上一個節點也就是頭節點的下一個節點的上一個節點
        (*lb)->next->prior->next = insert;//頭節點的下一個節點的上一個的下一個節點(最後一個節點的next)設定為插入節點
        (*lb)->next = insert;//將頭節點的下一個節點設定為插入的節點
    } else {//非1位置 正常操作
        insert->prior = currentLB;
        currentLB->next->prior = insert;
        currentLB->next = insert;
    }
    return 1;
}
複製程式碼

2.4、雙向迴圈連結串列的刪除

同樣基於雙向連結串列來操作, 需要考慮頭節點問題.

相對於雙向連結串列額外的分析:

  1. 當刪除的位置為第一個節點, 需要處理頭節點
  2. 因為是迴圈連結串列, 獲取刪除位置的連結串列的時候需要考慮要刪除的位置是否越界

程式碼實現:

Status LBDelete(LBList *lb, unsigned int index, LBData *data) {// data用於返回被刪除的資料, 給外界一個記錄
    if (lb == NULL || (*lb)->next == NULL || index <= 0) return 0;//非法引數
    LBList currentLB = (*lb);//記錄當前節點
    int i;
    for (i = 0; i < index && currentLB != NULL && currentLB != (*lb)->next->prior; i++, currentLB = currentLB->next);//通過for查詢到即將刪除的節點, 並設定為當前節點, 需要考慮是否越界
    if (!currentLB || i != index) return 0;//非法位置
    
    if (index == 1) {//當刪除當為第一個節點
        LBList firstNode = currentLB;//得到首節點
        LBList lastNode = currentLB->prior;//得到最後一個節點
        lastNode->next = firstNode->next;//最後一個節點當下一個節點為首節點的下一個節點
        firstNode->next->prior = lastNode;//首節點的下一個節點的上一個節點為最後一個節點
        (*lb)->next = firstNode->next;//將連結串列的頭節點設定為首節點的下一個節點
    } else {//其他位置正常操作
        currentLB->prior->next = currentLB->next;//將被刪除節點的上一個節點的下一個節點設定為被刪除節點的下一個節點
        currentLB->next->prior = currentLB->prior;//將被刪除節點的下一個節點的上一個節點設定為被刪除節點的上一個節點
    }
    data = &currentLB->data;//返回被刪除的資料給外界
    free(currentLB);//釋放掉被刪除的節點, 刪除完成
    return 1;
}
複製程式碼

三、總結

雙向連結串列相對於單向連結串列多了一個指向上一個節點的指標, 這樣的好處會使連結串列更加的靈活.

無論是逆向查詢還是正向查詢都可以進行. 而且定義了一個頭節點的data可以用來存放連結串列的長度. 根據需要對連結串列進行操作的位置來決定對連結串列進行逆向還是正向查詢.

相對的, 因為多加了一個指向上一個節點的指標, 對連結串列進行操作需要多進行一個設定上個節點的操作, 考慮的情況要稍微比單向複雜一點點.

相關文章