【数据结构】双向循环链表专题解析

实现自己既定的目标,必须能耐得住寂寞单干。💓💓💓

目录

•✨说在前面

🍋知识点一:双向链表的结构

 • 🌰1."哨兵位"节点

 • 🌰2.双向带头循环链表的结构

🍋知识点二:双向带头循环链表

 • 🌰1. 动态申请节点 

 • 🌰2. 双向链表的初始化

 • 🌰3. 双向链表元素的打印

 • 🌰4. 双向链表头部插入数据

 • 🌰5. 双向链表尾部插入数据

 • 🌰6. 指定位置pos之后插入数据

 • 🌰7.双向链表头部删除元素

 • 🌰8.双向链表尾部删除元素

 • 🌰9.删除指定位置pos节点

 • 🌰10.双向链表的查找

 • 🌰10.双向链表的销毁

• ✨SumUp结语


•✨说在前面

亲爱的读者们大家好!💖💖💖,我们又见面了,之前我们学习了顺序表后,又紧接着给大家讲解了链表中最典型的单向不循环链表,也是最常用的一种。但正所谓我们学习应该面面俱到,有了之前的学习基础,再学习双向链表实际上是非常简单的。

  

 如果你没有准备好的话,可以再复习一下单链表以及单链表相关LeetCode的OJ题。

   

👇👇👇
💘💘💘知识连线时刻(直接点击即可)

  🎉🎉🎉复习回顾🎉🎉🎉

【数据结构】单链表专题详细分析

    

  博主主页传送门:愿天垂怜的博客

 

 

🍋知识点一:双向链表的结构

 • 🌰1."哨兵位"节点

哨兵位指的是链表中指向链表第一个节点的节点,哨兵位不存储任何有效元素,只是在那里放哨的,顾称为哨兵位节点

注意:

这里的"带头"跟前面我们说的"链表中的第一个有效节点"是两个概念,实际单链表的头结点不是第一个有效节点,而是哨兵位节点。

"哨兵位"存在的意义:

遍历循环链表避免死循环。

具体带头比不带头有什么优势可以看我上一篇文章中的合并有序链表。

LeetCode/NowCoder-链表经典算法OJ练习1

 • 🌰2.双向带头循环链表的结构

结构如下:

 由于"哨兵位"节点的存在,我们再实现这样的链表时可以省去一些内容:

🎉插入操作时,不需要检查是否在头部插入,因为哨兵节点作为头结点,总是存在。

🎉删除操作时,不需要处理删除的是否是头节点的情况,因为哨兵节点不会被删除。

🎉简化了代码,因为不需要为头节点和普通节点编写不同的处理逻辑。

类比单链表的结构,可以定义出节点数据为整型的双向带头循环链表节点:

typedef int LTDataType;

typedef struct ListNode
{
	LTDataType data;
	struct ListNode* prev;//指向前一个节点
	struct ListNode* next;//指向后一个节点
}LTNode;

🍋知识点二:双向带头循环链表

 • 🌰1. 动态申请节点 

在双向带头循环链表提供的方法中,动态申请节点是必不可少的。

LTNode* LTBuyNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(1);
	}
	newnode->data = x;
	newnode->next = newnode->prev = newnode;
}

初始化双向链表其实就是创建头节点,那么就需要用到LTBuyNode来申请节点。由于是双向的,我们可以让它的next指针和prev指针先指向它自己。

那是否可以将哨兵位的next指针和prev指针初始化为NULL呢?答案是不行的。

如果哨兵位的前驱指针prev和后继指针next初始化为NULL,这样的链表虽然满足了双向,也满足了带头,但是不满足循环,所以不能这样初始化,因此在动态申请节点的时候,就让newnode的prev和next都指向它自己,这样就可以循环起来了。

 • 🌰2. 双向链表的初始化

初始化双向带头循环链表实际上就是创建"哨兵位"头节点。

写法1:传二级指针,函数为void型。

