双向链表(已测试)
/*************************************************
*
* file name:LkList.c
* author :miaowei023@163.com
* date :2025/04/23
* brief :通过构建双向链表学习顺序存储
* note :None
*
* CopyRight (c) 2025 miaowei023@163.com All Right Reseverd
*
**************************************************/
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
/*************************************************
*
* func name :
* brief :
* func parameter:
*
*
* return :None
* note :None
* func author :miaowei023@163.com
* date :2025/04/23
* version :V1.0
**************************************************/
// 结构体用于构建链表节点(指针域1+数据域+指针域2)
typedef int Datatype_t;
typedef struct DoulbeLkList
{
struct DoulbeLkList *Prev; // 直接前驱的指针域
Datatype_t Data; // 结点的数据域
struct DoulbeLkList *Next; // 直接后继的指针域
} DoulbeLkList_t;
// 创建一个双向空链表,使用头节点
DoulbeLkList_t *DoubleLkList_Create(void)
{
DoulbeLkList_t *Head = (DoulbeLkList_t *)calloc(1, sizeof(DoulbeLkList_t)); // 为头节点申请内存,并错误处理
if (NULL == Head)
{
perror("calloc memory for DoulbeLkList if failed!\n");
exit(-1);
}
// 对头结点进行初始化,头结点是不存储数据域,指针域指向NULL
Head->Next = NULL; // 对头结点进行初始化,头结点是不存储数据域,指针域指向NULL
Head->Prev = NULL;
return Head; // 返回头节点的地址
}
// 创建一个新节点,并对新节点初始化(指针域1+数据域+指针域2)
DoulbeLkList_t *DoulbeLkList_NewNode(Datatype_t data)
{
DoulbeLkList_t *NewNode = (DoulbeLkList_t *)calloc(1, sizeof(DoulbeLkList_t)); // 为新节点申请一个地址,
if (NULL == NewNode) // 申请新节点的错误处理
{
perror("calloc memory for NewNode if failed!\n");
exit(-1);
}
NewNode->Data = data; // 初始化数据域
return NewNode;
}
//****************************************************************************21 : 53
// 向链表中头插一个新节点
void DoulbeLkList_HeadAdd(DoulbeLkList_t *Head, Datatype_t data)
{
DoulbeLkList_t *NewNode = DoulbeLkList_NewNode(data);
if (NULL == Head->Next) // 判断链表是否为空
{
// 链表为空
Head->Next = NewNode;
return;
}
// 2.2链表非空
{
NewNode->Next = Head->Next; // 让新结点的Next指针指向首结点的地址
Head->Next->Prev = NewNode; // 让首结点的Prev指针指向新结点的地址
Head->Next = NewNode; // 让头节点的Next指针指向新结点的地址
}
}
// 向链表中尾插一个新节点
void DoulbeLkList_TailAdd(DoulbeLkList_t *Head, Datatype_t data)
{
DoulbeLkList_t *NewNode = DoulbeLkList_NewNode(data);
if (NULL == Head->Next) // 判断链表是否为空
{
// 链表为空
Head->Next = NewNode;
return;
}
// 2.2链表非空
DoulbeLkList_t *Phead = Head->Next; // 备份首结点的地址
// 遍历找到尾结点
while (Phead->Next) // 判断条件为:当前结点的Next指针是否指向NULL
{
Phead = Phead->Next; // Phead在循环结束时代表尾结点的地址
}
// 在尾端插入新节点
Phead->Next = NewNode; // 让尾结点的Next指针指向新结点的地址
NewNode->Prev = Phead; // 让新结点的Prev指针指向尾结点的地址
}
// 向链表中指定数据结点后插入一个新节点
void DoulbeLkList_AppointAdd(DoulbeLkList_t *Head, Datatype_t DestVal, Datatype_t data)
{
DoulbeLkList_t *NewNode = DoulbeLkList_NewNode(data);
if (NULL == Head->Next) // 判断链表是否为空
{
// 链表为空
Head->Next = NewNode;
return;
}
// 2.2链表非空
DoulbeLkList_t *Phead = Head->Next; // 备份首结点的地址
DoulbeLkList_t *Dest = NULL; // 用于判断是否成功查找到指定数值的结点
// 遍历找到指定数据结点
while (Phead->Data != DestVal) // 判断条件:当前结点的数据域的数据是否与目标值不同
{
Phead = Phead->Next; // 不同则偏移
if (Phead->Next == NULL && 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 = Phead; // 让新结点的Prev指针指向目标结点
Phead->Next = NewNode; // 让目标结点的Next指针指向新结点
}
}
// 在链表中头删一个节点
bool DoulbeLkList_HeadDel(DoulbeLkList_t *Head)
{
// 判断链表是否为空
if (NULL == Head->Next) // 判断链表是否为空
{
// 链表为空
return false;
}
// 链表非空
DoulbeLkList_t *Phead = Head->Next; // 备份首节点地址,用于最后释放首结点地址
Head->Next = Phead->Next; // 让头结点的Next指针指向首结点的直接后继结点
Phead->Next = NULL; // 让首结点的Next指针指向NULL
Head->Next->Prev = NULL; // 让首结点的直接后继结点的Prev指针指向NULL
free(Phead);
return true;
}
// 在链表中尾删一个节点
bool DoulbeLkList_TailDel(DoulbeLkList_t *Head)
{
// 判断链表是否为空
if (NULL == Head->Next) // 判断链表是否为空
{
// 链表为空
return false;
}
DoulbeLkList_t *Phead1 = Head->Next; // 备份首节点地址,用于遍历查找尾结点
DoulbeLkList_t *Phead2 = Head; // 备份头节点地址,用于遍历查找尾结点的直接前驱结点
// 遍历查找尾结点
while (Phead1->Next) // 判断条件为:当前结点的Next指针是否指向首结点
{
Phead1 = Phead1->Next; // Phead1在循环结束时代表尾结点的地址
Phead2 = Phead2->Next; // Phead2在循环结束时代表尾结点的直接前驱结点的地址
}
Phead1->Prev = NULL; // 将尾结点Prev指针指向NULL
Phead2->Next = NULL; // 将尾结点的直接前驱结点的Next指针指向NULL
free(Phead1);
return true;
}
// 在链表中删指定数据结点
bool DoulbeLkList_AppointDel(DoulbeLkList_t *Head, Datatype_t DestVal)
{
// 判断链表是否为空
if (NULL == Head->Next) // 判断链表是否为空
{
// 链表为空
return false;
}
DoulbeLkList_t *Phead1 = Head->Next; // 备份首结点的地址,用于遍历目标节点
DoulbeLkList_t *Phead2 = Head; // 备份头结点的地址,用于遍历目标节点的前驱结点
DoulbeLkList_t *Dest = NULL; // 用于判断是否成功查找到指定数值的结点
// 遍历找到指定数据结点
while (Phead1->Data != DestVal) // 判断条件:当前结点的数据域的数据是否与目标值不同
{
Phead1 = Phead1->Next; // 不同则指向目标结点的指针偏移
Phead2 = Phead2->Next; // 不同则指向目标结点的直接前驱的指针偏移
if (Phead1->Next == NULL && Phead1->Data != DestVal) // 判断条件:到达尾结点且未找到目标值则退出循环
{
Dest = Phead1; // 如果未找到目标值,则改Dest的值
break;
}
}
if (Dest) // 如果没有找到指定数值,则删除失败
{
printf("Can find the DestVal %d!\n", DestVal);
return false;
}
else // 如果成功查找到指定数值,则删除指定数据结点
{
// 1.目标结点为首节点(使用头删函数的代码)
if (Phead1 == Head->Next)
{
DoulbeLkList_t *Phead = Head->Next; // 备份首节点地址,用于最后释放首结点地址
Head->Next = Phead->Next; // 让头结点的Next指针指向首结点的直接后继结点
Phead->Next = NULL; // 让首结点的Next指针指向NULL
Head->Next->Prev = NULL; // 让首结点的直接后继结点的Prev指针指向NULL
free(Phead);
}
// 2.目标结点为尾节点(使用尾删函数的代码)
else if (Phead1->Next == NULL)
{
DoulbeLkList_t *Phead3 = Head->Next; // 备份首节点地址,用于遍历查找尾结点
DoulbeLkList_t *Phead4 = Head; // 备份头节点地址,用于遍历查找尾结点的直接前驱结点
// 遍历查找尾结点
while (Phead1->Next) // 判断条件为:当前结点的Next指针是否指向首结点
{
Phead3 = Phead3->Next; // Phead1在循环结束时代表尾结点的地址
Phead4 = Phead4->Next; // Phead2在循环结束时代表尾结点的直接前驱结点的地址
}
Phead3->Prev = NULL; // 将尾结点Prev指针指向NULL
Phead4->Next = NULL; // 将尾结点的直接前驱结点的Next指针指向NULL
free(Phead3);
}
// 3.目标结点在中间
else
{
Phead2->Next = Phead1->Next; // 让目标结点的直接前驱结点的Next指针指向目标结点的直接后继结点
Phead1->Next->Prev = Phead2; // 让目标结点的直接后继结点的Prev指针指向目标结点的直接前驱结点
Phead1->Next = NULL;
Phead1->Prev = NULL;
free(Phead1);
}
return true;
}
}
// 测试函数,用于遍历输出链表
// void Test(DoulbeLkList_t *Head)
// {
// DoulbeLkList_t *Phead = Head; // 备份头节点的地址,用于遍历结点
// while (Phead) // 遍历到尾结点
// {
// printf("%d ", Phead->Next->Data);
// Phead = Phead->Next;
// }
// printf("%d\n", Phead->Next->Data); // 打印尾结点的数据
// }
bool doubleLinkedList_Print(DoulbeLkList_t *Head)
{
DoulbeLkList_t *Phead = Head; // 备份头结点
if (NULL == Head->Next) // 判断链表是否为空,为空直接退出
{
printf("doubLinkedList is empty\n");
return false;
}
while (Phead->Next)
{
Phead = Phead->Next; // 遍历链表
printf(" %d", Phead->Data); // 打印链表
}
printf("\n");
return true;
}
int main(void)
{
DoulbeLkList_t *Head = DoubleLkList_Create();
// 测试头插
DoulbeLkList_HeadAdd(Head, 10);
DoulbeLkList_HeadAdd(Head, 20);
DoulbeLkList_HeadAdd(Head, 30);
DoulbeLkList_HeadAdd(Head, 40);
DoulbeLkList_HeadAdd(Head, 50);
doubleLinkedList_Print(Head);
// Test(Head);
// 测试尾插
DoulbeLkList_TailAdd(Head, 1);
DoulbeLkList_TailAdd(Head, 2);
DoulbeLkList_TailAdd(Head, 3);
DoulbeLkList_TailAdd(Head, 4);
DoulbeLkList_TailAdd(Head, 5);
doubleLinkedList_Print(Head);
// Test(Head);
// 测试指定插
DoulbeLkList_AppointAdd(Head, 20, 77);
DoulbeLkList_AppointAdd(Head, 5, 88);
DoulbeLkList_AppointAdd(Head, 50, 66);
doubleLinkedList_Print(Head);
// 测试头删
DoulbeLkList_HeadDel(Head);
DoulbeLkList_HeadDel(Head);
DoulbeLkList_HeadDel(Head);
DoulbeLkList_HeadDel(Head);
// Test(Head);
// 测试尾删
DoulbeLkList_TailDel(Head);
DoulbeLkList_TailDel(Head);
DoulbeLkList_TailDel(Head);
doubleLinkedList_Print(Head);
// Test(Head);
// 测试指定插
DoulbeLkList_AppointDel(Head, 77);
doubleLinkedList_Print(Head);
return 0;
}