數據結構編程筆記六:第二章 線性表 雙向循環鏈表的實現

上次我們一起看了靜態鏈表的實現,現在一起看看雙向循環鏈表的實現。此次我只實現了書上提到的算法,沒有對ADT裏面規定的操作全部實現。

還是老規矩:

程序在碼雲上可以下載。
地址:https://git.oschina.net/601345138/DataStructureCLanguage.git

大家要注意雙向循環鏈表的插入刪除操作裏面修改指針位置的幾句代碼,要注意這幾句代碼哪些代碼的位置可以相互交換,哪些不能,這也是考研的考點。

一起看看代碼吧:

#include <stdio.h>          //使用了標準庫函數 printf(),scanf()
#include <stdlib.h>         //使用了動態內存分配函數 malloc(),free()
#define OVERFLOW -2         //內存溢出錯誤常量
#define OK 1                //表示操作正確的常量 
#define ERROR 0             //表示操作錯誤的常量 
typedef int Status;         //用typedef給int起個別名,也便於程序的維護
typedef float ElemType;     //用typedef給float起個別名,也便於程序的維護
typedef struct DuLNode{     //雙向循環鏈表的C語言描述 
    ElemType data;          //數據域 
    struct DuLNode * prior; //前驅指針域 
    struct DuLNode * next;  //後繼指針域 
}DuLNode,* DuLinkList;

//-------------------------------雙向循環鏈表的主要操作------------------------------------

/*
    函數:MallocList_DL
    參數:DuLinkList &L 雙向循環鏈表頭結點的指針 
    返回值:無
    作用:申請結點的內存空間
*/
void MallocList_DL(DuLinkList &L){

    /*
        if(!(L = (DuLinkList)malloc(sizeof(DuLNode))))
        相當於以下兩行代碼:
        L = (DuLinkList)malloc(sizeof(DuLNode))
        if(!L) <=> if(L == NULL) 
    */
    if(!(L = (DuLinkList)malloc(sizeof(DuLNode)))){

        printf("申請內存失敗!\n");
        exit(OVERFLOW);            
    }//if
}//MallocList_DL

/*
    函數:InitList_D
    參數:DuLinkList &L 雙向循環鏈表頭結點的指針
          int n 初始化多少個數據節點 
    返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR 
    作用:初始化一個帶有n個結點的雙向循環鏈表
*/ 
Status InitList_DL(DuLinkList &L, int n){

    //接收從鍵盤輸入的結點數據值 
    ElemType e;

    //工作指針p 
    DuLinkList p;

    //先申請一個頭結點並將其鏈接在頭指針p(p暫時指向頭結點)的後面 
    MallocList_DL(p);

    //使頭指針L指向頭結點 
    L = p;

    //空的雙向循環鏈表頭結點包括一個前驅指針和一個後繼指針,且均指向頭結點數據域 
    L->prior = L->next = L;

    //從鍵盤接收數據,依次創建每一個數據節點 
    printf("請依次輸入每個元素,中間用空格隔開:\n");
    for(int i = 0; i < n; i++){

        //從鍵盤接收元素的值 
        scanf("%f", &e);

        //申請一個新的數據節點並將其鏈接在p指向的結點的後面 
        MallocList_DL(p->next);

        //建立p指向結點的後繼結點的前驅指針指針域關係 
        p->next->prior = p;

        //p指針向後移動 
        p = p->next;

        //在新的結點中填入從鍵盤接收的元素值 
        p->data = e;
    }//for

    //使最後一個結點的後繼指針域保存頭結點的地址 
    p->next = L;

    //使頭結點的前驅指針域保存雙向循環鏈表尾元結點的地址 
    L->prior = p;

    //操作成功 
    return OK;  
}//InitList_DL

/*
    函數:ListIsEmpty_DL
    參數:DuLinkList L 雙向循環鏈表頭結點的指針
    返回值:若雙向循環鏈表爲空,返回1,否則返回0
    作用:判斷雙向循環鏈表L是否爲空 
*/ 
int ListIsEmpty_DL(DuLinkList L) {

    //若頭結點的前驅指針域和後繼指針域都指向頭結點自身,說明雙向循環鏈表是空表 
    return L->prior == L->next;  
}//ListIsEmpty_DL

/*
    函數:ListLength
    參數:DuLinkList L 雙向循環鏈表頭結點的指針
    返回值:雙向循環鏈表L的表長 
    作用:獲得表長 
*/ 
int ListLength(DuLinkList L) {

    //i是計數器 
    int i = 0;

    //工作指針p指向首元結點 
    DuLinkList p = L->next;

    //p沒有到頭指針說明還沒有遍歷完整張表 
    while(p != L) {
        i++;
        p = p->next;
    }//while

    //返回表長 
    return i;
}//ListLength

