數據結構和算法(三)雙向鏈表與雙向循環鏈表的實現

數據結構和算法(一)線性表實現git

數據結構和算法(二)單向循環鏈表的建立插入刪除實現github

數據結構和算法(三)雙向鏈表與雙向循環鏈表的實現面試

數據結構和算法(四)鏈表相關面試題算法

數據結構和算法(五)棧和隊列的操做和實現shell

@TOCswift

回顧一下,前面一篇博客「數據結構和算法(二)單向循環鏈表的建立插入刪除實現」中講解了單向鏈表和單向循環鏈表。 這篇博客主要講解雙向鏈表和雙向循環鏈表的基本操做實現。數據結構

本篇博客的Demo下載:數據結構和算法

  1. 雙向鏈表Demo
  2. 雙向循環鏈表Demo

線性表彙總

1. 雙向鏈表

  • 咱們爲啥要使用雙向鏈表,相比單向鏈表,雙向鏈表的優勢是什麼?

單鏈表相對於順序表,確實在某些場景下解決了一些重要的問題,例如在須要插入或者刪除大量元素的時候,它並不須要像順序表同樣移動不少元素,只須要修改指針的指向就能夠了,其時間複雜度爲 O(1) 可是這但是有前提的,那就是這一切都基於肯定節點後,純粹考慮刪除和插入的狀況下,可是若是咱們仍未肯定節點的位置,那麼單鏈表就會出現一些問題了函數

咱們先來對比一下單鏈表和雙鏈表的刪除操做:post

回顧一下前面一篇博客「數據結構和算法(二)單向循環鏈表的建立插入刪除實現」中講解的單向鏈表刪除操做,以下圖咱們要刪除一個結點,假如想要刪除第2個節點 a1 只須要 將首元結點的指針指向到第三個節點的地址去:

單鏈表刪除結點
咱們遇到的問題就在於咱們如何獲得待刪除節點的前驅,也就是咱們圖中的首元結點,咱們給出兩種方法:

  • A:定位待刪除節點的同時,一直順便保存當前節點的前驅
  • B:刪除節點後,從新回到單鏈表表頭,定位到其指定前驅

可是不管咱們選擇哪種方法,指針的總移動數都會是 2n 次,而雙鏈表卻在這一類型問題上作出了很好的處理。

咱們接下來看一下雙鏈表的刪除結點的過程:

雙向鏈表刪除結點
雙鏈表相比單鏈表每一個結點都多了一個指向前驅的指針,這樣咱們不管是向前查找,仍是向後查找都比較方便。單鏈表中之因此出現問題,就是由於各個節點只有一個指向後繼的指針域 next,只能向後移動查找,一旦咱們想要查詢前一節點,就變得很麻煩,因此雙鏈表就在每一個節點前面增長一個指向前驅的指針域 prior,這樣咱們就能夠直接定位到咱們的前一個節點了,這也就是雙鏈表的優勢。

咱們能夠計算一下它們刪除是的操做數量:

單鏈表和雙鏈表刪除操做數量對比

雙鏈表雖然解決了單鏈表中的反向查找痛點,可是是以每一個結點增長了一個指針域爲代價的,也就是用空間來換取的。

接下來,咱們來了解一下雙向鏈表的基本操做代碼實現。

1.1 結點定義

雙向鏈表結點定義