void LTInit(LTNode** pphead)
{
	*pphead = LTBuyNode(-1);
}

写法2:不传参 ,在函数中创建节点,函数为LTNode*型。

LTNode* LTInit()
{
    LTNode* phead = LTBuyNode(-1);
	return phead;
}

我们将其中存储的值设为-1,表示其为头节点,此时链表中只有一个头节点:

 • 🌰3. 双向链表元素的打印

打印双向链表中的所有节点数据。

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

这个没什么好说的,和之前我们学习的单链表基本是一样的,不过是while循环的条件有所变化。我们如果初始化pcur为phead->next,那它循环完一轮之后会重新变成phead,此时就已经打印完成,不需要继续循环了。

 • 🌰4. 双向链表头部插入数据

向双向链表的头部插入节点和数据,指的是在头节点的后一个位置插入数据,而不是在头节点的前面(头插)。

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	newnode->prev = phead;
	newnode->next = phead->next;
	phead->next = newnode;
	newnode->next->prev = newnode;
}

双向带头循环链表的指针关系比较复杂,但是逻辑却很简单,我们一定要通过画图来理解,不要光看代码,这样是看不出东西的。

上述关系红色为先修改,蓝色为后修改。由于关系比较多,可以先从newnode入手,先处理newnode的prev和next指针,这样不会影响到原链表的结构。当设置完newnode时,如果先让phead的后继指针next指向newnode,那么图中红色所标注的phead->next不在是那个节点的地址,而是newnode的地址,此时它就应该是newnode->next。

 • 🌰5. 双向链表尾部插入数据

向双向链表的尾部插入节点和数据,可以理解为在图中的末尾插入数据,也可以理解为在头节点的前一个位置插入数据。

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;
}

由于是循环链表,画图的时候可以在节点顺序不改变的前提下灵活变换图像。

图1:

图2:


 两中图理解都是可以的,由于头节点的位置固定,上面两个图都是同一个双向链表。 

 • 🌰6. 指定位置pos之后插入数据

在指定位置pos的后面插入节点和数据,其实和头插方法很类似,甚至可以说头插其实就是pos后插入数据的一种特殊情况,只不过此时pos=phead而已。

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);
	newnode->prev = pos;
	newnode->next = pos->next;
	pos->next = newnode;
	newnode->next->prev = newnode;
}

可以发现,代码也是及其类似。

 • 🌰7.双向链表头部删除元素

删除链表中的第一个有效节点,即删除头节点的后一个节点。

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;
}

为了防止free释放后无法解引用找到下一个节点的问题,引入del保存要删除的节点的地址。

 • 🌰8.双向链表尾部删除元素

 删除双向链表中的最后一个数据,可以理解为删除图中末尾的数据,也可以理解为删除头节点前面一个数据。

void LTPopBack(LTNode* phead)
{
	assert(phead && phead->next != phead);
	LTNode* del = phead->prev;
	phead->next = del->prev;
	del->prev->next = phead;
	free(del);
	del = NULL;
}

两种画法和2.5思路一样,这里就画出一种,只要理解一种,另一种就很简单了。

 • 🌰9.删除指定位置pos节点

删除指定位置pos的节点,如法炮制。

void LTErase(LTNode* pos)
{
	//pos不能为哨兵位
	assert(pos);
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;
	free(pos);
	pos = NULL;
}

除了要保证pos不为NULL外,还需要保证pos不能为哨兵位,否则它不是有效节点。

 • 🌰10.双向链表的查找

查找链表中值为x的节点。

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

定义pcur,利用while循环遍历一轮,如果有值为x的节点,则返回该节点,遍历一轮后若仍没有该节点,则返回空指针NULL。

 • 🌰10.双向链表的销毁

与单链表和顺序表的思想如出一辙,如法炮制。

void LTDestory(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
}

• ✨SumUp结语

数据结构的学习一定要多画图,多理解,多思考,切忌直接抄写代码,就认为自己已经会了,一定到自己动手,才能明白自己哪个地方有问题。

