数据结构——带头循环双向链表(List)

1、带头双向循环链表介绍

        在上一篇博客中我们提到了链表有三个特性,可以组合成为8种不同类型的链表。单链表是其中比较重要的一种,那么这次我们选择和带头双向循环链表会会面,这样我们就见识过了所有三种特性的呈现。

        带头双向循环链表,听起来仿佛是一个很复杂的结构,但是真正了解后就发现,这种稍微复杂一点的结构实际上为链表提供了完善的功能,使得我们在操作链表时变得反而更简单。而这种链表因为自身结构复杂,功能结构完善,所以经常成为一个独立的数据存储结构,用来单独存储数据。

2、带头双向循环链表工程

对于一个带头双向循环链表工程,我们一般模式需要分为三部分。

        List.h 为头文件,其中包含库函数头文件的包含,顺序表结构体的定义与声明,接口函数的声明。

        List.c 包含接口函数的定义。

        Test.c 是我们的测试源文件,从这里进入main函数。

2.1 链表的定义

        为了实现链表的双向结构,我们需要从定义入手。单链表的链表结点定义是一个指向下一个结点的指针,而双向链表则需要两个指针,分别指向上一个和下一个结点。

typedef int LTDataType;typedef struct ListNode
{LTDataType val;struct ListNode* prev;struct ListNode* next;
}LTNode;

2.2 链表的函数接口

        用带头双向循环链表管理数据也需要一些常用的增删查改接口,但是因为有哨兵位的存在,所以在传参的时候我们只需要传递一级指针即可。

2.2.1 链表结点申请

        在链表插入与初始化的时候我们需要申请出一个结点,然后链接在链表之中。所以我们可以和单链表一样,把结点的申请封装成为一个函数。

LTNode* CreateLTNode(LTDataType x)
{LTNode* tmp = (LTNode*)malloc(sizeof(LTNode));if (tmp == NULL){perror("malloc fail");exit(-1);}tmp->val = x;tmp->next = NULL;tmp->prev = NULL;return tmp;
}

2.2.2 链表的初始化

        因为我们现在要创建的链表是一个带头链表,所以需要对一个链表进行初始化,即创造出一个哨兵结点,剩余的链表结点均在哨兵位之后进行操作。初始化无需参数,最后返回一个链表的头结点即可。

        对于哨兵结点而言,其值没有具体的意义,所以我们将其随便写作-1。因为我们的链表是双向循环链表,双向循环链表的尾结点的下一个结点指向头结点,头结点的上一个结点指向尾结点。因此,初始化的时候我们就要对哨兵结点正确赋值,保证即使空链表也满足要求的结构。因此,我们让哨兵结点的next和prev都指向自身即可。

LTNode* LTInit()
{LTNode* tmp = CreateLTNode(-1);tmp->next = tmp;tmp->prev = tmp;return tmp;
}

2.2.3 链表的结点插入

        带头双向循环链表的插入方式分为:头插,即在链表哨兵位之后插入结点;尾插,即在链表尾结点后插入一个结点;随机插入,即在指定结点前插入结点。

        对于带头双向循环链表的节点插入而言,由于双向循环的特性任何一个节点都可以轻松的找到自己的前驱结点,所以使得插入不再需要遍历链表。又由于其带头的特性,也使得我们无需考虑链表是否为空的情况。唯一需要注意的就是结点链接的顺序,避免出现修改了结点指针而找不到对应位置的结点,当然,如果给出一个临时变量存储对应的结点,顺序也可以无需考虑。

2.2.3.1 链表的头插

        带头双向循环链表的头插这里我采用定义一个临时变量而不考虑链接顺序。

void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* tmp = CreateLTNode(x);tmp->next = phead->next;tmp->next->prev = tmp;phead->next = tmp;tmp->prev = phead;
}
2.2.3.2 链表的尾插

        带头双向循环链表的尾插我同样采用定义一个临时变量而不考虑链接顺序。 