/*
    函數:GetElemP_DL
    參數:DuLinkList &L 雙向循環鏈表頭結點的指針
    返回值:若找到第i個結點,則返回一個指針指向該節點,否則返回NULL 
            i爲0返回頭結點的地址 
    作用:在雙向循環鏈表中查找第i個結點 
*/ 
DuLinkList GetElemP_DL(DuLinkList L, int i) { 

    //在空表中查找元素沒有意義,所以要先判斷雙向循環鏈表是否爲空 
    if(ListIsEmpty_DL(L)) {
        printf("雙向循環鏈表中沒有元素!\n");
    }//if 

    //設置一個工作指針p,指向頭結點 
    DuLinkList p = L;

    //檢查參數i是否合法,即i是否越界
    if(i < 0 || i > ListLength(L) + 1) {
        return NULL; 
    }//if

    //找到第i個結點 
    for(int j = 1; j <= i; j++) {
        p = p->next;
    }//for

    //返回指向第i個結點的指針 
    return p; 
}//GetElem_DL

/*
    函數:ListInsert_DL
    參數:DuLinkList &L 雙向循環鏈表頭結點的指針
          int i 插入位置i,i的合法值爲1 <= i <= 表長 + 1 
          ElemType e  
    返回值:雙向循環鏈表L的表長 
    作用:在帶頭結點的雙向循環線性表L中第i個位置之前插入元素e
*/ 
Status ListInsert_DL(DuLinkList &L, int i, ElemType e){ 

    //檢查i的值是否合法,i是否越界 
    if(i < 0){ 
        printf("您輸入的位置i不合法!!!\n");
        return ERROR;
    }//if

    //p是工作指針,s指向被插入的新結點 
    DuLinkList p, s;

    //找到第i個元素的位置
    //p = NULL表示沒有找到第i個結點,即插入位置不合法
    //if(!(p = GetElemP_DL(L, i)))  相當於以下兩行代碼:
    //p = GetElemP_DL(L, i);
    //if(!p)  <=>  if(p == NULL) 
    if(!(p = GetElemP_DL(L, i))) { 
         return ERROR; 
    }//if

    //申請一個新的結點作爲待插入結點,並使s指向它 
    MallocList_DL(s);

    //將e的值填入到新結點的數據域中 
    s->data = e;

    //建立s和p的前驅結點的關係 
    s->prior = p->prior;

    //建立p的前驅結點和s的關係 
    p->prior->next = s;

    //建立p和s的關係 
    s->next = p;

    //建立s和p的關係 
    p->prior = s;

    //操作成功   
    return OK;

}//ListInsert_DL

/*
    函數:ListDelete_DL
    參數:DuLinkList &L 雙向循環鏈表頭結點的指針
          int i 插入位置i,i的合法值爲i的合法值爲i <= i <= 表長
          ElemType e  
    返回值:雙向循環鏈表L的表長 
    作用:刪除帶頭結點的雙向循環鏈表L的第i個元素,並用e帶回被刪除結點的值 
*/  
Status ListDelete_DL(DuLinkList &L, int i, ElemType &e){ 

    //對空表做刪除操作沒有意義,刪除操作之前先要判斷鏈表是否爲空 
    if(ListIsEmpty_DL(L)) { 
        printf("雙向循環鏈表中沒有元素!\n");
    }//if

    //p是工作指針,指向被刪除結點 
    DuLinkList p;

    //遍歷鏈表找出第i個結點,並且使p指向它,若找不到,p的值爲NULL
    //if(!(p = GetElemP_DL(L, i))) 相當於以下兩句代碼
    //p = GetElemP_DL(L, i)
    //if(!p)  <=>   if(p = NULL)
    if(!(p = GetElemP_DL(L, i))) { 
       return ERROR;
    }//if

    //保存被刪除結點的值到e 
    e = p->data;

    //將p的前驅結點和p的後繼結點連接起來,將p指向的結點從鏈表中隔離出來 
    p->prior->next = p->next;
    p->next->prior = p->prior;

    //釋放p指向結點的內存空間 
    free(p);

    //操作成功 
    return OK; 
}//ListDelete_DL

/*
    函數:Print
    參數:ElemType e 被訪問的元素 
    返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR 
    作用:訪問元素e的函數,通過修改該函數可以修改元素訪問方式,
          該函數使用時需要配合遍歷函數一起使用。 
*/  
Status Print(ElemType e){  //元素訪問函數visit() 
    printf("%6.2f    ",e);
    return OK;
}//Print