//定義結點
typedef struct KNode{
    KElementType data;  //數據
    struct KNode *prior; //前驅指針 struct KNode *next; //後繼指針 }Node; 複製代碼

1.2 建表

非空的雙鏈表-帶頭結點

非空的雙鏈表-不帶頭結點

//5.1 建立雙向連接
KStatus createLinkList(LinkList *L){
    
    //*L 指向頭結點
    *L = (LinkList)malloc(sizeof(KNode));
    if (*L == NULL) return ERROR;
    
    (*L)->prior = NULL;
    (*L)->next = NULL;
    (*L)->data = -1;
    
    //新增數據
    LinkList p = *L;
    for(int i=0; i < 10;i++){
        
        //1.建立1個臨時的結點
        LinkList temp = (LinkList)malloc(sizeof(KNode));
        temp->prior = NULL;
        temp->next = NULL;
        temp->data = i;
        
        //2.爲新增的結點創建雙向鏈表關係
        //① temp 是p的後繼
        p->next = temp;
        //② temp 的前驅是p
        temp->prior = p;
        //③ p 要記錄最後的結點的位置,方便下一次插入
        p = p->next;
        
    }
    
    return OK;
}
複製代碼

1.3 插入結點

雙向鏈表-插入結點1

在第i個位置插入元素,大概步驟以下:

  • 建立新結點q
  • 遍歷鏈表,找到須要插入的位置的前一個元素p
  • 將新元素q的前驅指向p,後繼指向p->next
  • 將p->next的前驅指向新元素q
  • 將p的後繼指向新元素q

插入結點的實現代碼以下:

//5.3 雙向鏈表插入元素
KStatus ListInsert(LinkList *L, int i, KElementType data){
    
    //1. 插入的位置不合法 爲0或者爲負數
    if(i < 1) return ERROR;
    
    //2. 新建結點
    LinkList temp = (LinkList)malloc(sizeof(KNode));
    temp->data = data;
    temp->prior = NULL;
    temp->next = NULL;
    
    //3.將p指向頭結點!
    LinkList p = *L;
    
    //4. 找到插入位置i直接的結點
    for(int j = 1; j < i && p;j++)
        p = p->next;
    
    //5. 若是插入的位置超過鏈表自己的長度
    if(p == NULL){
        return  ERROR;
    }
    
    //6. 判斷插入位置是否爲鏈表尾部;
    if (p->next == NULL) {
        
        p->next = temp;
        temp->prior = p;
    }else
    {
        //1️⃣ 將p->next 結點的前驅prior = temp
        p->next->prior = temp;
        //2️⃣ 將temp->next 指向原來的p->next
        temp->next = p->next;
        //3️⃣ p->next 更新成新建立的temp
        p->next = temp;
        //4️⃣ 新建立的temp前驅 = p
        temp->prior = p;
    }
    
    return  OK;
}
複製代碼

1.4 刪除結點

雙向鏈表-刪除結點

刪除第i個位置的元素,大概步驟以下:

  • 遍歷鏈表,找到想要刪除的位置的前一個元素p
  • 建立新結點q,將要刪除的元素賦值給p
  • 將p->next指向q->next,將q->next的前驅指向p
  • 釋放q

刪除結點的代碼實現以下: (1) 刪除雙向鏈表指定位置上的結點

//5.4 刪除雙向鏈表指定位置上的結點
KStatus ListDelete(LinkList *L, int i, KElementType *e){
    
    int k = 1;
    LinkList p = (*L);
    
    //1.判斷雙向鏈表是否爲空,若是爲空則返回ERROR;
    if (*L == NULL) {
        return ERROR;
    }
    
  
    //2. 將指針p移動到刪除元素位置前一個
    while (k < i && p != NULL) {
        p = p->next;
        k++;
    }
    
    //3.若是k>i 或者 p == NULL 則返回ERROR
    if (k>i || p == NULL) {
        return  ERROR;
    }
    
    //4.建立臨時指針delTemp 指向要刪除的結點,並將要刪除的結點的data 賦值給*e,帶回到main函數
    LinkList delTemp = p->next;
    *e = delTemp->data;
    
    //5. p->next 等於要刪除的結點的下一個結點
    p->next = delTemp->next;
    
    //6. 若是刪除結點的下一個結點不爲空,則將將要刪除的下一個結點的前驅指針賦值p;
    if (delTemp->next != NULL) {
        delTemp->next->prior = p;
    }
    
    //7.刪除delTemp結點
    free(delTemp);
    
    return OK;
    
}
複製代碼

(2) 刪除雙向鏈表指定的元素

//5.5 刪除雙向鏈表指定的元素
KStatus LinkListDeletVAL(LinkList *L, int data){
    
    LinkList p = *L;
    
    //1.遍歷雙向循環鏈表
    while (p) {
       
        //2.判斷當前結點的數據域和data是否相等,若相等則刪除該結點
        if (p->data == data) {
            
            //修改被刪除結點的前驅結點的後繼指針,參考圖上步驟1️⃣
            p->prior->next = p->next;
            //修改被刪除結點的後繼結點的前驅指針,參考圖上步驟2️⃣
            if(p->next != NULL){
                p->next->prior = p->prior;
            }
            //釋放被刪除結點p
            free(p);
            //退出循環
            break;
        }
        
        //沒有找到該結點,則繼續移動指針p
        p = p->next;
    }
    
    return OK;
    
}
複製代碼

1.5 遍歷

//5.2 打印循環鏈表的元素
void display(LinkList L){
    
    LinkList temp = L->next;
    
    if(temp == NULL){
        printf("打印的雙向鏈表爲空!\n");
        return;
    }
    
    while (temp) {
        printf("%d ",temp->data);
        temp = temp->next;
    }
    printf("\n");
    
}
複製代碼

1.6 查找

//5.6.1 在雙向鏈表中查找元素
int selectElem(LinkList L,KElementType elem){
    
    LinkList p = L->next;
    int i = 1;
    while (p) {
        if (p->data == elem) {
            return i;
        }
        
        i++;
        p = p->next;
    }
    
    return  -1;
}
複製代碼

1.7 更新

//5.6.2 在雙向鏈表中更新結點
KStatus replaceLinkList(LinkList *L,int index,KElementType newElem){
    LinkList p = (*L)->next;
    
    for (int i = 1; i < index; i++) {
        p = p->next;
    }
    
    p->data = newElem;
    return OK;
}

複製代碼

1.8 單元測試

void test () {
    KStatus iStatus = 0;
      LinkList L;
      int temp,item,e;
      
      iStatus =  createLinkList(&L);
      printf("iStatus = %d\n",iStatus);
      printf("鏈表建立成功,打印鏈表:\n");
      display(L);
      
      printf("請輸入插入的位置\n");
      scanf("%d %d",&temp,&item);
      iStatus = ListInsert(&L, temp, item);
      printf("插入數據,打印鏈表:\n");
      display(L);
      
      printf("請輸入插入的位置\n");
      scanf("%d %d",&temp,&item);
      iStatus = ListInsert(&L, temp, item);
      printf("插入數據,打印鏈表:\n");
      display(L);
      
      printf("請輸入插入的位置\n");
      scanf("%d %d",&temp,&item);
      iStatus = ListInsert(&L, temp, item);
      printf("插入數據,打印鏈表:\n");
      display(L);
      
      printf("請輸入刪除的位置\n");
      scanf("%d",&temp);
      iStatus = ListDelete(&L, temp, &e);
      printf("刪除元素: 刪除位置爲%d,data = %d\n",temp,e);
      printf("刪除操做以後的,雙向鏈表:\n");
      display(L);
      
      printf("請輸入你要刪除的內容\n");
      scanf("%d",&temp);
      iStatus = LinkListDeletVAL(&L, temp);
      printf("刪除指定data域等於%d的結點,雙向鏈表:\n",temp);
      display(L);

      printf("請輸入你要查找的內容\n");
       scanf("%d",&temp);
      KElementType index = selectElem(L, temp);
      printf("在雙向鏈表中查找到數據域爲%d的結點,位置是:%d\n",temp,index);

      printf("請輸入你要更新的結點以及內容\n");
      scanf("%d %d",&temp,&item);
      iStatus = replaceLinkList(&L, temp, item);
      printf("更新結點數據後的雙向鏈表:\n");
      display(L);
}
複製代碼

輸出結果:

Hello, World!
iStatus = 1
鏈表建立成功,打印鏈表:
0  1  2  3  4  5  6  7  8  9  
請輸入插入的位置
1 66
插入數據,打印鏈表:
66  0  1  2  3  4  5  6  7  8  9  
請輸入插入的位置
10 66
插入數據,打印鏈表:
66  0  1  2  3  4  5  6  7  66  8  9  
請輸入插入的位置
12 77
插入數據,打印鏈表:
66  0  1  2  3  4  5  6  7  66  8  77  9  
請輸入刪除的位置
1 66
刪除元素: 刪除位置爲1,data = 66
刪除操做以後的,雙向鏈表:
0  1  2  3  4  5  6  7  66  8  77  9  
請輸入你要刪除的內容
刪除指定data域等於66的結點,雙向鏈表:
0  1  2  3  4  5  6  7  8  77  9  
請輸入你要查找的內容
77
在雙向鏈表中查找到數據域爲77的結點,位置是:10
請輸入你要更新的結點以及內容
複製代碼

1.9 完整代碼

//
// main.c
// 005_DoubleLinkedList
//
// Created by 孔雨露 on 2020/4/5.
// Copyright © 2020 Apple. All rights reserved.
//

#include <stdio.h>
#include "string.h"
#include "ctype.h"
#include "stdlib.h"
#include "math.h"
#include "time.h"

#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1

#define MAXSIZE 20 /* 存儲空間初始分配量 */

typedef int KStatus;/* Status是函數的類型,其值是函數結果狀態代碼,如OK等 */
typedef int KElementType;/* KElementType類型根據實際狀況而定,這裏假設爲int */

//定義結點
typedef struct KNode{
    KElementType data;
    struct KNode *prior; struct KNode *next; }KNode; typedef struct KNode * LinkList; //5.1 建立雙向連接 KStatus createLinkList(LinkList *L){
    
    //*L 指向頭結點
    *L = (LinkList)malloc(sizeof(KNode));
    if (*L == NULL) return ERROR;
    
    (*L)->prior = NULL;
    (*L)->next = NULL;
    (*L)->data = -1;
    
    //新增數據
    LinkList p = *L;
    for(int i=0; i < 10;i++){
        
        //1.建立1個臨時的結點
        LinkList temp = (LinkList)malloc(sizeof(KNode));
        temp->prior = NULL;
        temp->next = NULL;
        temp->data = i;
        
        //2.爲新增的結點創建雙向鏈表關係
        //① temp 是p的後繼
        p->next = temp;
        //② temp 的前驅是p
        temp->prior = p;
        //③ p 要記錄最後的結點的位置,方便下一次插入
        p = p->next;
        
    }
    
    return OK;
}

