網(wǎng)站首頁 編程語言 正文
在上一篇所講述的單鏈表中,存在一些缺陷:
1、在進行尾插和尾刪時,需要遍歷鏈表找到尾結(jié)點
2、在進行中間插入和刪除時,也需要先遍歷鏈表找到前一個結(jié)點
對于這些缺陷,可以采用一種結(jié)構(gòu)更為復(fù)雜的鏈表 雙向帶頭循環(huán)鏈表
雙向帶頭循環(huán)鏈表結(jié)構(gòu)雖然復(fù)雜,但在鏈表的操作上帶來了很大的優(yōu)勢
一、雙向帶頭循環(huán)鏈表的結(jié)構(gòu)
//存儲數(shù)據(jù)的類型,這里以 int 來舉例
typedef int LTDataType;
//結(jié)點的類型
typedef struct ListNode
{
LTDataType data;
struct ListNode* prev;
struct ListNode* next;
}LTNode;
二、雙向帶頭循環(huán)鏈表的函數(shù)接口
1. 申請結(jié)點
在插入等操作時需要申請結(jié)點,為了避免麻煩重復(fù)的操作,這里將申請結(jié)點封裝為一個函數(shù)
申請結(jié)點函數(shù)如下:
LTNode* BuyLTNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
//開辟空間失敗,打印錯誤信息
perror("malloc");
//結(jié)束程序
exit(-1);
}
newnode->data = x;
newnode->prev = newnode->next = NULL;
return newnode;
}
2. 初識化
在雙向帶頭循環(huán)鏈表中,即使沒有存儲數(shù)據(jù)也 至少會包含一個哨兵位的頭結(jié)點
初始化函數(shù)如下:
LTNode* InitLT()
{
//申請頭結(jié)點,頭結(jié)點的數(shù)據(jù)存什么無關(guān)緊要
LTNode* phead = BuyLTNode(-1);
//改變指針指向,構(gòu)成循環(huán)
phead->prev = phead->next = phead;
return phead;
}
3. 打印
為了驗證插入、刪除等得到的結(jié)果是否正確,提供打印函數(shù),這里數(shù)據(jù)類型以 int 為例,當讀者采用的類型不同時,自行更改函數(shù)即可
打印函數(shù)如下:
void LTPrint(LTNode* phead)
{
//鏈表不能為空
assert(phead);
LTNode* cur = phead->next;
printf("head->");
while (cur != phead)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("head\n");
}
4. 尾插尾刪
尾插:在鏈表的最后一個結(jié)點之后插入結(jié)點
尾插函數(shù)如下:
void LTPushBack(LTNode* phead, LTDataType x)
{
//鏈表不能為空
assert(phead);
LTNode* newnode = BuyLTNode(x);
//找到尾結(jié)點
LTNode* tail = phead->prev;
//改變指針指向
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
尾刪:刪除鏈表最后一個結(jié)點
尾刪函數(shù)如下:
void LTPopBack(LTNode* phead)
{
assert(phead); //鏈表不能為空
assert(phead->next != phead); //空鏈表不能刪
//找尾結(jié)點及尾結(jié)點的前一個結(jié)點
LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
//改變指針指向
tailPrev->next = phead;
phead->prev = tailPrev;
free(tail);
}
5. 頭插頭刪
頭插: 在第一個結(jié)點之前插入新結(jié)點
頭插函數(shù)如下:
void LTPushFront(LTNode* phead, LTDataType x)
{
//鏈表不能為空
assert(phead);
LTNode* newnode = BuyLTNode(x);
//找到頭結(jié)點后的第一個結(jié)點
LTNode* first = phead->next;
//改變指針指向
phead->next = newnode;
newnode->prev = phead;
newnode->next = first;
first->prev = newnode;
}
頭刪:刪除鏈表的第一個結(jié)點
頭刪函數(shù)如下:
void LTPopFront(LTNode* phead)
{
assert(phead); //鏈表不能為空
assert(phead->next != phead); //空鏈表不能刪
//找到頭結(jié)點后的第一個和第二個結(jié)點
LTNode* first = phead->next;
LTNode* second = first->next;
//改變指針指向
phead->next = second;
second->prev = phead;
free(first);
}
6. 查找
查找:如果數(shù)據(jù)存在,返回該數(shù)據(jù)結(jié)點的指針,不存在返回 NULL
查找函數(shù)如下:
LTNode* LTFind(LTNode* phead, LTDataType x)
{
//鏈表不能為空
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x) return cur;
cur = cur->next;
}
return NULL;
}
7. 中間插入和刪除
中間插入:通過查找函數(shù) LTFind 獲得指向結(jié)點的指針 pos,在 pos 指向的 結(jié)點之前 插入結(jié)點
在 pos 之前插入結(jié)點函數(shù)如下:
void LTInsert(LTNode* pos, LTDataType x)
{
//pos 不能為空
assert(pos);
LTNode* newnode = BuyLTNode(x);
//找到 pos 的前一個結(jié)點
LTNode* posPrev = pos->prev;
//改變指針指向
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
在調(diào)用中間插入函數(shù) LTInsert 時
- 如果在鏈表頭結(jié)點之前插入數(shù)據(jù),便和尾插函數(shù)的功能一樣
- 如果在鏈表頭結(jié)點之后插入數(shù)據(jù),便和頭插函數(shù)的功能一樣
因此在尾插和頭插函數(shù)的實現(xiàn)中可以直接調(diào)用中間插入函數(shù) LTInsert
尾插和頭插函數(shù)更改如下:
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
//鏈表不能為空
assert(phead);
LTInsert(phead, x);
}
//頭插
void LTPushFront(LTNode* phead, LTDataType x)
{
//鏈表不能為空
assert(phead);
LTInsert(phead->next, x);
}
中間刪除:通過查找函數(shù) LTFind 獲得指向結(jié)點的指針 pos,刪除 pos 指向的結(jié)點
刪除 pos 指向的結(jié)點函數(shù)如下:
void LTErase(LTNode* pos)
{
//pos 不能為空
assert(pos);
//找到 pos 的前一個和后一個結(jié)點
LTNode* posPrev = pos->prev;
LTNode* posNext = pos->next;
//改變指針指向
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
}
在調(diào)用中間刪除函數(shù) LTErase 時
- 如果刪除鏈表頭結(jié)點的前一個結(jié)點,便和尾刪函數(shù)的功能一樣
- 如果刪除鏈表頭結(jié)點的后一個結(jié)點,便和頭刪函數(shù)的功能一樣
因此在尾刪和頭刪函數(shù)的實現(xiàn)中可以直接調(diào)用中間刪除函數(shù) LTErase
尾刪和頭刪函數(shù)更改如下:
//尾刪
void LTPopBack(LTNode* phead)
{
assert(phead); //鏈表不能為空
assert(phead->next != phead); //空鏈表不能刪
LTErase(phead->prev);
}
//頭刪
void LTPopFront(LTNode* phead)
{
assert(phead); //鏈表不能為空
assert(phead->next != phead); //空鏈表不能刪
LTErase(phead->next);
}
8. 判空及求鏈表長度
判空:判斷鏈表是否為空
判空函數(shù)如下:
bool LTEmpty(LTNode* phead)
{
//鏈表不能為空
assert(phead);
return phead->next == phead;
}
鏈表長度:鏈表有效數(shù)據(jù)個數(shù)
鏈表長度函數(shù)如下:
size_t LTSize(LTNode* phead)
{
//鏈表不能為空
assert(phead);
size_t size = 0;
LTNode* cur = phead->next;
while (cur != phead)
{
size++;
cur = cur->next;
}
return size;
}
9. 銷毀單鏈表
在鏈表中,存儲數(shù)據(jù)的結(jié)點是由自己開辟的,當不使用鏈表時,應(yīng)將其銷毀
銷毀鏈表函數(shù)如下:
void LTDestroy(LTNode* phead)
{
//鏈表不能為空
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* curNext = cur->next;
free(cur);
cur = curNext;
}
free(phead);
}
原文鏈接:https://blog.csdn.net/qq_70793373/article/details/128361715
相關(guān)推薦
- 2022-07-21 nginx的禁止ip訪問的配置方法和不緩存html
- 2022-09-03 pandas?如何保存數(shù)據(jù)到excel,csv_python
- 2022-05-13 三分鐘搞懂react-hooks及實例代碼_React
- 2022-09-21 Golang?channel為什么不會阻塞的原因詳解_Golang
- 2023-05-11 SQL?多表聯(lián)合查詢的幾種方式詳解_MsSql
- 2022-12-14 Android?Studio?gradle配置packagingOptions打包so庫重復(fù)_And
- 2022-09-22 Mybaits一級緩存和二級緩存分別是什么,區(qū)別是什么?
- 2022-05-07 MVC中Action方法的返回類型介紹_基礎(chǔ)應(yīng)用
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支