双向循环链表接口设计
/*************************************************
*
* file name:DoubleDoubleCirLkList.c
* author :momolyl@126.com
* date :2024/04/25
* brief :通过构建双向循环链表学习顺序存储
* note :None
*
* CopyRight (c) 2024 momolyl@126.com All Right Reseverd
*
**************************************************/
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
/*************************************************
*
* func name :
* brief :
* func parameter:
*
*
* return :None
* note :None
* func author :momolyl@126.com
* date :2024/04/25
* version :V1.0
**************************************************/
// 结构体用于构建链表节点(指针域1+数据域+指针域2)
typedef int Datatype_t;
typedef struct DoubleCirLkList
{
struct DoubleCirLkList *Prev; // 直接前驱的指针域
Datatype_t Data; // 结点的数据域
struct DoubleCirLkList *Next; // 直接后继的指针域
} DoubleCirLkList_t;
// 创建一个空链表,使用头节点
DoubleCirLkList_t *DoubleCirLkList_Create(void)
{
DoubleCirLkList_t *Head = (DoubleCirLkList_t *)calloc(1, sizeof(DoubleCirLkList_t)); // 为头节点申请内存,并错误处理
if (NULL == Head)
{
perror("calloc memory for DoubleCirLkList if failed!\n");
exit(-1);
}
Head->Next = Head; // 将头节点的Next指针域指向Head,体现循环链表的特性
Head->Prev = Head; // 将头节点的Prev指针域指向Head,体现循环链表的特性
return Head; // 返回头节点的地址
}
// 创建一个新节点,并对新节点初始化(指针域1+数据域+指针域2)
DoubleCirLkList_t *DoubleCirLkList_NewNode(Datatype_t data)
{
DoubleCirLkList_t *NewNode = (DoubleCirLkList_t *)calloc(1, sizeof(DoubleCirLkList_t)); // 为新节点申请一个地址,
if (NULL == NewNode) // 申请新节点的错误处理
{
perror("calloc memory for NewNode if failed!\n");
exit(-1);
}
NewNode->Data = data; // 初始化数据域
NewNode->Prev = NewNode; // 初始化指针域1
NewNode->Next = NewNode; // 初始化指针域2
return NewNode;
}
// 向链表中头插一个新节点
void DoubleCirLkList_HeadAdd(DoubleCirLkList_t *Head, Datatype_t data)
{
DoubleCirLkList_t *NewNode = DoubleCirLkList_NewNode(data);
if (Head == Head->Next) // 判断链表是否为空
{
// 链表为空
Head->Next = NewNode; // 让头结点的Next指针指向新结点
NewNode->Next = NewNode; // 让新结点的Next指针指向新结点
NewNode->Prev = NewNode; // 让新结点的Prev指针指向新结点
return;
}
// 2.2链表非空
{
NewNode->Next = Head->Next; // 新结点的Next指针指向首结点
NewNode->Prev = Head->Next->Prev; // 新结点的Prev指针指向尾结点
Head->Next->Prev->Next = NewNode; // 尾结点的Next指针指向新结点
Head->Next->Prev = NewNode; // 首结点的Prev指针指向新结点
Head->Next = NewNode; // 头节点的Next指针指向新结点
}
}
// 向链表中尾插一个新节点
void DoubleCirLkList_TailAdd(DoubleCirLkList_t *Head, Datatype_t data)
{
DoubleCirLkList_t *Phead = Head->Next;
DoubleCirLkList_t *NewNode = DoubleCirLkList_NewNode(data);
if (Head == Phead) // 判断链表是否为空
{
// 链表为空
Head->Next = NewNode; // 让头结点的Next指针指向新结点
NewNode->Next = NewNode; // 让新结点的Next指针指向新结点
NewNode->Prev = NewNode; // 让新结点的Prev指针指向新结点
return;
}
// 2.2链表非空
NewNode->Prev = Phead->Prev; // 新节点的Prev指针指向尾结点
NewNode->Next = Phead; // 新结点的Next指针指向首结点
Phead->Prev->Next = NewNode; // 尾结点的Next指针指向新结点
Phead->Prev = NewNode; // 首结点的Prev指针指向新结点
}
// 向链表中指定数据结点后插入一个新节点
void DoubleCirLkList_AppointAdd(DoubleCirLkList_t *Head, Datatype_t DestVal, Datatype_t data)
{
DoubleCirLkList_t *NewNode = DoubleCirLkList_NewNode(data);
if (Head == Head->Next) // 判断链表是否为空
{
// 链表为空
Head->Next = NewNode; // 让头结点的Next指针指向新结点
NewNode->Next = NewNode; // 让新结点的Next指针指向新结点
NewNode->Prev = NewNode; // 让新结点的Prev指针指向新结点
return;
}
// 2.2链表非空
DoubleCirLkList_t *Phead = Head->Next; // 备份首结点的地址,用于遍历找到目标结点的地址
DoubleCirLkList_t *Dest = NULL; // 用于判断是否成功查找到指定数值的结点
// 遍历找到目标结点
while (Phead->Data != DestVal) // 判断条件:当前结点的数据域的数据是否与目标值不同
{
Phead = Phead->Next; // 不同则偏移
if (Phead->Next == Head->Next && Phead->Data != DestVal) // 判断条件:到达尾结点且未找到目标值则退出循环
{
Dest = Phead; // 如果未找到目标值,则改Dest的值
break;
}
}
if (Dest) // 未到指定数值,则插入失败
{
printf("Can find the DestVal %d!\n", DestVal);
return;
}
// 如果成功查找到指定数值,则在指定数据结点后面插入新节点
if (Phead->Next) // 目标结点为首结点或中间结点
{
NewNode->Next = Phead->Next; // 让新结点的Next指针指向目标地址的直接后继结点
Phead->Next->Prev = NewNode; // 让目标结点的直接后继结点的Prev指针指向新结点
Phead->Next = NewNode; // 让目标结点的Next指针指向新结点的地址
NewNode->Prev = Phead; // 让新结点的Prev指针指向目标结点的地址
}
else // 目标结点为尾结点
{
NewNode->Prev = Head->Next->Prev; // 新节点的Prev指针指向尾结点
NewNode->Next = Head; // 新结点的Next指针指向首结点
Head->Prev->Next = NewNode; // 尾结点的Next指针指向新结点
Head->Prev = NewNode; // 首结点的Prev指针指向新结点
}
}
// 在链表中头删一个节点
bool DoubleCirLkList_HeadDel(DoubleCirLkList_t *Head)
{
// 判断链表是否为空
if (Head == Head->Next) // 头节点的Next指针指向头节点的地址时,链表为空
return false; // 为空则删除失败
DoubleCirLkList_t *Phead = Head->Next; // 备份首节点地址,用于最后释放首结点地址
Phead->Prev->Next = Phead->Next; // 让尾结点的Next指针指向首节点的直接后继结点
Phead->Next->Prev = Phead->Prev; // 让首结点的直接后继结点的Prev指针指向尾结点
Head->Next = Phead->Next; // 让头结点的Next指针指向首结点的直接后继结点
Phead->Next = NULL; // 让首结点的Next指针指向NULL
Phead->Prev = NULL; // 让首结点的Prev指针指向NULL
free(Phead);
return true;
}
// 在链表中尾删一个节点
bool DoubleCirLkList_TailDel(DoubleCirLkList_t *Head)
{
// 判断链表是否为空
// 链表为空
if (Head == Head->Next) // 头节点的Next指针指向头节点的地址时,链表为空
return false; // 为空则删除失败
// 链表非空
DoubleCirLkList_t *Phead = Head->Next->Prev; // 备份尾节点地址,用于最后释放首结点地址
Head->Next->Prev = Phead->Prev; // 首结点的Prev指针指向尾结点的直接前驱结点
Phead->Prev->Next = Head->Next; // 尾结点的直接前驱结点的Next指针指向首结点
Phead->Prev = NULL; // 将尾结点Prev指针指向NULL
Phead->Next = NULL; // 将尾结点的直接前驱结点的Next指针指向NULL
free(Phead);
return true;
}
// 在链表中删指定数据结点
bool DoubleCirLkList_AppointDel(DoubleCirLkList_t *Head, Datatype_t DestVal)
{
// 判断链表是否为空
// 链表为空
if (Head == Head->Next) // 头节点的Next指针指向头节点的地址时,链表为空
return false; // 为空则删除失败
// 链表非空
DoubleCirLkList_t *Phead = Head->Next; // 备份首结点的地址,用于遍历目标节点
DoubleCirLkList_t *Dest = NULL; // 用于判断是否成功查找到指定数值的结点
// 遍历找到目标结点
while (Phead->Data != DestVal) // 判断条件:当前结点的数据域的数据是否与目标值不同
{
Phead = Phead->Next; // 不同则指向目标结点的指针偏移
if (Phead->Next == Head->Next && Phead->Data != DestVal) // 判断条件:到达尾结点且未找到目标值则退出循环
{
Dest = Phead; // 如果未找到目标值,则改Dest的值
break;
}
}
if (Dest) // 如果没有找到指定数值,则删除失败
{
printf("Can find the DestVal %d!\n", DestVal);
return false;
}
else // 如果成功查找到指定数值,则删除指定数据结点
{
// 1.目标结点为头节点(使用头删函数的代码)
if (Phead == Head->Next)
{
Phead->Prev->Next = Phead->Next; // 让尾结点的Next指针指向
Phead->Next->Prev = Phead->Prev; // 让首结点的直接后继结点的Prev指针指向尾结点
Head->Next = Phead->Next; // 让头结点的Next指针指向首结点的直接后继结点
Phead->Next = NULL; // 让首结点的Next指针指向NULL
Phead->Prev = NULL; // 让首结点的Prev指针指向NULL
free(Phead);
}
// 2.目标结点为尾节点(使用尾删函数的代码)
else if (Phead->Next == Head->Next)
{
Head->Next->Prev = Phead->Prev; // 首结点的Prev指针指向尾结点的直接前驱结点
Phead->Prev->Next = Head->Next; // 尾结点的直接前驱结点的Next指针指向首结点
Phead->Prev = NULL; // 将尾结点Prev指针指向NULL
Phead->Next = NULL; // 将尾结点的直接前驱结点的Next指针指向NULL
free(Phead);
}
// 3.目标结点在中间
else
{
Phead->Prev->Next = Phead->Next; // 让目标结点的直接前驱结点的Next指针指向目标结点的直接后继结点
Phead->Next->Prev = Phead->Prev; // 让目标结点的直接后继结点的Prev指针指向目标结点的直接前驱结点
Phead->Next = NULL;
Phead->Prev = NULL;
free(Phead);
}
return true;
}
}
// void Test(DoubleCirLkList_t *Head)
// {
// DoubleCirLkList_t *Phead = Head->Next; // 备份首节点的地址,用于遍历结点
// while (Phead->Next != Head->Next) // 遍历到尾结点
// {
// printf("%d ", Phead->Data);
// Phead = Phead->Next;
// }
// printf("%d\n", Phead->Data); // 打印尾结点的数据
// }
bool DoubleCirLkList_Print(DoubleCirLkList_t *Head)
{
DoubleCirLkList_t *Phead = Head->Next; // 备份头结点
// if (Head == Head->Next) // 判断链表是否为空,为空直接退出
// {
// printf("DoubleCirLkList is empty\n");
// return false;
// }
while (Phead->Next != Head->Next)
{
printf("%d ", Phead->Data); // 打印链表
Phead = Phead->Next; // 遍历链表
} // while (Phead != Head); // while (Phead->Next != Head->Next);
// printf("%p,%p", Phead->Next, Head->Next);
printf("%d ", Phead->Data);
printf("\n");
return true;
}
int main(void)
{
DoubleCirLkList_t *Head = DoubleCirLkList_Create();
// 头插
DoubleCirLkList_HeadAdd(Head, 10);
DoubleCirLkList_HeadAdd(Head, 20);
DoubleCirLkList_HeadAdd(Head, 30);
DoubleCirLkList_HeadAdd(Head, 40);
DoubleCirLkList_HeadAdd(Head, 50);
DoubleCirLkList_Print(Head);
// 尾插
DoubleCirLkList_TailAdd(Head, 1);
DoubleCirLkList_TailAdd(Head, 2);
DoubleCirLkList_TailAdd(Head, 3);
DoubleCirLkList_TailAdd(Head, 4);
DoubleCirLkList_TailAdd(Head, 5);
DoubleCirLkList_Print(Head);
// 指定插
DoubleCirLkList_AppointAdd(Head, 5, 88);
DoubleCirLkList_AppointAdd(Head, 30, 35);
DoubleCirLkList_Print(Head);
// return 0;
// 头删
DoubleCirLkList_HeadDel(Head);
DoubleCirLkList_HeadDel(Head);
DoubleCirLkList_Print(Head);
// 尾删
DoubleCirLkList_TailDel(Head);
DoubleCirLkList_Print(Head);
// 指定删
DoubleCirLkList_AppointDel(Head, 1);
DoubleCirLkList_Print(Head);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术