//5.2 打印循環鏈表的元素
void display(LinkList L){
    
    LinkList temp = L->next;
    
    if(temp == NULL){
        printf("打印的雙向鏈表爲空!\n");
        return;
    }
    
    while (temp) {
        printf("%d ",temp->data);
        temp = temp->next;
    }
    printf("\n");
    
}

//5.3 雙向鏈表插入元素
KStatus ListInsert(LinkList *L, int i, KElementType data){
    
    //1. 插入的位置不合法 爲0或者爲負數
    if(i < 1) return ERROR;
    
    //2. 新建結點
    LinkList temp = (LinkList)malloc(sizeof(KNode));
    temp->data = data;
    temp->prior = NULL;
    temp->next = NULL;
    
    //3.將p指向頭結點!
    LinkList p = *L;
    
    //4. 找到插入位置i直接的結點
    for(int j = 1; j < i && p;j++)
        p = p->next;
    
    //5. 若是插入的位置超過鏈表自己的長度
    if(p == NULL){
        return  ERROR;
    }
    
    //6. 判斷插入位置是否爲鏈表尾部;
    if (p->next == NULL) {
        
        p->next = temp;
        temp->prior = p;
    }else
    {
        //1️⃣ 將p->next 結點的前驅prior = temp
        p->next->prior = temp;
        //2️⃣ 將temp->next 指向原來的p->next
        temp->next = p->next;
        //3️⃣ p->next 更新成新建立的temp
        p->next = temp;
        //4️⃣ 新建立的temp前驅 = p
        temp->prior = p;
    }
    
    return  OK;
}

