数据结构(4)——带哨兵位循环双向链表

news2025/4/1 3:15:41

目录

前言

一、带哨兵的循环双向链表是什么

二、链表的实现

2.1规定结构体

2.2创建节点

2.3初始化

2.4打印

2.5检验是否为空

2.6销毁链表

2.7尾插

2.8尾删

2.9头插

2.10头删

2.11寻找特定节点

2.12任意位置插入(pos前)

2.13删除任意节点

总结


前言

前面我们学习了单链表的一些知识,由单链表引申出双向链表,同时带哨兵位或者不带哨兵位是两种,但大差不差,这里学习一下带哨兵位的循环双向链表。

其实有很多链表的结构,组成它们的也就是循环非循环,带哨兵位不带哨兵位,双向还是单向。


一、带哨兵的循环双向链表是什么

无哨兵单向非循环链表:结构简单,也就是我们常说的单链表,一般不会用来单独存数据,实际中更多是作为其它数据的子结构,如哈希桶,图的邻接表等等。

带哨兵双向循环链表:结构最复杂,一般用来单独存储数据,实际中使用的链表数据结构,都是带哨兵(头)双向链表,另外这个结构虽然复杂,但是使用代码实现以后会发现结构带来很多优势,实现反而简单了。

二、链表的实现

这里我们要实现的有

//创建节点
LTNode* BuyListNode(LTDataType x)
//初始化
LTNode* LTInit();
//打印
void LTPrint(LTNode* phead);
//销毁链表
void LTDestory(LTNode* phead);
//检验是否为空
bool LTEmpty(LTNode* phead)
//尾插尾删
void LTPushBack(LTNode* phead, LTDataType x);
void LTPopBack(LTNode* phead);
//检验链表是否为空
bool LTEmpty(LTNode* phead);
//头插头删
void LTPushFront(LTNode* phead, LTDataType x);
void LTPopFront(LTNode* phead);
//在pos之前加入一个值
void LTInsert(LTNode* pos, LTDataType x);
//删除节点
void LTErase(LTNode* pos);
//寻找特定节点
LTNode* LTFind(LTNode* phead, LTDataType x);

2.1规定结构体

要想实现一个链表,就要首先规定一下每一个节点的结构体组成部分,这里我们先使用结构体来定义一下。

每一个节点内包括数据域,头指针和尾指针。

typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* next;//头指针
	struct ListNode* prev;//尾指针

	LTDataType data;//数据
}LTNode;

基于这个结构,我们就可以实现后期的一系列操作内容,包括哨兵位的创建。

2.2创建节点

这里我们命名为BuyListNode,返回类型为结构体也就是LTNode*,通过传入一个数据LTDataType x,从而实现对节点的创建。

LTNode* BuyListNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	node->next = NULL;
	node->prev = NULL;
	node->data = x;
	return node;
}

通过malloc分配一个节点的内存,然后把头指针尾指针都赋为空,因为这里不知道头尾指针指向谁,把传入数据,最后返回节点。

2.3初始化

初始化就是初始化一个哨兵位节点,也就是一个头,这里把哨兵位数据定义为-1,头尾指针指向自己,因为这里是双向循环链表,返回此节点。

//初始化
LTNode* LTInit()
{
	LTNode* phead = BuyListNode(-1);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

2.4打印

打印这里就是不断访问当前节点的下一个节点,之后打印此节点的数据。

//打印
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	printf("head<=>");
	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
}

这里为了形象,打印出来的后面会接上一个<<=>>,来代表双向循环链表。

2.5检验是否为空

 通过一个布尔值来检验是否为空,主要就是检查哨兵位有没有。

//检验是否为空
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

2.6销毁链表

我们用 LTDestory来作为销毁链表的函数名字。实际上销毁链表就是先把除了哨兵位(头节点)以外的所有节点删除释放,最后再释放哨兵位,实现是这么实现的:

//销毁释放
void LTDestory(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	phead = NULL;
}

先检查是否有节点,然后把当前cur节点定义为哨兵位的下一个节点,如果cur不为哨兵位(因为是循环链表,所以最后肯定有一个时间,它的尾节点一定指向自己),在循环里面,先用next节点记住cur当前节点的下一个节点,然后释放掉当前节点,再把之前定义的next赋给当前节点,就相当于cur不断再往后走,不断再释放,最后到了循环结束的条件后,释放一下哨兵位就可以实现全部释放。

2.7尾插

这个尾插实现就基于之前创建节点的函数BuyListNode,先创建节点,找到尾节点后,再进行修改。

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyListNode(x);
	LTNode* tail = phead->prev;//找尾节点

	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;

}

用newnode来代表要插入的新节点,找到尾节点,因为哨兵位的头指针指向最后一个节点,这时候把新的newnode插入到尾节点后面就可以,注意此时新的节点成为了尾节点,所以要更新一下头尾指针的指向关系。

2.8尾删

