数据结构---手撕图解双向循环链表

news2025/1/9 1:50:37

文章目录

  • 写在前面
  • 双向循环链表的构造布局
    • 带有哨兵位的布局
  • 链表的构建
  • 链表的销毁
  • 链表的输出
  • 链表的尾插
  • 链表的尾删
  • 链表的头插
  • 链表的头删
  • 链表的查找
  • 链表的插入
  • 链表的删除

写在前面

在前面学完单链表后,我们思考这样一个问题,单链表和顺序表比起来,功能确实相当强大,有很多优势,但是于此同时,我们也应思考下面的问题

单链表有什么不足的地方?

如果你把单链表的各个函数都自己实现过,那么下面的问题你一定有相同的感悟

  1. 单链表实现尾插尾删等操作,需要寻找尾部节点,而寻找尾部节点是一个相当繁琐的过程,需要从前向后寻找尾
  2. 单链表实现插入功能,对于单链表来说,通常实现的是在pos后插入,而不在pos前插入,原因就在于pos向前插入需要寻找前面的节点,而这个节点只能从前向后遍历寻找
  3. 单链表在实现函数时,要时时刻刻想到链表为空的情况,对于链表为空的情况要有特殊的处理方式

那么能不能想办法解决下面这些问题?

  1. 找尾部节点方便
  2. 找一个节点的上一个节点方便
  3. 不需要考虑空链表的空指针问题

于是数据结构中对于双向循环链表就解决了这个问题


双向循环链表的构造布局

带有哨兵位的布局

首先介绍什么是哨兵位

和它的名字一样,所谓哨兵位就是一个站哨的位置,它并不属于真实的队伍中的成员,而在链表中也是如此,在前面总结单链表所学的知识时,没有引入哨兵位的链表,而是直接用空链表进行写入,这样的目的不仅仅在于方便后续的OJ练习,同时也是适应特殊情况,为后续的c++学习做铺垫

而在双向循环链表中我们引入哨兵位的概念,作为哨兵的位置,它本身并不属于链表中的一部分,只是充当一个头的位置

哨兵位怎么设置?

链表的每一个节点我们都是通过结构体创建出来的,而很明显,哨兵位也是链表的节点,就意味着哨兵位也有指针部分和数据部分,那么对于数据部分我们应该怎么对它定义?

在一部分书中,哨兵位的数字部分会定义的链表的长度,也就是链表中元素的个数,这样乍一看似乎还不错,借助这个哨兵位还能获取到链表的长度

但是这样真的没问题吗?

事实上这是存在一定的问题的,假设这里选取的是char类型的数据类型,对于char类型的数据范围是从-128到127(char类型占1个字节决定的) 那么这里就有了一个新的问题,假设链表中存储的是char类型的数据,那么假设链表的长度为128呢?那么就会导致链表的实际长度和存储长度有很大差距

于是这里对于哨兵位我并不对它的数据部分有特殊的意义,单纯给它赋予一个值-1

带哨兵位的双向循环链表如下

在这里插入图片描述
上面就是双向循环链表的示意图,从中可以看出,每一个节点可以轻松找到它的下一个节点,以及最后一个节点和头节点是循环在一起的

既然是双向的链表,那么在定义结构体的过程就和单链表有所不同,这里的指针部分应该有两个,prev和next部分,那么结构体的定义如下

typedef int LTDataType;
typedef struct ListNode
{
	LTDataType _data;
	struct ListNode* _next;
	struct ListNode* _prev;
}ListNode;

这样就实现了对链表的定义,prev和next均有

和单链表相同,双向循环链表也离不开增删查改的基本操作,那么这里我们一个一个来实现这些功能

链表的构建

链表的构建就是构建一个phead的哨兵位节点,这个过程还是很简单的

ListNode* ListCreate()
{
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	if (phead == NULL)
	{
		perror("malloc fail");
	}
	assert(phead);
	phead->_data = -1;
	phead->_next = phead;
	phead->_prev = phead;
	return phead;
}

链表的销毁

有创建就离不开销毁,销毁的过程和单链表基本相似,都是通过指针把一个节点单独拿出来,free后再置空,代码实现如下

