(叠甲:如有侵权请联系,内容都是自己学习的总结,一定不全面,仅当互相交流(轻点骂)我也只是站在巨人肩膀上的一个小卡拉米,已老实,求放过)
链表(link list)的定义与特点
链表是一种线性动态数据结构 ,由若干个「节点(Node)」串联而成。与数组的「连续内存存储」本质不同,链表的节点在内存中可以分散存放,节点之间通过指针(引用) 建立连接,从而形成一条 "数据链"。
它的核心特性是:不需要预先分配固定内存,插入 / 删除元素时无需移动大量数据;但失去了随机访问能力,无法通过下标直接定位元素。
常见链表类型
1. 单链表(Singly Linked List)
最基础的链表形态,每个节点只有一个 next 指针,指向后继节点。
- 特点:只能从头到尾单向遍历,无法反向访问前驱节点
- 尾节点的
next指向nullptr,表示链表结束
2. 双向链表(Doubly Linked List)
每个节点包含两个指针:next 指向后继节点,prev 指向前驱节点。
- 特点:支持双向遍历,查找前驱节点的时间复杂度从 O (n) 降为 O (1)
- 代价:每个节点多占用一个指针的内存空间
- 典型场景:需要频繁前后遍历、快速删除已知节点的场景(如 LRU 缓存)
3. 循环链表(Circular Linked List)
尾节点的 next 不再指向空,而是回头指向头节点,形成闭环。
- 分为单向循环链表 和双向循环链表
- 特点:从任意节点出发都能遍历完整条链表
- 典型场景:约瑟夫环问题、操作系统进程调度队列
核心操作与时间复杂度
注意:很多资料会说 "链表插入删除是 O (1)",这个结论有前提 ------必须提前定位到插入 / 删除位置的前驱节点。如果包含 "查找位置" 的过程,整体时间复杂度仍是 O (n)。
1. 遍历与查找
从头节点出发,逐个通过 next 指针向后移动,直到找到目标或到达链表末尾。
- 时间复杂度:O(n),n 为链表长度
- 不支持随机访问,不能像数组一样通过下标 O (1) 定位元素
2. 插入操作
分为头插、尾插、中间插入三种场景:
- 头部插入 :新节点的 next 指向原头节点,更新头指针 → O(1)
- 尾部插入 :需先遍历找到尾节点,再修改指针 → O (n)(若维护尾指针
tail则为 O (1)) - 中间插入 :先找到插入位置的前驱节点,再修改两个指针
- 核心步骤:
newNode->next = prev->next; prev->next = newNode; - 查找前驱 O (n),修改指针 O (1),整体O(n)
- 核心步骤:
3. 删除操作
- 找到待删除节点的前驱节点
prev - 修改指针跳过待删节点:
prev->next = prev->next->next; - 手动释放待删除节点的内存(C++ 等手动内存管理语言)
- 时间复杂度:查找前驱 O (n),删除操作 O (1),整体O(n)
- 双向链表中,若已知待删节点本身,可直接通过前驱指针完成删除,时间为 O (1)
实用技巧:虚拟头节点
为了统一处理「头节点插入 / 删除」和「中间节点插入 / 删除」的逻辑,避免单独判断头节点的边界情况,常引入虚拟头节点(dummy node):
- 在原链表头部前新增一个无实际意义的节点,让
dummy->next指向原头节点 - 最终返回结果时返回
dummy->next即可
| 对比维度 | 数组(连续存储) | 链表(链式存储) |
|---|---|---|
| 内存分布 | 连续内存块,需预先分配大小 | 分散内存,动态分配,无固定容量 |
| 随机访问 | O (1),通过下标直接定位 | O (n),必须从头遍历 |
| 头部 / 中间插入删除 | O (n),需要移动大量元素 | O (n)(查找位置)+ O (1)(修改指针) |
| 尾部插入删除 | 空间充足时 O (1) | 维护尾指针时 O (1),无尾指针则 O (n) |
| 空间利用率 | 固定大小易浪费,扩容成本高 | 按需分配,利用率高 |
| 缓存友好性 | 高,连续内存易命中 CPU 缓存 | 低,节点分散,缓存命中率低 |
常用的链表
链表总共有8种,但是用的多些的为一下两种
-
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结 构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
-
带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向 循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。
一、无头单向非循环单链表(SList)
特点:无哨兵位头结点,节点只有后继指针,非循环结构。所有可能修改头节点的操作都需要传入二级指针。
cpp
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
// 数据类型定义
typedef int SLDataType;
// 链表节点结构体
typedef struct SListNode
{
SLDataType data; // 节点存储的数据
struct SListNode* next; // 指向后继节点的指针
} SListNode;
// 1. 创建新节点
SListNode* CreateNewNode(SLDataType val)
{
SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
if (newNode == NULL)
{
perror("CreateNewNode malloc failed");
return NULL;
}
newNode->data = val;
newNode->next = NULL;
return newNode;
}
// 2. 头插:在链表头部插入新节点
void SLFrontInsert(SListNode** pphead, SLDataType val)
{
assert(pphead); // 二级指针本身不能为空
SListNode* newNode = CreateNewNode(val);
if (newNode == NULL) return;
newNode->next = *pphead;
*pphead = newNode;
}
// 3. 尾插:在链表尾部插入新节点
void SLBackInsert(SListNode** pphead, SLDataType val)
{
assert(pphead);
SListNode* newNode = CreateNewNode(val);
if (newNode == NULL) return;
// 空链表:新节点直接作为头节点
if (*pphead == NULL)
{
*pphead = newNode;
return;
}
// 非空链表:遍历找到尾节点
SListNode* cur = *pphead;
while (cur->next != NULL)
{
cur = cur->next;
}
cur->next = newNode;
}
// 4. 查找元素:返回第一个值为val的节点指针,找不到返回NULL
SListNode* SLFindElement(SListNode* phead, SLDataType val)
{
SListNode* cur = phead;
while (cur != NULL)
{
if (cur->data == val)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
// 5. 指定节点后插入:在值为posVal的节点之后插入新数据
void SLInsertAfter(SListNode** pphead, SLDataType posVal, SLDataType newVal)
{
assert(pphead);
SListNode* pos = SLFindElement(*pphead, posVal);
if (pos == NULL)
{
printf("未找到指定位置节点,插入失败\n");
return;
}
SListNode* newNode = CreateNewNode(newVal);
if (newNode == NULL) return;
// 先连后半段,再连前半段,防止断链
newNode->next = pos->next;
pos->next = newNode;
}
// 6. 头删:删除链表第一个有效节点
void SLFrontDelete(SListNode** pphead)
{
assert(pphead);
if (*pphead == NULL)
{
printf("链表为空,无法头删\n");
return;
}
SListNode* del = *pphead;
*pphead = (*pphead)->next; // 注意运算符优先级,必须加括号
free(del);
}
// 7. 尾删:删除链表最后一个节点
void SLBackDelete(SListNode** pphead)
{
assert(pphead);
if (*pphead == NULL)
{
printf("链表为空,无法尾删\n");
return;
}
// 只有一个节点的情况
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
return;
}
// 多个节点:找到倒数第二个节点
SListNode* cur = *pphead;
while (cur->next->next != NULL)
{
cur = cur->next;
}
free(cur->next);
cur->next = NULL;
}
// 8. 求链表长度
int SLLength(SListNode* phead)
{
int len = 0;
SListNode* cur = phead;
while (cur != NULL)
{
len++;
cur = cur->next;
}
return len;
}
// 9. 指定值删除:删除第一个值为val的节点
void SLRemoveByVal(SListNode** pphead, SLDataType val)
{
assert(pphead);
if (*pphead == NULL)
{
printf("链表为空,删除失败\n");
return;
}
// 头节点就是目标节点,直接复⽤头删
if ((*pphead)->data == val)
{
SLFrontDelete(pphead);
return;
}
// 查找目标节点的前驱节点
SListNode* prev = *pphead;
while (prev->next != NULL && prev->next->data != val)
{
prev = prev->next;
}
// 遍历完未找到目标值
if (prev->next == NULL)
{
printf("未找到值为%d的节点,删除失败\n", val);
return;
}
// 移除并释放目标节点
SListNode* del = prev->next;
prev->next = del->next;
free(del);
}
// 10. 指定位置删除:删除第pos个节点(pos从1开始计数)
void SLDeleteByPos(SListNode** pphead, int pos)
{
assert(pphead);
int len = SLLength(*pphead);
if (pos < 1 || pos > len)
{
printf("位置%d非法,删除失败(有效范围1~%d)\n", pos, len);
return;
}
// 第一个节点:复⽤头删
if (pos == 1)
{
SLFrontDelete(pphead);
return;
}
// 找到第pos-1个节点(前驱节点)
SListNode* prev = *pphead;
for (int i = 1; i < pos - 1; i++)
{
prev = prev->next;
}
SListNode* del = prev->next;
prev->next = del->next;
free(del);
}
// 11. 打印链表
void SLPrint(SListNode* phead)
{
SListNode* cur = phead;
while (cur != NULL)
{
printf("%d -> ", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
// 12. 销毁链表(释放所有节点,头指针置空)
void SLDestroy(SListNode** pphead)
{
assert(pphead);
SListNode* cur = *pphead;
while (cur != NULL)
{
SListNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
printf("链表销毁完成\n");
}
我们来看一下以下代码的经典错误
cpp
#include<stdio.h>
#include<assert.h>
#include<stdbil.h>
//无头+单向+非循环链表
typedef int SLDataType;
typedef struct SListNode
{
SLDataType Data;
struct SListNode* next;
}SListNode;
SListNode* CreateNewNode(SLDataType val)
{
SlistNode* temp = (SListNode*)malloc(sizeof(SListNode));
if(!temp)
{
perror("Failed to create a new node!\n");
return NULL;
}
temp->Data = val;
temp->next = NULL;
return temp;
}
void SLFrontInsert(SListNode** FirstNode,SLDataType val)
{
assert(FirstNode);
SListNode* temp = CreateNewNode(val);
temp->next = *FirstNode;
*FirstNode = temp;
}
void SLBackInsert(SListNode** FirstNode,SLDataType val)
{
assert(FirstNode);
SListNode* temp = CreateNewNode(val);
if(!*FirstNode)
{
*FirstNode = temp;
}
SlistNode* Move = *FirstNode;
while(Move->next)
{
Move = Move->next;
}
Move->next = temp;
}
SListNode* SLFindElement(SListNode* Head,SLDataType val)
{
assert(Head);
while(Head->next)
{
if(Head->Data==val)
{
return Head;
printf("searching operation is successful!\n");
}
Head = Head->next;
}
Printf("searching operation is failed!\n");
return NULL;
}
void SLInsertPointPosition(SListNode** FirstNode,SLDataType Val,SLDataType NewData)
{
assert(FirstNode);
SListNode* pointposition = SLFindElement(*FirstNode,val);
if(!pointposition)
{
return;
}
SListNode* temp = pointpositon->next;
SListNode* NewNode = CreateNewNode(NewData);
if(!NewNode)
{
return;
}
pointposition->next = NewNode;
NewNode->next = temp;
}
void SLFrontDelete(SListNode** FirstNode)
{
assert(FirstNode);
if(!*FirstNode)
{
printf("此链表已为空\n");
return;
}
SListNode* temp = *FirstNode;
*FirstNode = *FirstNode->next;
free(temp);
}
void SLBackDelete(SListNode** FirstNode)
{
assert(FirstNode);
if(!*FirstNode)
{
printf("此链表已为空\n");
return;
}
SListNode* Move = *FirstNode;
if(!Move->next)
{
free(Move);
}
while(Move->next->next)
{
Move = Move->next;
}
SListNode* temp = Move->next;
Move->next = NULL;
free(temp);
}
void SLPrint(SListNode* FirstNode)
{
assert(FirstNode);
SListNode* Move = FirstNode;
while(Move)
(
printf("d% ",Move->Data);
Move = Move->next;
)
printf("\n");
}
void SLDestory(SListNode**FirstNode)
{
assert(FirstNode);
if(!*FirstNode)
{
printf("此链表已为空\n");
return;
}
SListNode* Move = *FirstNode;
*FirstNode = NULL;
SListNode* temp = NULL;
while(Move)
{
temp = Move;
Move = Move->next;
free(temp);
}
printf("链表注销成功\n");
}
一、编译级错误(直接无法通过编译)
1. 头文件拼写错误
#include<stdbil.h> 拼写错误,内存申请 malloc/free、错误提示 perror 都在 stdlib.h 中。
- 修正:
#include <stdlib.h>
2. 结构体类型名大小写不匹配
C 语言大小写敏感,你定义的类型是 SListNode(L 大写),但多处写成 SlistNode(l 小写):
CreateNewNode中:SlistNode* tempSLBackInsert中:SlistNode* Move
3. 变量名拼写 / 大小写不一致
SLInsertPointPosition形参是Val(大写),调用SLFindElement时传的是val(小写),属于两个不同变量,编译报 "未定义标识符"。- 同函数内,变量定义为
pointposition,使用时写成pointpositon(漏写字母 i),名称不匹配。
4. 运算符优先级错误(经典坑)
SLFrontDelete 中:
cpp
*FirstNode = *FirstNode->next;
-> 优先级高于解引用 *,代码等价于 *(FirstNode->next),但 FirstNode 是二级指针,本身没有 next 成员,直接编译报错。
- 修正:必须加括号
(*FirstNode)->next,先解引用得到一级指针,再访问成员。
5. 语法与库函数拼写错误
SLFindElement中Printf大写 P,标准库函数是小写printf。SLPrint中while(Move)后用了圆括号()包裹循环体,C 语言循环体必须用花括号{},属于语法错误。SLPrint中格式化符写反:"d% "→ 正确为"%d "。
二、运行时致命错误(编译通过也会崩溃 / 死循环)
1. 尾插空链表场景形成自环(死循环根源)
SLBackInsert 中,空链表插入时你设置了新节点为头节点,但没有 return,代码会继续向下执行遍历逻辑:
cpp
if(!*FirstNode)
{
*FirstNode = temp;
}
// 没有return,继续执行
SListNode* Move = *FirstNode; // Move 就是新节点本身
while(Move->next) { ... } // 新节点next为NULL,循环不执行
Move->next = temp; // 节点自己指向自己,形成循环链表
后续遍历会直接死循环。
- 修正:空链表处理完必须加
return退出函数。
2. 尾删单节点场景野指针
SLBackDelete 中,只有一个节点时,你释放了节点,但:
- 没有把头指针
*FirstNode置为NULL,头指针变成野指针; - 没有
return,代码继续向后执行,访问已释放内存的Move->next->next,触发野指针崩溃。
3. 查找函数漏掉最后一个节点
SLFindElement 循环条件写为 while(Head->next),当 Head 指向最后一个节点时,Head->next 为 NULL,循环直接终止,最后一个节点永远不会被判断,漏查尾节点。
- 修正:循环条件应为
while(Head),逐个判断每个节点。
4. 空指针解引用风险
多处调用 CreateNewNode 后,没有检查返回值是否为 NULL。如果内存申请失败,后续直接访问 temp->next 就是空指针解引用,程序直接崩溃。
三、逻辑与设计缺陷
1. assert 滥用
assert 应用于检查 "绝对不应该出现的非法入参"(比如二级指针本身为 NULL),但你在:
SLFindElement中assert(Head):空链表查找是正常场景,应该返回NULL,而不是直接断言终止程序。SLPrint中assert(FirstNode):空链表打印是正常需求,不应该崩溃。
2. 死代码
SLFindElement 中 return Head; 之后的 printf 永远不会执行,属于无效代码。
四、修正后的完整可运行代码
cpp
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
// 无头+单向+非循环链表
typedef int SLDataType;
typedef struct SListNode
{
SLDataType Data;
struct SListNode* next;
} SListNode;
// 创建新节点
SListNode* CreateNewNode(SLDataType val)
{
SListNode* temp = (SListNode*)malloc(sizeof(SListNode));
if (!temp)
{
perror("Failed to create a new node!");
return NULL;
}
temp->Data = val;
temp->next = NULL;
return temp;
}
// 头插
void SLFrontInsert(SListNode** FirstNode, SLDataType val)
{
assert(FirstNode);
SListNode* temp = CreateNewNode(val);
if (temp == NULL) return; // 防御:内存申请失败直接返回
temp->next = *FirstNode;
*FirstNode = temp;
}
// 尾插
void SLBackInsert(SListNode** FirstNode, SLDataType val)
{
assert(FirstNode);
SListNode* temp = CreateNewNode(val);
if (temp == NULL) return;
// 空链表:新节点直接作为头节点
if (*FirstNode == NULL)
{
*FirstNode = temp;
return; // 关键:处理完直接返回,不执行后续遍历
}
// 非空链表:遍历找尾节点
SListNode* Move = *FirstNode;
while (Move->next)
{
Move = Move->next;
}
Move->next = temp;
}
// 查找元素
SListNode* SLFindElement(SListNode* Head, SLDataType val)
{
SListNode* cur = Head;
while (cur)
{
if (cur->Data == val)
{
printf("searching operation is successful!\n");
return cur;
}
cur = cur->next;
}
printf("searching operation is failed!\n");
return NULL;
}
// 在指定值的节点后面插入新节点
void SLInsertPointPosition(SListNode** FirstNode, SLDataType val, SLDataType NewData)
{
assert(FirstNode);
SListNode* pointposition = SLFindElement(*FirstNode, val);
if (!pointposition)
{
return;
}
SListNode* NewNode = CreateNewNode(NewData);
if (!NewNode)
{
return;
}
// 插入逻辑:先连后面,再连前面
NewNode->next = pointposition->next;
pointposition->next = NewNode;
}
// 头删
void SLFrontDelete(SListNode** FirstNode)
{
assert(FirstNode);
if (!*FirstNode)
{
printf("此链表已为空\n");
return;
}
SListNode* temp = *FirstNode;
*FirstNode = (*FirstNode)->next; // 修正运算符优先级
free(temp);
}
// 尾删
void SLBackDelete(SListNode** FirstNode)
{
assert(FirstNode);
if (!*FirstNode)
{
printf("此链表已为空\n");
return;
}
SListNode* Move = *FirstNode;
// 只有一个节点的情况
if (!Move->next)
{
free(Move);
*FirstNode = NULL; // 头指针置空,避免野指针
return; // 处理完直接返回
}
// 多个节点:找到倒数第二个节点
while (Move->next->next)
{
Move = Move->next;
}
SListNode* temp = Move->next;
Move->next = NULL;
free(temp);
}
// 打印链表
void SLPrint(SListNode* FirstNode)
{
SListNode* Move = FirstNode;
while (Move)
{
printf("%d ", Move->Data);
Move = Move->next;
}
printf("\n");
}
// 销毁链表
void SLDestory(SListNode** FirstNode)
{
assert(FirstNode);
if (!*FirstNode)
{
printf("此链表已为空\n");
return;
}
SListNode* Move = *FirstNode;
*FirstNode = NULL;
SListNode* temp = NULL;
while (Move)
{
temp = Move;
Move = Move->next;
free(temp);
}
printf("链表注销成功\n");
}
五、补充测试建议
可以用以下主函数验证所有边界场景:空链表插入、单节点删除、尾节点查找、连续增删等:
cpp
int main()
{
SListNode* head = NULL;
SLBackInsert(&head, 1); // 空链表尾插
SLBackInsert(&head, 2);
SLBackInsert(&head, 3);
SLFrontInsert(&head, 0); // 头插
SLPrint(head); // 预期输出:0 1 2 3
SLInsertPointPosition(&head, 2, 99); // 在2后面插99
SLPrint(head); // 预期输出:0 1 2 99 3
SLFrontDelete(&head);
SLBackDelete(&head);
SLPrint(head); // 预期输出:1 2 99
SLDestory(&head);
SLPrint(head); // 空链表,输出换行
return 0;
}
二、有头循环双向链表(带头结点 + 双向 + 循环)
特点:带有哨兵位头结点(不存储有效数据),每个节点有前驱 + 后继双指针,首尾相连形成循环。 优势:尾插、尾删、指定位置插入删除均为 O (1) 时间复杂度,无需单独处理空链表边界,代码更简洁健壮。
cpp
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
// 数据类型定义
typedef int LTDataType;
// 双向链表节点结构体
typedef struct ListNode
{
LTDataType data; // 节点存储的数据
struct ListNode* next; // 后继指针
struct ListNode* prev; // 前驱指针
} ListNode;
// 1. 创建新节点
ListNode* CreateListNode(LTDataType val)
{
ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
if (newNode == NULL)
{
perror("CreateListNode malloc failed");
return NULL;
}
newNode->data = val;
newNode->next = NULL;
newNode->prev = NULL;
return newNode;
}
// 2. 链表初始化:创建哨兵位头结点,形成自循环
ListNode* ListInit()
{
ListNode* phead = CreateListNode(0); // 头结点数据无实际意义
if (phead == NULL) return NULL;
// 循环结构:空链表时头结点的前驱、后继都指向自身
phead->next = phead;
phead->prev = phead;
return phead;
}
// 3. 尾插:在链表尾部插入新节点(O(1),头结点的prev就是尾节点)
void ListPushBack(ListNode* phead, LTDataType val)
{
assert(phead);
ListNode* newNode = CreateListNode(val);
if (newNode == NULL) return;
ListNode* tail = phead->prev; // 直接获取尾节点
// 链接新节点与原尾节点
tail->next = newNode;
newNode->prev = tail;
// 链接新节点与头结点,保持循环结构
newNode->next = phead;
phead->prev = newNode;
}
// 4. 头插:在链表头部(头结点之后)插入新节点
void ListPushFront(ListNode* phead, LTDataType val)
{
assert(phead);
ListNode* newNode = CreateListNode(val);
if (newNode == NULL) return;
ListNode* first = phead->next; // 原第一个有效节点
// 链接头结点与新节点
phead->next = newNode;
newNode->prev = phead;
// 链接新节点与原第一个节点
newNode->next = first;
first->prev = newNode;
}
// 5. 查找元素:返回第一个值为val的节点指针,找不到返回NULL
ListNode* ListFind(ListNode* phead, LTDataType val)
{
assert(phead);
ListNode* cur = phead->next;
// 循环遍历,回到头结点时表示遍历结束
while (cur != phead)
{
if (cur->data == val)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
// 6. 指定位置插入:在pos节点之前插入新节点(O(1))
void ListInsert(ListNode* pos, LTDataType val)
{
assert(pos);
ListNode* newNode = CreateListNode(val);
if (newNode == NULL) return;
ListNode* prev = pos->prev;
// 链接前驱节点与新节点
prev->next = newNode;
newNode->prev = prev;
// 链接新节点与pos节点
newNode->next = pos;
pos->prev = newNode;
}
// 7. 头删:删除第一个有效节点
void ListPopFront(ListNode* phead)
{
assert(phead);
// 空链表:只有头结点
if (phead->next == phead)
{
printf("链表为空,无法头删\n");
return;
}
ListNode* del = phead->next;
ListNode* next = del->next;
// 头结点直接链接第二个有效节点
phead->next = next;
next->prev = phead;
free(del);
}
// 8. 尾删:删除最后一个有效节点(O(1))
void ListPopBack(ListNode* phead)
{
assert(phead);
if (phead->next == phead)
{
printf("链表为空,无法尾删\n");
return;
}
ListNode* tail = phead->prev;
ListNode* prev = tail->prev;
// 倒数第二个节点直接链接头结点
prev->next = phead;
phead->prev = prev;
free(tail);
}
// 9. 指定位置删除:删除pos节点(O(1))
void ListErase(ListNode* pos)
{
assert(pos);
// 禁止删除哨兵位头结点
assert(pos->next != pos);
ListNode* prev = pos->prev;
ListNode* next = pos->next;
// 前后节点直接相连,跳过目标节点
prev->next = next;
next->prev = prev;
free(pos);
}
// 10. 求链表长度
int ListLength(ListNode* phead)
{
assert(phead);
int len = 0;
ListNode* cur = phead->next;
while (cur != phead)
{
len++;
cur = cur->next;
}
return len;
}
// 11. 打印链表
void ListPrint(ListNode* phead)
{
assert(phead);
printf("头结点 <-> ");
ListNode* cur = phead->next;
while (cur != phead)
{
printf("%d <-> ", cur->data);
cur = cur->next;
}
printf("头结点\n");
}
// 12. 销毁链表(释放所有节点,包括哨兵位头结点)
void ListDestroy(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(phead); // 最后释放头结点
printf("链表销毁完成\n");
}
补充说明
双向循环链表的 ListInsert 和 ListErase 是通用核心接口,其余增删操作都可以直接复用它们:
- 尾插 = 在头结点前插入:
ListInsert(phead, val) - 头插 = 在首节点前插入:
ListInsert(phead->next, val) - 尾删 = 删除尾节点:
ListErase(phead->prev) - 头删 = 删除首节点:
ListErase(phead->next)