尾删就是找到当前尾节点,然后把尾节点的上一个节点作为最后一个节点,更新当前节点的头尾指针,删除当前尾节点。

//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));

	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;
	tailPrev->next = phead;
	phead->prev = tailPrev;
	free(tail);
	tail = NULL;

}

当然这里也要断言一下,看看是否为空,为空就不需要删除。

2.9头插

头插实际上就是在哨兵位的下一个节点插入,实现过程就是类似链表的插入一样。

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyListNode(x);
	phead->next->prev = newnode;
	newnode->next = phead->next;
	phead->next = newnode;
	newnode->prev = phead;

}

插入进去后更新前后指针的指向就可以。

2.10头删

头删就是指定哨兵位的下一个节点,然后这个节点的下一个节点的头指针更新指向哨兵位,哨兵位的下一节点指向它,就然后释放掉刚才的头节点可以。

//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	LTNode* destorynode = phead->next;
	phead->next->next->prev = phead;
	phead->next = phead->next->next;
	free(destorynode);
	destorynode = NULL;
}

上面给出了头删的代码。

2.11寻找特定节点

寻找特定节点,就是通过数据x寻找,之后返回一个节点的地址,这里就是通过一个循环寻找的特定节点,然后返回。

//寻找特定节点
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);//带头双向循环是定不为空的
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

2.12任意位置插入(pos前)

基于之前的寻找特定的节点,以及新节点的创建,在这里对新节点进行插入,插入到pos的下一个节点,并且更新指针。

//任意位置插入
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);

	prev->next = newnode;
	newnode->prev = prev;

	newnode->next = pos;
	pos->prev = newnode;
}

2.13删除任意节点

通过传入一个pos节点,进行删除,逻辑和前面的删除差不多。

//删除节点
void LTErase(LTNode* pos)
{
	assert(pos);

	LTNode* p = pos->prev;
	LTNode* n = pos->next;

	p->next = n;
	n->prev = p;
	
	free(pos);
	pos = NULL;
}

总结

这里对循环有哨兵位双链表进行基本功能的编写和学习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2324590.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MyBatis】MyBatis 操作数据库(入门)

文章目录 前言一、什么是MyBatis&#xff1f;二、MyBatis入门2.1、准备工作2.1.1 创建工程2.1.2、数据准备 2.2、配置数据库连接字符串2.3、写持久层代码2.4 单元测试 三、MyBatis的基础操作3.1 打印日志3.2、参数传递3.3、增(Insert)3.4、 删(Delete)3.5、改(Update)3.6、查(S…

高速电路中的存储器应用与设计四

5 SRAM介绍及其应用要点 DRAM的性能在很大程度上受到刷新操作的影响&#xff0c;而SRAM则不涉及刷新&#xff0c;因此在相同时钟频率的条件下&#xff0c;SRAM的性能远高于DRAM。 SRAM的缺点是集成度低、容量小、功耗大、价格高。 在应用的场合上&#xff0c;SRAM毫不逊色于…

Vue2 项目将网页内容转换为图片并保存到本地

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

HT81697——30W内置升压单声道D类/AB类音频功放