void ListDestory(ListNode* pHead)
{
	assert(pHead);

	ListNode* cur = pHead->_next;
	while (cur != pHead)
	{
		ListNode* next = cur->_next;
		free(cur);
		cur = next;
	}
}

链表的输出

链表要打印在屏幕上的基本思路也和单链表基本一致,但需要注意的是,单链表的截止条件是当指针访问到空,而双向循环链表的条件是指针访问到头

void ListPrint(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->_next;
	printf("guard<==>");
	while (cur != pHead)
	{
		printf("%d<==>", cur->_data);
		cur = cur->_next;
	}
	printf("\n");
}

这样,双向循环链表也可以进行可视化管理了,那么下面就开始实现增删查改四大功能

链表的尾插

和单链表相似,但和它比起来更加简单,示意图如下:

在这里插入图片描述
那么代码是如何实现的?代码实现如下

ListNode* BuyListnode(LTDataType x)
{
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	if (phead == NULL)
	{
		perror("malloc fail");
	}
	assert(phead);
	phead->_data = x;
	phead->_next = NULL;
	phead->_prev = NULL;
	return phead;
}

void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newnode = BuyListnode(x);
	ListNode* prevnode = pHead->_prev;

	prevnode->_next = newnode;
	newnode->_prev = prevnode;

	newnode->_next = pHead;
	pHead->_prev = newnode;
}

对比单链表的尾插不难发现,这个过程比单链表简单了很多,首先对于空链表的判断就更为简单,同时双向循环链表由于存在双向箭头,导致插入是很便利的,这个过程在pos位置插入时候会体现出双向链表特有的优势

链表的尾删

在这里插入图片描述
代码实现如下

bool LTempty(ListNode* pHead)
{
	assert(pHead);
	return  pHead->_next == pHead;
}

void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(!LTempty(pHead));

	ListNode* tail = pHead->_prev;

	pHead->_prev = tail->_prev;
	tail->_prev->_next = pHead;

	free(tail);
	tail = NULL;
}

== 这里我们对bool返回值的函数进行补充==

bool值意为真和假,在进行尾删时,我们需要首先进行判断链表到底是否为空,但由于双向循环链表,于是我们并不能直观通过判断空指针,这里封装了一个函数,用来判断是否为空,如果为空就返回真,如果不为空就返回假,那么我们在函数体内断言只需要看它不为空即可

链表的头插

在这里插入图片描述
代码实现如下:

void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* next = pHead->_next;
	ListNode* newnode = BuyListnode(x);
	assert(newnode);

	pHead->_next = newnode;
	newnode->_prev = pHead;

	next->_prev = newnode;
	newnode->_next = next;
}

链表的头删

从头删中其实就体现出了双向链表的优越性

在这里插入图片描述

void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(!LTempty(pHead));
	ListNode* first = pHead->_next;
	ListNode* second = first->_next;

	pHead->_next = second;
	second->_prev = pHead;

	free(first);
	first = NULL;
}

链表的查找

和单链表基本类似,这里不多介绍

ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* cur = pHead->_next;

	while (cur->_data != x)
	{
		cur = cur->_next;
	}

	return cur;
}

链表的插入

在pos前插入的原理如下

在这里插入图片描述

从中和单链表一对比就能够体现出双向链表的优越的地方,在单链表中我们通常不在pos前插入,原因就是pos前面的节点并不方便寻找,而双向链表就解决了这个问题

代码实现如下

void ListInsert(ListNode* pos, LTDataType x)
{
	ListNode* posprev = pos->_prev;
	ListNode* newnode = BuyListnode(x);
	assert(newnode);
	assert(pos);

	posprev->_next = newnode;
	newnode->_prev = posprev;

	newnode->_next = pos;
	pos->_prev = newnode;
}

链表的删除

在这里插入图片描述
这里和单链表的删除相似,不多描述

void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* posprev = pos->_prev;
	ListNode* posnext = pos->_next;

	posprev->_next = posnext;
	posnext->_prev = posprev;

	free(pos);
	pos = NULL;
}

自此,双向循环链表就实现完毕了,和单链表比起来,双向循环链表确实有它独特强大的地方,而真正使用什么数据结构还需要根据实际情况进行设计

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

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

