數據結構(5)線性表之雙向鏈表

前言

在先前討論的鏈式存儲結構中,無論是單鏈表還是單循環鏈表,都只有一個指向後繼的指針,因此可以很容易找到它的後繼,但是要找它的前驅卻很麻煩。那麼可不可以再增加一個指向前驅的指針,這樣在遍歷鏈表時不僅能從前往後遍歷,也能從後往前遍歷呢?這就是我們今天所說的雙向鏈表。

顧名思義,雙向鏈表就是其結點中有兩個指針域,一個指向直接前驅,一個指向直接後繼。當然,鏈表的第一個結點無直接前驅,最後一個結點無直接後繼,設置爲空即可。

img_1

實現雙向鏈表的相關操作,思路上同單鏈表是一致的,唯一的區別是還需要考慮Prior指針的指向問題。

增加雙向鏈表的管理結構

我們同樣爲雙向鏈表設置管理結構,first指針指向頭結點,last指針指向最後一個結點,size保存鏈表結點的數量。同單鏈表一樣,在增加了管理結構之後,在做插入、刪除操作時,除了保證鏈表本身的正確性,還要考慮到管理結構的正確性,也就是注意first和last指針指向的正確,和size的數量正確。

img_2

雙向鏈表的插入

與單鏈表不同的是,雙向鏈表的改動除了涉及Next指針外,還涉及Prior指針,因此每當我們要執行插入操作時,一般要考慮四個指針的指向

  • 待插入結點的Prior指針
  • 待插入結點的Next指針
  • 待插入結點前驅的Next指針
  • 待插入結點後繼的Prior指針

img_3

只要想好了這四個指針的指向問題,雙向鏈表的操作也就沒什麼難點了

尾部插入

尾部插入只涉及兩個結點,因此只需要考慮兩個指針,以及管理結構的last指針即可

img_4

頭部與按值插入

頭部插入是直接插在頭結點後的位置,按值插入是找到插入位置後直接進行插入(如果是最後一位直接進行尾部插入),兩種方式都需要考慮四個指針的指向

同時,在設置四個指針指向的時候要注意先後的順序,避免出現指針丟失的問題

img_5

雙向鏈表的刪除

雙向鏈表的刪除比單鏈表的刪除方便,因爲可以找到直接前驅,所以也不需要使用修改值的方法了,直接修改直接前驅和直接後繼的指針就好了。尾部、頭部和按值刪除的方法都類似,只是涉及到的指針有區別,不詳談了。

img_6

排序與逆置

與單鏈表的思路一樣,在進行雙向鏈表的排序時,可以借鑑按值插入的思路,將原有的雙向鏈表斷開,再遍歷一個個取出結點,進行按值插入。而在進行逆置時,則進行頭部插入即可。

在這裏我是利用了已經寫好的方法,直接調用方法進行按值插入,再將原來的結點釋放掉。

    while (q != NULL) {
        //取出後續結點
        s = q;
        q = q->next;
        //進行按值插入
        insert_val(list, s->data);
        //釋放原來的結點
        free(s);
        list->size --;
    }