void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* tail = phead->prev;LTNode* tmp = CreateLTNode(x);tmp->next = phead;tmp->prev = tail;tail->next = tmp;phead->prev = tmp;
}
2.2.3.3 链表的随机插入

        带头双向循环链表的随机插入指在指定结点pos后插入一个结点。 

//在pos前插入
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* tmp = CreateLTNode(x);tmp->next = pos;tmp->prev = pos->prev;tmp->prev->next = tmp;pos->prev = tmp;
}

2.2.4 链表的结点删除

        带头双向循环链表的删除方式分为:头删,即删除在链表哨兵位之后的结点;尾删,即删除链表尾结点;随机插入,即删除指定结点。

        同样的,由于双向循环的特性使得不再需要遍历链表寻找前驱结点,所以删除的时候只需要将待删除结点的前后结点链接起来,然后释放掉待删除结点。

2.2.4.1 链表的头删
void LTPopBack(LTNode* phead)
{assert(phead);assert(phead->next != phead);LTNode* tail = phead->prev;phead->prev = tail->prev;tail->prev->next = phead;free(tail);tail = NULL;
}
2.2.4.2 链表的尾删
void LTPopFront(LTNode* phead)
{assert(phead);assert(phead->next != phead);LTNode* first = phead->next;phead->next = first->next;first->prev = phead;free(first);first = NULL;
}
2.2.4.3 链表的随机删除
//删除pos位置节点
void LTErase(LTNode* pos)
{assert(pos);LTNode* prepos = pos->prev;prepos->next = pos->next;pos->next->prev = prepos;free(pos);pos = NULL;
}

2.2.5 链表的查找

        用于查找指定值的结点并返回,和单链表一样,只是遍历结束条件是遍历到了哨兵位。

LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){if (cur->val == x){return cur;}else{cur = cur->next;}}return NULL;
}

2.2.6 链表的打印

        很简单的接口,遍历方法可以参照链表的查找。

void LTPrint(LTNode* phead)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){printf("%d<=>", cur->val);cur = cur->next;}printf("NULL\n");
}

2.2.7 链表的销毁

        销毁链表需要遍历链表,释放每个节点,最后释放哨兵位即可。注意,这里的销毁只是释放了所有结点,因为是一级指针传参,所以说明主函数中链表的指针在销毁后成为了野指针,需要函数调用者自行置空。

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

3、链表反思

        在完成了单链表和带头双向循环链表后,需要深刻理解到不同的特性对于我们写代码有哪些限制与遍历,从而在合适的场景下合理地做出选择。

        总结一下同为线性表的顺序表与链表之间的优劣与区别。

        对于顺序表,先说说优势。顺序表最大的特征就是物理储存空间连续,这就代表其可以支持随机访问,无论是哪个位置的数据,都可以以O(1)的代价获取。储存空间连续还使得操作系统在访问其数据时是非常高效的,操作系统读取一次连续的空间对其数据的覆盖率很高,缓存利用率高。顺序表同样也有劣势,其插入数据时可能需要搬动数据,使得插入效率低,同时空间需要扩容,就面临着空间浪费,使得空间存在一定程度的浪费。

        对于链表而言,其优势是空间分配非常灵活,不存在浪费的情况,多一个数据就开辟一个结点,删除数据就即刻释放空间。插入和删除不存在挪动数据的情况,只需要链接指针。但是由于其储存空间不连续,所以链表也有一定劣势。由于寻找第k个结点只能遍历,链表访问数据的代价为O(N),对比线性表很大。除此之外,不连续的物理空间储存使得操作系统缓存时对链表数据覆盖率不高,缓存利用率较低。

        由此看来,顺序表和链表二者互为补充,所以我们在选择方面要有所区别。对于需要大量访问的数据而言,顺序表效率明显更高;而对于需要频繁插入删除的数据,链表由于灵动的空间布局而略胜一筹。对二者的合理谋划发挥最佳效果便是每个学习者需要不断探索的“内功”了。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于C#实现优先队列