//5.4 刪除雙向鏈表指定位置上的結點
KStatus ListDelete(LinkList *L, int i, KElementType *e){
    
    int k = 1;
    LinkList p = (*L);
    
    //1.判斷雙向鏈表是否爲空,若是爲空則返回ERROR;
    if (*L == NULL) {
        return ERROR;
    }
    
  
    //2. 將指針p移動到刪除元素位置前一個
    while (k < i && p != NULL) {
        p = p->next;
        k++;
    }
    
    //3.若是k>i 或者 p == NULL 則返回ERROR
    if (k>i || p == NULL) {
        return  ERROR;
    }
    
    //4.建立臨時指針delTemp 指向要刪除的結點,並將要刪除的結點的data 賦值給*e,帶回到main函數
    LinkList delTemp = p->next;
    *e = delTemp->data;
    
    //5. p->next 等於要刪除的結點的下一個結點
    p->next = delTemp->next;
    
    //6. 若是刪除結點的下一個結點不爲空,則將將要刪除的下一個結點的前驅指針賦值p;
    if (delTemp->next != NULL) {
        delTemp->next->prior = p;
    }
    
    //7.刪除delTemp結點
    free(delTemp);
    
    return OK;
    
}

//5.5 刪除雙向鏈表指定的元素
KStatus LinkListDeletVAL(LinkList *L, int data){
    
    LinkList p = *L;
    
    //1.遍歷雙向循環鏈表
    while (p) {
       
        //2.判斷當前結點的數據域和data是否相等,若相等則刪除該結點
        if (p->data == data) {
            
            //修改被刪除結點的前驅結點的後繼指針,參考圖上步驟1️⃣
            p->prior->next = p->next;
            //修改被刪除結點的後繼結點的前驅指針,參考圖上步驟2️⃣
            if(p->next != NULL){
                p->next->prior = p->prior;
            }
            //釋放被刪除結點p
            free(p);
            //退出循環
            break;
        }
        
        //沒有找到該結點,則繼續移動指針p
        p = p->next;
    }
    
    return OK;
    
}

