一、概述
前幾篇文章介紹了怎樣去實(shí)現(xiàn)單鏈表、單循環(huán)鏈表,這篇文章主要介紹
雙向鏈表
以及實(shí)現(xiàn)雙向鏈表的步驟,最后提供我自己根據(jù)理解實(shí)現(xiàn)雙向鏈表的C語言代碼。跟著后面實(shí)現(xiàn)思路看下去,應(yīng)該可以看懂代碼,看懂代碼后,就對雙向鏈表有了比較抽象的理解了,最后自己再動手寫一個雙向鏈表,就基本理解這個東西了。
二、雙向鏈表
雙向鏈表
:在單鏈表的每個結(jié)點(diǎn)中,再設(shè)置一個指向其前驅(qū)結(jié)點(diǎn)的指針域。
下圖是 單鏈表:下圖是 雙向鏈表:
雙向鏈表的特點(diǎn):
- 雙向鏈表可以反向訪問到鏈表的結(jié)點(diǎn),因?yàn)樗兄赶蚯耙粋€結(jié)點(diǎn)的指針
prior
;- 帶有頭結(jié)點(diǎn)的雙向鏈表,為空鏈表時,頭結(jié)點(diǎn)的兩個指針域都指向
NULL
。![]()
- 帶有頭結(jié)點(diǎn)的雙向鏈表,為非空鏈表時,
頭結(jié)點(diǎn)的前驅(qū)指針域指向NULL
,后驅(qū)指針域指向第一個結(jié)點(diǎn);
最后一個結(jié)點(diǎn)的前驅(qū)指針域指向前一個結(jié)點(diǎn),后驅(qū)指針域指向NULL
;
其他結(jié)點(diǎn)的前驅(qū)指針域指向前一個結(jié)點(diǎn),后驅(qū)指針域指向后一個結(jié)點(diǎn);![]()
三、雙向鏈表實(shí)現(xiàn)步驟
從上面知道了雙向鏈表的相關(guān)概念和一些特點(diǎn),接下來開始實(shí)現(xiàn)雙向鏈表,這里使用帶有頭結(jié)點(diǎn)的雙向鏈表進(jìn)行講解,從初始化雙向鏈表、插入數(shù)據(jù)、刪除數(shù)據(jù)、查找數(shù)據(jù)、銷毀雙向鏈表
5個操作進(jìn)行說明,需要注意的是,雙向鏈表的插入、刪除操作需要改變兩個指針域;其他操作基本和單鏈表一致。
??3.1 C語言定義雙向鏈表結(jié)點(diǎn)
為了和前幾篇文章的鏈表做比較,雙向鏈表結(jié)構(gòu)體也盡量定義相似的。
typedef int ElemType;
typedef struct _DoubleListNode
{
ElemType data;
struct _DoubleListNode *prior; // 前驅(qū)指針
struct _DoubleListNode *next; // 后驅(qū)指針
}DoubleListNode;
typedef DoubleListNode* DoubleLinkList;
??3.2 雙向鏈表初始化
因?yàn)閹в蓄^結(jié)點(diǎn),初始化時就需要分配一個頭結(jié)點(diǎn)的內(nèi)存空間,且頭指針會一直指向頭結(jié)點(diǎn)。
雙向鏈表初始化算法思路如下:
1、分配一個結(jié)點(diǎn)的存儲空間作為頭結(jié)點(diǎn),并將頭指針指向頭結(jié)點(diǎn);
2、讓頭結(jié)點(diǎn)的 prior指針 和 next指針 都指向NULL,頭結(jié)點(diǎn)的數(shù)據(jù)填一個無效值;
3、將頭指針返回給函數(shù)調(diào)用者。
C語言實(shí)現(xiàn)代碼如下:
DoubleLinkList ListInit()
{
DoubleLinkList list = (DoubleLinkList)malloc(sizeof(DoubleListNode));
list->prior = NULL;
list->next = NULL;
list->data = -1;
return list;
}
??3.3 雙向鏈表插入數(shù)據(jù)
雙向鏈表插入數(shù)據(jù)大致分為兩個步驟:首先,找到插入位置n的前一個結(jié)點(diǎn);其次,是插入新結(jié)點(diǎn),可以:先連接新結(jié)點(diǎn)、再指向新結(jié)點(diǎn)
的順序。
先連接新結(jié)點(diǎn):是先把新結(jié)點(diǎn)的兩個指針域分別連接當(dāng)前結(jié)點(diǎn)和下個結(jié)點(diǎn),new->prior = cur;
、new->next = cur->next;
再指向新結(jié)點(diǎn):將當(dāng)前節(jié)點(diǎn)的的指針域指向新節(jié)點(diǎn),與舊節(jié)點(diǎn)斷開,cur->next->prior = new;
、cur->next = new;
雙向鏈表在第n個位置插入數(shù)據(jù)的算法思路:
1、定義一個結(jié)點(diǎn)指針cur指向頭結(jié)點(diǎn),用來遍歷鏈表;
2、定義一個變量cur_i,用來表示當(dāng)前結(jié)點(diǎn)的序號,初始化為0表示當(dāng)前指向頭結(jié)點(diǎn);
3、將cur指針不斷往后移動,直到下個位置就是插入位置n,即當(dāng)cur_i==(n-1)跳出循環(huán);
4、若結(jié)束循環(huán)后是當(dāng)前結(jié)點(diǎn)無效,說明鏈表長度不夠;
5、否則,說明當(dāng)前結(jié)點(diǎn)cur的下個位置就是插入位置n,分配存儲空間給新結(jié)點(diǎn)new;
6、把值填進(jìn)新節(jié)點(diǎn)的數(shù)據(jù)域,用新結(jié)點(diǎn)prior指向當(dāng)前結(jié)點(diǎn),next指向當(dāng)前節(jié)點(diǎn)的下個節(jié)點(diǎn);
7、再將下個結(jié)點(diǎn)的prior指向新結(jié)點(diǎn),當(dāng)前結(jié)點(diǎn)的next指向新結(jié)點(diǎn),完成插入操作。
C語言實(shí)現(xiàn)代碼如下:
int ListInsert(DoubleLinkList list, int data, int n)// 將node插入到第n位,n從1開始
{
if(list==NULL || n<1) // 判斷參數(shù)有效性
return -1;
DoubleListNode* cur = list; // cur指向當(dāng)前結(jié)點(diǎn),初始化指向頭結(jié)點(diǎn)
int cur_i=0; // cur_i表示當(dāng)前結(jié)點(diǎn)的序號,0-頭結(jié)點(diǎn)
while(cur && cur_i<(n-1))// 當(dāng)前結(jié)點(diǎn)有效,且不是插入位置的前一個結(jié)點(diǎn),就后移一個
{
cur = cur->next;
cur_i++;
}
if(!cur) // 當(dāng)前結(jié)點(diǎn)無效,說明已經(jīng)移動到最后
{
printf("[%s %d]error din't have No.%d\n", __FUNCTION__,__LINE__, n);
return -1; // 鏈表沒有 n 那么長
}
DoubleListNode* new = (DoubleListNode*)malloc(sizeof(DoubleListNode));
new->data = data;
new->prior = cur;
new->next = cur->next;
if(cur->next) // 在最后一個結(jié)點(diǎn)插入時,cur->next==NULL
cur->next->prior = new;
cur->next = new;
return 0;
}
??3.4 雙向鏈表刪除數(shù)據(jù)
雙向鏈表刪除結(jié)點(diǎn)也是需要改變兩個指針域,大致步驟如下,首先,找到刪除位置n的前一個結(jié)點(diǎn);其次,“把前一個結(jié)點(diǎn)的next
指針域指向刪除結(jié)點(diǎn)del的下個結(jié)點(diǎn)
”,“再把下個結(jié)點(diǎn)的prior
指針域指向刪除結(jié)點(diǎn)del的前個結(jié)點(diǎn)
”,這樣就刪除了下一個結(jié)點(diǎn)。
雙向鏈表刪除第n個數(shù)據(jù)的算法思路:
1、定義一個結(jié)點(diǎn)指針cur指向頭結(jié)點(diǎn),用來遍歷鏈表;
2、定義一個變量cur_i,用來表示下個結(jié)點(diǎn)的序號,初始化為0表示當(dāng)前指向頭結(jié)點(diǎn);
3、將cur指針不斷往后移動,直到下個位置就是刪除位置n,即當(dāng)cur_i==(n-1)跳出循環(huán);
4、若結(jié)束循環(huán)后是最后一個結(jié)點(diǎn)(cur->next==NULL),說明鏈表長度不夠;
5、否則,說明下個結(jié)點(diǎn)(cur->next)就是刪除位置n的結(jié)點(diǎn)delete,賦值delete = cur->next;
6、將前一個結(jié)點(diǎn)的next指針域指向 del 的下個結(jié)點(diǎn) ,delete->prior->next = delete->next;
7、將下一個結(jié)點(diǎn)的prior指針域指向 del 的前個結(jié)點(diǎn) ,delete->next->prior = delete->prior;;
8、最后釋放delete結(jié)點(diǎn)的內(nèi)存,完成刪除操作。
C語言實(shí)現(xiàn)代碼如下,刪除結(jié)點(diǎn)更關(guān)注的是下個結(jié)點(diǎn)(cur->next
)的有效性:
// 刪除第n個結(jié)點(diǎn),且將刪除的值通過data傳出
int ListDelete(DoubleLinkList list, int *data, int n)
{
if(list==NULL || data==NULL || n<1)
return -1;
DoubleListNode* cur = list; // cur指向當(dāng)前結(jié)點(diǎn),初始化指向頭結(jié)點(diǎn)
int cur_i=0; // cur_i表示當(dāng)前結(jié)點(diǎn)的序號,0-頭結(jié)點(diǎn)
while(cur->next && cur_i<(n-1))
{// 下個結(jié)點(diǎn)有效,且當(dāng)前位置不是刪除位置的前一個,就后移一個
cur = cur->next;
cur_i++;
}
if(!cur->next) // 下個結(jié)點(diǎn)無效,說明已經(jīng)移動到最后
{
printf("[%s %d]error din't have No.%d\n", __FUNCTION__,__LINE__, n);
return -1; // 鏈表沒有 n 那么長
}
DoubleListNode *delete = cur->next;
delete->prior->next = delete->next;
delete->next->prior = delete->prior;
free(delete);
return 0;
}
??3.5 雙向鏈表查找數(shù)據(jù)
查找數(shù)據(jù)時,將指針指向第一個結(jié)點(diǎn)而非頭結(jié)點(diǎn),下面函數(shù)中list
是頭指針,指向頭結(jié)點(diǎn),雙向鏈表非空時,list->next
就是第一個結(jié)點(diǎn);雙向鏈表為空時,list->next == NULL
。雙向鏈表 和 單鏈表 查找數(shù)據(jù)的算法是一樣的。
雙向鏈表查找第n個數(shù)據(jù)的算法思路:
1、定義一個結(jié)點(diǎn)指針cur指向第一個結(jié)點(diǎn)(list->next),用來遍歷鏈表;
2、定義一個變量cur_i,用來表示當(dāng)前結(jié)點(diǎn)的序號,初始化為1(第一步指向的就是第一個結(jié)點(diǎn));
3、若當(dāng)前結(jié)點(diǎn)有效,且當(dāng)前位置不是查找位置n,就繼續(xù)后移,直到最后結(jié)點(diǎn)或cur_i==n跳出循環(huán);
4、若結(jié)束循環(huán)后,當(dāng)前結(jié)點(diǎn)無效,說明已經(jīng)移動到最后,鏈表長度不夠;
5、否則,說明當(dāng)前結(jié)點(diǎn)(cur)就是查找位置n的結(jié)點(diǎn);返回結(jié)點(diǎn)數(shù)據(jù)*data = cur->data。
C語言實(shí)現(xiàn)代碼如下:
int ListFind(DoubleLinkList list, int *data, int n)
{
if(list==NULL || data==NULL || n<1)
return -1;
DoubleListNode* cur = list->next;// 指向第一個節(jié)點(diǎn)
int cur_i=1; // i表示當(dāng)前結(jié)點(diǎn)的序號
while(cur && cur_i<n) // 當(dāng)前結(jié)點(diǎn)有效,且當(dāng)前位置不是查找位置n,就往后移動一個
{
cur = cur->next;
cur_i++;
}
if(!cur) // 當(dāng)前結(jié)點(diǎn)無效,說明已經(jīng)移動到最后
{
printf("[%s %d]error din't have No.%d\n", __FUNCTION__,__LINE__, n);
return -1; // 鏈表沒有 n 那么長
}
*data = cur->data;
printf("[%s %d]find No.%d = %d\n", __FUNCTION__,__LINE__, n,*data);
return 0;
}
??3.6 雙向鏈表的銷毀
雙向鏈表銷毀的算法思路:
1、定義一個結(jié)點(diǎn)指針cur指向第一個結(jié)點(diǎn),用來遍歷鏈表;
2、定義一個結(jié)點(diǎn)指針next,保存下個結(jié)點(diǎn)地址;
3、當(dāng)前指針不是指向最后一個結(jié)點(diǎn)的指針域就后移,進(jìn)入循環(huán):
3.1、先保存下個結(jié)點(diǎn)地址,因?yàn)橄聜€結(jié)點(diǎn)本來保存在cur->next,直接free(cur)會丟掉下個結(jié)點(diǎn);
3.2、刪除當(dāng)前結(jié)點(diǎn),釋放內(nèi)存
3.3、將當(dāng)前指針指向前面保存好的下個結(jié)點(diǎn)。
4、結(jié)束循環(huán)后,已經(jīng)刪除完所有節(jié)點(diǎn),此時需要將頭結(jié)點(diǎn)的兩個指針域都指向NULL,表示空鏈表。
C語言實(shí)現(xiàn)代碼如下:
void ListDestroy(DoubleLinkList list)
{
DoubleListNode* cur = list->next; // 指向第一個節(jié)點(diǎn)
DoubleListNode* next = NULL; // 用于保存下個結(jié)點(diǎn)地址
while(cur) // 當(dāng)前結(jié)點(diǎn)有效,就往后移動
{
next = cur->next; // 保存下個結(jié)點(diǎn)地址
//printf("[%s %d]delete %d\n", __FUNCTION__,__LINE__, cur->data);
free(cur); // 刪除當(dāng)前結(jié)點(diǎn)、并釋放內(nèi)存
cur = next; // 將當(dāng)前結(jié)點(diǎn)指針指向下個結(jié)點(diǎn)
}
list->prior = NULL;
list->next = NULL;
}
文章來源:http://www.zghlxwxcb.cn/news/detail-428261.html
四、雙向鏈表完整代碼
代碼只是為了更好地了解循環(huán)鏈表,實(shí)現(xiàn)過程可能存在不足,有發(fā)現(xiàn)的,歡迎指正,謝謝?。?!
代碼已在Ubuntu編譯通過,可執(zhí)行。文章來源地址http://www.zghlxwxcb.cn/news/detail-428261.html
// DoubleList.c
#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
typedef struct _DoubleListNode
{
ElemType data;
struct _DoubleListNode *prior; // 前驅(qū)指針
struct _DoubleListNode *next; // 后驅(qū)指針
}DoubleListNode;
typedef DoubleListNode* DoubleLinkList;
DoubleLinkList ListInit()
{
DoubleLinkList list = (DoubleLinkList)malloc(sizeof(DoubleListNode));
list->prior = NULL;
list->next = NULL;
list->data = -1;
return list;
}
int ListInsert(DoubleLinkList list, int data, int n)// 將node插入到第n位,n從1開始
{
if(list==NULL || n<1) // 判斷參數(shù)有效性
return -1;
DoubleListNode* cur = list; // cur指向當(dāng)前結(jié)點(diǎn),初始化指向頭結(jié)點(diǎn)
int cur_i=0; // cur_i表示當(dāng)前結(jié)點(diǎn)的序號,0-頭結(jié)點(diǎn)
while(cur && cur_i<(n-1))// 當(dāng)前結(jié)點(diǎn)有效,且不是插入位置的前一個結(jié)點(diǎn),就后移一個
{
cur = cur->next;
cur_i++;
}
if(!cur) // 當(dāng)前結(jié)點(diǎn)無效,說明已經(jīng)移動到最后
{
printf("[%s %d]error din't have No.%d\n", __FUNCTION__,__LINE__, n);
return -1; // 鏈表沒有 n 那么長
}
DoubleListNode* new = (DoubleListNode*)malloc(sizeof(DoubleListNode));
new->data = data;
new->prior = cur;
new->next = cur->next;
if(cur->next) // 在最后一個結(jié)點(diǎn)插入時,cur->next==NULL
cur->next->prior = new;
cur->next = new;
return 0;
}
// 刪除第n個結(jié)點(diǎn),且將刪除的值通過data傳出
int ListDelete(DoubleLinkList list, int *data, int n)
{
if(list==NULL || data==NULL || n<1)
return -1;
DoubleListNode* cur = list; // cur指向當(dāng)前結(jié)點(diǎn),初始化指向頭結(jié)點(diǎn)
int cur_i=0; // cur_i表示當(dāng)前結(jié)點(diǎn)的序號,0-頭結(jié)點(diǎn)
while(cur->next && cur_i<(n-1))
{// 下個結(jié)點(diǎn)有效,且當(dāng)前位置不是刪除位置的前一個,就后移一個
cur = cur->next;
cur_i++;
}
if(!cur->next) // 下個結(jié)點(diǎn)無效,說明已經(jīng)移動到最后
{
printf("[%s %d]error din't have No.%d\n", __FUNCTION__,__LINE__, n);
return -1; // 鏈表沒有 n 那么長
}
DoubleListNode *delete = cur->next;
delete->prior->next = delete->next;
delete->next->prior = delete->prior;
free(delete);
return 0;
}
int ListFind(DoubleLinkList list, int *data, int n)
{
if(list==NULL || data==NULL || n<1)
return -1;
DoubleListNode* cur = list->next;// 指向第一個節(jié)點(diǎn)
int cur_i=1; // i表示當(dāng)前結(jié)點(diǎn)的序號
while(cur && cur_i<n) // 當(dāng)前結(jié)點(diǎn)有效,且當(dāng)前位置不是查找位置n,就往后移動一個
{
cur = cur->next;
cur_i++;
}
if(!cur) // 當(dāng)前結(jié)點(diǎn)無效,說明已經(jīng)移動到最后
{
printf("[%s %d]error din't have No.%d\n", __FUNCTION__,__LINE__, n);
return -1; // 鏈表沒有 n 那么長
}
*data = cur->data;
printf("[%s %d]find No.%d = %d\n", __FUNCTION__,__LINE__, n,*data);
return 0;
}
void ListDestroy(DoubleLinkList list)
{
DoubleListNode* cur = list->next; // 指向第一個節(jié)點(diǎn)
DoubleListNode* next = NULL; // 用于保存下個結(jié)點(diǎn)地址
while(cur) // 當(dāng)前結(jié)點(diǎn)有效,就往后移動
{
next = cur->next; // 保存下個結(jié)點(diǎn)地址
//printf("[%s %d]delete %d\n", __FUNCTION__,__LINE__, cur->data);
free(cur); // 刪除當(dāng)前結(jié)點(diǎn)、并釋放內(nèi)存
cur = next; // 將當(dāng)前結(jié)點(diǎn)指針指向下個結(jié)點(diǎn)
}
list->prior = NULL;
list->next = NULL;
}
void ListPrintf(DoubleLinkList list)
{
DoubleListNode* cur = list->next;// 指向第一個節(jié)點(diǎn)
printf("list:[");
while(cur)
{
printf("%d,",cur->data);
cur = cur->next;
}
printf("]\n");
}
int main()
{
DoubleLinkList list=ListInit();
int data=0;
printf("Linklist is empty !!! \n");
ListInsert(list, 2, 2); // 空鏈表時,驗(yàn)證插入
ListDelete(list, &data, 1); // 空鏈表時,驗(yàn)證刪除
ListFind(list, &data, 1); // 空鏈表時,驗(yàn)證查詢
ListDestroy(list); // 空鏈表時,驗(yàn)證銷毀
printf("\ninsert 3 data\n");
// 正常插入3個數(shù)據(jù)
ListInsert(list, 1, 1);
ListInsert(list, 2, 2);
ListInsert(list, 3, 3);
ListPrintf(list);
printf("\n驗(yàn)證錯誤值\n");
ListInsert(list, 5, 5); // 驗(yàn)證插入
ListDelete(list, &data, 4); // 驗(yàn)證刪除
ListFind(list, &data, 4); // 驗(yàn)證查詢
printf("\n正常操作\n");
// 正常操作
ListFind(list, &data, 2);
printf("delete 2,now\n");
ListDelete(list, &data, 2);
ListPrintf(list);
printf("Insert 4 to 2,now\n");
ListInsert(list, 4, 2);
ListPrintf(list);
printf("Destroy ,now\n");
ListDestroy(list);
ListPrintf(list);
return 0;
}
到了這里,關(guān)于【數(shù)據(jù)結(jié)構(gòu)與算法】 - 雙向鏈表 - 詳細(xì)實(shí)現(xiàn)思路及代碼的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!