也可以不調用方法,通過設置指針的方式將結點重新連接到鏈表中

  while (q != NULL) {
        //取出後續結點
        s = q;
        q = q->next;
    
        Node *p = list->first;
          //尋找插入位置
          while (p->next != NULL && p->next->data<s->data) {
              p = p->next;
          }
          
          //判斷是否是最後一個
          if (p->next == NULL) {
              //是,進行尾部插入
              s->next = NULL;
              s->prior = list->last;
              list->last->next = s;
              list->last = s;
          }else{      
              //s的後繼指向p的後繼
              s->next = p->next;
              //s的前驅指向p
              s->prior = p;
              //p後繼的前驅指向s
              p->next->prior = s;
              //p的後繼指向s
              p->next = s;
          }

逆置同理

全部代碼

DList.h

#ifndef DList_h
#define DList_h

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#define ElemType int

typedef struct Node{
    ElemType data;
    struct Node *prior;
    struct Node *next;
}Node,*PNode;

typedef struct List{
    PNode first;
    PNode last;
    int size;
}List;

//初始化雙鏈表
void InitDList(List *list);

//1.尾部插入
void push_back(List *list,ElemType x);
//2.頭部插入
void push_fount(List *list,ElemType x);
//3.展示
void show_list(List *list);
//4.尾部刪除
void pop_back(List *list);
//5.頭部刪除
void pop_fount(List *list);
//6.按值插入(要求插入前的鏈表是有序的(此處爲順序
void insert_val(List *list,ElemType x);
//7.按值查找
Node* find(List *list,ElemType x);
//8.獲取長度
int length(List *list);
//9.按值刪除
void  delete_val(List *list,ElemType x);
//10.排序
void sort(List *list);
//11.逆置(前後轉換
void resver(List *list);
//12.清除單鏈表 ->只清除數據,保留頭結點
void clearList(List *list);
//13.摧毀 ->包括頭結點在內一起摧毀
void destroy(List *list);

//生成一個結點
Node* getNode(ElemType x);

#endif /* DList_h */

DList.cpp

#include "DList.h"

//初始化
void InitDList(List *list){
    Node *s = (Node *)malloc(sizeof(Node));
    assert(s != NULL);
    
    list->first = list->last = s;
    list->first->next = NULL;
    list->first->prior = NULL;
    list->size = 0;
}

//1.尾部插入
void push_back(List *list,ElemType x){
    //獲得一個結點
    Node *s = getNode(x);
    
    //s的前驅指向最後一個結點
    s->prior = list->last;
    //最後一個結點的後繼指向s
    list->last->next = s;
    //重新設置last指針的指向
    list->last = s;
    
    list->size ++;
}

//2.頭部插入
void push_fount(List *list,ElemType x){
    Node *s = getNode(x);
    //判斷是否是第一個結點
    if (list->first == list->last) {
        //是第一個結點,s的後繼不需要設置
        //s的前驅指向頭結點
        //s->prior = list->first;
        //重新設置first指針的指向,使s成爲新的首元結點
        //list->first->next = s;
        
        //重新設置last指針的指向
        list->last = s;
    }else{
        //s的後繼指向首元結點
        s->next = list->first->next;
        //s的前驅指向頭結點
        //s->prior = list->first;
        
        //首元結點的前驅指向s
        //s->next->prior = s;
        list->first->next->prior = s;
        //重新設置first指針的指向,使s成爲新的首元結點
        list->first->next = s;
        
    }
    //if-else語句中有重複的部分,可以提出來
    //s的前驅指向頭結點
    s->prior = list->first;
    //重新設置first指針的指向,使s成爲新的首元結點
    list->first->next = s;
    
    list->size ++;
}

//3.展示
void show_list(List *list){
    Node *p = list->first->next;
    
    while (p != NULL) {
        printf("%4d",p->data);
        p = p->next;
    }
    printf("\n");
}
//4.尾部刪除
void pop_back(List *list){
    if (list->size == 0) {
        printf("鏈表已空!\n");
        return;
    }
    
    //方法1
    Node *p = list->first;
    //循環到鏈表的最後一位
    while (p->next != NULL) {
        p = p->next;
    }

    //將last指針指向倒數第二位(即p的前一位
    list->last = p->prior;
    //釋放該結點的空間
    free(p);
    //此時鏈表最後一位的後繼爲空
    list->last->next = NULL;
    
    list->size --;
    
    /*
     //方法2
    Node *p = list->first;
    //循環到鏈表倒數第二位
    while (p->next != list->last) {
        p = p->next;
    }
    //釋放最後一個結點的空間
    free(p->next);
    p->next = NULL;
    //重新設置last指針的指向
    list->last = p;
    list->size --;
    */
}

//5.頭部刪除
void pop_fount(List *list){
    if (list->size == 0) {
        printf("鏈表已空!\n");
        return;
    }
    
    Node *s = list->first->next;
    
    //判斷是否是最後一個結點
    if (s->next == NULL) {
        //是,則s沒有後繼,不需要修改後繼的指針
        //修改last指針的指向
        list->last = list->first;
        list->last->next = NULL;
    }else{
        
        //s的後繼的prior指針指向頭結點
        s->next->prior = list->first;
        //頭結點的next指針指向s的後繼
        list->first->next = s->next;
    }

    //釋放該結點
    free(s);
    list->size --;
}

//6.按值插入(要求插入前的鏈表是有序的(此處爲順序
void insert_val(List *list,ElemType x){
    Node *p = list->first;
    //尋找插入位置
    while (p->next != NULL && p->next->data<x) {
        p = p->next;
    }
    
    //判斷是否是最後一個
    if (p->next == NULL) {
        //是,直接進行尾部插入
        push_back(list, x);
    }else{
        Node *s = getNode(x);
        
        //s的後繼指向p的後繼
        s->next = p->next;
        //s的前驅指向p
        s->prior = p;
        //p後繼的前驅指向s
        p->next->prior = s;
        //p的後繼指向s
        p->next = s;
        
        list->size ++;
    }
}

//7.按值查找
Node* find(List *list,ElemType x){
    Node *p = list->first->next;
    while (p != NULL && p->data != x) {
        p = p->next;
    }
    return p;
}

//8.獲取長度
int length(List *list){
    return list->size;
}

//9.按值刪除
void  delete_val(List *list,ElemType x){
    if (list->size == 0) {
        printf("鏈表已空!\n");
        return;
    }
    
    Node *p = find(list, x);
    if (p == NULL) {
        printf("要刪除的值不存在!\n");
        return;
    }
    //判斷是否是最後一個節點
    if (p == list->last) {
        //直接進行尾部刪除
        pop_back(list);
    }else{
        //p後繼的前驅指向p的前驅
        p->next->prior = p->prior;
        //p前驅的後繼指向p的後繼
        p->prior->next = p->next;
        //釋放結點空間
        free(p);
        list->size --;
    }
}

//10.排序
void sort(List *list){
    if (list->size <= 1) {
        //不需要排序
        return;
    }
    
    Node *s = list->first->next;
    Node *q = s->next;
    //將鏈表斷開
    list->last = s;
    list->last->next = NULL;
    
    while (q != NULL) {
        //取出後續結點
        s = q;
        q = q->next;
        //進行按值插入
        insert_val(list, s->data);
        //釋放原來的結點
        free(s);
        list->size --;
        
//        Node *p = list->first;
//          //尋找插入位置
//          while (p->next != NULL && p->next->data<s->data) {
//              p = p->next;
//          }
//
//          //判斷是否是最後一個
//          if (p->next == NULL) {
//              //是,直接進行尾部插入
//              s->next = NULL;
//              s->prior = list->last;
//              list->last->next = s;
//              list->last = s;
//          }else{
//
//              //s的後繼指向p的後繼
//              s->next = p->next;
//              //s的前驅指向p
//              s->prior = p;
//              //p後繼的前驅指向s
//              p->next->prior = s;
//              //p的後繼指向s
//              p->next = s;
//          }
        
    }
}

//11.逆置(前後轉換
void resver(List *list){
    if (list->size <= 1) {
        //不需要逆置
        return;
    }
    
    Node *s = list->first->next;
    Node *q = s->next;
    
    //將鏈表斷開
    list->last = s;
    list->last->next = NULL;
    
    while (q != NULL) {
        s = q;
        q = q->next;
        //直接進行頭部插入
        push_fount(list, s->data);
        //釋放原有結點
        free(s);
        list->size --;
    }
}
//12.清除單鏈表 ->只清除數據,保留頭結點
void clearList(List *list){
    if (list->size == 0) {
        printf("鏈表已空!\n");
        return;
    }
    
    Node *s = list->first;
    Node *p = s->next;
    //遍歷整個鏈表
    while (p != NULL) {
        s = p;
        p = p->next;
        //釋放結點
        free(s);
        list->size --;
    }

    list->last = list->first;
    list->last->next = NULL;
}
//13.摧毀 ->包括頭結點在內一起摧毀
void destroy(List *list){
    clearList(list);
    free(list->first);
    list->first = list->last = NULL;
}


//生成一個結點
Node* getNode(ElemType x){
    Node *s = (Node *)malloc(sizeof(Node));
    assert(s != NULL);
    
    s->data = x;
    s->prior = NULL;
    s->next = NULL;
    
    return s;
}

Main.cpp

#include "DList.h"

int main(int argc, const char * argv[]) {

    List myList;
    InitDList(&myList);
    
    //保存要輸入的數據
       ElemType item;
       //保存要查找的地址
       Node *p = NULL;
       int select = 1;
       while (select) {
           printf("**************************************\n");
           printf("* [1] push_back      [2] push_front  *\n");
           printf("* [3] show_list      [4] pop_back    *\n");
           printf("* [5] pop_front      [6] insert_val  *\n");
           printf("* [7] find           [8] length      *\n");
           printf("* [9] delete_val     [10] sort       *\n");
           printf("* [11] resver        [12] clear      *\n");
           printf("* [13*] destroy       [0] quit_system*\n");
           printf("**************************************\n");
           printf("請選擇:");
           scanf("%d",&select);
           if (select == 0) {
               break;
           }
           switch (select) {
               case 1:
                   //尾部插入
                   printf("請輸入要插入的數據(-1結束):");
                   scanf("%d",&item);
                   while (item != -1) {
                       push_back(&myList, item);
                       scanf("%d",&item);
                   }
                   break;
               case 2:
                   //頭部插入
                   printf("請輸入要插入的數據(-1結束):");
                   scanf("%d",&item);
                   while (item != -1) {
                       push_fount(&myList, item);
                       scanf("%d",&item);
                   }
                   break;
               case 3:
                   //展示單鏈表
                   show_list(&myList);
                   break;
               case 4:
                   //尾部刪除
                   pop_back(&myList);
                   break;
               case 5:
                   //頭部刪除
                   pop_fount(&myList);
                   break;
               case 6:
                   //按值插入
                   printf("請輸入要插入的數據:\n");
                   scanf("%d",&item);
                   insert_val(&myList, item);
                   break;
               case 7:
                   //按值查找
                   printf("請輸入要查找的值:\n");
                   scanf("%d",&item);
                   p = find(&myList, item);
                   if (p == NULL) {
                       printf("要查找的數據不存在!\n");
                   }else{
                       printf("地址爲:%p\n",p);
                   }
                   break;
               case 8:
                   //展示鏈表長度
                   printf("鏈表的長度爲:%d\n",length(&myList));
                   break;
               case 9:
                   //按值刪除
                   printf("請輸入要刪除的值:\n");
                   scanf("%d",&item);
                   delete_val(&myList, item);
                   break;
               case 10:
                   //排序
                   sort(&myList);
                   break;
               case 11:
                   //逆置(前後轉換
                   resver(&myList);
                   break;
               case 12:
                   //清除
                   clearList(&myList);
                   break;
               case 13:
                   destroy(&myList);
                   break;
               default:
                   printf("輸入的命令有誤,請重新插入\n");
                   break;
           }
       }
    
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章