【数据结构——双向链表】

目录

一、双向链表的定义

双向链表:双向带头不循环链表

组成: 数据+指向下一个节点的指针+指向上一个节点的指针

定义双向链表节点的结构:

c 复制代码
typedef int LTDataType;//方便替换数据类型
typedef struct ListNode
{
	LTDataType data;//节点存储的数据
	struct ListNode* next;//指向下一个节点的指针
	struct ListNode* prev;//指向上一个节点的指针
}LTNode;

二、申请节点

c 复制代码
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;
}

三、初始化

初始化申请一个自循环的哨兵卫节点

双向链表初始化时若next和prev都初始化为NULL,不满足循环要求

c 复制代码
void LTInit(LTNode** pphead)
{
	//创建哨兵位
	*pphead=LTBuyNode(-1);//哨兵卫不存储有效值,但是这里参数必须传值
}

这里可以以参数形式返回哨兵卫,也可以以返回值的形式返回哨兵卫

c 复制代码
LTNode* LTInit()
{
	LTNode* phead=LTBuyNode(-1);
	return phead;
}

四、尾插

不管是尾插还是头插或者是后面的删除操作的过程中,都没有去改变哨兵卫

头结点phead不能被删除,地址也不能被改变,因此这里传一级指针即可

因为有哨兵卫节点,所以这里不用再考虑头结点为空的情况

受到影响的是原尾节点的next指针和哨兵卫的prev指针

为避免影响原链表,先修改新节点newnode,让新节点的prve指针指向原尾节点,next指针指向哨兵卫;通过phead的prev指针找到原尾节点,让原尾节点的next指针指向新节点;让phead的prev指向新节点

Tips:最后两步顺序不能交换,否则无法找到原尾节点

空链表尾插:

让新节点newnode的prev指针指向phead,next指针指向phead,phead的next指针和prev指针都指向newnode,仍然成立

c 复制代码
void LTPushBack(LTNode* phead,LTDataType x)
{
	assert(phead);//哨兵卫不为空
	LTNode* newnode=LTBuyNode(x);//申请新节点
	newnode->prev=phead->prev;
	newnode->next=phead;
	phead->prev->next=newnode;
	phead->prev=newnode;
}

五、打印链表

这里循环条件不能是(pcur!=NULL)否则会造成死循环

且这里不需要打印phead,phead无有效数据,因此从phead的下一个节点开始打印

结合来看,循环结束条件可以为pcur!=phead

c 复制代码
void LTPrint(LTNode* phead)
{
	LTNode* pcur=phead->next;
	while(pcur!=phead)
	{
		printf("%d\n",pcur->data);
		pcur=pcur->next;
	}
	printf("\n");
}

六、头插

往第一个有效节点前面插入(哨兵卫的后一个节点)

受到影响的有phead、phead->next、newnode三个节点

首先还是修改newnode,让它的prev指向哨兵卫,next指向哨兵卫的原下一个节点;哨兵卫的原下一个节点的prev指向newnode;让phead的next指向newnode

Tips:这里的最后两步也不能交换

c 复制代码
void LTPushFront(LTNode* phead,LTDataType x)
{
	assert(phead);//哨兵卫不得为空
	LTNode* newnode=LTBuyNode(x);
	newnode->next=phead->next;
	newnode->prev=phead;
	phead->next->prev=newnode;
	phead->next=newnode;
}

七、尾删

先修改原尾节点的前一个节点(phead->prev->prev),让它的next指向phead;让phead的prev指向原尾节点的前一个节点;

这个时候无法再通过phead找到原尾节点了,因此这里可以给原尾节点改个名字叫del,这个时候可以简化上面的书写:修改phead->prev->prev->next为del->prev->next;phead->prev=del->prev;销毁del节点

c 复制代码
void LTPopBack(LTNode* phead)
{
	assert(phead&&phead->next!=phead);//哨兵卫不得为空,链表也不得为空(只有一个哨兵卫)
	LTNode* del=phead->prev;
	del->prev->next=phead;
	phead->prev=del->prev;
	free(del);//销毁del节点
	del=NULL;
}

八、头删

由尾删可知,在删除节点的过程中我们可能找不到要删除的节点,因此我们可以先用del将该节点存下来,del=phead->next

再让第一个有效节点的下一个节点指向phead,接着让phead的next指向第一个有效节点的下一个节点,再销毁del节点

c 复制代码
void LTPopFront(LTNode* phead)
{
	assert(phead&&phead->next!=phead);//链表有效且链表不能为空
	LTNode* del=phead->next;
	phead->next=del->next;
	del->next->prev=phead;
	//这里上面两步可以交换
	//删除del节点
	free(del);
	del=NULL;
}

九、查找

定义一个指针pcur指向第一个有效节点,遍历链表

c 复制代码
LTNode* LTFind(LTNode* phead,LTDataType x)
{
	LTNode* pcur=phead->next;
	while(pcur!=phead)
	{
		if(pcur->data==x)
		{
			return pcur;
		}
		pcur=pcur->next;
	}
	return NULL;//没有找到
}

