柚子快報(bào)激活碼778899分享:數(shù)據(jù)結(jié)構(gòu)--雙鏈表
柚子快報(bào)激活碼778899分享:數(shù)據(jù)結(jié)構(gòu)--雙鏈表
目錄
一、引言
二 、鏈表的分類(lèi)
1.單向或雙向
2.帶頭或不帶頭?
3.循環(huán)或不循環(huán)?
?三、雙鏈表的概念與基本結(jié)構(gòu)
1.概念
2.基本結(jié)構(gòu)?
三、雙鏈表的常見(jiàn)操作
1.創(chuàng)建節(jié)點(diǎn)
2.初始化?
3.頭插
4.尾插
5.頭刪
6.尾刪
7.打印
8.查找
9.插入節(jié)點(diǎn)
10.刪除節(jié)點(diǎn)
11.銷(xiāo)毀鏈表
五、完整代碼
1.List.h
2.List.c
3.test.c
六、總結(jié)
一、引言
雙鏈表是一種在節(jié)點(diǎn)之間通過(guò)兩個(gè)指針進(jìn)行連接的數(shù)據(jù)結(jié)構(gòu),每個(gè)節(jié)點(diǎn)都有兩個(gè)指針:一個(gè)指向前一個(gè)節(jié)點(diǎn),另一個(gè)指向下一個(gè)節(jié)點(diǎn)。帶頭節(jié)點(diǎn)的雙鏈表在實(shí)際應(yīng)用中非常常見(jiàn),本文將詳細(xì)介紹其實(shí)現(xiàn)方法,并提供完整的 C 語(yǔ)言代碼示例。
二 、鏈表的分類(lèi)
鏈表的結(jié)構(gòu)?常多樣,以下情況組合起來(lái)就有8種(2 x 2 x 2)鏈表結(jié)構(gòu):
1.單向或雙向
單向鏈表:
每個(gè)節(jié)點(diǎn)包含一個(gè)指向下一個(gè)節(jié)點(diǎn)的指針。只能從頭到尾單向遍歷。插入和刪除操作較簡(jiǎn)單,但需要從頭開(kāi)始查找節(jié)點(diǎn)。 雙向鏈表:
每個(gè)節(jié)點(diǎn)包含兩個(gè)指針,一個(gè)指向下一個(gè)節(jié)點(diǎn),一個(gè)指向前一個(gè)節(jié)點(diǎn)??梢詮念^到尾或尾到頭雙向遍歷。插入和刪除操作更靈活,但每個(gè)節(jié)點(diǎn)占用更多內(nèi)存。
2.帶頭或不帶頭?
帶頭節(jié)點(diǎn):
鏈表有一個(gè)額外的頭節(jié)點(diǎn),它不存儲(chǔ)實(shí)際數(shù)據(jù),只作為鏈表的起始點(diǎn)。操作如插入和刪除更簡(jiǎn)單,因?yàn)轭^節(jié)點(diǎn)簡(jiǎn)化了邊界條件處理。 不帶頭節(jié)點(diǎn):
鏈表從第一個(gè)實(shí)際數(shù)據(jù)節(jié)點(diǎn)開(kāi)始,沒(méi)有額外的頭節(jié)點(diǎn)。需要特別處理空鏈表和邊界情況。
3.循環(huán)或不循環(huán)?
循環(huán)鏈表:
鏈表的尾節(jié)點(diǎn)指向頭節(jié)點(diǎn),形成一個(gè)循環(huán)結(jié)構(gòu)。遍歷時(shí)可以從任何節(jié)點(diǎn)開(kāi)始,不會(huì)遇到“末尾”問(wèn)題。 非循環(huán)鏈表:
鏈表的尾節(jié)點(diǎn)指向 NULL,表示鏈表的結(jié)束。遍歷時(shí)需要檢查是否到達(dá)鏈表末尾。
雖然有這么多的鏈表的結(jié)構(gòu),但是我們實(shí)際中最常?還是兩種結(jié)構(gòu):?jiǎn)捂湵砗碗p向帶頭循環(huán)鏈表1. ?頭單向?循環(huán)鏈表:結(jié)構(gòu)簡(jiǎn)單,?般不會(huì)單獨(dú)?來(lái)存數(shù)據(jù)。實(shí)際中更多是作為其他數(shù)據(jù)結(jié) 構(gòu)的?結(jié)構(gòu),如哈希桶、圖的鄰接表等等。另外這種結(jié)構(gòu)在筆試?試中出現(xiàn)很多。 2. 帶頭雙向循環(huán)鏈表:結(jié)構(gòu)最復(fù)雜,?般?在單獨(dú)存儲(chǔ)數(shù)據(jù)。實(shí)際中使?的鏈表數(shù)據(jù)結(jié)構(gòu),都 是帶頭雙向循環(huán)鏈表。另外這個(gè)結(jié)構(gòu)雖然結(jié)構(gòu)復(fù)雜,但是使?代碼實(shí)現(xiàn)以后會(huì)發(fā)現(xiàn)結(jié)構(gòu)會(huì)帶 來(lái)很多優(yōu)勢(shì),實(shí)現(xiàn)反?簡(jiǎn)單了,后?我們代碼實(shí)現(xiàn)了就知道了。
本節(jié)我們所講的雙鏈表即為雙向帶頭循環(huán)鏈表。?
?三、雙鏈表的概念與基本結(jié)構(gòu)
1.概念
雙鏈表簡(jiǎn)介 雙鏈表是一種鏈表變體,每個(gè)節(jié)點(diǎn)都包含三個(gè)部分: 存儲(chǔ)的數(shù)據(jù)。 指向前一個(gè)節(jié)點(diǎn)的指針(prev)。 指向下一個(gè)節(jié)點(diǎn)的指針(next)。 帶頭節(jié)點(diǎn)的雙鏈表有一個(gè)特殊的節(jié)點(diǎn)稱(chēng)為頭節(jié)點(diǎn),它不存儲(chǔ)有效數(shù)據(jù),只是作為鏈表的一個(gè)起始輔助節(jié)點(diǎn)存在。頭節(jié)點(diǎn)的 prev 指針指向自己,next 指針指向鏈表的第一個(gè)有效節(jié)點(diǎn)。
2.基本結(jié)構(gòu)?
雙鏈表的基本結(jié)構(gòu)如下:
typedef struct ListNode
{
DataType data;//數(shù)據(jù)域
struct ListNode* prev;//指針域,指向前一個(gè)節(jié)點(diǎn)
struct ListNode* next;//指針域,指向后一個(gè)節(jié)點(diǎn)
}LN;
三、雙鏈表的常見(jiàn)操作
1.創(chuàng)建節(jié)點(diǎn)
//申請(qǐng)節(jié)點(diǎn)
LN* ListBuyNode(DataType x)
{
LN* node = (LN*)malloc(sizeof(LN));
if (node == NULL)
{
perror("malloc fail!");
exit(1);
}
node->data = x;
node->prev = node->next = node;//前后指針都指向自己,使得鏈表循環(huán)起來(lái)
}
2.初始化?
//初始化
void ListInit(LN** pphead)
{
*pphead = ListBuyNode(-1);//頭節(jié)點(diǎn),隨便給一個(gè)值(用不到)
}
3.頭插
//頭插
void ListPushFront(LN* phead, DataType x)
{
assert(phead);
LN* newnode = ListBuyNode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
//上面兩行代碼位置不能交換
}
4.尾插
//尾插
void ListPushBack(LN* phead, DataType x)
{
assert(phead);
LN* newnode = ListBuyNode(x);
newnode->prev = phead->prev;//新節(jié)點(diǎn)prev指向原來(lái)的尾節(jié)點(diǎn)
newnode->next = phead;//新節(jié)點(diǎn)next指向頭節(jié)點(diǎn)
phead->prev->next = newnode;//原來(lái)的尾節(jié)點(diǎn)next指向newnode
phead->prev = newnode;//頭節(jié)點(diǎn)的prev指向newnode
//上面兩行代碼位置不能交換
}
5.頭刪
//頭刪
void ListPopFront(LN* phead)
{
assert(phead && phead->next != phead);//鏈表必須有效且鏈表不為空
LN* del = phead->next;
del->next->prev = phead;
phead->next = del->next;
//刪除del節(jié)點(diǎn)
free(del);
del = NULL;
}
?
6.尾刪
//尾刪
void ListPopBack(LN* phead)
{
assert(phead&&phead->next!=phead);//鏈表必須有效且鏈表不為空
LN* del = phead->prev;
del->prev->next = phead;
phead->prev = del->prev;
//刪除del節(jié)點(diǎn)
free(del);
del = NULL;
}
7.打印
//打印
void ListPrint(LN* phead)
{
LN* pcur = phead->next;
while (pcur != phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
8.查找
//查找
LN* ListFind(LN* phead, DataType x)
{
LN* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
return pcur;//找到了
}
pcur = pcur->next;
}
return NULL;//沒(méi)有找到
}
9.插入節(jié)點(diǎn)
//插入,pos節(jié)點(diǎn)后插入
void ListInsert(LN* pos, DataType x)
{
assert(pos);
LN* newnode = ListBuyNode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
10.刪除節(jié)點(diǎn)
//刪除
void ListErase(LN* pos)//這里不傳二級(jí)指針,是為了保持接口一致性
{
assert(pos);
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
pos = NULL;
}
11.銷(xiāo)毀鏈表
//銷(xiāo)毀鏈表
void ListDestory(LN* phead)
{
assert(phead);
LN* pcur = phead->next;
while (pcur != phead)
{
LN* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
phead = NULL;
}
注:
LTErase和LTDestroy參數(shù)理論上要傳一級(jí),因?yàn)槲覀冃枰屝螀⒌母淖冇绊懙綄?shí)參,但是為了保持接口一致性才傳的一級(jí)。傳一級(jí)存在的問(wèn)題是,當(dāng)形參phead置為NULL后,實(shí)參plist不會(huì)被修改為NULL,因此解決辦法是:調(diào)用完方法后手動(dòng)將實(shí)參置為NULL~?
五、完整代碼
1.List.h
該部分放順序表結(jié)構(gòu)定義、函數(shù)的聲明
#pragma once
#include
#include
#include
typedef int DataType;
typedef struct ListNode
{
DataType data;//數(shù)據(jù)域
struct ListNode* prev;//指針域,指向前一個(gè)節(jié)點(diǎn)
struct ListNode* next;//指針域,指向后一個(gè)節(jié)點(diǎn)
}LN;
//初始化
void ListInit(LN** pphead);
//尾插
void ListPushBack(LN*phead,DataType x);
//頭插
void ListPushFront(LN*phead,DataType x);
//打印
void ListPrint(LN* phead);
//尾刪
void ListPopBack(LN* phead);
//頭刪
void ListPopFront(LN* phead);
//查找
LN* ListFind(LN* phead, DataType x);
//插入,pos節(jié)點(diǎn)后插入
void ListInsert(LN* pos, DataType x);
//刪除
void ListErase(LN* pos);
//銷(xiāo)毀鏈表
void ListDestory(LN* phead);
2.List.c
該部分是函數(shù)功能的實(shí)現(xiàn)
#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
//申請(qǐng)節(jié)點(diǎn)
LN* ListBuyNode(DataType x)
{
LN* node = (LN*)malloc(sizeof(LN));
if (node == NULL)
{
perror("malloc fail!");
exit(1);
}
node->data = x;
node->prev = node->next = node;//前后指針都指向自己,使得鏈表循環(huán)起來(lái)
}
//初始化
void ListInit(LN** pphead)
{
*pphead = ListBuyNode(-1);//頭節(jié)點(diǎn),隨便給一個(gè)值(用不到)
}
//尾插
void ListPushBack(LN* phead, DataType x)
{
assert(phead);
LN* newnode = ListBuyNode(x);
newnode->prev = phead->prev;//新節(jié)點(diǎn)prev指向原來(lái)的尾節(jié)點(diǎn)
newnode->next = phead;//新節(jié)點(diǎn)next指向頭節(jié)點(diǎn)
phead->prev->next = newnode;//原來(lái)的尾節(jié)點(diǎn)next指向newnode
phead->prev = newnode;//頭節(jié)點(diǎn)的prev指向newnode
//上面兩行代碼位置不能交換
}
//頭插
void ListPushFront(LN* phead, DataType x)
{
assert(phead);
LN* newnode = ListBuyNode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
//上面兩行代碼位置不能交換
}
//尾刪
void ListPopBack(LN* phead)
{
assert(phead&&phead->next!=phead);//鏈表必須有效且鏈表不為空
LN* del = phead->prev;
del->prev->next = phead;
phead->prev = del->prev;
//刪除del節(jié)點(diǎn)
free(del);
del = NULL;
}
//頭刪
void ListPopFront(LN* phead)
{
assert(phead && phead->next != phead);//鏈表必須有效且鏈表不為空
LN* del = phead->next;
del->next->prev = phead;
phead->next = del->next;
//刪除del節(jié)點(diǎn)
free(del);
del = NULL;
}
//打印
void ListPrint(LN* phead)
{
LN* pcur = phead->next;
while (pcur != phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
//查找
LN* ListFind(LN* phead, DataType x)
{
LN* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
return pcur;//找到了
}
pcur = pcur->next;
}
return NULL;//沒(méi)有找到
}
//插入,pos節(jié)點(diǎn)后插入
void ListInsert(LN* pos, DataType x)
{
assert(pos);
LN* newnode = ListBuyNode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
//刪除
void ListErase(LN* pos)//這里不傳二級(jí)指針,是為了保持接口一致性
{
assert(pos);
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
pos = NULL;
}
//銷(xiāo)毀鏈表
void ListDestory(LN* phead)
{
assert(phead);
LN* pcur = phead->next;
while (pcur != phead)
{
LN* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
phead = NULL;
}
3.test.c
測(cè)試,函數(shù)的調(diào)用
#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
void test01()
{
LN* plist = NULL;
ListInit(&plist);
/*ListPushBack(plist, 3);
ListPushBack(plist, 2);
ListPushBack(plist, 1);
ListPushBack(plist, 0);*/
ListPushFront(plist, 1);
ListPushFront(plist, 2);
ListPushFront(plist, 3);
ListPushFront(plist, 4);
// ListPopBack(plist);
//ListPopFront(plist);
LN* find = ListFind(plist, 3);
//if (find == NULL)
// printf("沒(méi)找到\n");
//else
// printf("找到了\n");
ListInsert(find, 99);
ListErase(find);
find = NULL;
ListPrint(plist);
ListDestory(plist);
plist = NULL;
}
int main()
{
test01();
return 0;
}
六、總結(jié)
帶頭節(jié)點(diǎn)的雙鏈表在進(jìn)行節(jié)點(diǎn)的插入和刪除操作時(shí)具有較好的靈活性。頭節(jié)點(diǎn)的存在簡(jiǎn)化了鏈表操作的邊界條件,減少了對(duì)空鏈表和鏈表邊界的特殊處理。通過(guò)本文的實(shí)現(xiàn)和示例代碼,你應(yīng)該能掌握雙鏈表的基本操作,并能夠?qū)⑵鋺?yīng)用于實(shí)際的編程任務(wù)中。 希望這個(gè)博客對(duì)你有幫助!如果你有任何問(wèn)題或者需要進(jìn)一步的解釋?zhuān)瑲g迎在評(píng)論區(qū)留言。
柚子快報(bào)激活碼778899分享:數(shù)據(jù)結(jié)構(gòu)--雙鏈表
文章鏈接
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。