//5.6.1 在雙向鏈表中查找元素
int selectElem(LinkList L,KElementType elem){
    
    LinkList p = L->next;
    int i = 1;
    while (p) {
        if (p->data == elem) {
            return i;
        }
        
        i++;
        p = p->next;
    }
    
    return  -1;
}

//5.6.2 在雙向鏈表中更新結點
KStatus replaceLinkList(LinkList *L,int index,KElementType newElem){
    LinkList p = (*L)->next;
    
    for (int i = 1; i < index; i++) {
        p = p->next;
    }
    
    p->data = newElem;
    return OK;
}


void test () {
    KStatus iStatus = 0;
      LinkList L;
      int temp,item,e;
      
      iStatus =  createLinkList(&L);
      printf("iStatus = %d\n",iStatus);
      printf("鏈表建立成功,打印鏈表:\n");
      display(L);
      
      printf("請輸入插入的位置\n");
      scanf("%d %d",&temp,&item);
      iStatus = ListInsert(&L, temp, item);
      printf("插入數據,打印鏈表:\n");
      display(L);
      
      printf("請輸入插入的位置\n");
      scanf("%d %d",&temp,&item);
      iStatus = ListInsert(&L, temp, item);
      printf("插入數據,打印鏈表:\n");
      display(L);
      
      printf("請輸入插入的位置\n");
      scanf("%d %d",&temp,&item);
      iStatus = ListInsert(&L, temp, item);
      printf("插入數據,打印鏈表:\n");
      display(L);
      
      printf("請輸入刪除的位置\n");
      scanf("%d",&temp);
      iStatus = ListDelete(&L, temp, &e);
      printf("刪除元素: 刪除位置爲%d,data = %d\n",temp,e);
      printf("刪除操做以後的,雙向鏈表:\n");
      display(L);
      
      printf("請輸入你要刪除的內容\n");
      scanf("%d",&temp);
      iStatus = LinkListDeletVAL(&L, temp);
      printf("刪除指定data域等於%d的結點,雙向鏈表:\n",temp);
      display(L);

      printf("請輸入你要查找的內容\n");
       scanf("%d",&temp);
      KElementType index = selectElem(L, temp);
      printf("在雙向鏈表中查找到數據域爲%d的結點,位置是:%d\n",temp,index);

      printf("請輸入你要更新的結點以及內容\n");
      scanf("%d %d",&temp,&item);
      iStatus = replaceLinkList(&L, temp, item);
      printf("更新結點數據後的雙向鏈表:\n");
      display(L);
}

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    test();

    return 0;
}


複製代碼

2. 雙向循環鏈表

2.1 結點定義

雙向循環鏈表的結點定義和雙向鏈表是同樣的

雙向鏈表結點定義

//定義結點
typedef struct KNode{
    KElementType data;
    struct KNode *prior; struct KNode *next; }KNode; 複製代碼

2.2 建表

建表過程,實際上就是從一個空表,按照咱們給到的數據建立一個非空鏈表的過程。

