数据结构(4)——带哨兵位循环双向链表
目录
前言
一、带哨兵的循环双向链表是什么
二、链表的实现
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;
}
总结
这里对循环有哨兵位双链表进行基本功能的编写和学习。
相关文章:
数据结构(4)——带哨兵位循环双向链表
目录 前言 一、带哨兵的循环双向链表是什么 二、链表的实现 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删除任意节点 …...
【MyBatis】MyBatis 操作数据库(入门)
文章目录 前言一、什么是MyBatis?二、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…...
Numpy进行数组函数操作
在编程语言中,数组(Array)是最常用的数据结构之一,它可以存储一系列相同类型的元素,并且通过索引来访问或修改这些元素。在Python中,数组不仅可以通过内置的list数据类型实现,还可以借助第三方库,如NumPy来操作多维数组。掌握数组的内置函数和常用方法是成为熟练程序员…...
高速电路中的存储器应用与设计四
5 SRAM介绍及其应用要点 DRAM的性能在很大程度上受到刷新操作的影响,而SRAM则不涉及刷新,因此在相同时钟频率的条件下,SRAM的性能远高于DRAM。 SRAM的缺点是集成度低、容量小、功耗大、价格高。 在应用的场合上,SRAM毫不逊色于…...
Vue2 项目将网页内容转换为图片并保存到本地
🌟 前言 欢迎来到我的技术小宇宙!🌌 这里不仅是我记录技术点滴的后花园,也是我分享学习心得和项目经验的乐园。📚 无论你是技术小白还是资深大牛,这里总有一些内容能触动你的好奇心。🔍 &#x…...
汽车加气站操作工证书报考条件是什么?
关于汽车加气站操作工的资格证书: 一、核心证书要求 CNG充装人员上岗证 这是加气站加气工的核心资质证书,需通过专业培训并考核。该证书由相关部门颁发,证明持证人具备从事CNG(压缩天然气)充装操作的专业技能…...
动态规划--线性规划
一、https://www.lanqiao.cn/problems/3512/learning/ 解题步骤:1.dp[i]表示以i结尾最长接龙数列长度 2.每读入一个数字x...y,关注头尾的x,y来更新dp[y] 3.dp【y】 max(dp【y】,dp【x】1):如果当前数字接…...
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中加载影像数据,符号系统中渲染参数的解析
今天遇到一个很有意思的问题,故记录下来,以作参考和后续的研究。欢迎随时沟通交流。如果表达错误或误导,请各位指正。 正文 当我们拿到一幅成果影像数据的时候,在不同的GIS软件中会有不同效果呈现,但这其实是影像是…...
GAMMA数据处理(十)
今天向别人请教了一个问题,刚无意中搜索到了一模一样的问题 不知道这个怎么解决... ok 解决了 有一个GAMMA的命令可转换 但是很奇怪 完全对不上 转换出来的行列号 不知道为啥 再试试 是因为经纬度坐标的小数点位数 de as...
Spring Boot属性设置方法及优先级完整说明+表格对比
Spring Boot属性设置方法及优先级完整说明 官网参考: https://docs.spring.io/spring-boot/3.4-SNAPSHOT/reference/features/external-config.html#features.external-config.files 属性设置方法优先级顺序(从高到低) 命令行参数…...
基于改进粒子群算法的多目标分布式电源选址定容规划(附带Matlab代码)
通过分析分布式电源对配电网的影响,以有功功率损耗、电压质量及分布式电源总容量为优化目标,基于模糊理论建立了分布式电源在配电网中选址定容的多目标优化模型,并提出了一种改进粒子群算法进行求解。在算例仿真中,基于IEEE-14标准…...
SAP 学习笔记 - 系统移行业务 - MALSY(由Excel 移行到SAP 的收费工具)
以前有关移行,也写过一些文章,比如 SAP 学习笔记 - 系统移行业务 - Migration cockpit工具 - 移行Material(品目)-CSDN博客 SAP 学习笔记 - 系统移行业务 - Migration cockpit工具2 - Lot导入_sap cockpit-CSDN博客 SAP学习笔记…...
2025美国网络专线国内服务商推荐
在海外业务竞争加剧的背景下,稳定高效的美国网络专线已成为外贸企业、跨国电商及跨国企业的刚需。面对复杂的国际网络环境和严苛的业务要求,国内服务商Ogcloud凭借其创新的SD-WAN技术架构与全球化网络布局,正成为企业拓展北美市场的优选合作伙…...
如何正确地在 Postman 中添加认证 Token?
在 Postman 中设置 token。我们知道 HTTP 是无状态的。token 是保持用户的登录状态或者其他数据的一种机制,从而让用户在不同页面之间保持一致的体验。 在 Postman 中添加认证 token 教程...
c++-引用
一、引用的基本概念 引用是C中一种特殊的变量别名机制,本质上是指针常量(编译器自动解引用),但语法更简洁安全。 核心特性: 必须初始化:引用在定义时必须绑定到一个已存在的对象。 类型严格匹配…...
SpringCould微服务架构之Docker(6)
容器的基本命令: 1. docker exec :进入容器执行命令 2. docker logs: -f 持续查看容器的运行日志 3. docker ps:查看所有运行的容器和状态 案例:创建运行一个容Nginx容器 docker run--name myNginx -p 80:80 -d nginx 命…...
Linux|gitlab|二进制快速安装部署gitlab-ce教程
一、 gitlab二进制文件下载地址: 官方网站: gitlab/gitlab-ce - Packages packages.gitlab.com 清华镜像站: Index of /gitlab-ce/yum/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror gitlab分为ce也就是社区版本和ee版本,…...
java网盘项目,文件和文件夹用两个表还是一个表,两个表理论查询效率慢了为啥要用,有啥优势
java网盘项目,文件和文件夹用两个表还是一个表,两个表理论查询效率慢了为啥要用,有啥优势 根据网盘系统设计经验与数据库优化原则,独立文件夹表和文件表的设计在复杂场景下具有显著优势。以下是分表方案的核心价值与效率优化策略…...
NixVis 开源轻量级 Nginx 日志分析工具
NixVis NixVis 是一款基于 Go 语言开发的、开源轻量级 Nginx 日志分析工具,专为自部署场景设计。它提供直观的数据可视化和全面的统计分析功能,帮助您实时监控网站流量、访问来源和地理分布等关键指标,无需复杂配置即可快速部署使用。 演示…...
vscode正则表达式使用
小标题 ^\d.\d.\d\s.*$ ^表示匹配字符串的开头。\d\.\d\.\d表示匹配一到多个数字,接着一个小数点,再接着一到多个数字,然后又一个小数点和一到多个数字,用来匹配类似 “2.1.1” 这样的标题号部分。\s表示匹配一个空格。.*表示匹配…...
OpenAI API - Realtime 实时
文章目录 实时 API(Beta)使用实时API入门示例应用合作伙伴集成 用例通过 WebRTC 连接概述连接详情创建一个临时token发送和接收事件 使用 WebSockets 连接概述连接详情 实时对话Beta实时语音到语音会话会话生命周期事件文本输入和输出音频输入和输出语音…...
PE文件(十三)资源表
所谓的资源也就是我们之前学的MFC中的对话框,按钮,编辑框之类的东西。不仅MFC有资源,我们平时熟悉的控制台程序也有资源 当我们平时写一些程序或者木马时,我们通常对其定义一个随机的名称或者路径,然后再向外界进行释…...
丝杆升降机行程控制:精准运行的奥秘
丝杆升降机作为机械传动领域的 “得力干将”,在环保设备、工业生产线、建筑施工等众多场景中发挥着关键作用。其能够实现重物的升降、平移等操作,而行程控制对于丝杆升降机而言,就如同给机器设定了行动边界,不仅关乎设备能否精准达…...
为什么使用Flask + uWSGI + Nginx 部署服务?
概述 在Python开发的web应用中,我们通常能够看到flask、uWSGI、Nginx出现在一起,他们之间的关系是什么?为什么总是被应用在一起?  三者共同使用为了实现一个目的:客户端向服务端发送数据请求,服…...
力扣.旋转矩阵Ⅱ
59. 螺旋矩阵 II - 力扣(LeetCode) 代码区: 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 使用入门
资源 下载资源: https://download.csdn.net/download/wangjun_huster/90547193 下载破解: https://download.csdn.net/download/wangjun_huster/90547551 安装 https://www.bilibili.com/list/ml3403866295?oid925751664&bvidBV1CT4y1u7LB 入门…...
对内核fork进程中写时复制的理解记录
前言 文章写于学习Redis时对aof后台重写中写时复制的疑问 一、感到不理解的歧义 在部分技术文档中(以小林的文章为例),对写时复制后的内存权限存在如歧义: ! 二、正确技术表述 根据Linux内核实现(5.15版本&#x…...
ubuntu 升级补丁,备份备份备份
一、常规软件包更新(安全补丁和软件升级) 更新软件包列表 从软件源服务器获取最新的软件包信息: sudo apt update升级已安装的软件包 安装所有可用的更新(安全补丁、功能更新): sudo apt upgrade处理依赖…...
HarmonyOS-ArkUI Navigation (导航组件)-第一部分
导航组件主要实现页面间以及组件内部的界面跳转,支持在不同的组件间进行参数的传递,提供灵活的跳转栈操作,从而便捷的实现对不同页面的访问和复用。 我们之前学习过Tabs组件,这个组件里面也有支持跳转的方式,Navigati…...