如果大家觉得有帮助,麻烦大家点点赞,如果有错误的地方也欢迎大家指出~

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[C#] 使用HttpClient请求https地址报错的解决方案

当使用HttpClient请求HTTPS地址遇到报错时,下面将解析并提供可能的解决方案供参考。 文章目录 异常代码无法定位错误的准确定位错误的 常见错误错误1错误2 解决问题生产环境开发环境 异常代码 首先,需要查看引发异常的代码部分, 无法定位错误的 以下代…

《完美黑暗》重启版6月发布,分析指出开发“没有问题” 状况没那么

易采游戏网5月12日消息,在21世纪初的游戏界,一款名为《完美黑暗》的FPS游戏在N64平台上崭露头角,以其独特的剧情设定和丰富的武器系统赢得了众多玩家的喜爱。然而,这款作品在推出时也并非一帆风顺,受到了不少玩家的吐槽…

C++ | Leetcode C++题解之第77题组合

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<int> temp;vector<vector<int>> ans;vector<vector<int>> combine(int n, int k) {// 初始化// 将 temp 中 [0, k - 1] 每个位置 i 设置为 i 1&#xff0c;即 [0, k - 1] 存…

springcloud整合网关(springcloud-gateway) 跨域处理

pom引入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!-- 服务注册 --><dependency><groupId>com.alibaba.cloud</groupId&…

使用Pandas对Data列进行基于顺序的分组排列

目录 一、引言 二、Pandas库简介 三、按照数据列中元素出现的先后顺序进行分组排列 四、案例分析 五、技术细节探讨与扩展应用 1. 技术细节 2. 扩展应用 3. 示例代码&#xff1a;用户行为分析 4. 进阶应用&#xff1a;分组后的聚合操作 5. 分组后的数据筛选 6. 分组…

【算法】滑动窗口——找到字符串中所有字母异位词

本节博客是对题目——找到字符串中所有字母异位词的从读题到代码实现以及优化的详细解读&#xff0c;有需要借鉴即可。 目录 1.题目2.滑动窗口 哈希数组3.异位词优化4.总结 1.题目 题目链接&#xff1a;LINK 首先来解释一下什么是异位词&#xff0c;所谓“异位词”&#xf…

JavaWeb文件上传/下载(Servlet)

效果 文件下载 文件上传 项目概述 Jakarta EE9&#xff0c;Web项目 项目文件结构 0 maven依赖&#xff0c;资源文件 <!-- lombok插件--> <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId&g…

最新的云渲染100活动有哪些?渲染100邀请码1a12

随着科技的进步&#xff0c;云渲染已经成为设计行业的必备工具&#xff0c;各个云渲染平台为了吸引用户也推出各种各样的活动&#xff0c;今天我们以广受好评的渲染100为例&#xff0c;来说下它们的活动体系。 1、新用户活动 渲染100对新用户很友好&#xff0c;提供了充足的测…

欢乐钓鱼大师攻略,怎么获取道具?

在《欢乐钓鱼大师》的游戏世界中&#xff0c;道具是提升钓鱼体验、解锁新功能以及完成挑战的关键。通过多种方式获取道具&#xff0c;能够帮助玩家更好地探索游戏世界、挑战自我&#xff0c;以及与其他玩家展开竞争。以下是关于如何获取道具的详细攻略&#xff0c;让你能够在游…

AI写作推荐-写文ai-AI在线写作生成器-3步完成写作任务

AI写作利器&#xff1a;推荐几款神助攻文案创作工具 随着技术的进步&#xff0c;人工智能&#xff08;AI&#xff09;已达到高级水平&#xff0c;在众多领域展现其强大能力。 在文本创作的领域&#xff0c;人工智能&#xff08;AI&#xff09;应用已显著地提升了写作效率和创意…

Java后端实现对象与文件接收数据(minio测试)

实现思路&#xff1a; 1. 两个接口实现&#xff0c;一个接对象数据(file)&#xff0c;一个接文件数据(json)。 2. json对象(base64String) 实体类信息 &#xff0c;请求体统一接收 3. file, String name ,String password ,String name &#xff0c; Controller层接收 统一…

环形链表(给定一个链表的头节点 head ,返回链表开始入环的第一个节点)的原理讲解

一&#xff1a;题目 二&#xff1a;原理讲解 解决这个题目 &#xff0c;我们得先理解它的原理。 1&#xff1a; 首先假设两个指针&#xff0c;一个快指针fast&#xff0c;一个慢指针slow&#xff0c;fast一次移动两个节点&#xff0c;slow一次移动一个节点。&#xff08;前面…

MF自定义控件方法

在MFC中&#xff0c;您可以通过自定义控件来实现特定的用户界面元素或功能&#xff0c;以满足您的应用程序需求。自定义控件通常是从CWnd类派生的子类&#xff0c;您可以在其中重写绘制、处理事件等方法&#xff0c;以实现您想要的功能和外观。以下是一般步骤&#xff1a; 创建…

【Java】变量类型

类变量&#xff1a;独立于方法之外的变量&#xff0c;用static修饰实例变量&#xff1a;独立于方法之外的变量&#xff0c;不过没有static修饰局部变量&#xff1a;类的方法中的变量 示例1&#xff1a; public class test_A {static int a;//类变量(静态变量)String b;//实例…

【JAVA进阶篇教学】第十篇:Java中线程安全、锁讲解

博主打算从0-1讲解下java进阶篇教学&#xff0c;今天教学第十篇&#xff1a;Java中线程安全、锁讲解。 当涉及到多线程编程时&#xff0c;保证线程安全是至关重要的。线程安全意味着在多个线程访问共享资源时&#xff0c;不会发生数据错乱或不一致的情况。为了实现线程安全&am…

问题:幂等性 分布式session

web项目中请求线程到service层的时候远程调用服务之前是串行化执行每个任务都要get阻塞等待任务完成&#xff0c;举例当用户在购物车页面点击去结算就会请求后台toTrade请求获取订单确认的详情数据并渲染到订单详情页&#xff0c;现在在toTrade请求中使用异步任务编排Completab…

微信授权登录02-移动端

目录 ## 前言 1.准备工作 1.1 网站域名 1.2 微信公众号 2.授权登录开发 2.1 前端开发 2.1.1 调起微信授权页面 ## 调起微信授权页面效果图 2.1.2 用户允许授权后回调处理 2.2 后端开发 2.2.1 根据code查询用户信息 2.2.2 自动注册登录 ## 后记 ## 前言 上一篇写…

Nios-II编程入门实验

文章目录 一 Verilog实现流水灯二 Nios实现流水灯2.1 创建项目2.2 SOPC添加模块2.3 SOPC输入输出连接2.4 Generate2.5 软件部分2.6 运行结果 三 Verilog实现串口3.1 代码3.2 引脚3.3 效果 四 Nios2实现串口4.1 sopc硬件设计4.2 top文件4.3 软件代码4.4 实现效果 五 参考资料六 …

nodeJs用ffmpeg直播推流到rtmp服务器上

总结 最近在写直播项目 目前比较重要的点就是推拉流 自己也去了解了一下 ffmpeg FFmpeg 是一个开源项目&#xff0c;它提供了一个跨平台的命令行工具&#xff0c;以及一系列用于处理音频和视频数据的库。FFmpeg 能够执行多种任务&#xff0c;包括解封装、转封装、视频和音频…

Ps各种修改文字超实用方法

介绍 在日常生活中,难免会遇到进行文字修改的ps场景,此时就需要用到比较专业的ps进行文字修改,博主特意整合了多种情况下的文字p图方法进行记录,但是不包含全部情况,只记录日常中常见的情况,也可以解决大部分场景了 原图有可用文字素材 在p图时,我们可以先观察我们要p的图中…