以下圖定義的是一個空的雙向循環鏈表,帶頭結點的,頭結點的前驅指針prior和後繼指針next都指向它本身:

空的雙向循環鏈表

以下圖:是上面空鏈表一次從尾部插入A,B元素後獲得的鏈表,另外咱們還能夠從首部插入B,A 元素,也能夠獲得下面的表:

非空的雙向循環鏈表

下面的代碼是循環鏈表建立過程,代碼中咱們經過尾部插入法,插入0,1,2,3,4,5,6,7,8,9 總共10個元素。

//6.1 雙向循環鏈表初始化
KStatus creatLinkList(LinkList *L){
    
    *L = (LinkList)malloc(sizeof(KNode));
    if (*L == NULL) {
        return ERROR;
    }
    
    (*L)->next = (*L);
    (*L)->prior = (*L);
    
    //新增數據
    LinkList p = *L;
    for(int i=0; i < 10;i++){
        
        //1.建立1個臨時的結點
        LinkList temp = (LinkList)malloc(sizeof(KNode));
        temp->data = i;
        
        //2.爲新增的結點創建雙向鏈表關係
        //① temp 是p的後繼
        p->next = temp;
        //② temp 的前驅是p
        temp->prior = p;
        //③ temp的後繼是*L
        temp->next = (*L);
        //④ p 的前驅是新建的temp
        p->prior = temp;
        //⑤ p 要記錄最後的結點的位置,方便下一次插入
        p = p->next;
        
    }
    
    return OK;
   
}
複製代碼

2.3 插入結點

雙向循環鏈表-插入結點

雙向循環鏈表的插入過程以下:

  • 先找到要插入的元素的前驅,(如上圖咱們找到B結點的前驅A,用p指針指向A)
  • 新建一個結點賦值爲CC,並用臨時指針temp指向CC結點:temp->data = e;
  • 將結點temp 的前驅結點爲p: temp->prior = p;
  • 將新建結點temp的後繼結點指向p->next: temp->next = p->next;
  • 讓p的後繼指向新結點temp: p->next = temp;
  • 判斷當前要插入的新結點temp是否是最後一個結點,若是是則:讓L的前驅結點指向temp: (*L)->prior = temp;;若是不是是則將temp節點的下一個結點的前驅爲temp 結點:temp->next->prior = temp;

插入結點的代碼實現以下:

//6.2 雙向循環鏈表插入元素
/*當插入位置超過鏈表長度則插入到鏈表末尾*/
KStatus LinkListInsert(LinkList *L, int index, KElementType e){
   
    //1. 建立指針p,指向雙向鏈表頭
    LinkList p = (*L);
    int i = 1;
    
    //2.雙向循環鏈表爲空,則返回error
    if(*L == NULL) return ERROR;
   
    //3.找到插入前一個位置上的結點p
    while (i < index && p->next != *L) {
        p = p->next;
        i++;
    }
    
    //4.若是i>index 則返回error
    if (i > index)  return ERROR;
    
    //5.建立新結點temp
    LinkList temp = (LinkList)malloc(sizeof(KNode));
    
    //6.temp 結點爲空,則返回error
    if (temp == NULL) return ERROR;
    
    //7.將生成的新結點temp數據域賦值e.
    temp->data = e;
    
    //8.將結點temp 的前驅結點爲p;
    temp->prior = p;
    //9.temp的後繼結點指向p->next;
    temp->next = p->next;
    //10.p的後繼結點爲新結點temp;
    p->next = temp;
    
    //若是temp 結點不是最後一個結點
    if (*L != temp->next) {
        
        //11.temp節點的下一個結點的前驅爲temp 結點
        temp->next->prior = temp;
    }else{

        (*L)->prior = temp;
        
    }
    
    return OK;
}
複製代碼

2.4 刪除結點

雙向循環鏈表-刪除結點

