一、前言:为什么要学带头双向循环链表?
在数据结构的学习中,链表是线性表的核心实现之一,而带头双向循环链表是链表家族中最实用、最优雅的结构:
- 带头:拥有一个不存储有效数据的头节点,极大简化插入 / 删除逻辑,无需处理头节点为空的边界情况;
- 双向:每个节点同时保存前驱指针(
prev)和后继指针(next),支持正向、反向双向遍历; - 循环:尾节点的后继指针指向头节点,头节点的前驱指针指向尾节点,无
NULL指针,避免空指针访问异常。
它完美解决了单链表查找前驱节点困难、尾插 / 尾删效率低的痛点,是实际开发中(如 LRU 缓存、底层队列)的首选链表结构。
本篇博客将基于标准 C 语言实现,逐行解析代码,精准讲解每个接口的功能、实现逻辑,带你彻底掌握带头双向循环链表。
二、基础结构定义
在实现链表功能前,我们先定义节点结构和数据类型,为后续操作打下基础:
c
#pragma once // 防止头文件重复包含
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
// 重定义链表存储的数据类型,方便后期修改类型(如改为char/double)
typedef int ListDataType;
// 带头双向循环链表节点结构
typedef struct LTNode
{
ListDataType data; // 节点存储的数据
struct LTNode* prev; // 前驱指针:指向当前节点的上一个节点
struct LTNode* next; // 后继指针:指向当前节点的下一个节点
}LTNode;
核心结构说明
ListDataType:使用typedef重定义数据类型,后期只需修改这一行,就能让链表存储任意类型数据;prev指针:双向链表核心,让每个节点能快速找到前驱节点;next指针:基础指针,用于向后遍历节点。
三、工具函数:创建新节点
所有插入操作都需要先创建新节点,我们封装一个独立工具函数:
c
// 功能:创建一个新节点,初始化数据和指针,返回新节点地址
// 参数:x - 新节点要存储的数据
// 返回值:新节点的指针
LTNode* BuyLTNode(ListDataType x)
{
// 申请节点内存
LTNode* NewNode = (LTNode*)malloc(sizeof(LTNode));
// 内存申请失败处理
if (NewNode == NULL)
{
perror("malloc failed!"); // 打印错误信息
exit(-1); // 终止程序
}
// 初始化节点数据
NewNode->data = x;
// 新节点默认前驱、后继指针置空
NewNode->next = NewNode->prev = NULL;
return NewNode;
}
实现逻辑
- 内存申请 :使用
malloc为新节点分配堆内存,大小为LTNode结构体大小; - 异常判断:检查内存申请结果,失败则打印错误并退出程序,避免空指针操作;
- 节点初始化:给节点数据域赋值,将前驱、后继指针初始化为
NULL; - 返回节点:返回创建完成的新节点指针,供后续插入操作使用。
四、核心接口完整解析
1. 创建并初始化带头节点的双向循环链表
c
// 功能:创建一个带头节点的双向循环链表,初始化头节点
// 返回值:链表头节点的指针
LTNode* ListCreateHead()
{
// 创建头节点,数据域填-1(标识为头节点,无实际意义)
LTNode* Head = BuyLTNode(-1);
// 双向循环核心:空链表时,头节点的prev和next都指向自己
Head->next = Head->prev = Head;
return Head;
}
实现逻辑
- 创建头节点:调用
BuyLTNode创建头节点,数据域填充标识值; - 循环初始化:空链表状态下,头节点的
prev和next指针都指向自身,满足双向循环特性; - 返回头节点:返回链表头指针,作为整个链表的操作入口。
2. 判断链表是否为空
c
// 功能:判断链表是否为空链表(仅存头节点)
// 参数:phead - 链表头节点指针
// 返回值:空链表返回true,非空返回false
bool ListEmpty(LTNode* phead)
{
assert(phead); // 断言:头节点指针不能为空
// 核心判断:头节点的next指向自己,说明无有效数据节点
return phead->next == phead;
}
实现逻辑
- 合法性校验:用
assert断言头节点非空,防止空指针访问; - 空链表判断:带头双向循环链表中,仅头节点存在时即为空,判断条件为
phead->next == phead; - 返回结果:返回
bool值表示链表是否为空。
3. 正向遍历打印链表
c
// 功能:正向遍历链表,打印所有有效数据节点
// 参数:phead - 链表头节点指针
void ListPrintForward(LTNode* phead)
{
// 断言:链表非空,禁止打印空链表
assert(!ListEmpty(phead));
// 正向遍历起始点:头节点的下一个节点
LTNode* pcur = phead->next;
printf("PrintForward: ");
// 循环遍历:直到回到头节点停止
while (pcur != phead)
{
printf("%d", pcur->data);
// 非尾节点时打印分隔符,美化输出
if (pcur->next != phead)
{
printf(" -> ");
}
pcur = pcur->next; // 指针后移
}
printf("\n");
}
实现逻辑
- 合法性校验:断言链表非空,避免空链表打印;
- 遍历初始化:正向遍历从
phead->next(第一个有效数据节点)开始; - 循环打印:循环条件为
pcur != phead,遍历到尾节点(回到头节点)时停止; - 格式优化:非尾节点时打印分隔符,保证输出格式美观;
- 指针移动:每次循环通过next指针向后移动遍历指针。
4. 反向遍历打印链表
c
// 功能:反向遍历链表,打印所有有效数据节点
// 参数:phead - 链表头节点指针
void ListPrintBackward(LTNode* phead)
{
// 断言:链表非空
assert(!ListEmpty(phead));
// 反向遍历起始点:头节点的前驱节点(最后一个有效数据节点)
LTNode* pcur = phead->prev;
printf("PrintBackward: ");
// 循环遍历:直到回到头节点停止
while (pcur != phead)
{
printf("%d", pcur->data);
// 非首节点时打印分隔符
if (pcur->prev != phead)
{
printf(" -> ");
}
pcur = pcur->prev; // 指针前移
}
printf("\n");
}
实现逻辑
- 合法性校验:断言链表非空;
- 遍历初始化:反向遍历从phead->prev(最后一个有效数据节点)开始;
- 循环打印:循环条件为pcur != phead,遍历到第一个节点(回到头节点)时停止;
- 格式优化:非首节点时打印分隔符;
- 指针移动:每次循环通过prev指针向前移动遍历指针。
5. 销毁链表(释放所有内存)
c
// 功能:销毁整个链表,释放所有节点内存,头节点置空
// 参数:pphead - 链表头节点的二级指针(需要修改头指针本身)
void ListDestroy(LTNode** pphead)
{
assert(pphead); // 断言:二级指针不能为空
// 空链表直接返回,无需释放
if (ListEmpty(*pphead))
{
return;
}
// 从第一个数据节点开始遍历释放
LTNode* pcur = (*pphead)->next;
while (pcur != (*pphead))
{
LTNode* del = pcur; // 记录待释放节点
pcur = pcur->next; // 指针后移,防止断链
free(del); // 释放节点内存
del = NULL; // 指针置空,防止野指针
}
free(*pphead); // 释放头节点内存
*pphead = NULL; // 头指针置空,彻底销毁链表
}
实现逻辑
- 合法性校验:断言二级指针非空;
- 空链表处理:若链表为空,直接返回;
- 遍历释放数据节点:从第一个数据节点开始,逐个释放内存,释放前先移动指针,避免断链;
- 释放头节点:数据节点释放完成后,释放头节点内存;
- 头指针置空:通过二级指针将外部头指针置空,防止野指针。
6. 清空链表(保留头节点)
c
// 功能:清空链表所有有效数据节点,仅保留头节点
// 参数:phead - 链表头节点指针
void ListKeepHead(LTNode* phead)
{
assert(phead); // 断言:头节点非空
// 空链表无需清空
if (ListEmpty(phead))
{
return;
}
// 遍历释放所有数据节点
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* del = pcur;
pcur = pcur->next;
free(del);
del = NULL;
}
// 恢复空链表状态:头节点指针指向自己
phead->prev = phead->next = phead;
}
实现逻辑
- 合法性校验:断言头节点非空;
- 空链表处理:空链表直接返回;
- 释放数据节点:遍历并释放所有有效数据节点;
- 恢复空链表结构:将头节点的
prev和next指针重新指向自身,回到初始空链表状态。
7. 头插法(在头节点后插入新节点)
c
// 功能:头插法,在头节点后插入新节点(链表最前端)
// 参数:phead - 头节点指针,x - 待插入数据
void ListPushFront(LTNode* phead, ListDataType x)
{
assert(phead); // 断言:头节点非空
LTNode* NewNode = BuyLTNode(x); // 创建新节点
// 第一步:新节点绑定前后指针
NewNode->next = phead->next; // 新节点后继指向原第一个数据节点
NewNode->prev = phead; // 新节点前驱指向头节点
// 第二步:原链表节点绑定新节点
phead->next->prev = NewNode; // 原第一个节点前驱指向新节点
phead->next = NewNode; // 头节点后继指向新节点
}
实现逻辑
- 合法性校验:断言头节点非空;
- 创建新节点:调用
BuyLTNode创建待插入节点; - 新节点指针绑定:新节点
next指向原第一个数据节点,prev指向头节点; - 原链表节点绑定:原第一个数据节点的
prev指向新节点,头节点的next指向新节点; - 核心优势:带头节点无需处理空链表,时间复杂度
O (1)。
8. 尾插法(在链表尾部插入新节点)
c
// 功能:尾插法,在链表尾部插入新节点
// 参数:phead - 头节点指针,x - 待插入数据
void ListPushBack(LTNode* phead, ListDataType x)
{
assert(phead); // 断言:头节点非空
LTNode* NewNode = BuyLTNode(x); // 创建新节点
// 第一步:新节点绑定前后指针
NewNode->next = phead; // 新节点后继指向头节点(循环特性)
NewNode->prev = phead->prev; // 新节点前驱指向原尾节点
// 第二步:原链表节点绑定新节点
phead->prev->next = NewNode; // 原尾节点后继指向新节点
phead->prev = NewNode; // 头节点前驱指向新节点(更新尾节点)
}
实现逻辑
- 合法性校验:断言头节点非空;
- 创建新节点:创建待插入节点;
- 新节点指针绑定:新节点
next指向头节点,prev指向原尾节点; - 原链表节点绑定:原尾节点
next指向新节点,头节点prev指向新节点; - 核心优势:双向循环链表无需遍历找尾节点,时间复杂度
O (1)。
9. 头删(删除第一个数据节点)
c
// 功能:头删,删除链表第一个有效数据节点
// 参数:phead - 头节点指针
void ListPopFront(LTNode* phead)
{
assert(phead); // 断言:头节点非空
assert(!ListEmpty(phead)); // 断言:链表非空,禁止删除空链表
LTNode* del = phead->next; // 记录待删除的第一个数据节点
// 重新绑定链表指针,断开待删除节点
phead->next = phead->next->next; // 头节点后继指向第二个数据节点
phead->next->prev = phead; // 第二个数据节点前驱指向头节点
free(del); // 释放节点内存
del = NULL; // 指针置空
}
实现逻辑
- 双重校验:断言头节点非空、链表非空;
- 记录待删节点:保存第一个数据节点地址;
- 指针重绑定:头节点指向第二个数据节点,第二个数据节点指向头节点;
- 释放内存:释放待删节点,指针置空防止野指针。
10. 尾删(删除最后一个数据节点)
c
// 功能:尾删,删除链表最后一个有效数据节点
// 参数:phead - 头节点指针
void ListPopBack(LTNode* phead)
{
assert(phead); // 断言:头节点非空
assert(!ListEmpty(phead)); // 断言:链表非空
LTNode* del = phead->prev; // 记录待删除的尾节点
// 重新绑定链表指针
phead->prev = phead->prev->prev; // 头节点前驱指向倒数第二个节点
phead->prev->next = phead; // 倒数第二个节点后继指向头节点
free(del); // 释放内存
del = NULL; // 指针置空
}
实现逻辑
- 双重校验:断言头节点非空、链表非空;
- 记录待删节点:通过phead->prev直接找到尾节点;
- 指针重绑定:头节点指向倒数第二个节点,倒数第二个节点指向头节点;
- 释放内存:释放尾节点,指针置空。
11. 查找节点(按值查找)
c
// 功能:查找第一个值为x的节点,返回节点地址,找不到返回NULL
// 参数:phead - 头节点指针,x - 待查找数据
// 返回值:找到返回节点指针,否则返回NULL
LTNode* ListFind(LTNode* phead, ListDataType x)
{
assert(phead); // 断言:头节点非空
LTNode* pcur = phead->next; // 从第一个数据节点开始遍历
// 遍历整个链表
while (pcur != phead)
{
if (pcur->data == x) // 找到目标节点
{
return pcur;
}
pcur = pcur->next; // 指针后移
}
return NULL; // 未找到返回NULL
}
实现逻辑
- 合法性校验:断言头节点非空;
- 遍历初始化:从第一个数据节点开始查找;
- 循环匹配:遍历链表,对比节点数据,匹配则返回节点指针;
- 未找到处理:遍历结束未找到,返回
NULL。
12. 指定节点前插入(通用插入)
c
// 功能:在指定节点pos前插入新节点(最通用的插入接口)
// 参数:phead - 头节点指针,pos - 指定节点,x - 待插入数据
void ListInsertBefore(LTNode* phead, LTNode* pos, ListDataType x)
{
assert(phead); // 断言:头节点非空
assert(pos); // 断言:指定节点非空
LTNode* NewNode = BuyLTNode(x); // 创建新节点
// 绑定新节点与pos前驱节点
pos->prev->next = NewNode;
NewNode->prev = pos->prev;
// 绑定新节点与pos节点
NewNode->next = pos;
pos->prev = NewNode;
}
实现逻辑
- 合法性校验:断言头节点、指定节点均非空;
- 创建新节点:创建待插入节点;
- 前驱节点绑定:
pos的前驱节点后继指向新节点,新节点前驱指向 pos 的前驱节点; - 目标节点绑定:新节点后继指向
pos,pos前驱指向新节点; - 通用性:头插、尾插都可通过该函数实现。
13. 指定节点后插入
c
// 功能:在指定节点pos后插入新节点
// 参数:phead - 头节点指针,pos - 指定节点,x - 待插入数据
void ListInsertAfter(LTNode* phead, LTNode* pos, ListDataType x)
{
assert(phead); // 断言:头节点非空
assert(pos); // 断言:指定节点非空
LTNode* NewNode = BuyLTNode(x);
// 绑定新节点与pos后继节点
pos->next->prev = NewNode;
NewNode->next = pos->next;
// 绑定新节点与pos节点
NewNode->prev = pos;
pos->next = NewNode;
}
实现逻辑
- 合法性校验:断言头节点、指定节点均非空;
- 创建新节点:创建待插入节点;
- 后继节点绑定:
pos的后继节点前驱指向新节点,新节点后继指向pos的后继节点; - 目标节点绑定:新节点前驱指向
pos,pos后继指向新节点。
14. 获取链表有效节点个数
c
// 功能:获取链表中有效数据节点的总个数
// 参数:phead - 头节点指针
// 返回值:有效节点数量
int ListSize(LTNode* phead)
{
assert(phead); // 断言:头节点非空
LTNode* pcur = phead->next;
int count = 0; // 计数器
// 遍历统计节点数量
while (pcur != phead)
{
count++;
pcur = pcur->next;
}
return count;
}
实现逻辑
- 合法性校验:断言头节点非空;
- 初始化计数器:定义变量统计节点数;
- 遍历统计:遍历所有有效数据节点,每遍历一个节点计数器
+ 1; - 返回结果:返回有效节点总数。
15. 按位置插入(前插)
c
// 功能:在第pos个数据节点前插入(pos从1开始)
// 参数:phead - 头节点指针,pos - 插入位置,x - 待插入数据
void ListInsertByPosBefore(LTNode* phead, int pos, ListDataType x)
{
assert(phead); // 头节点非空
assert(!ListEmpty(phead)); // 链表非空
assert(pos >= 1 && pos <= ListSize(phead)); // 位置合法
LTNode* pcur = phead->next;
int count = 1;
// 遍历找到第pos个节点
while (count < pos)
{
count++;
pcur = pcur->next;
}
ListInsertBefore(phead, pcur, x); // 调用通用前插函数
}
实现逻辑
- 三重校验:校验头节点、链表非空、插入位置合法;
- 定位目标节点:遍历找到第
pos个数据节点; - 复用接口:调用
ListInsertBefore完成插入,代码复用。
16. 按位置插入(后插)
c
// 功能:在第pos个数据节点后插入(pos从1开始)
void ListInsertByPosAfter(LTNode* phead, int pos, ListDataType x)
{
assert(phead);
assert(!ListEmpty(phead));
assert(pos >= 1 && pos <= ListSize(phead));
LTNode* pcur = phead->next;
int count = 1;
while (count < pos)
{
count++;
pcur = pcur->next;
}
ListInsertAfter(phead, pcur, x);
}
实现逻辑
- 合法性校验:校验头节点、链表非空、插入位置合法;
- 定位目标节点:遍历找到第
pos个数据节点; - 复用接口:调用
ListInsertAfter完成插入。
17. 删除指定节点
c
// 功能:删除指定节点pos
// 参数:phead - 头节点指针,pos - 待删除节点
void ListPopPos(LTNode* phead, LTNode* pos)
{
assert(phead); // 头节点非空
assert(pos); // 待删节点非空
// 重新绑定指针,跳过待删节点
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos); // 释放内存
pos = NULL; // 指针置空
}
实现逻辑
- 合法性校验:断言头节点、待删节点非空;
- 指针断链重连:待删节点的前驱节点直接指向其后继节点,完成断链;
- 释放内存:释放待删节点,指针置空。
18. 删除第一个值为 x 的节点
c
// 功能:删除第一个值为x的有效数据节点
void ListPopFirstX(LTNode* phead, ListDataType x)
{
assert(phead);
assert(!ListEmpty(phead));
// 查找目标节点
LTNode* del = ListFind(phead, x);
// 找到则删除
if (del != NULL)
{
ListPopPos(phead, del);
}
}
实现逻辑
- 合法性校验:校验头节点、链表非空;
- 查找节点:调用
ListFind找到第一个值为x的节点; - 删除节点:找到节点后调用
ListPopPos删除,未找到则不操作。
19. 删除所有值为 x 的节点
c
// 功能:删除链表中所有值为x的节点
void ListPopAllX(LTNode* phead, ListDataType x)
{
assert(phead);
assert(!ListEmpty(phead));
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
LTNode* del = pcur; // 记录待删节点
pcur = pcur->next; // 指针先后移,防止断链
ListPopPos(phead, del);
}
else
{
pcur = pcur->next;
}
}
}
实现逻辑
- 合法性校验:校验头节点、链表非空;
- 遍历判断:遍历链表,判断节点数据是否等于目标值;
- 安全删除:匹配到目标值时,先移动指针再删除,避免断链;
- 循环执行:直到遍历完整个链表,删除所有目标节点。
20. 按位置删除节点
c
// 功能:删除第pos个数据节点(pos从1开始)
void ListEraseByPos(LTNode* phead, int pos)
{
assert(phead);
assert(!ListEmpty(phead));
assert(pos >= 1 && pos <= ListSize(phead));
int count = 1;
LTNode* pcur = phead->next;
// 定位到第pos个节点
while (count != pos)
{
count++;
pcur = pcur->next;
}
ListPopPos(phead, pcur); // 删除节点
}
实现逻辑
- 合法性校验:校验头节点、链表非空、删除位置合法;
- 定位节点:遍历找到第pos个数据节点;
- 复用接口:调用ListPopPos完成删除。
21. 按位置修改节点值
c
// 功能:修改第pos个节点的数据为x
void ListModifyByPos(LTNode* phead, int pos, ListDataType x)
{
assert(phead);
assert(!ListEmpty(phead));
assert(pos >= 1 && pos <= ListSize(phead));
int count = 1;
LTNode* pcur = phead->next;
// 定位目标节点
while (count != pos)
{
count++;
pcur = pcur->next;
}
pcur->data = x; // 修改数据
}
实现逻辑
- 合法性校验:校验头节点、链表非空、位置合法;
- 定位节点:遍历找到第pos个数据节点;
- 修改数据:直接修改节点数据域的值。
22. 修改第一个值为 oldX 的节点
c
// 功能:将第一个值为oldX的节点修改为newX
void ListModifyX(LTNode* phead, ListDataType oldX, ListDataType newX)
{
assert(phead);
assert(!ListEmpty(phead));
// 新旧值相同,无需修改
if (oldX == newX)
{
return;
}
// 查找目标节点
LTNode* Modify = ListFind(phead, oldX);
if (Modify != NULL)
{
Modify->data = newX;
}
}
实现逻辑
- 合法性校验:校验头节点、链表非空;
- 冗余判断:新旧值相同直接返回,提升效率;
- 查找修改:找到第一个目标节点后修改数据,未找到则不操作。
23. 修改所有值为 oldX 的节点
c
// 功能:将所有值为oldX的节点修改为newX
void ListModifyAllX(LTNode* phead, ListDataType oldX, ListDataType newX)
{
assert(phead);
assert(!ListEmpty(phead));
if (oldX == newX)
{
return;
}
LTNode* pcur = phead->next;
// 遍历所有节点
while (pcur != phead)
{
if (pcur->data == oldX)
{
pcur->data = newX;
}
pcur = pcur->next;
}
}
实现逻辑
- 合法性校验:校验头节点、链表非空;
- 冗余判断:新旧值相同直接返回;
- 遍历修改:遍历链表,所有匹配节点数据全部修改。
24. 获取第一个数据节点
c
// 功能:获取链表第一个有效数据节点
// 返回值:第一个数据节点的指针
LTNode* ListGetFront(LTNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
return phead->next;
}
- 实现逻辑
- 合法性校验:校验头节点、链表非空;
- 直接返回:第一个数据节点就是phead->next,直接返回指针。
25. 获取最后一个数据节点
c
// 功能:获取链表最后一个有效数据节点
// 返回值:最后一个数据节点的指针
LTNode* ListGetBack(LTNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
return phead->prev;
}
实现逻辑
- 合法性校验:校验头节点、链表非空;
- 直接返回:尾节点就是phead->prev,直接返回指针。
26. 按位置获取节点
c
// 功能:获取第pos个有效数据节点
// 返回值:第pos个节点的指针
LTNode* ListGetNodeByPos(LTNode* phead, int pos)
{
assert(phead);
assert(!ListEmpty(phead));
assert(pos >= 1 && pos <= ListSize(phead));
int count = 1;
LTNode* pcur = phead->next;
// 定位目标节点
while (count != pos)
{
count++;
pcur = pcur->next;
}
return pcur;
}
实现逻辑
- 合法性校验:校验头节点、链表非空、位置合法;
- 定位节点:遍历找到第
pos个数据节点; - 返回节点:返回目标节点指针。
五、所有代码
List.h
c
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
typedef int ListDataType;
typedef struct LTNode
{
ListDataType data;
struct LTNode* prev;
struct LTNode* next;
}LTNode;
// 创建并初始化带头节点的双向循环链表
LTNode* ListCreateHead();
// 判断链表是不是空链表
bool ListEmpty(LTNode* phead);
// 打印链表(正向遍历)
void ListPrintForward(LTNode* phead);
// 打印链表(反向遍历)
void ListPrintBackward(LTNode* phead);
// 链表销毁
void ListDestroy(LTNode** pphead);
// 清空链表:保留头节点,删除所有数据节点
void ListKeepHead(LTNode* phead);
// 头插法:在头节点后插入新节点
void ListPushFront(LTNode* phead, ListDataType x);
// 尾插法:在链表尾部插入新节点
void ListPushBack(LTNode* phead, ListDataType x);
// 头删:删除第一个数据节点
void ListPopFront(LTNode* phead);
// 尾删:删除最后一个数据节点
void ListPopBack(LTNode* phead);
// 查找第一个值为 x 的节点,返回节点地址,找不到返回NULL
LTNode* ListFind(LTNode* phead, ListDataType x);
// 在指定节点 pos 前插入(最通用的插入)
void ListInsertBefore(LTNode* phead, LTNode* pos, ListDataType x);
// 在指定节点 pos 后插入
void ListInsertAfter(LTNode* phead, LTNode* pos, ListDataType x);
// 获取链表有效数据节点个数
int ListSize(LTNode* phead);
// 按位置插入(在第 pos 个数据节点后插入,pos从1开始)
void ListInsertByPosAfter(LTNode* phead, int pos, ListDataType x);
// 按位置插入(在第 pos 个数据节点前插入,pos从1开始)
void ListInsertByPosBefore(LTNode* phead, int pos, ListDataType x);
// 删除指定节点 pos
void ListPopPos(LTNode* phead, LTNode* pos);
// 删除第一个值为 x 的节点
void ListPopFirstX(LTNode* phead, ListDataType x);
// 删除所有值为 x 的节点
void ListPopAllX(LTNode* phead, ListDataType x);
// 按位置删除(删除第 pos 个数据节点,pos 从1开始)
void ListEraseByPos(LTNode* phead, int pos);
// 修改第 pos 个节点的值
void ListModifyByPos(LTNode* phead, int pos, ListDataType x);
// 修改第一个值为 oldX(旧数据) 的节点为 newX(新数据)
void ListModifyX(LTNode* phead, ListDataType oldX, ListDataType newX);
// 修改所有值为 oldX(旧数据) 的节点为 newX(新数据)
void ListModifyAllX(LTNode* phead, ListDataType oldX, ListDataType newX);
// 获取第一个数据节点
LTNode* ListGetFront(LTNode* phead);
// 获取最后一个数据节点
LTNode* ListGetBack(LTNode* phead);
// 获取第 pos 个节点
LTNode* ListGetNodeByPos(LTNode* phead, int pos);
List.c
c
#include "List.h"
// 功能:创建一个新的链表节点
// 参数:x - 节点存储的数据
// 返回:新节点的指针
LTNode* BuyLTNode(ListDataType x)
{
// 为新节点申请堆内存,大小为链表节点结构体大小
LTNode* NewNode = (LTNode*)malloc(sizeof(LTNode));
// 判断内存申请是否失败
if (NewNode == NULL)
{
// 打印malloc失败的原因
perror("malloc failed!");
// 退出程序,终止运行
exit(-1);
}
// 给新节点的数据域赋值
NewNode->data = x;
// 新节点前驱指针初始化为NULL
NewNode->prev = NULL;
// 新节点后继指针初始化为NULL
NewNode->next = NULL;
// 返回创建好的新节点
return NewNode;
}
// 功能:创建并初始化一个带头节点的双向循环链表
// 返回:链表的头节点指针
LTNode* ListCreateHead()
{
// 创建头节点,数据域填-1作为标记,不存储有效数据
LTNode* Head = BuyLTNode(-1);
// 空链表:头节点的next指向自己
Head->next = Head;
// 空链表:头节点的prev指向自己
Head->prev = Head;
// 返回初始化完成的头节点
return Head;
}
// 功能:判断链表是否为空
// 参数:phead - 链表头节点
// 返回:空返回true,非空返回false
bool ListEmpty(LTNode* phead)
{
// 断言保证头节点不为空
assert(phead);
// 双向循环链表空的条件:头节点next指向自己
return phead->next == phead;
}
// 功能:正向打印链表(从头节点后第一个节点开始遍历)
// 参数:phead - 链表头节点
void ListPrintForward(LTNode* phead)
{
// 断言:链表不能为空,否则不能打印
assert(!ListEmpty(phead));
// 正向遍历指针,从头节点后第一个节点开始
LTNode* pcur = phead->next;
// 打印提示信息
printf("PrintForward: ");
// 循环遍历:没有回到头节点就继续
while (pcur != phead)
{
// 打印当前节点的数据
printf("%d", pcur->data);
// 如果当前节点不是最后一个节点,打印分隔符
if (pcur->next != phead)
{
printf(" -> ");
}
// 遍历指针向后移动
pcur = pcur->next;
}
// 换行
printf("\n");
}
// 功能:反向打印链表(从尾节点开始遍历)
// 参数:phead - 链表头节点
void ListPrintBackward(LTNode* phead)
{
// 断言:链表不能为空
assert(!ListEmpty(phead));
// 反向遍历指针,从最后一个节点开始
LTNode* pcur = phead->prev;
// 打印提示信息
printf("PrintBackward: ");
// 循环遍历:没有回到头节点就继续
while (pcur != phead)
{
// 打印当前节点数据
printf("%d", pcur->data);
// 如果不是第一个节点,打印分隔符
if (pcur->prev != phead)
{
printf(" -> ");
}
// 遍历指针向前移动
pcur = pcur->prev;
}
// 换行
printf("\n");
}
// 功能:销毁整个链表,释放所有内存
// 参数:pphead - 二级指针,用来修改外部头指针
void ListDestroy(LTNode** pphead)
{
// 断言:二级指针不能为空
assert(pphead);
// 如果链表为空,直接返回
if (ListEmpty(*pphead))
{
return;
}
// 从第一个数据节点开始遍历
LTNode* pcur = (*pphead)->next;
// 没有回到头节点就继续释放
while (pcur != (*pphead))
{
// 记录当前要释放的节点
LTNode* del = pcur;
// 遍历指针先向后移动,防止断链
pcur = pcur->next;
// 释放节点内存
free(del);
// 指针置空,避免野指针
del = NULL;
}
// 释放头节点
free(*pphead);
// 外部头指针置空
*pphead = NULL;
}
// 功能:清空链表,只保留头节点
// 参数:phead - 链表头节点
void ListKeepHead(LTNode* phead)
{
// 断言:头节点不能为空
assert(phead);
// 如果链表为空,无需清空
if (ListEmpty(phead))
{
return;
}
// 从第一个数据节点开始遍历
LTNode* pcur = phead->next;
// 遍历释放所有数据节点
while (pcur != phead)
{
// 记录待释放节点
LTNode* del = pcur;
// 指针后移
pcur = pcur->next;
// 释放节点
free(del);
// 指针置空
del = NULL;
}
// 恢复空链表状态
phead->next = phead;
phead->prev = phead;
}
// 功能:头插,在头节点后插入新节点
// 参数:phead - 头节点,x - 插入数据
void ListPushFront(LTNode* phead, ListDataType x)
{
// 断言:头节点不能为空
assert(phead);
// 创建新节点
LTNode* NewNode = BuyLTNode(x);
// 新节点next指向原第一个数据节点
NewNode->next = phead->next;
// 新节点prev指向头节点
NewNode->prev = phead;
// 原第一个节点prev指向新节点
phead->next->prev = NewNode;
// 头节点next指向新节点
phead->next = NewNode;
}
// 功能:尾插,在链表尾部插入新节点
// 参数:phead - 头节点,x - 插入数据
void ListPushBack(LTNode* phead, ListDataType x)
{
// 断言:头节点不能为空
assert(phead);
// 创建新节点
LTNode* NewNode = BuyLTNode(x);
// 新节点next指向头节点
NewNode->next = phead;
// 新节点prev指向原尾节点
NewNode->prev = phead->prev;
// 原尾节点next指向新节点
phead->prev->next = NewNode;
// 头节点prev指向新节点(更新尾节点)
phead->prev = NewNode;
}
// 功能:头删,删除第一个数据节点
// 参数:phead - 头节点
void ListPopFront(LTNode* phead)
{
// 断言:头节点不能为空
assert(phead);
// 断言:链表不能为空,不能删除空链表
assert(!ListEmpty(phead));
// 记录要删除的第一个节点
LTNode* del = phead->next;
// 头节点next指向第二个节点
phead->next = phead->next->next;
// 第二个节点prev指向头节点
phead->next->prev = phead;
// 释放删除节点
free(del);
// 指针置空
del = NULL;
}
// 功能:尾删,删除最后一个数据节点
// 参数:phead - 头节点
void ListPopBack(LTNode* phead)
{
// 断言:头节点不能为空
assert(phead);
// 断言:链表不能为空
assert(!ListEmpty(phead));
// 记录要删除的尾节点
LTNode* del = phead->prev;
// 头节点prev指向倒数第二个节点
phead->prev = phead->prev->prev;
// 倒数第二个节点next指向头节点
phead->prev->next = phead;
// 释放节点
free(del);
// 指针置空
del = NULL;
}
// 功能:查找值为x的节点
// 参数:phead - 头节点,x - 查找目标
// 返回:找到返回节点地址,找不到返回NULL
LTNode* ListFind(LTNode* phead, ListDataType x)
{
// 断言:头节点不能为空
assert(phead);
// 从第一个数据节点开始遍历
LTNode* pcur = phead->next;
// 遍历整个链表
while (pcur != phead)
{
// 如果当前节点数据等于目标值
if (pcur->data == x)
{
// 返回节点地址
return pcur;
}
// 继续向后遍历
pcur = pcur->next;
}
// 遍历结束没找到,返回NULL
return NULL;
}
// 功能:在指定节点pos前插入新节点
// 参数:phead - 头节点,pos - 指定位置,x - 插入数据
void ListInsertBefore(LTNode* phead, LTNode* pos, ListDataType x)
{
// 断言:头节点不能为空
assert(phead);
// 断言:链表不能为空
assert(!ListEmpty(phead));
// 断言:指定位置节点不能为空
assert(pos);
// 创建新节点
LTNode* NewNode = BuyLTNode(x);
// pos的前一个节点next指向新节点
pos->prev->next = NewNode;
// 新节点prev指向pos的前一个节点
NewNode->prev = pos->prev;
// 新节点next指向pos
NewNode->next = pos;
// pos的prev指向新节点
pos->prev = NewNode;
}
// 功能:在指定节点pos后插入新节点
// 参数:phead - 头节点,pos - 指定位置,x - 插入数据
void ListInsertAfter(LTNode* phead, LTNode* pos, ListDataType x)
{
// 断言:头节点不能为空
assert(phead);
// 断言:链表不能为空
assert(!ListEmpty(phead));
// 断言:指定节点不能为空
assert(pos);
// 创建新节点
LTNode* NewNode = BuyLTNode(x);
// pos的后一个节点prev指向新节点
pos->next->prev = NewNode;
// 新节点next指向pos的后一个节点
NewNode->next = pos->next;
// 新节点prev指向pos
NewNode->prev = pos;
// pos的next指向新节点
pos->next = NewNode;
}
// 功能:获取链表有效节点个数
// 参数:phead - 头节点
// 返回:有效节点数量
int ListSize(LTNode* phead)
{
// 断言:头节点不能为空
assert(phead);
// 从第一个数据节点开始遍历
LTNode* pcur = phead->next;
// 节点计数器
int count = 0;
// 遍历所有有效节点
while (pcur != phead)
{
// 计数+1
count++;
// 指针后移
pcur = pcur->next;
}
// 返回总数
return count;
}
// 功能:按位置前插,在第pos个节点前插入
// 参数:phead - 头节点,pos - 位置,x - 数据
void ListInsertByPosBefore(LTNode* phead, int pos, ListDataType x)
{
// 断言:头节点非空
assert(phead);
// 断言:链表非空
assert(!ListEmpty(phead));
// 断言:位置合法(1~节点总数)
assert((pos >= 1) && (pos <= ListSize(phead)));
// 从第一个节点开始找
LTNode* pcur = phead->next;
// 位置计数器
int count = 1;
// 找到第pos个节点
while (count < pos)
{
count++;
pcur = pcur->next;
}
// 调用通用前插函数
ListInsertBefore(phead, pcur, x);
}
// 功能:按位置后插,在第pos个节点后插入
void ListInsertByPosAfter(LTNode* phead, int pos, ListDataType x)
{
// 断言头节点非空
assert(phead);
// 断言链表非空
assert(!ListEmpty(phead));
// 断言位置合法
assert((pos >= 1) && (pos <= ListSize(phead)));
// 从第一个节点开始找
LTNode* pcur = phead->next;
int count = 1;
// 找到第pos个节点
while (count < pos)
{
count++;
pcur = pcur->next;
}
// 调用通用后插函数
ListInsertAfter(phead, pcur, x);
}
// 功能:删除指定节点pos
// 参数:phead - 头节点,pos - 待删节点
void ListPopPos(LTNode* phead, LTNode* pos)
{
// 断言头节点非空
assert(phead);
// 断言链表非空
assert(!ListEmpty(phead));
// 断言待删节点非空
assert(pos);
// pos的前一个节点next指向pos的后一个节点
pos->prev->next = pos->next;
// pos的后一个节点prev指向pos的前一个节点
pos->next->prev = pos->prev;
// 释放pos节点
free(pos);
// 指针置空
pos = NULL;
}
// 功能:删除第一个值为x的节点
void ListPopFirstX(LTNode* phead, ListDataType x)
{
// 断言头节点非空
assert(phead);
// 断言链表非空
assert(!ListEmpty(phead));
// 查找值为x的节点
LTNode* del = ListFind(phead, x);
// 如果找到
if (del != NULL)
{
// 删除该节点
ListPopPos(phead, del);
}
}
// 功能:删除所有值为x的节点
void ListPopAllX(LTNode* phead, ListDataType x)
{
// 断言头节点非空
assert(phead);
// 断言链表非空
assert(!ListEmpty(phead));
// 从第一个节点开始遍历
LTNode* pcur = phead->next;
// 遍历整个链表
while (pcur != phead)
{
// 如果当前节点数据等于x
if (pcur->data == x)
{
// 记录待删节点
LTNode* del = pcur;
// 指针先向后移动,防止断链
pcur = pcur->next;
// 删除节点
ListPopPos(phead, del);
}
else
{
// 不匹配则继续向后
pcur = pcur->next;
}
}
}
// 功能:按位置删除第pos个节点
void ListEraseByPos(LTNode* phead, int pos)
{
// 断言头节点非空
assert(phead);
// 断言链表非空
assert(!ListEmpty(phead));
// 断言位置合法
assert((pos >= 1) && (pos <= ListSize(phead)));
// 计数器
int count = 1;
// 从第一个节点开始
LTNode* pcur = phead->next;
// 找到第pos个节点
while (count != pos)
{
count++;
pcur = pcur->next;
}
// 删除该位置节点
ListPopPos(phead, pcur);
}
// 功能:按位置修改节点数据
void ListModifyByPos(LTNode* phead, int pos, ListDataType x)
{
// 断言头节点非空
assert(phead);
// 断言链表非空
assert(!ListEmpty(phead));
// 断言位置合法
assert((pos >= 1) && (pos <= ListSize(phead)));
int count = 1;
// 从第一个节点开始找
LTNode* pcur = phead->next;
// 找到第pos个节点
while (count != pos)
{
count++;
pcur = pcur->next;
}
// 修改数据
pcur->data = x;
}
// 功能:修改第一个值为oldX的节点为newX
void ListModifyX(LTNode* phead, ListDataType oldX, ListDataType newX)
{
// 断言头节点非空
assert(phead);
// 断言链表非空
assert(!ListEmpty(phead));
// 如果新旧值相同,无需修改
if (oldX == newX)
{
return;
}
// 查找旧值节点
LTNode* Modify = ListFind(phead, oldX);
// 如果找到
if (Modify != NULL)
{
// 修改为新值
Modify->data = newX;
}
}
// 功能:修改所有值为oldX的节点为newX
void ListModifyAllX(LTNode* phead, ListDataType oldX, ListDataType newX)
{
// 断言头节点非空
assert(phead);
// 断言链表非空
assert(!ListEmpty(phead));
// 新旧值相同直接返回
if (oldX == newX)
{
return;
}
// 从第一个节点遍历
LTNode* pcur = phead->next;
while (pcur != phead)
{
// 匹配旧值
if (pcur->data == oldX)
{
// 修改为新值
pcur->data = newX;
}
// 继续向后
pcur = pcur->next;
}
}
// 功能:获取第一个数据节点
LTNode* ListGetFront(LTNode* phead)
{
// 断言头节点非空
assert(phead);
// 断言链表非空
assert(!ListEmpty(phead));
// 第一个节点就是头节点next
return phead->next;
}
// 功能:获取最后一个数据节点
LTNode* ListGetBack(LTNode* phead)
{
// 断言头节点非空
assert(phead);
// 断言链表非空
assert(!ListEmpty(phead));
// 尾节点就是头节点prev
return phead->prev;
}
// 功能:按位置获取第pos个节点
LTNode* ListGetNodeByPos(LTNode* phead, int pos)
{
// 断言头节点非空
assert(phead);
// 断言链表非空
assert(!ListEmpty(phead));
// 断言位置合法
assert((pos >= 1) && (pos <= ListSize(phead)));
int count = 1;
// 从第一个节点开始
LTNode* pcur = phead->next;
// 找到目标位置
while (count != pos)
{
count++;
pcur = pcur->next;
}
// 返回节点指针
return pcur;
}