數據結構與算法 - 雙向鏈表

本文首發於 我的博客markdown

以前的 一篇文展 咱們講述了單鏈表的概念和實現,咱們知道單向鏈表只有一個方向的,每個節點只能找到其直接後繼節點也就是 next 指針,當咱們要找到一個節點只能從單鏈表頭處循環判斷,在咱們須要直接找到一個節點的前驅節點的時候,咱們就須要擴展咱們的數據結構讓其支持這種邏輯,其大概結構是:數據結構

這樣咱們就很容易寫出其數據結構:app

typedef struct Node {
    struct Node *prior;
    ListData data;
    struct Node *next;
} Node;
複製代碼

雙向鏈表

雙向鏈表最終會展現成這樣子(本篇文章默認都是 帶頭節點的鏈表):oop

  • 除尾節點外,每個節點的的 prior指針 都指向前一個節點
  • 除頭節點外,每個節點的 next指針 都指向後一個節點

初始化

一樣咱們仍是使用尾插法來初始化指定數值的雙向鏈表post

Node * InitList(int total)  {
    //建立頭節點 
    Node *list = (Node *)malloc(sizeof(Node));
    if (list == NULLreturn ERROR;
    list->prior = NULL;
    list->next = NULL;
    list->data = -1;

    Node *target = list;
    Node *temp;
    for (int i = 1; i <= total; i ++) {
        temp = (Node *)malloc(sizeof(Node));
        temp->data = i;
        temp->prior = target;
        temp->next = NULL;
        target->next = temp;
        target = target->next;
    }
    return list;
}
複製代碼

打印

打印方法就比較簡單了spa

// 打印鏈表
void PrintList (Node *list) {
    if (list == NULL) {
        printf("啥都沒有\n");
        return;
    }
    Node *target = list->next;
    if (target == NULL) {
        printf("鏈表爲空 \n");
        return;
    }
    while (target) {
        printf("%d - ",target->data);
        target = target->next;
    }
    printf("\n");
}
複製代碼

插入數據

找到要插入的前一個節點 target,好比咱們要插入順序3的位置,那麼就要找位置2的節點指針

  1. 新節點 Tempnext 指向 targetnext
  2. 新節點 Tempprior 指向 target
  3. targetnext 指向 Temp
  4. target-> nextprior 指向 Temp
// 指定位置插入節點
Status InserListData(Node **list,int location, ListData data) {
    Node *target = *list;
    int i ;
    // 找到要插入的前一個節點
    for (i = 1; i < location && target; i ++) {
        target = target->next;
    }
    Node *temp = (Node *)malloc(sizeof(Node));
    temp->data = data;
    temp->next = target->next;
    temp->prior = target;
    target->next = temp;
    temp->next->prior = temp;
    return SUCCESS;
}
複製代碼

刪除數據

  1. 找到要刪除的節點,並用 target 對齊進行保留
  2. target 前驅節點的 next 指向 target 的 後驅節點
  3. target 後驅節點的 prior 指向 target 的 前驅節點
  4. 釋放 target
// 刪除指定位置的節點
Status DeleteData(Node **list,int location, ListData *data) {
    if (location <= 0return ERROR;
    Node *target = *list;
    // 找到要刪除的節點
    int i;
    for (i = 0; i < location && target != NULL; i ++) {
        target = target->next;
    }
    if (i > location || target == NULL) {
        return ERROR;
    }
    target->prior->next = target->next;
    if (target->next != NULL) {
        target->next->prior = target->prior;
    }
    *data = target->data;
    free(target);
    return SUCCESS;
}
複製代碼

固然你也能夠用另一個變量保存要刪除節點的前驅節點,這裏由於考慮是雙向鏈表,單單一個要刪除的節點變量就夠用了。code

驗證

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    Node *list = InitList(3);
    printf("打印雙向鏈表: \n");
    PrintList(list);

    InserListData(&list,2,998);
    printf("第2個位置插入998以後打印: \n");
    PrintList(list);

    ListData data;
    Status status = DeleteData(&list2,&data);
    printf("刪除第二個位置的節點以後打印: \n");
    PrintList(list);
    if (status == SUCCESS) {
        printf("被刪除的數字是: %d\n",data);
    }
    return 0;
}
----------------------------- 打印數據
Hello, World!
打印雙向鏈表: 
1 - 2 - 3 - 
2個位置插入998以後打印: 
1 - 998 - 2 - 3 - 
刪除第二個位置的節點以後打印: 
1 - 2 - 3 - 
被刪除的數字是: 998
Program ended with exit code: 0
複製代碼

雙向循環鏈表

跟單向循環鏈表同樣,雙向循環鏈表其實節點結構不變,只是首尾相連而已,其大體像這樣:orm

那麼咱們初始化一個空的雙向循環鏈表應該是這樣:get

這裏圖形邏輯同雙向鏈表差很少,咱們不作過多的圖形展現,如下僅以代碼概述。

循環鏈表初始化

#define SUCCESS 1
#define ERROR 0

typedef int ListData;
typedef int Status;

typedef struct Node {
    struct Node *prior;
    ListData data;
    struct Node *next;
} Node;

typedef Node* LinkList;

Status CreatList (LinkList *L, int n) {
    *L = (LinkList)malloc(sizeof(Node));
    if (!*L) return ERROR;
    LinkList list = *L;
    Node *target = list;
    // 本身指向本身
    list->next = list;
    list->prior = list;
    // 新增數據
    for (int i = 1; i <= n; i ++) {
        Node *temp = (Node *)malloc(sizeof(Node));
        temp->data = i;
        temp->next = list;
        list->prior = temp;
        target->next = temp;
        temp->prior = target;
        target = target->next;
    }
    return SUCCESS;
}
複製代碼

初始化的時候,咱們注意空鏈表的時候建立的頭節點都指向本身便可,其餘新加的數據都默認採用後插法插入到鏈表中。

循環鏈表插入節點

Status InsertData(LinkList *L,int location, ListData data) {
    // 找到要插入位置的前一個位置的節點
    Node *target = *L;
    if (!target) return ERROR;
    int i;
    for (i = 1; i < location && target->next != *L; i ++) {
        target = target->next;
    }
    // 超出範圍
    if (i < location) return ERROR;
    Node *temp = (Node *)malloc(sizeof(Node));
    if (!temp) return ERROR;
    temp->data = data;
    temp->next = target->next;
    temp->next->prior = temp;
    target->next = temp;
    temp->prior = target;
    return SUCCESS;
}
複製代碼

思路依舊是找到要插入節點的前一個節點,而後經過後插法的邏輯來進行插入數據,細節方面就是要注意超出範圍的判斷以及循環的時候到尾節點就結束了,不能繞圈圈。

循環鏈表刪除節點

Status DeleteData(LinkList *L,int location, ListData *data) {
    if (*L == NULLreturn ERROR;
    Node *target = (*L)->next;
    // 若是隻剩下首元節點,直接清空*L
    if (target->next == *L) {
        free(*L);
        (*L) = NULL;
        return SUCCESS;
    }
    for (int i = 1; i <= location && target->next != *L; i ++) {
        target = target->next;
    }
    target->prior->next = target->next;
    target->next->prior = target->prior;
    *data = target->data;
    free(target);
    return SUCCESS;
}
複製代碼

刪除節點這裏有個細節就是若是隻剩下 首元節點 就對鏈表進行清空,注意這裏不是頭節點,而是判斷首元節點。

打印循環鏈表

Status PrintList(LinkList L) {
    if (L == NULL) {
        printf("空鏈表");
        return ERROR;
    }
    printf("雙向循環鏈表的內容: ");
    Node *target = L->next;
    while (target != L) {
        printf("%d--",target->data);
        target = target->next;
    }
    printf("\n");
    return SUCCESS;
}
複製代碼

驗證

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    LinkList list;
    CreatList(&list10);
    printf("初始化帶10個數據的雙向循環鏈表的數據是:\n");
    PrintList(list);

    InsertData(&list344);
    printf("第三個位置插入44後打印:\n");
    PrintList(list);

    InsertData(&list20999);
    printf("第20個位置插入999後打印:\n");
    PrintList(list);

    ListData data;
    DeleteData(&list3, &data);
    printf("刪除第3個節點以後打印:\n");
    PrintList(list);
    printf("刪除的數據是:%d \n",data);

    return 0;
}
---------------------------------打印結果
Hello, World!
初始化帶10個數據的雙向循環鏈表的數據是:
雙向循環鏈表的內容: 1--2--3--4--5--6--7--8--9--10--
第三個位置插入44後打印:
雙向循環鏈表的內容: 1--2--44--3--4--5--6--7--8--9--10--
20個位置插入999後打印:
雙向循環鏈表的內容: 1--2--44--3--4--5--6--7--8--9--10--
刪除第3個節點以後打印:
雙向循環鏈表的內容: 1--2--3--4--5--6--7--8--9--10--
刪除的數據是:44 
Program ended with exit code: 0
複製代碼

總結

雙向鏈表和雙向循環鏈表的節點在結構上是如出一轍的,不一樣之處就是一個首尾相連構成一個閉環,但願這篇文章可以將雙向鏈表的邏輯和實現講清楚,還望不吝賜教。

相關文章
相關標籤/搜索