網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
創(chuàng)建鏈表存儲(chǔ)結(jié)構(gòu)
我們需要?jiǎng)?chuàng)建一個(gè)結(jié)構(gòu)體來(lái)存儲(chǔ)一個(gè)鏈表結(jié)點(diǎn)的相關(guān)信息。
typedef int ListDataType;//將ListDataType先定義為int類型,根據(jù)需要可以改為不同的類型
//創(chuàng)建一個(gè)鏈表的結(jié)構(gòu)體
typedef struct ListNode
{
ListDataType data;//存儲(chǔ)數(shù)據(jù)
struct ListNode* next;//存儲(chǔ)下一個(gè)結(jié)點(diǎn)的地址的指針
struct ListNode* prev;//存儲(chǔ)上一個(gè)結(jié)點(diǎn)的地址的指針
}ListNode;
創(chuàng)建結(jié)點(diǎn)
我們每次增加數(shù)據(jù)都要?jiǎng)?chuàng)建一個(gè)新的結(jié)點(diǎn),但每次都創(chuàng)建就會(huì)非常的麻煩。所以我們考慮把創(chuàng)建一個(gè)結(jié)點(diǎn)這個(gè)功能封裝成一個(gè)函數(shù),每次要使用只要調(diào)用一下就可以了。
ListNode* BuyListNode(ListDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));//向內(nèi)存申請(qǐng)一個(gè)新的結(jié)點(diǎn)
if (newnode == NULL)
{
printf("BuyListNode::%s\n", strerror(errno));//打印結(jié)點(diǎn)空間申請(qǐng)失敗的原因
exit(-1);//終止程序
}
newnode->data = x;//將x放入申請(qǐng)的結(jié)點(diǎn)數(shù)據(jù)區(qū)
newnode->next = NULL;//讓新結(jié)點(diǎn)的next指向空
newnode->prev = NULL;//讓新結(jié)點(diǎn)的prev指向空
return newnode;//返回新結(jié)點(diǎn)的地址
}
鏈表的初始化
帶頭雙向循環(huán)鏈表我們對(duì)其初始化就是申請(qǐng)一個(gè)帶哨兵位的頭結(jié)點(diǎn)。接下來(lái)我們看初始化的兩種方法。
void ListInit(ListNode** pphead)//這里我們需要對(duì)頭結(jié)點(diǎn)進(jìn)行改變,所以傳二級(jí)指針
{
assert(pphead);//檢測(cè)傳過(guò)來(lái)的pphead的地址是否為空
*pphead = BuyListNode(0);//創(chuàng)建一個(gè)新的哨兵位頭結(jié)點(diǎn)
(*pphead)->next = *pphead;//讓這個(gè)結(jié)點(diǎn)指向自己
(*pphead)->prev = *pphead;
}
ListNode* ListInit()
{
ListNode* phead = BuyListNode(0);//創(chuàng)建一個(gè)新的哨兵位頭結(jié)點(diǎn)
phead->next = phead;//讓這個(gè)結(jié)點(diǎn)的next指向自己
phead->prev = phead;//讓prev指向自己
return phead;//返回哨兵位頭結(jié)點(diǎn)的地址
}
雙向鏈表的打印
我們才用遍歷鏈表的方式來(lái)打印鏈表中的數(shù)據(jù)。
void ListPrint(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;//讓cur指向哨兵位的下一個(gè)結(jié)點(diǎn)
while (cur != phead)//鏈表打印是否結(jié)束的條件判斷
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
雙向鏈表尾插
雙向循環(huán)鏈表結(jié)構(gòu)比較優(yōu),所以很方便找到尾結(jié)點(diǎn)。尾結(jié)點(diǎn)就是哨兵位結(jié)點(diǎn)prev指向的結(jié)點(diǎn)。
void ListPushBack(ListNode* phead, ListDataType x)
{
assert(phead);//檢查傳過(guò)來(lái)的頭結(jié)點(diǎn)是否為空
ListNode* tail = phead->prev;//找到鏈表的尾結(jié)點(diǎn)
ListNode* newnode = BuyListNode(x);//創(chuàng)建一個(gè)新的結(jié)點(diǎn)
tail->next = newnode;//讓尾結(jié)點(diǎn)的next指向新的結(jié)點(diǎn)
newnode->prev = tail;//讓新結(jié)點(diǎn)的prev指向之前的尾結(jié)點(diǎn)
newnode->next = phead;//讓新的結(jié)點(diǎn)的next指向頭結(jié)點(diǎn)
phead->prev = newnode;//讓頭結(jié)點(diǎn)指向新的尾結(jié)點(diǎn)
}
雙向鏈表尾刪
void ListPopBack(ListNode* phead)
{
assert(phead);
if (phead->next == phead)//如果鏈表只有哨兵位結(jié)點(diǎn)的情況,就不能繼續(xù)刪除了
return;
ListNode* tail = phead->prev;//找到尾結(jié)點(diǎn)
ListNode* tailPrev = tail->prev;//定義尾結(jié)點(diǎn)的前一個(gè)結(jié)點(diǎn)
free(tail);//釋放要?jiǎng)h除的結(jié)點(diǎn)
tailPrev->next = phead;//讓前一個(gè)結(jié)點(diǎn)指向哨兵位結(jié)點(diǎn)
phead->prev = tailPrev;//讓哨兵位結(jié)點(diǎn)指向新的尾結(jié)點(diǎn)
}
雙向鏈表頭插
雙向鏈表的頭插就是在哨兵位結(jié)點(diǎn)的下一個(gè)位置插入一個(gè)新的數(shù)據(jù)。
注意:這一種方法種的指向關(guān)系不能隨意顛倒,否則就會(huì)出錯(cuò)。
void ListPushFront(ListNode* phead, ListDataType x)
{
assert(phead);
ListNode* newnode = BuyListNode(x);//創(chuàng)建一個(gè)新結(jié)點(diǎn)
newnode->next = phead->next;//新結(jié)點(diǎn)的next指向頭結(jié)點(diǎn)的下一個(gè)結(jié)點(diǎn)
phead->next->prev = newnode;//頭結(jié)點(diǎn)的下一個(gè)結(jié)點(diǎn)指向新結(jié)點(diǎn)
phead->next = newnode;//頭結(jié)點(diǎn)指向新結(jié)點(diǎn)
newnode->prev = phead;//新結(jié)點(diǎn)prev指向頭結(jié)點(diǎn)
}
我們可以對(duì)上一種情況進(jìn)行優(yōu)化,定義一個(gè)next記錄頭結(jié)點(diǎn)的下一個(gè)結(jié)點(diǎn)的地址。這樣我們就可以不用在意指向順序的問(wèn)題,可以減少出錯(cuò)的概率。
void ListPushFront(ListNode* phead, ListDataType x)
{
assert(phead);
ListNode* newnode = BuyListNode(x);//創(chuàng)建一個(gè)新結(jié)點(diǎn)
ListNode* next = phead->next;//定義next記錄頭節(jié)點(diǎn)的下一個(gè)結(jié)點(diǎn)的位置
phead->next = newnode;//頭結(jié)點(diǎn)next指向新結(jié)點(diǎn)
newnode->prev = phead;//新結(jié)點(diǎn)的prev指向頭結(jié)點(diǎn)
next->prev = newnode;//頭結(jié)點(diǎn)的下一個(gè)結(jié)點(diǎn)的prev指向新階段
newnode->next = next;//新結(jié)點(diǎn)的next指向原頭結(jié)點(diǎn)的下一個(gè)結(jié)點(diǎn)
}
雙向鏈表頭刪
我們先找到頭結(jié)點(diǎn)的下一個(gè)結(jié)點(diǎn),然后在把它釋放掉。這里需要注意的是如果鏈表為空,只有哨兵位頭結(jié)點(diǎn)的情況,我們需要對(duì)其進(jìn)行特殊的處理。
void ListPopFront(ListNode* phead)
{
assert(phead);
if (phead->next == NULL)//只有哨兵位頭結(jié)點(diǎn)的情況
return;
ListNode* next = phead->next->next;//定義next指向要?jiǎng)h除結(jié)點(diǎn)的下一個(gè)結(jié)點(diǎn)
free(phead->next);//釋放要?jiǎng)h除的結(jié)點(diǎn)
phead->next = next;//讓哨兵位頭結(jié)點(diǎn)指向要?jiǎng)h除的下一個(gè)結(jié)點(diǎn)
next->prev = phead;//讓要?jiǎng)h除的下一個(gè)結(jié)點(diǎn)的prev指向toujied
}
雙向鏈表查找
若我們想要對(duì)指定的位置的結(jié)點(diǎn)進(jìn)行相應(yīng)的改變就得先找到對(duì)應(yīng)的結(jié)點(diǎn),所以我們將查找指定數(shù)據(jù)封裝成一個(gè)函數(shù)。方便后續(xù)調(diào)用。
ListNode* ListFind(ListNode* phead, ListDataType x)
{
assert(phead);
ListNode* cur = phead->next;//讓cur指向哨兵位頭結(jié)點(diǎn)的下一個(gè)結(jié)點(diǎn)
while (cur != phead)//鏈表循環(huán)是否結(jié)束的判斷條件
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;//找不到對(duì)于的結(jié)點(diǎn)
}
雙向鏈表pos前插入結(jié)點(diǎn)
推薦使用第二種方法,不容易出錯(cuò)。
void ListInsert(ListNode* pos, ListDataType x)
{
assert(pos);
ListNode* newnode = BuyListNode(x);//創(chuàng)建一個(gè)新的結(jié)點(diǎn)
pos->prev->next = newnode;//pos的前一個(gè)結(jié)點(diǎn)的next指向新結(jié)點(diǎn)
newnode->prev = pos->prev;//新結(jié)點(diǎn)的prev指向pos的前一個(gè)結(jié)點(diǎn)
newnode->next = pos;//新結(jié)點(diǎn)的next指向pos結(jié)點(diǎn)
pos->prev = newnode;//pos的prev指向新結(jié)點(diǎn)
}
void ListInsert(ListNode* pos, ListDataType x)
{
assert(pos);
ListNode* newnode = BuyListNode(x);//創(chuàng)建一個(gè)新的結(jié)點(diǎn)
ListNode* posPrev = pos->prev;//定義pos的前一個(gè)結(jié)點(diǎn)
posPrev->next = newnode;//讓pos的pos前一個(gè)結(jié)點(diǎn)的next指向新結(jié)點(diǎn)
newnode->prev = posPrev;//新結(jié)點(diǎn)的prev指向pos的前一個(gè)結(jié)點(diǎn)
newnode->next = pos;//新結(jié)點(diǎn)的next指向pos
pos->prev = newnode;//pos的prev指向新結(jié)點(diǎn)
}
雙向鏈表刪除pos位置的結(jié)點(diǎn)
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* prev = pos->prev;//prev為pos的前一個(gè)結(jié)點(diǎn)
ListNode* next = pos->next;//next為pos的后一個(gè)結(jié)點(diǎn)
free(pos);//釋放posjied
prev->next = next;
next->prev = prev;
}
雙向鏈表的銷毀
void ListDestroy(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;//指向哨兵位結(jié)點(diǎn)的下一個(gè)結(jié)點(diǎn)
while (cur != phead)//依次循環(huán)找鏈表的每一個(gè)結(jié)點(diǎn)
{
ListNode* next = cur->next;//記錄cur的下一個(gè)結(jié)點(diǎn)位置
free(cur);//釋放cur位置的結(jié)點(diǎn)
cur = next;//指向下一個(gè)結(jié)點(diǎn)
}
free(phead);//釋放哨兵位頭結(jié)點(diǎn)
}
注意:在寫雙向鏈表的頭插,頭刪,尾插,尾刪時(shí)我們可以復(fù)用雙向鏈表的任意位置插入和任意位置刪除那兩個(gè)函數(shù)。那兩個(gè)函數(shù)就可以完成頭插,頭刪,尾插,尾刪這幾個(gè)功能。
順序表和鏈表的區(qū)別
順序表優(yōu)點(diǎn):
1.物理空間是連續(xù)的,方便用下標(biāo)隨機(jī)訪問(wèn)。
2.CPU高速緩存命中率會(huì)更高。
順序表缺點(diǎn):
1.由于需要物理空間連續(xù),空間不夠需要擴(kuò)容。擴(kuò)容本身有一定消耗。其次擴(kuò)容機(jī)制還存在一定的空間浪費(fèi)。
2.頭部或者中部插入刪除,挪動(dòng)數(shù)據(jù),效率低。O(N)
鏈表優(yōu)點(diǎn):
1.任意位置插入刪除數(shù)據(jù)效率高。O(1)
2.按需申請(qǐng)和釋放空間
鏈表缺點(diǎn):
不支持下標(biāo)的隨機(jī)訪問(wèn)。有些算法不適合在它上面運(yùn)行。如:二分查找、排序等。
關(guān)于CPU相關(guān)的知識(shí)可以參考這一篇文章:CPU緩存知識(shí)
原文鏈接:https://blog.csdn.net/qq_61939403/article/details/123854225
相關(guān)推薦
- 2022-05-04 C#設(shè)計(jì)模式之單例模式_C#教程
- 2024-03-23 springboot項(xiàng)目中如何獲取請(qǐng)求頭當(dāng)中的token
- 2022-10-24 C語(yǔ)言進(jìn)程程序替換的實(shí)現(xiàn)詳解_C 語(yǔ)言
- 2022-06-09 FreeRTOS動(dòng)態(tài)內(nèi)存分配管理heap_2示例_操作系統(tǒng)
- 2022-01-11 slice、substring、substr比較
- 2022-08-03 Android開(kāi)發(fā)手冊(cè)Chip監(jiān)聽(tīng)及ChipGroup監(jiān)聽(tīng)_Android
- 2022-03-26 C++實(shí)現(xiàn)簡(jiǎn)單猜數(shù)字小游戲_C 語(yǔ)言
- 2022-09-06 本地搭建minio文件服務(wù)器(使用bat腳本啟動(dòng))的方法_服務(wù)器其它
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支