自定义双向循环链表基本函数接口
自定义双向循环链表的函数接口
/*******************************************************************
*
* 文件名称 : 双向循环链表的函数接口
* 文件作者 : mailLinL@163.com
* 创建日期 : 2024/04/24
* 文件功能 : 对双向链表的增删改查功能的定义
* 注意事项 : None
*
* CopyRight (c) 2024 mailLinL@163.com All Right Reseverd
*
* *****************************************************************/
指的是双向链表中的结点有效数据类型,用户可以根据需要进行修改
typedef int DataType_t;
构造双向链表的结点,链表中所有结点的数据类型应该是相同的
typedef struct Double_Cric_Linked_List
{
DataType_t data; //结点的数据域
struct DoubleCricLList_t *prev; //直接前驱的指针域
struct DoubleCricLList_t *next; //直接后继的指针域
}DoubleCricLList_t;
创建双向链表,并创建管理双向循环链表的头结点
/*******************************************************************
*
* 函数名称: DoubleCricLList_Create
* 函数功能: 创建双向链表,并创建管理双向循环链表的头结点
* 函数参数: none
* 返回结果:
* 注意事项: None
* 函数作者: mailLinL@163.com
* 创建日期: 2024/04/24
* 修改历史:
* 函数版本: V1.0
* *****************************************************************/
// 创建一个空双向循环链表,空链表应该有一个头结点,对链表进行初始化
DoubleCricLList_t *DoubleCricLList_Create(void)
{
// 1.创建一个头结点并对头结点申请内存
DoubleCricLList_t *Head = (DoubleCricLList_t *)calloc(1, sizeof(DoubleCricLList_t));
if (NULL == Head)
{
perror("Calloc memory for Head is Failed");
exit(-1);
}
// 2.对头结点进行初始化,头结点是不存储数据域,指针指向自身,体现“循环”
Head->prev = Head;
Head->next = Head;
// 3.把头结点的地址返回即可
return Head;
}
创建新的双向循环链表结点
/*******************************************************************
*
* 函数名称: DoubleCricLList_NewNode
* 函数功能: 创建新的双向循环链表结点
* 函数参数:
* @data : 传入结点的数据域的值
* 返回结果:
* @New : 返回新结点地址便于操作
* 注意事项: None
* 函数作者: mailLinL@163.com
* 创建日期: 2024/04/23
* 修改历史:
* 函数版本: V1.0
* *****************************************************************/
DoubleCricLList_t *DoubleCricLList_NewNode(DataType_t data)
{
// 1.创建一个新结点并对新结点申请内存
DoubleCricLList_t *New = (DoubleCricLList_t *)calloc(1, sizeof(DoubleCricLList_t));
if (NULL == New)
{
perror("Calloc memory for NewNode is Failed");
return NULL;
}
// 2.对新结点的数据域和指针域(2个)进行初始化 指向自身 体现“循环”
New->data = data;
New->prev = New;
New->next = New;
return New;
}
向双向循环链表中的头部插入新结点
/*******************************************************************
*
* 函数名称: DoubleCricLList_HeadInsert
* 函数功能: 向双向循环链表中的头部插入新结点
* 函数参数:
* @Head :传入需要操作的链表头结点
* @data :传入需要插入的新结点的数据
* 返回结果:
* 注意事项: None
* 函数作者: mailLinL@163.com
* 创建日期: 2024/04/23
* 修改历史:
* 函数版本: V1.0
* *****************************************************************/
bool DoubleCricLList_HeadInsert(DoubleCricLList_t *Head, DataType_t data)
{
// 1.创建新结点,定义指针记录新结点的地址
DoubleCricLList_t *New = DoubleCricLList_NewNode(data);
// 2.判断新结点是否申请成功
if (NULL == New)
{
printf("Calloc memory for NewNode is Failed");
return false;
}
// 3.当需要操作的链表为空
if (Head->next == Head)
{
Head->next = New; // 将头结点的next指针指向新结点的地址
return true;
}
// 4.当需要操作的链表非空时
DoubleCricLList_t *Phead = Head->next; // 记录首结点的地址
// 当链表只有一个结点时 即首结点的next指针指向的地址是它自身
if (Phead->next == Phead)
{
Phead->next = New; // 将首结点的next指针指向New的地址
Phead->prev = New; // 将首结点的prev指针指向New的地址
New->next = Phead; // 将新结点的next指针指向首结点
New->prev = Phead; // 将新结点的prev指针指向首结点
Head->next = New;
}
// 当链表不止一个结点时 即首结点的next指针指向的地址不是它自身
else
{
Phead->prev->next = New; // 将尾结点的next指针指向新结点
New->prev = Phead->prev; // 将新结点的prev指针指向尾结点
New->next = Phead; // 将新结点的next指针指向首结点
Phead->prev = New; // 将首结点的prev指针指向新结点
Head->next = New; // 将头结点的next指针指向新结点
Head->next = New;
}
return true;
}
向双向循环链表中的尾部插入新结点
/*******************************************************************
*
* 函数名称: DoubleCricLList_TailInsert
* 函数功能: 向双向循环链表中的尾部插入新结点
* 函数参数:
* @Head :传入需要操作的链表头结点
* @data :传入需要插入的新结点的数据
* 返回结果:
* 注意事项: None
* 函数作者: mailLinL@163.com
* 创建日期: 2024/04/23
* 修改历史:
* 函数版本: V1.0
* *****************************************************************/
bool DoubleCricLList_TailInsert(DoubleCricLList_t *Head, DataType_t data)
{
// 1.创建新结点,定义指针记录新结点的地址
DoubleCricLList_t *New = DoubleCricLList_NewNode(data);
// 2.判断新结点是否申请成功
if (NULL == New)
{
printf("Calloc memory for NewNode is Failed");
return false;
}
// 3.当需要操作的链表为空
if (Head->next == Head)
{
Head->next = New; // 将头结点的next指针指向新结点的地址
return true;
}
// 4.当链表非空时,定义指针记录首结点的地址
DoubleCricLList_t *Phead = Head->next;
// 当链表只有一个结点时 即首结点的next指针指向的地址是它自身
if (Phead->next == Phead)
{
Phead->next = New; // 将首结点的next指针指向New的地址
Phead->prev = New; // 将首结点的prev指针指向New的地址
New->next = Phead; // 将新结点的next指针指向首结点
New->prev = Phead; // 将新结点的prev指针指向首结点
}
// 当链表不止一个结点时 即首结点的next指针指向的地址不是它自身
else if (Phead->next != Phead)
{
Phead->prev->next = New; // 将尾结点的next指针指向新结点
New->prev = Phead->prev; // 将新结点的prev指针指向尾结点
New->next = Phead; // 将新结点的next指针指向首结点
Phead->prev = New; // 将首结点的prev指针指向新结点
}
return true;
}
向双向循环链表中的目标结点后插入新结点
/*******************************************************************
*
* 函数名称: DoubleCricLList_DestInsert
* 函数功能: 向双向循环链表中的目标结点后插入新结点
* 函数参数:
* @Head :传入需要操作的链表头结点
* @data :传入需要插入的新结点的数据
* @destval :传入目标结点的数据域的值
* 返回结果:
* 注意事项: None
* 函数作者: mailLinL@163.com
* 创建日期: 2024/04/24
* 修改历史:
* 函数版本: V1.0
* *****************************************************************/
bool DoubleLList_DestInsert(DoubleCricLList_t *Head, DataType_t data, DataType_t destval)
{
// 1.创建新结点,定义指针记录新结点的地址
DoubleCricLList_t *New = DoubleCricLList_NewNode(data);
// 2.判断新结点是否申请成功
if (NULL == New)
{
printf("Calloc memory for NewNode is Failed");
return false;
}
// 3.当需要操作的链表为空,即没有目标结点,插入失败
if (Head->next == Head)
{
printf("No target node!");
return false;
}
// 备份首结点的地址
DoubleCricLList_t *Phead = Head->next;
// 4.遍历链表寻找目标结点
while (Phead->next)
{
if (Phead->data == destval)
{ // 当前结点数据域与目标值相等时 跳出循环
break;
}
if (Phead->next == Head->next)
{ // 当前结点是尾结点时 无目标结点 退出函数
printf("No destval!");
return false;
}
Phead = Phead->next;
}
// 5.在目标结点后插入新结点
New->prev = Phead;
New->next = Phead->next;
Phead->next->prev = New;
Phead->next = New;
return true;
}
###遍历打印链表
```c
/*******************************************************************
*
* 函数名称: DoubleCricLList_PrintList
* 函数功能: 遍历打印链表
* 函数参数:
* @data : 传入结点的数据域的值
* 返回结果:
* @New : 返回新结点地址便于操作
* 注意事项: None
* 函数作者: mailLinL@163.com
* 创建日期: 2024/04/25
* 修改历史:
* 函数版本: V1.0
* *****************************************************************/
int DoubleCricLList_PrintList(DoubleCricLList_t *Head)
{
// 判断链表是否为空
if (Head == Head->next)
{
printf("Linked List is emtoy!");
return 0;
}
// 备份首结点地址
DoubleCricLList_t *Phead = Head->next;
// 当链表只有一个结点
if (Head->next == Phead->next)
{
printf("%2d\n", Phead->data);
}
// 当链表结点不唯一
else
{
while (Head->next != Phead->next)
{
printf("%2d", Phead->data);
Phead = Phead->next;
}
printf("%2d", Phead->data);
printf("\n");
}
return 0;
}
删除双向循环链表中的首结点
/*******************************************************************
*
* 函数名称: DoubleCricLList_HeadDel
* 函数功能: 删除双向循环链表中的首结点
* 函数参数:
* @Head :传入需要操作的链表的头结点
* 返回结果:
* 注意事项: None
* 函数作者: mailLinL@163.com
* 创建日期: 2024/04/24
* 修改历史:
* 函数版本: V1.0
* *****************************************************************/
bool DoubleCricLList_HeadDel(DoubleCricLList_t *Head)
{
// 1.判断链表是否为空
if (Head->next == NULL)
{
printf("Double linked list is empty!\n");
return false;
}
// 2.备份首结点的地址
DoubleCricLList_t *Phead = Head->next;
// 3.如果非空,判断链表是否仅有一个结点
if (Phead->next == Phead)
{
Phead->next = NULL; // 将首结点的next指针指向NULL
Phead->prev = NULL; // 将首结点的prev指针指向NULL
Head->next = Head; // 将头结点的next指针指向自身 体现“循环”
free(Phead); // 释放首结点的内存空间
}
// 4.如果不是只有一个结点
else if (Phead->next != Phead)
{
Phead->prev->next = Phead->next; // 将尾结点的prev指针指向首结点直接后继的地址
Phead->next->prev = Phead->prev; // 将首结点直接后继的prev指针指向尾结点的地址
Head->next = Phead->next; // 将头结点的next指针指向首结点直接后继的地址
Phead->next = NULL; // 将首结点的next指针指向NULL
Phead->prev = NULL; // 将首结点的prev指针指向NULL
free(Phead); // 释放原首结点的内存空间
}
}
删除双向循环链表中的尾结点
/*******************************************************************
*
* 函数名称: DoubleCricLList_t_DestDel
* 函数功能: 删除双向循环链表中的指定结点
* 函数参数:
* @Head :传入需要操作的链表的头结点
* @destval :传入与目标结点数据与相同的数值
* 返回结果:
* 注意事项: None
* 函数作者: mailLinL@163.com
* 创建日期: 2024/04/23
* 修改历史:
* 函数版本: V1.0
* *****************************************************************/
bool DoubleCricLList_DestDel(DoubleCricLList_t *Head, DataType_t destval)
{
// 1.判断链表是否为空
if (Head->next == Head)
{
printf("Double linked list is empty!\n");
return false;
}
// 2.备份首结点的地址
DoubleCricLList_t *Phead = Head->next;
// 3.遍历寻找链表中的目标结点
while (Phead->next)
{
if (Phead->data == destval)
{ // 当前结点数据域与目标值相等时 跳出循环
break;
}
if (Phead->next == Head->next)
{ // 当前结点是尾结点时 无目标结点 退出函数
printf("No destval!");
return false;
}
Phead = Phead->next;
}
// 4.删除目标结点
// 当链表只有一个结点
if (Phead == Phead->next)
{
Phead->next = NULL; // 将首结点的next指针指向NULL
Phead->prev = NULL; // 将首结点的prev指针指向NULL
Head->next = Head; // 将头结点的next指针指向自身 体现“循环”
free(Phead); // 释放首结点的内存空间
}
// 当链表不止有一个节点
else
{
if (Head->next = Phead)
{ // 当结点为首结点
Phead->prev->next = Phead->next; // 将尾结点的prev指针指向首结点直接后继的地址
Phead->next->prev = Phead->prev; // 将首结点直接后继的prev指针指向尾结点的地址
Head->next = Phead->next; // 将头结点的next指针指向首结点直接后继的地址
Phead->next = NULL; // 将首结点的next指针指向NULL
Phead->prev = NULL; // 将首结点的prev指针指向NULL
free(Phead); // 释放原首结点的内存空间
}
else
{
Phead->prev->next = Phead->next;
Phead->next->prev = Phead->prev;
Phead->next = NULL; // 将尾结点的next指针指向NULL
Phead->prev = NULL; // 将尾结点的prev的指针指向NULL
free(Phead); // 释放原尾结点的内存空间
}
}
return true;
}
测试代码
指定删除目标结点仍有小错误,谨慎使用
int main()
{
// 创建新双向循环链表 返回头结点地址
DoubleCricLList_t *Head = DoubleCricLList_Create();
// 头插法插入新结点 1 2 3
DoubleCricLList_HeadInsert(Head, 3);
DoubleCricLList_PrintList(Head);
DoubleCricLList_HeadInsert(Head, 2);
DoubleCricLList_PrintList(Head);
DoubleCricLList_HeadInsert(Head, 1);
DoubleCricLList_PrintList(Head);
// 尾插法插入新结点 1 2 3 4
DoubleCricLList_TailInsert(Head, 4);
DoubleCricLList_PrintList(Head);
// 目标结点后插入新结点 1 2 3 4 5
DoubleLList_DestInsert(Head, 5, 4);
DoubleCricLList_PrintList(Head);
// 删除头结点 2 3 4 5
DoubleCricLList_HeadDel(Head);
DoubleCricLList_PrintList(Head);
// 删除尾结点 2 3 4
DoubleCricLList_TailDel(Head);
DoubleCricLList_PrintList(Head);
// 删除指定结点
DoubleCricLList_DestDel(Head, 4);
return 0;
}
以下为测试结果
总结:
双向循环链表与双向不循环链表的差别只有首结点有无直接前驱以及尾结点有无直接后继
双向循环链表中头结点在链表为空时,next指针指向自身地址
操作双向链表的首结点及尾结点时,一定要注意首结点prev指针以及尾结点next指针的指向问题
向双向循环链表中首结点前及尾结点后插入新结点时,不要忘记首结点prev指针以及尾结点next指针的指向问题
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步