循環雙鏈表刪除結點的過程以下:

  • 1.找到要刪除的結點(如上圖用temp指針指向要刪除的結點B)
  • 2.給e賦值要刪除結點的數據域:*e = temp->data;
  • 3.修改被刪除結點的前驅結點的後繼指針 圖1️⃣:temp->prior->next = temp->next; (也就B結點的前驅結點A的next指向B結點的後繼C)
  • 4.修改被刪除結點的後繼結點的前驅指針 圖2️⃣:temp->next->prior = temp->prior; (讓B結點的後繼結點C的前驅指針指向B結點的前驅A)
    1. 刪除結點temp,釋放內存:free(temp);

循環雙鏈表刪除結點的實現代碼以下:

//6.4 雙向循環鏈表刪除結點
KStatus LinkListDelete(LinkList *L,int index,KElementType *e){
    
    int i = 1;
    LinkList temp = (*L)->next;
    
    if (*L == NULL) {
        return  ERROR;
    }
    
    //①.若是刪除到只剩下首元結點了,則直接將*L置空;
    if(temp->next == *L){
        free(*L);
        (*L) = NULL;
        return OK;
    }
    
    //1.找到要刪除的結點
    while (i < index) {
        temp = temp->next;
        i++;
    }

    //2.給e賦值要刪除結點的數據域
    *e = temp->data;
    
    //3.修改被刪除結點的前驅結點的後繼指針 圖1️⃣
    temp->prior->next = temp->next;
    //4.修改被刪除結點的後繼結點的前驅指針 圖2️⃣
    temp->next->prior = temp->prior;
    //5. 刪除結點temp
    free(temp);
    
    return OK;
    
}
複製代碼

2.5 遍歷

//6.3 遍歷雙向循環鏈表
KStatus Display(LinkList L){
    
    if (L == NULL) {
        printf("打印的雙向循環鏈表爲空!\n\n");
        return ERROR;
    }
    printf("雙向循環鏈表內容: ");
    
    LinkList p = L->next;
    while (p != L) {

        printf("%d ",p->data);
        p = p->next;
    }
    printf("\n\n");
    return OK;
}
複製代碼

2.6 單元測試

void test() {
    LinkList L;
    KStatus iStatus;
    KElementType temp,item;
    
    iStatus = creatLinkList(&L);
    printf("雙向循環鏈表初始化是否成功(1->YES)/ (0->NO): %d\n\n",iStatus);
    Display(L);
    
    printf("輸入要插入的位置和數據用空格隔開:");
    scanf("%d %d",&temp,&item);
    iStatus = LinkListInsert(&L,temp,item);
    Display(L);

    printf("輸入要刪除位置:");
    scanf("%d",&temp);
    iStatus = LinkListDelete(&L, temp, &item);
    printf("刪除鏈表位置爲%d,結點數據域爲:%d\n",temp,item);
    Display(L);
}
複製代碼

輸出結果:

Hello, World!
雙向循環鏈表初始化是否成功(1->YES)/ (0->NO):  1

雙向循環鏈表內容:  0  1  2  3  4  5  6  7  8  9  

輸入要插入的位置和數據用空格隔開:1 88
雙向循環鏈表內容:  88  0  1  2  3  4  5  6  7  8  9  

輸入要刪除位置:1 88
刪除鏈表位置爲1,結點數據域爲:88
雙向循環鏈表內容:  0  1  2  3  4  5  6  7  8  9  
複製代碼

2.7 完整代碼

//
// main.c
// 006_DoubleCircularLinkList
//
// Created by 孔雨露 on 2020/4/5.
// Copyright © 2020 Apple. All rights reserved.
//

#include <stdio.h>
#include "string.h"
#include "ctype.h"
#include "stdlib.h"
#include "math.h"
#include "time.h"

#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1

#define MAXSIZE 20 /* 存儲空間初始分配量 */

typedef int KStatus;/* Status是函數的類型,其值是函數結果狀態代碼,如OK等 */
typedef int KElementType;/* KElementType類型根據實際狀況而定,這裏假設爲int */