1 特性 ● 防削顶失真功能(防破音,Anti-Clipping Function,ACF) ● 扩频技术 ● 输出功率 28W (VBAT7.2V, RL4Ω, THDN10%, PVDD 15.5V, fiN 1kHz) 22W (VBAT7.2V,RL4Ω, THDN1%, PVDD 15.5V, fin 1kHz) 16.5W (VBAT3.7V, RL4Ω, THDN10%, PVDD 12V, fiN 1kHz) 12.8W (VBAT…

关于ArcGIS中加载影像数据,符号系统中渲染参数的解析

今天遇到一个很有意思的问题&#xff0c;故记录下来&#xff0c;以作参考和后续的研究。欢迎随时沟通交流。如果表达错误或误导&#xff0c;请各位指正。 正文 当我们拿到一幅成果影像数据的时候&#xff0c;在不同的GIS软件中会有不同效果呈现&#xff0c;但这其实是影像是…

GAMMA数据处理(十)

今天向别人请教了一个问题&#xff0c;刚无意中搜索到了一模一样的问题 不知道这个怎么解决... ok 解决了 有一个GAMMA的命令可转换 但是很奇怪 完全对不上 转换出来的行列号 不知道为啥 再试试 是因为经纬度坐标的小数点位数 de as

基于改进粒子群算法的多目标分布式电源选址定容规划(附带Matlab代码)

通过分析分布式电源对配电网的影响&#xff0c;以有功功率损耗、电压质量及分布式电源总容量为优化目标&#xff0c;基于模糊理论建立了分布式电源在配电网中选址定容的多目标优化模型&#xff0c;并提出了一种改进粒子群算法进行求解。在算例仿真中&#xff0c;基于IEEE-14标准…

SAP 学习笔记 - 系统移行业务 - MALSY(由Excel 移行到SAP 的收费工具)

以前有关移行&#xff0c;也写过一些文章&#xff0c;比如 SAP 学习笔记 - 系统移行业务 - Migration cockpit工具 - 移行Material&#xff08;品目&#xff09;-CSDN博客 SAP 学习笔记 - 系统移行业务 - Migration cockpit工具2 - Lot导入_sap cockpit-CSDN博客 SAP学习笔记…

2025美国网络专线国内服务商推荐

在海外业务竞争加剧的背景下&#xff0c;稳定高效的美国网络专线已成为外贸企业、跨国电商及跨国企业的刚需。面对复杂的国际网络环境和严苛的业务要求&#xff0c;国内服务商Ogcloud凭借其创新的SD-WAN技术架构与全球化网络布局&#xff0c;正成为企业拓展北美市场的优选合作伙…

如何正确地在 Postman 中添加认证 Token?

在 Postman 中设置 token。我们知道 HTTP 是无状态的。token 是保持用户的登录状态或者其他数据的一种机制&#xff0c;从而让用户在不同页面之间保持一致的体验。 在 Postman 中添加认证 token 教程

SpringCould微服务架构之Docker(6)

容器的基本命令&#xff1a; 1. docker exec &#xff1a;进入容器执行命令 2. docker logs: -f 持续查看容器的运行日志 3. docker ps&#xff1a;查看所有运行的容器和状态 案例&#xff1a;创建运行一个容Nginx容器 docker run--name myNginx -p 80:80 -d nginx 命…

Linux|gitlab|二进制快速安装部署gitlab-ce教程

一、 gitlab二进制文件下载地址&#xff1a; 官方网站: gitlab/gitlab-ce - Packages packages.gitlab.com 清华镜像站&#xff1a; Index of /gitlab-ce/yum/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror gitlab分为ce也就是社区版本和ee版本&#xff0c;…

NixVis 开源轻量级 Nginx 日志分析工具

NixVis NixVis 是一款基于 Go 语言开发的、开源轻量级 Nginx 日志分析工具&#xff0c;专为自部署场景设计。它提供直观的数据可视化和全面的统计分析功能&#xff0c;帮助您实时监控网站流量、访问来源和地理分布等关键指标&#xff0c;无需复杂配置即可快速部署使用。 演示…

vscode正则表达式使用

小标题 ^\d.\d.\d\s.*$ ^表示匹配字符串的开头。\d\.\d\.\d表示匹配一到多个数字&#xff0c;接着一个小数点&#xff0c;再接着一到多个数字&#xff0c;然后又一个小数点和一到多个数字&#xff0c;用来匹配类似 “2.1.1” 这样的标题号部分。\s表示匹配一个空格。.*表示匹配…

OpenAI API - Realtime 实时

文章目录 实时 API&#xff08;Beta&#xff09;使用实时API入门示例应用合作伙伴集成 用例通过 WebRTC 连接概述连接详情创建一个临时token发送和接收事件 使用 WebSockets 连接概述连接详情 实时对话Beta实时语音到语音会话会话生命周期事件文本输入和输出音频输入和输出语音…

PE文件(十三)资源表

所谓的资源也就是我们之前学的MFC中的对话框&#xff0c;按钮&#xff0c;编辑框之类的东西。不仅MFC有资源&#xff0c;我们平时熟悉的控制台程序也有资源 当我们平时写一些程序或者木马时&#xff0c;我们通常对其定义一个随机的名称或者路径&#xff0c;然后再向外界进行释…

丝杆升降机行程控制:精准运行的奥秘

丝杆升降机作为机械传动领域的 “得力干将”&#xff0c;在环保设备、工业生产线、建筑施工等众多场景中发挥着关键作用。其能够实现重物的升降、平移等操作&#xff0c;而行程控制对于丝杆升降机而言&#xff0c;就如同给机器设定了行动边界&#xff0c;不仅关乎设备能否精准达…

力扣.旋转矩阵Ⅱ

59. 螺旋矩阵 II - 力扣&#xff08;LeetCode&#xff09; 代码区&#xff1a; class Solution {const int MAX25; public:vector<vector<int>> generateMatrix(int n) {vector<vector<int>> ans;vector<int> hang;int len_nn;int arry[25][25]…

HFSS 使用入门

资源 下载资源&#xff1a; https://download.csdn.net/download/wangjun_huster/90547193 下载破解&#xff1a; https://download.csdn.net/download/wangjun_huster/90547551 安装 https://www.bilibili.com/list/ml3403866295?oid925751664&bvidBV1CT4y1u7LB 入门…

对内核fork进程中写时复制的理解记录

前言 文章写于学习Redis时对aof后台重写中写时复制的疑问 一、感到不理解的歧义 在部分技术文档中&#xff08;以小林的文章为例&#xff09;&#xff0c;对写时复制后的内存权限存在如歧义&#xff1a; ! 二、正确技术表述 根据Linux内核实现&#xff08;5.15版本&#x…