/*
    函數:ListTraverse_DL
    參數:LinkList L 單鏈表L的頭指針 
          Status(* visit)(ElemType) 函數指針,指向元素訪問函數。 
    返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR 
    作用:依次對雙向循環鏈表的每個元素調用函數visit ()。一旦visit ()失敗,則操作失敗。
*/
Status ListTraverse_DL(DuLinkList L, Status (* visit)(ElemType)) {

    //對空表的遍歷沒有意義 
    if(ListIsEmpty_DL(L)) { 
        printf("雙向循環鏈表中沒有元素!\n");
    }//if

    //設置一個工作指針並且使之指向首元結點
    DuLinkList p = L->next;

    //遍歷整個雙向循環鏈表 
    while(p != L){

        //一旦visit ()失敗,則操作失敗。
        //if(!visit(p->data)) <=>  if(visit(p->data) == ERROR)
        if(!visit(p->data)) {
            printf("輸出發生錯誤!\n");
        }//if 

        //p後移
        p = p->next;   
    }//while

    //輸出換行使控制檯輸出美觀 
    printf("\n");

    //操作成功 
    return OK; 
}//ListTraverse_DL

/*
    函數:DestoryList_DL
    參數:LinkList L 單鏈表L的頭指針 
          Status(* visit)(ElemType) 函數指針,指向元素訪問函數。 
    返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR 
    作用:依次對雙向循環鏈表的每個元素調用函數visit ()。一旦visit ()失敗,則操作失敗。
*/
Status DestoryList_DL(DuLinkList &L){

    //工作指針q
    DuLinkList q;

    //遍歷整個鏈表,釋放全部結點的內存空間 
    while(q != L){
        //逐個釋放結點的內存空間直至返回頭結點 
        q = L->next;
        free(L);
        L = q;
    }//while

    printf("線性單鏈表內存釋放成功!\n");

    //操作成功 
    return OK; 
} //DestoryList_DL 

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

    //雙向鏈表頭結點指針L    
    DuLinkList L;

    //臨時變量,接收從鍵盤接收的元素的值 
    ElemType e;

    //n是雙向循環鏈表元素的個數
    //i是臨時變量,用於接收插入和刪除的變量 
    int n = 0, i;

    printf("\n--------------------------------雙向循環鏈表引用版------------------------------\n");

    printf("->初始化雙向循環鏈表L\n");
    printf("您想給雙向循環鏈表L設置多少個元素:");         
    scanf("%d", &n);     
    InitList_DL(L, n);
    printf("->輸出雙向循環鏈表L\n");      
    ListTraverse_DL(L, Print);                       

    printf("->您想在雙向循環鏈表L的哪個位置之前插入值?\n");
    scanf("%d", &i);
    printf("->您想在雙向循環鏈表L的該位置之前插入的值爲多少?\n");
    scanf("%f", &e);
    ListInsert_DL(L,i,e);
    printf("->執行插入操作後雙向循環鏈表L的所有元素爲:\n");      
    ListTraverse_DL(L,Print);                    

    printf("->您想在雙向循環鏈表L的哪個位置之前刪除值?\n");
    scanf("%d",&i);
    ListDelete_DL(L,i,e);
    printf("->被刪除的元素爲%6.2f\n",e);
    printf("->執行刪除操作後雙向循環鏈表L的所有元素爲:\n");       
    ListTraverse_DL(L,Print);

    printf("->銷燬雙向循環鏈表L:");
    DestoryList_DL(L);  

    return 0;   
}

看看測試數據和輸出的結果:

--------------------------------雙向循環鏈表引用版------------------------------

->初始化雙向循環鏈表L
您想給雙向循環鏈表L設置多少個元素:10
請依次輸入每個元素,中間用空格隔開:
1 45 56 78 54 23 56 89 158 56
->輸出雙向循環鏈表L
  1.00     45.00     56.00     78.00     54.00     23.00     56.00     89.00
158.00     56.00
->您想在雙向循環鏈表L的哪個位置之前插入值?
11
->您想在雙向循環鏈表L的該位置之前插入的值爲多少?
23
->執行插入操作後雙向循環鏈表L的所有元素爲:
  1.00     45.00     56.00     78.00     54.00     23.00     56.00     89.00
158.00     23.00     56.00
->您想在雙向循環鏈表L的哪個位置之前刪除值?
1
->被刪除的元素爲  1.00
->執行刪除操作後雙向循環鏈表L的所有元素爲:
 45.00     56.00     78.00     54.00     23.00     56.00     89.00    158.00
 23.00     56.00
->銷燬雙向循環鏈表L:線性單鏈表內存釋放成功!

--------------------------------
Process exited with return value 0
Press any key to continue . . .

下一次的文章是一元多項式的代碼實現,不見不散。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章