//定義結點
typedef struct KNode{
    KElementType data;
    struct KNode *prior; struct KNode *next; }KNode; typedef struct KNode * LinkList; //6.1 雙向循環鏈表初始化 KStatus creatLinkList(LinkList *L){
    
    *L = (LinkList)malloc(sizeof(KNode));
    if (*L == NULL) {
        return ERROR;
    }
    
    (*L)->next = (*L);
    (*L)->prior = (*L);
    
    //新增數據
    LinkList p = *L;
    for(int i=0; i < 10;i++){
        
        //1.建立1個臨時的結點
        LinkList temp = (LinkList)malloc(sizeof(KNode));
        temp->data = i;
        
        //2.爲新增的結點創建雙向鏈表關係
        //① temp 是p的後繼
        p->next = temp;
        //② temp 的前驅是p
        temp->prior = p;
        //③ temp的後繼是*L
        temp->next = (*L);
        //④ p 的前驅是新建的temp
        p->prior = temp;
        //⑤ p 要記錄最後的結點的位置,方便下一次插入
        p = p->next;
        
    }
    
    return OK;
   
}

//6.2 雙向循環鏈表插入元素
/*當插入位置超過鏈表長度則插入到鏈表末尾*/
KStatus LinkListInsert(LinkList *L, int index, KElementType e){
   
    //1. 建立指針p,指向雙向鏈表頭
    LinkList p = (*L);
    int i = 1;
    
    //2.雙向循環鏈表爲空,則返回error
    if(*L == NULL) return ERROR;
   
    //3.找到插入前一個位置上的結點p
    while (i < index && p->next != *L) {
        p = p->next;
        i++;
    }
    
    //4.若是i>index 則返回error
    if (i > index)  return ERROR;
    
    //5.建立新結點temp
    LinkList temp = (LinkList)malloc(sizeof(KNode));
    
    //6.temp 結點爲空,則返回error
    if (temp == NULL) return ERROR;
    
    //7.將生成的新結點temp數據域賦值e.
    temp->data = e;
    
    //8.將結點temp 的前驅結點爲p;
    temp->prior = p;
    //9.temp的後繼結點指向p->next;
    temp->next = p->next;
    //10.p的後繼結點爲新結點temp;
    p->next = temp;
    
    //若是temp 結點不是最後一個結點
    if (*L != temp->next) {
        
        //11.temp節點的下一個結點的前驅爲temp 結點
        temp->next->prior = temp;
    }else{

        (*L)->prior = temp;
        
    }
    
    return OK;
}


//6.3 遍歷雙向循環鏈表
KStatus Display(LinkList L){
    
    if (L == NULL) {
        printf("打印的雙向循環鏈表爲空!\n\n");
        return ERROR;
    }
    printf("雙向循環鏈表內容: ");
    
    LinkList p = L->next;
    while (p != L) {

        printf("%d ",p->data);
        p = p->next;
    }
    printf("\n\n");
    return OK;
}

//6.4 雙向循環鏈表刪除結點
KStatus LinkListDelete(LinkList *L,int index,KElementType *e){
    
    int i = 1;
    LinkList temp = (*L)->next;
    
    if (*L == NULL) {
        return  ERROR;
    }
    
    //①.若是刪除到只剩下首元結點了,則直接將*L置空;
    if(temp->next == *L){
        free(*L);
        (*L) = NULL;
        return OK;
    }
    
    //1.找到要刪除的結點
    while (i < index) {
        temp = temp->next;
        i++;
    }

    //2.給e賦值要刪除結點的數據域
    *e = temp->data;
    
    //3.修改被刪除結點的前驅結點的後繼指針 圖1️⃣
    temp->prior->next = temp->next;
    //4.修改被刪除結點的後繼結點的前驅指針 圖2️⃣
    temp->next->prior = temp->prior;
    //5. 刪除結點temp
    free(temp);
    
    return OK;
    
}

void test() {
    LinkList L;
    KStatus iStatus;
    KElementType temp,item;
    
    iStatus = creatLinkList(&L);
    printf("雙向循環鏈表初始化是否成功(1->YES)/ (0->NO): %d\n\n",iStatus);
    Display(L);
    
    printf("輸入要插入的位置和數據用空格隔開:");
    scanf("%d %d",&temp,&item);
    iStatus = LinkListInsert(&L,temp,item);
    Display(L);

    printf("輸入要刪除位置:");
    scanf("%d",&temp);
    iStatus = LinkListDelete(&L, temp, &item);
    printf("刪除鏈表位置爲%d,結點數據域爲:%d\n",temp,item);
    Display(L);
}

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    test();
    
    return 0;
}


複製代碼

相關文章
相關標籤/搜索