相关文章

RK3588平台开发系列讲解(Camera篇)V4L2 视频采集步骤

文章目录 一、V4L2 视频采集步骤1.1、查询设备能力1.2、设置采集参数1.3、请求帧缓冲1.4、映射帧缓冲1.5、启动视频采集1.6、停止视频采集沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章主要讲解V4L2 视频采集步骤。 一、V4L2 视频采集步骤 V4L2 视频采集的常用…

28. 在O(1)时间删除链表结点

链接&#xff1a; 链接 题目&#xff1a; 给定单向链表的一个节点指针&#xff0c;定义一个函数在O(1)时间删除该结点。 假设链表一定存在&#xff0c;并且该节点一定不是尾节点。 数据范围 链表长度 [1,500][1,500]。 样例 输入&#xff1a;链表 1->4->6->8删掉节点&…

指针和数组笔试题解析(最详细解析,没有之一)

指针和数组笔试题解析&#xff08;最详细解析&#xff0c;没有之一&#xff09; 前言1. 一维数组和指针相关笔试题2. 字符数组和指针相关笔试题2.1 题型一&#xff1a;2.2 题型二&#xff1a;2.3 题型三&#xff1a;2.4 题型四&#xff1a; 3. 指针和字符串相关面试题3.1 题型一…

华为云CodeArts Check代码检查插件3大版本使用指南

华为云CodeArts Check是自主研发的代码检查服务。为用户提供代码风格、通用质量与网络安全风险等丰富的检查能力&#xff0c;提供全面质量报告、便捷的问题闭环处理帮助企业有效管控代码质量&#xff0c;助力企业成功。 本插件致力于守护开发人员代码质量&#xff0c;成为开发…

【低代码专题方案】使用iPaaS平台下发数据,快捷集成MDM类型系统

01 场景背景 伴随着企业信息化建设日趋完善化、体系化&#xff0c;使用的应用系统越来越多&#xff0c;业务发展中沉淀了大量数据。主数据作为数据治理中枢&#xff0c;保存大量标准数据库&#xff0c;如何把庞大的数据下发到各个业务系统成了很棘手的问题。 传统的数据下发方…

英国大学生用AI写论文拿到1等成绩!ChatGPT写论文教程+提示词分享

今年期末季与往年的一大不同就是有了“哆啦C梦”——ChatGPT。过于高效智能的它&#xff0c;上线初期就引起了学术界关于“学术不端”的热烈讨论… 目录 01.用AI写论文获1等成绩 02.如何用ChatGPT辅助学习/写作 01.迅速get知识点 02.辅助理解内容 03.辅助起草论文大纲 0…

3Ds max入门教程:创建埃菲尔铁塔

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 创建任何细节并不总是必要的&#xff0c;有时在场景中您需要在后台添加一些元素&#xff0c;这给人的印象是事件正在特定地点发生。这种方法可以节省大量的时间和精力。因此&#xff0c;在本教程中&#xf…

SpringBoot整合ZooKeeper完整教程

目录 ZooKeeper简单介绍 一、安装zookeeper 二、springboot整合zookeeper ZooKeeper简单介绍 zookeeper是为分布式应用程序提供的高性能协调服务。zookeeper将命名、配置管理、同步和组服务等常用服务公开在一个简单的接口中&#xff0c;因此用户无需从头开始编写这些服务。可…

【SpringBoot3】--03.数据访问、基础特性(外部化和内部外配置、整合JUnit)

文章目录 SpringBoot3-数据访问1.整合SSM场景1.1创建SSM整合项目1.2配置数据源1.3配置MyBatis1.4CRUD编写 2.自动配置原理3.扩展&#xff1a;整合其他数据源3.1 Druid 数据源 SpringBoot3-基础特性1. SpringApplication1.1 自定义 banner1.2.自定义 SpringApplication1.3Fluent…

【ELK企业级日志分析系统】部署Filebeat+ELK详解

