双链表的基本操作实现

双链表节点的定义
双链表的每个节点包含三个部分:数据域、前驱指针和后继指针。

typedef struct DListNode {
int data;
struct DListNode* prev;
struct DListNode* next;
} DListNode;
申请新节点
1、动态申请节点内存:
使用 malloc 函数为新节点(LTNode 类型)分配内存。
检查内存分配是否成功:若 malloc 返回 NULL(表示内存分配失败),则用 perror 打印错误信息(“malloc fail!”),并通过 exit(1) 终止程序。
2、初始化节点数据与指针:
将传入的参数 x 赋值给新节点的数据域(node->data = x)。
把新节点的 next(指向下一节点的指针)和 prev(指向前一节点的指针)都指向节点自身(node->next = node->prev = node),这是循环双向链表的核心特点 —— 单个节点形成自循环(后续添加更多节点时,会打破自循环并构建成多节点的循环链)。
3、返回新节点:
完成初始化后,返回新创建节点的指针,供循环双向链表的其他操作(如插入、删除等)使用。
//申请节点
LTNode* LTBuyNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc fail!");
exit(1);
}
node->data = x;
node->next = node->prev = node;
return node;
}
双链表的初始化
1、函数定义:LTNode* LTInit() 函数的作用是初始化双向链表,返回类型为指向 LTNode 类型的指针(即链表头节点的指针)。
2、创建哨兵位头节点:
调用 LTBuyNode(-1) 函数来创建一个新节点。这里传入 -1 作为节点的初始数据,这个节点会作为双向链表的哨兵位头节点(哨兵位节点不存储实际业务数据,主要用于简化链表操作,比如统一空链表和非空链表的操作逻辑)。
将创建好的哨兵位节点的指针赋值给 phead。
3、返回头节点:函数最后返回 phead,即双向链表的哨兵位头节点指针,后续对双向链表的操作(如插入、删除等)都可以基于这个头节点展开。
//初始化
LTNode* LTInit()
{
LTNode* phead = LTBuyNode(-1);
return phead;
}
打印双链表

1、定义一个指针pcur,让它指向哨兵位头节点(phead)的下一个节点(即链表的第一个有效节点)。
2、通过while (pcur != phead)循环遍历链表(只要pcur没有回到哨兵位头节点,就继续遍历,因为双向链表是循环的,哨兵位头节点是循环的 “终点” 标志)。
3、在循环中,打印当前节点(pcur)的数据(格式为“数据->”),然后让pcur指向下一个节点(pcur = pcur->next;),继续打印下一个节点。
4、当循环结束(pcur回到哨兵位头节点,说明遍历完链表了),打印一个换行符\n。
//打印
void LTPrint(LTNode* phead)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
尾插

1、首先用assert(phead);确保传入的头节点指针phead有效(不是NULL)。
2.、调用LTBuyNode(x)创建一个包含数据x的新节点。
3.、进行指针调整,将新节点插入到链表尾部:
新节点的prev指针指向原来的尾节点(phead->prev,因为phead是哨兵位头节点,它的prev指向链表的尾节点)。
新节点的next指针指向哨兵位头节点phead。
原来的尾节点的next指针指向新节点(phead->prev->next = newnode;)。
哨兵位头节点的prev指针指向新节点(phead->prev = newnode;),这样新节点就成为了新的尾节点。
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
//phead phead->prev newnode
newnode->prev = phead->prev;
newnode->next = phead;
phead->prev->next = newnode;
phead->prev = newnode;
}
头插

1、首先用assert(phead);确保传入的头节点指针phead有效(不是NULL)。
2、 调用LTBuyNode(x)创建一个包含数据x的新节点。
3.、进行指针调整,将新节点插入到链表头部:
新节点的next指针指向哨兵位头节点原本的下一个节点(phead->next)。
新节点的prev指针指向哨兵位头节点phead。
哨兵位头节点原本的下一个节点的prev指针指向新节点(phead->next->prev = newnode;)。
哨兵位头节点的next指针指向新节点(phead->next = newnode;),这样新节点就成为了链表的第一个有效节点。
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
//phead newnode phead->next
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
尾删
此时让phead->prev原本要指向d3(del)现在让它指向d2(del->prev)
del->prev->next(d2->d3)原本要指向d3现在让它指向phead