一、堆结构 1.1性质 堆是一种很松散的序结构树&#xff0c;只保存了父节点和孩子节点的大小关系&#xff0c;并不规定左右孩子的大小&#xff0c;不像排序树那样严格&#xff0c;又因为堆是一种完全二叉树&#xff0c;设节点为 i,则 i/2 是 i 的父节点&#xff0c;2i 是 i 的…

Pytorch 基于 deeplabv3_resnet50 迁移训练自己的图像语义分割模型

一、图像语义分割 图像语义分割是计算机视觉领域的一项重要任务&#xff0c;旨在将图像中的每个像素分配到其所属的语义类别&#xff0c;从而实现对图像内容的细粒度理解。与目标检测不同&#xff0c;图像语义分割要求对图像中的每个像素进行分类&#xff0c;而不仅仅是确定物…

图形数据库的实战应用:如何在 Neo4j 中有效管理复杂关系

关系数据库管理系统( RDBMS ) 代表了最先进的技术&#xff0c;这在一定程度上要归功于其由周边技术、工具和广泛的专业技能组成的完善的生态系统。 在这个涵盖信息技术(IT) 和运营技术(OT) 的技术革命时代&#xff0c;人们普遍认识到性能方面出现了重大挑战&#xff0c;特别是…

【广州华锐互动】Web3D云展编辑器能为展览行业带来哪些便利?

在数字时代中&#xff0c;传统的展览方式正在被全新的技术和工具所颠覆。其中&#xff0c;最具有革新意义的就是Web3D云展编辑器。这种编辑器以其强大的功能和灵活的应用&#xff0c;正在为展览设计带来革命性的变化。 广州华锐互动开发的Web3D云展编辑器是一种专门用于创建、编…

关于网站的favicon.ico图标的设置需要注意的几点

01-必须在网页的head标签中放上对icon图标的说明语句&#xff1a; 比如下面这样的语句&#xff1a; <link rel"shortcut icon" href"/favicon.ico">否则&#xff0c;浏览器虽然能读到图标&#xff0c;但是不会把图标显示在标签上。 02-为了和本地开…

DHCP、ARP、FTP、DNS、VRRP、STP、报文交互流程

目录 一、DHCP 1、DHCP终结 1、DHCP discover 2、DHCP offer 3、DHCP request 4、DHCP ack 5、DHCP request 6、DHCP 续租 2、DHCP终结 二、ARP 1、ARP类型 动态ARP 静态ARP ARP代理 ARP代理的分类&#xff1a;路由式代理、VLAN内的ARP代理、VLAN间的ARP代理。 6…

【Hadoop】分布式文件系统 HDFS

目录 一、介绍二、HDFS设计原理2.1 HDFS 架构2.2 数据复制复制的实现原理 三、HDFS的特点四、图解HDFS存储原理1. 写过程2. 读过程3. HDFS故障类型和其检测方法故障类型和其检测方法读写故障的处理DataNode 故障处理副本布局策略 一、介绍 HDFS &#xff08;Hadoop Distribute…

electron调用dll问题总汇

通过一天的调试安装&#xff0c;electron调用dll成功&#xff0c;先列出当前的环境&#xff1a;node版本: 18.12.0&#xff0c;32位的&#xff08;因为dll为32位的&#xff09; VS2019 python node-gyp 1、首先要查看报错原因&#xff0c;通常在某一行会有提示&#xff0c;常…

C#常见的设计模式-行为型模式

前言 行为型模式是面向对象设计中的一类设计模式&#xff0c;它关注对象之间的通信和相互作用&#xff0c;以实现特定的行为或功能。在C#中&#xff0c;有许多常见的行为型模式&#xff0c;下面将对其中10种行为型模式进行介绍&#xff0c;并给出相应的代码示例。 目录 前言1.…

什么是网络爬虫技术?它的重要用途有哪些?

网络爬虫&#xff08;Web Crawler&#xff09;是一种自动化的网页浏览程序&#xff0c;能够根据一定的规则和算法&#xff0c;从互联网上抓取和收集数据。网络爬虫技术是随着互联网的发展而逐渐成熟的一种技术&#xff0c;它在搜索引擎、数据挖掘、信息处理等领域发挥着越来越重…