部署FilebeatELK详解 1. 部署Filebeat节点1.1 部署Apache服务1.2 部署Filebeat服务 2. filter插件2.1 grok正则捕获插件2.1.1 内置正则表达式调用2.1.2 自定义表达式调用2.1.3 设置正则表达式过滤条件 2.2 mutate数据修改插件2.2.1 Mutate过滤器常用的配置选项2.2.2 Mutate过滤…

ROS2学习(一 、ROS2安装)

ROS2官方安装 https://docs.ros.org/en/galactic/Installation/Ubuntu-Install-Debians.html 本来想找一下和ros1一样的安装指导文档&#xff0c;可以根据自己的系统选择。不过没有找到一个直接的说明教程。 不过在ROS2的各个版本安装说明里面有写支持哪些版本的系统。 比如我…

AWS 中文入门开发教学 45- Cloud9 - Node.js的开发与调试

知识点 在 Cloud9 环境中开发调试 Node.js 应用程序实战演习 $ mkdir expressweb $ cd expressweb $ npm init -y $ npm install express --save $ nano app.js ... $ curl http://httpbin.org/ip #查看当前主机的ip地址 $ node app.jscloud9还提供了一个非常好用的debug工具:…

23款迈巴赫S480新提车升级头等舱行政四座,五座改四座案例改装

新款的迈巴赫S480五座改四座成了必改的配置&#xff0c;改装的车主非常的多&#xff0c;不仅提供宽大的空间&#xff0c;而且后排中央扶手马鞍处还增加了一块Pad&#xff0c;用来控制空调&#xff0c;遮阳帘等。更有一众黑科技提供的后排乘客的舒适性。一起来看看五座迈巴赫S48…

Android 学习笔记: 三种基本布局的使用介绍

一、概述 布局是一种可用于放置很多控件的容器&#xff0c;它可以按照一定的规律调整内部控件的位置&#xff0c;从而编写出精美的界面。 布局的内部除了放置控件外&#xff0c;也可以放置布局。 三种常用布局 LinearLayoutRelativeLayoutFrameLayout 二、 LinearLayout …

列举几个常用的淘宝API接口(详情页面数据接口,评论接口,关键词搜索接口,店铺所有商品接口)

目前各大电商平台都有自己的开放平台&#xff0c;通过API接口开放本电商平台的相关数据和功能&#xff0c;以自由开放的姿态来占领更多的市场份额。也让更多的人能来电商市场分得一杯羹。 下面列举几个常用的API接口&#xff0c;量大择优hui&#xff0c; 注册key和secret可测…

基于SpringBoot+Vue的校园博客系统设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

mysql将某一列的数据根据固定字符拆分后判断存不存在

如果数据库的某个字段在存储时是用的逗号分割&#xff0c;如下图所示&#xff1a; 例如这张表中有一个字段是用的逗号分割&#xff0c;其实这种设计违背了数据库三范式设计原则&#xff0c;如何判断这一列的值包不包含某一个值&#xff1f; 方法一 用mysql自带的字符串函数去判…

浅析路灯电击安全问题与选型

摘要&#xff1a;介绍了路灯设计安全防护的几个要点&#xff0c;简单分析了路灯低压配电接地系统形式TN-S和TT的选择及剩余电流保护装置在路灯设计中设置的必要性&#xff0c;列举了路灯灯具和灯杆的一些安全技术质量要求。 关键词&#xff1a;路灯配电系统接地形式&#xff1b…

ModaHub魔搭社区:AI原生云向量数据库Zilliz Cloud与 LangChain 集成搭建智能文档问答系统

目录 准备工作 主要参数 准备数据 开始提问 本文将演示如何使用 Zilliz Cloud 和 LangChain 搭建基于大语言模型(LLM)的问答系统。在本例中,我们将使用一个 1 CU 的 Cluster,还将使用 OpenAI 的 Embedding API 来获取指定文本的向量表示。现在就让我们开始吧。 准备工作…

谈谈对SpringMVC的理解

1、SpringMVC是属于SpringFramework生态里面的一个模块&#xff0c;它是在Servelet基础上构建的&#xff0c;并且使用了MVC模式设计的一个Web框架&#xff1b; 2、它的主要目的是为了简化传统模式下的Serveletjsp的开发模式&#xff0c;其次SpringMVC的架构模式是对于Java的web…