1、首先用assert(phead && phead->next != phead);确保链表有效且不为空(即不是只有哨兵位头节点)。
2、找到要删除的尾节点,用del指针指向它(LTNode* del = phead->prev;,因为phead是哨兵位头节点,它的prev指向链表的尾节点)。
3、进行指针调整,将尾节点从链表中移除:
尾节点的前一个节点的next指针指向哨兵位头节点(del->prev->next = phead;)。
哨兵位头节点的prev指针指向尾节点的前一个节点(phead->prev = del->prev;)。
4、释放被删除的尾节点的内存(free(del);),并将del指针置为NULL,防止野指针。
//尾删
void LTPopBack(LTNode* phead)
{
//链表必须有效且链表不能为空(只有一个哨兵位)
assert(phead && phead->next != phead);
LTNode* del = phead->prev;
//phead del->prev del
del->prev->next = phead;
phead->prev = del->prev;
//删除del节点
free(del);
del = NULL;
}
头删

1、首先用assert(phead && phead->next != phead);确保链表有效且不为空(即不是只有哨兵位头节点)。
2、找到要删除的头节点(哨兵位头节点的下一个节点),用del指针指向它(LTNode* del = phead->next;)。
3、进行指针调整,将该头节点从链表中移除:
哨兵位头节点的next指针指向del的下一个节点(phead->next = del->next;)。
del的下一个节点的prev指针指向哨兵位头节点(del->next->prev = phead;)。
4、释放被删除的头节点的内存(free(del);),并将del指针置为NULL,防止野指针。
//头删
void LTPopFront(LTNode* phead)
{
assert(phead && phead->next != phead);
LTNode* del = phead->next;
//phead del del->next
phead->next = del->next;
del->next->prev = phead;
//删除del节点
free(del);
del = NULL;
}
查找
1、定义一个指针 pcur,从哨兵位头节点的下一个节点(即链表的第一个有效节点)开始。
2、通过 while (pcur != phead) 循环遍历链表(只要 pcur 没有回到哨兵位头节点,就继续遍历,因为双向链表是循环的,哨兵位头节点是循环的 “终点” 标志)。
3、在循环中,判断当前节点(pcur)的数据是否等于要查找的 x:
如果等于,直接返回当前节点的指针(return pcur;)。
如果不等于,让 pcur 指向下一个节点(pcur = pcur->next;),继续查找。
4、如果循环结束(pcur 回到哨兵位头节点,说明遍历完链表了),还没找到,就返回 NULL。
//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
//没有找到
return NULL;
}
在指定位置之后插入数据

1、首先用assert(pos);确保指定的位置pos有效(不是NULL)。
2、调用LTBuyNode(x)创建一个包含数据x的新节点。
3、进行指针调整:
先让新节点的next指针指向pos节点原本的下一个节点(newnode->next = pos->next;)。
再让新节点的prev指针指向pos节点(newnode->prev = pos;)。
然后让pos节点原本的下一个节点的prev指针指向新节点(pos->next->prev = newnode;)。
最后让pos节点的next指针指向新节点(pos->next = newnode;)。这样就完成了在pos节点之后插入新节点的操作。
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = LTBuyNode(x);
//pos newnode pos->next
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
删除指定节点

1、首先用assert(pos);确保要删除的节点pos有效(不是NULL)。
2、 进行指针调整,将pos节点从链表中移除:
让pos节点的下一个节点的prev指针指向pos节点的前一个节点(pos->next->prev = pos->prev;)。
让pos节点的前一个节点的next指针指向pos节点的下一个节点(pos->prev->next = pos->next;)。
3、释放pos节点的内存(free(pos);),并将pos指针置为NULL,防止野指针
//删除pos节点
void LTErase(LTNode* pos)
{
//pos理论上来说不能为phead,但是没有参数phead,无法增加校验
assert(pos);
//pos->prev pos pos->next
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
pos = NULL;
}
销毁双链表
1、首先用assert(phead);确保头节点指针phead有效(不是NULL)。
2、定义一个指针pcur,从哨兵位头节点的下一个节点(即链表的第一个有效节点)开始。
3、通过while (pcur != phead)循环遍历链表(只要pcur没有回到哨兵位头节点,就继续遍历)。
4、在循环中,先保存pcur的下一个节点到next指针中,然后释放pcur节点的内存,再让pcur指向next节点,继续释放下一个节点。
5、当循环结束(pcur回到哨兵位头节点),释放哨兵位头节点的内存,并将phead指针置为NULL,防止野指针。
//销毁
void LTDesTroy(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
//此时pcur指向phead,而phead还没有被销毁
free(phead);
phead = NULL;
}
1万+

被折叠的 条评论
为什么被折叠?



