双向循环链表————遍历、查找、插入结点 (基于C语言实现)
以下是我学习双向循环链表时所用到的函数,其中包括遍历,删除,插入结点等。
下面是我踩过的坑:
- 遍历函数需要考虑退出循环的情况
- 头删、指定删、尾删函数要考虑链表为空以及链表中只有一个结点的情况,链表中只有一个结点时,一定要记得将头结点的next指针重新指向头结点自身,否则会段错误!指定删函数需要考虑目标结点是否在链表,以及目标结点在头、在尾、在中间的情况
- 头插、指定插、尾插函数要考虑链表为空的情况
- 如需对链表做遍历找到目标结点时,需考虑要不要对头结点做备份!
下面是我的函数接口:
定义双向循环链表中的结点的数据的类型
// 指的是双向循环链表中的结点有效数据类型,用户可以根据需要进行修改
typedef int DataType_t;
构造双向循环链表的结点,链表中所有结点的数据类型应该是相同的
typedef struct DoubleCircularLinkedList
{
DataType_t data; // 结点的数据域
struct DoubleCircularLinkedList *prev; // 直接前驱的指针域
struct DoubleCircularLinkedList *next; // 直接后继的指针域
} DoubleCirLList_t;
创建一个空双向循环链表,空链表应该有一个头结点,对链表进行初始化
/******************************************************
*
* name : DoubleCirLList_Create
* function : 创建一个空双向循环链表,空链表应该有一个头结点,对链表进行初始化
* argument :None
* retval : 头结点的地址
* author : Dazz
* date : 2024/4/24
* note : None
*
* *******************************************************/
DoubleCirLList_t *DoubleCirLList_Create(void)
{
// 1.创建一个头结点并对头结点申请内存
DoubleCirLList_t *Head = (DoubleCirLList_t *)calloc(1, sizeof(DoubleCirLList_t));
if (NULL == Head)
{
perror("Calloc memory for Head is Failed");
exit(-1);
}
// 2.对头结点进行初始化,头结点是不存储数据域,指针域指向自身即可,体现“循环”
Head->prev = Head;
Head->next = Head;
// 3.把头结点的地址返回即可
return Head;
}
创建一个新的结点,并对该结点进行初始化(数据域 + 指针域)
/******************************************************
*
* name : DoubleCirLList_NewNode
* function : 创建一个新的结点,并对该结点进行初始化(数据域 + 指针域)
* argument
* @data :需要传入结点中的数据
*
* retval : 新结点的地址
* author : Dazz
* date : 2024/4/24
* note : None
*
* *******************************************************/
DoubleCirLList_t *DoubleCirLList_NewNode(DataType_t data)
{
// 1.创建一个新结点并对新结点申请内存
DoubleCirLList_t *New = (DoubleCirLList_t *)calloc(1, sizeof(DoubleCirLList_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;
}
遍历链表,打印每个结点中的数据
/******************************************************
*
* name : PrintfAllNodes
* function : 遍历链表,打印每个结点中的数据
* argument
* @Head :头结点的地址
*
* retval : None
* author : Dazz
* date : 2024/4/24
* note : None
*
* *******************************************************/
void PrintfAllNodes(DoubleCirLList_t *Head)
{
// 备份头结点
DoubleCirLList_t *phead = Head;
// 考虑空链表的情况
if (Head->next == Head)
{
printf("为空链表\n");
return;
}
// 考虑链表中只有一个结点的情况
if (phead->next == Head->next->next)
{
printf("%d\n", phead->next->data);
return;
}
while (1)
{
phead = phead->next;
printf("%d\n", phead->data);
if (phead->next == Head->next)
break;
}
return;
}
将新的结点插入在双向循环链表的头部
/******************************************************
*
* name : DouCircLList_HeadInsertCircLList_HeadInsert
* function : 将新的结点插入在双向循环链表的头部
* argument
* @head : 头结点的地址
* @data : 结点数据域的数据
*
* retval : 成功返回1,否则为0
* author : Dazz
* date : 2024/4/24
* note : None
*
* *******************************************************/
bool DouCircLList_HeadInsert(DoubleCirLList_t *Head, DataType_t data)
{
// 创建新结点,并对新结点初始化
DoubleCirLList_t *New = DoubleCirLList_NewNode(data);
if (NULL == New)
{
return false;
}
// 判断链表是否为空
if (Head->next == Head)
{
// 将头结点的next指针指向新结点
Head->next = New;
return true;
}
// 链表不为空的情况
Head->next->prev->next = New; // 将尾结点的next指针指向新结点
New->prev = Head->next->prev; // 将新结点的prev指针指向尾结点
New->next = Head->next; // 将新结点的next指针指向首结点
Head->next->prev = New; // 将首结点的prev指针指向新结点
Head->next = New; // 将头节点的next指针指向新结点
return true;
}
将新的结点插入在双向循环链表的尾部
/******************************************************
*
* name : DoubleCirLList_TailInsert
* function : 将新的结点插入在双向循环链表的尾部
* argument
* @head : 头结点的地址
* @data : 结点数据域的数据
*
* retval : 成功为1,否则为0
* author : Dazz
* date : 2024/4/24
* note : None
*
* *******************************************************/
// 尾插
bool DoubleCirLList_TailInsert(DoubleCirLList_t *Head, DataType_t data)
{
// 创建新结点,并对新结点初始化
DoubleCirLList_t *New = DoubleCirLList_NewNode(data);
if (NULL == New)
{
return false;
}
// 判断链表是否为空
if (Head->next == Head)
{
// 将头结点的next指针指向新结点
Head->next = New;
return true;
}
// 列表不为空的情况
New->prev = Head->next->prev; // 将新结点的prev指针指向尾结点
New->next = Head->next; // 将新界点的next指针指向首结点
New->prev->next = New; // 将尾结点的next指针指向新结点
Head->next->prev = New; // 将首结点的prev指针指向新结点
return true;
}
将新的结点插入在双向循环链表指定结点的后面
/******************************************************
*
* name : DoubleCircLList_DestInsert
* function : 将新的结点插入在双向循环链表指定结点的后面
* argument
* @Head : 头结点的地址
* @data : 结点数据域的数据
* @destval : 需要插入位置的结点的数据域
*
* retval : 成功为1,否则为0
* author : Dazz
* date : 2024/4/23
* note : None
*
* *******************************************************/
bool DoubleCircLList_DestInsert(DoubleCirLList_t *Head, DataType_t destval, DataType_t data)
{
// 创建新结点,并对新结点初始化
DoubleCirLList_t *New = DoubleCirLList_NewNode(data);
if (NULL == New)
{
return false;
}
// 判断链表是否为空
if (Head->next == Head)
{
printf("链表为空列表,找不到目标结点,无法插入\n");
return false;
}
// 备份头结点
DoubleCirLList_t *phead = Head;
// 遍历链表,找到目标结点
while (1)
{
phead = phead->next;
if (destval == phead->data)
break;
if (phead->next == Head->next)
break;
}
// 判断结点是否在链表中,如不在直接退出
if (phead->next == Head->next && destval != phead->data)
{
printf("找不到目标结点\n");
return false;
}
// 如果目标结点在尾部
if (phead->next == Head->next)
{
New->prev = Head->next->prev; // 将新结点的prev指针指向尾结点
New->next = Head->next; // 将新界点的next指针指向首结点
New->prev->next = New; // 将尾结点的next指针指向新结点
Head->next->prev = New; // 将首结点的prev指针指向新结点
}
else // 目标结点不在尾部
{
New->next = phead->next; // 将新结点的next指针指向目标结点的直接后继
New->prev = phead; // 将新结点的prev指针指向目标结点
New->next->prev = New; // 将目标结点的直接后继的prev指针指向新结点
phead->next = New; // 将目标结点的next指针指向新结点
}
return true;
}
删除链表的首结点
/******************************************************
*
* name : DoubleCircLList_HeadDel
* function : 删除链表的首结点
* argument
* @Head : 头结点的地址
*
* retval : 成功为1,否则为0
* author : Dazz
* date : 2024/4/24
* note : None
*
* *******************************************************/
bool DoubleCircLList_HeadDel(DoubleCirLList_t *Head)
{
// 判断链表是否为空
if (Head->next == Head)
{
printf("链表为空列表\n");
return false;
}
// 判断链表是否只有一个结点
if (Head->next->next == Head->next)
{
Head->next->next = NULL; // 将首结点的next指针指向NULL
Head->next->prev = NULL; // 将首结点的prev指针指向NULL
free(Head->next); // 释放首结点
Head->next = Head; // 将头节点的next指针指向头节点 这步非常重要!!!!不做会段错误
}
else // 链表不只一个结点的情况
{
DoubleCirLList_t *temp = Head->next; // 对首结点的地址做备份
Head->next->prev->next = Head->next->next; // 将尾部结点的next指针指向首结点的直接后继
Head->next->next->prev = Head->next->prev; // 将首结点的直接后继的prev指针指向尾结点
Head->next = temp->next; // 将头结点的next指针指向首结点的直接后继
// 将首结点的next指针和prev指针指向NULL
temp->next = NULL;
temp->prev = NULL;
// 释放首结点
free(temp);
}
return true;
}
删除链表的尾结点
/******************************************************
*
* name : DoubleCircLList_HailDel
* function : 删除链表的尾结点
* argument
* @Head : 头结点的地址
*
* retval : 成功为1,否则为0
* author : Dazz
* date : 2024/4/23
* note : None
*
* *******************************************************/
bool DoubleCircLList_HailDel(DoubleCirLList_t *Head)
{
// 判断链表是否为空
if (Head->next == Head)
{
printf("链表为空列表\n");
return false;
}
// 判断链表是否只有一个结点
if (Head->next->next == Head->next)
{
Head->next->next = NULL; // 将首结点的next指针指向NULL
Head->next->prev = NULL; // 将首结点的prev指针指向NULL
free(Head->next); // 释放首结点
Head->next = Head; // 将头节点的next指针指向头节点 这步非常重要!!!!不做会段错误
}
else // 链表不只一个结点的情况
{
DoubleCirLList_t *temp = Head->next->prev; // 对尾结点做备份
Head->next->prev->next->next = Head->next; // 将尾结点的直接前驱的next指针指向首结点
Head->next->prev = temp->next; // 将首结点的prev指针指向尾结点的直接前驱
Head->next->prev->prev = NULL; // 将尾结点的prev指针指向NULL
temp->next = NULL; // 将尾结点的next指针指向NULL
temp->prev = NULL; // 将尾结点的prev指针指向NULL
free(temp); // 释放尾结点
}
return true;
}
将链表的指定结点删除
/******************************************************
*
* name : DoubleCircLList_DestNode
* function : 将链表的指定结点删除
* argument
* @head : 头结点的地址
* @destval : 需要插入位置的结点的数据域
*
* retval : 成功为1,否则为0
* author : Dazz
* date : 2024/4/23
* note : None
*
* *******************************************************/
bool DoubleCircLList_DestNode(DoubleCirLList_t *Head, DataType_t destval)
{
// 判断链表是否为空
if (Head->next == Head)
{
printf("链表为空列表\n");
return false;
}
// 备份头结点
DoubleCirLList_t *phead = Head;
// 遍历链表,找到目标结点
while (1)
{
phead = phead->next;
if (destval == phead->data)
break;
if (phead->next == Head->next)
break;
}
// 判断目标结点是否在链表中,如不在直接退出
if (phead->next == Head->next && destval != phead->data)
{
printf("找不到目标结点\n");
return false;
}
// 目标结点在链表,判断链表是否只有一个结点,如是,直接删除
if (Head->next == Head->next->next)
{
Head->next->next = NULL; // 将首结点的next指针指向NULL
Head->next->prev = NULL; // 将首结点的prev指针指向NULL
free(Head->next); // 释放首结点
Head->next = Head; // 将头节点的next指针指向头节点 这步非常重要!!!!不做会段错误
}
else // 目标结点在链表,且链表中不只一个结点的情况
{
// 目标结点在头的情况
if (phead == Head->next)
{
DoubleCirLList_t *temp = Head->next; // 对首结点的地址做备份
Head->next->prev->next = Head->next->next; // 将尾部结点的next指针指向首结点的直接后继
Head->next->next->prev = Head->next->prev; // 将首结点的直接后继的prev指针指向尾结点
Head->next = temp->next; // 将头结点的next指针指向首结点的直接后继
// 将首结点的next指针和prev指针指向NULL
temp->next = NULL;
temp->prev = NULL;
// 释放首结点
free(temp);
}
// 目标结点在尾的情况
else if (phead == Head->next->prev)
{
DoubleCirLList_t *temp = Head->next->prev; // 对尾结点做备份
Head->next->prev->next->next = Head->next; // 将尾结点的直接前驱的next指针指向首结点
Head->next->prev = temp->next; // 将首结点的prev指针指向尾结点的直接前驱
Head->next->prev->prev = NULL; // 将尾结点的prev指针指向NULL
temp->next = NULL; // 将尾结点的next指针指向NULL
temp->prev = NULL; // 将尾结点的prev指针指向NULL
free(temp); // 释放尾结点
}
// 目标结点在中间的情况
else
{
phead->prev->next = phead->next; // 将目标结点的直接前驱的next指针指向目标结点的直接后继
phead->next->prev = phead->prev; // 将目标结点的直接后继的prev指针指向目标结点的直接前驱
phead->next = NULL; // 将目标结点的next指针指向NULL
phead->prev = NULL; // 将目标结点的prev指针指向NULL
}
}
return true;
}