十、在pos位置之后插入数据

先修改newnode指向,让newnode的prev指向pos,next指向pos->next;让pos的原下一个节点(newnode->next)的prev指向newnode;让pos的next指向newnode

如果指定位置是原尾节点:

c 复制代码
void LTInsert(LTNode* pos,LTDataType x)
{
	assert(pos);
	LTNode*newnode=LTBuyNode(x);//申请新节点
	newnode->next=pos->next;
	newnode->prev=pos;
	pos->next->prev=newnode;
	pos->next=newnode;
}

十一、删除pos节点

让pos的下一个节点的prev指针指向pos的前一个节点;让pos的前一个节点的next指针指向pos的下一个节点,释放pos节点

c 复制代码
void LTErase(LTNode* pos)
{
	assert(pos);//pos不能为空
	assert(pos->next!=pos);//pos不能为哨兵卫节点
	pos->next->prev=pos->prev;
	pos->prev->next=pos->next;
	free(pos);
	pos=NULL;
}

这里删除pos并置空,是形参改变实参,可传二级指针,但是为了保持接口的一致性,这里我们传一级指针,降低记忆成本

LTErase的参数是节点指针,不是数据值,要先通过LTFind拿到对应节点地址

这里调用完LTErase之后要把find手动置空,避免野指针

十二、销毁链表

从第一个有效节点开始删除,删除了当前节点pcur无法走到下一个节点,因此要先用next把下一个节点存储起来,让pcur走到next的位置,next往后走,pcur删除next原来所在位置,不断将有效节点全部删除

出循环后,消除哨兵卫

c 复制代码
void LTDesTroy(LTNode* phead)
{
	assert(phead);//phead不能为空,这里是销毁,所以链表可以为空
	LTNode* pcur=phead->next;
	while(pcur!=phead)
	{
		LTNode*next=pcur->next;
		free(pcur);
		pcur=next;
	}
	//此时pcur指向phead,但phead还没有被销毁
	free(phead);
	phead=NULL;
}

销毁完之后phead置为空了,但是plist还没有置为空,因此调用时需要手动置空一下

十三、完整代码

List.h

c 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;
LTNode* LTBuyNode(LTDataType x);
void LTInit(LTNode** pphead);
void LTPushBack(LTNode* phead, LTDataType x);
void LTPrint(LTNode* phead);
void LTPushFront(LTNode* phead, LTDataType x);
void LTPopBack(LTNode* phead);
void LTPopFront(LTNode* phead);
LTNode* LTFind(LTNode* phead, LTDataType x);
void LTInsert(LTNode* pos, LTDataType x);
void LTErase(LTNode* pos);
void LTDesTroy(LTNode* phead);

List.c

c 复制代码
#include"List.h"
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;
}
void LTInit(LTNode** pphead)
{
	*pphead = LTBuyNode(-1);
}
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	newnode->prev = phead->prev;
	newnode->next = phead;
	phead->prev->next = newnode;
	phead->prev = newnode;
}
void LTPrint(LTNode* phead)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d\n", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	newnode->next = phead->next;
	newnode->prev = phead;
	phead->next->prev = newnode;
	phead->next = newnode;
}
void LTPopBack(LTNode* phead)
{
	assert(phead && phead->next != phead);
	LTNode* del = phead->prev;
	del->prev->next = phead;
	phead->prev = del->prev;
	free(del);
	del = NULL;
}
void LTPopFront(LTNode* phead)
{
	assert(phead && phead->next != phead);
	LTNode* del = phead->next;
	phead->next=del->next;
	del->next->prev = phead;
	free(del);
	del = 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;
}
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);
	newnode->next = pos->next;
	newnode->prev = pos;
	pos->next->prev = newnode;
	pos->next = newnode;
}
void LTErase(LTNode* pos)
{
	assert(pos);
	assert(pos->next!=pos);
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;
	free(pos);
	pos = NULL;
}
void LTDesTroy(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
}
相关推荐
法雅特吉他1 小时前
吉他面板材质怎么选?西提卡云杉单板深度解析
经验分享·新媒体运营·学习方法·流量运营·材质·内容运营
少司府1 小时前
C++进阶:AVL树
开发语言·数据结构·c++·二叉树·avl树
AIHR数智引擎2 小时前
AI组织进化论:拆解微软、英伟达、Anthropic与Open AI如何重写组织
人工智能·经验分享·microsoft·职场和发展·aihr
LaughingZhu2 小时前
Product Hunt 每日热榜 | 2026-06-08
人工智能·经验分享·深度学习·神经网络·产品运营
孬甭_2 小时前
从基础到优化:深入理解插入排序与希尔排序
数据结构·算法·排序算法
问心无愧05132 小时前
ctf show web入门104
笔记
ん贤2 小时前
深度学习入门笔记(一)
人工智能·笔记·深度学习
AF_INET62 小时前
sensor笔记(一)imx415
c语言·经验分享·音视频·linux驱动·sensor·imx415·datasheet
如竟没有火炬2 小时前
恢复二叉搜索树
数据结构·数据库·python·leetcode·动态规划