线性分组码的奇偶校验矩阵均匀性分析

回顾信道编解码知识&#xff0c;我们知道信道编码要求编码具有检纠错能力&#xff0c;作为FEC&#xff08;forward error correction&#xff09;前向纠错编码的一类&#xff0c;线性分组码表示校验位与信息位的关系能够线性表示。 在这篇文章中&#xff0c;并不是要讨论信道编…

【古月居《ros入门21讲》学习笔记】09_订阅者Subscriber的编程实现

目录 说明&#xff1a; 1. 话题模型 图示 说明 2. 实现过程&#xff08;C&#xff09; 创建订阅者代码&#xff08;C&#xff09; 配置发布者代码编译规则 编译并运行 编译 运行 3. 实现过程&#xff08;Python&#xff09; 创建订阅者代码&#xff08;Python&…

MYSQL索引使用注意事项

索引使用注意事项&#xff1a; 1.索引列运算 不要在索引列上进行运算操作&#xff0c;否则索引将失效&#xff1b; 2.字符串不加引号 字符串类型使用时&#xff0c;不加引号&#xff0c;否则索引将失效&#xff1b; 3.模糊查询 如果仅仅是尾部模糊匹配&#xff0c;索引将不会失…

WSL中安装的Pycharm如何在Windows的开始菜单中新建图标?或WSL中的Pycharm经常花屏

WSL中安装的Pycharm如何在Windows的开始菜单中新建图标&#xff1f;或WSL中的Pycharm经常花屏 ⚙️1.软件环境⚙️&#x1f50d;2.问题描述&#x1f50d;&#x1f421;3.解决方法&#x1f421;&#x1f914;4.结果预览&#x1f914; ⚙️1.软件环境⚙️ Windows10 教育版64位 W…

【云栖 2023】姜伟华:Hologres Serverless 之路——揭秘弹性计算组

云布道师 本文根据 2023 云栖大会演讲实录整理而成&#xff0c;演讲信息如下&#xff1a; 演讲人&#xff1a;姜伟华 | 阿里云计算平台事业部资深技术专家、阿里云实时数仓 Hologres 研发负责人 演讲主题&#xff1a;Hologres Serverless 之路——揭秘弹性计算组 实时化成为…

牛客算法心得——abb(dp)

大家好&#xff0c;我是晴天学长&#xff0c;传智杯的题&#xff0c;我准备写一个题解&#xff0c;需要的小伙伴可以关注支持一下哦&#xff01;后续会继续更新的。&#x1f4aa;&#x1f4aa;&#x1f4aa; 1) .abb leafee 最近爱上了 abb 型语句&#xff0c;比如“叠词词”、…

【物联网与大数据应用】Hadoop数据处理

Hadoop是目前最成熟的大数据处理技术。Hadoop利用分而治之的思想为大数据提供了一整套解决方案&#xff0c;如分布式文件系统HDFS、分布式计算框架MapReduce、NoSQL数据库HBase、数据仓库工具Hive等。 Hadoop的两个核心解决了数据存储问题&#xff08;HDFS分布式文件系统&#…

【Java学习笔记】75 - 算法优化入门 - 马踏棋盘问题

一、意义 1.算法是程序的灵魂&#xff0c;为什么有些程序可以在海量数据计算时&#xff0c;依然保持高速计算? 2.拿老韩实际工作经历来说&#xff0c;在Unix下开发服务器程序&#xff0c;功能是要支持上千万人同时在线&#xff0c;在上线前&#xff0c; 做内测&#xff0c;一…

常用服务注册中心与发现(Eurake、zookeeper、Nacos)笔记(一)基础概念

基础概念 注册中心 在服务治理框架中&#xff0c;通常都会构建一个注册中心&#xff0c;每个服务单元向注册中心登记自己提供的服务&#xff0c;将主机与端口号、版本号、通信协议等一些附加信息告知注册中心&#xff0c;注册中心按照服务名分类组织服务清单&#xff0c;服务…

OpenGL之Mesa3D编译for Ubuntu20.04(三十六)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药. 更多原创,欢迎关注:Android…