数据结构——带头循环双向链表(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.mzph.cn/news/171870.shtml

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

相关文章

安卓使用MediaRecorder录制音频的详细使用

安卓使用MediaRecorder录制音频的详细使用 文章目录 安卓使用MediaRecorder录制音频的详细使用引言使用 MediaRecorder 的步骤常见问题及解决思路无法访问存储卡目录录制的音频文件没有声音录制过程中出现异常MediaRecorder无法正常启动录制的音质或者画质很差录制的文件无法播…

Object.is和====和==的区别

Object.is() 方法和比较操作符 、 用于比较两个值的相等性&#xff0c;但它们在比较方式和行为上有一些区别。Object.is() 方法是严格相等比较&#xff0c;而 操作符也是严格相等比较&#xff0c;但 操作符是相等比较。 严格相等比较&#xff08; &#xff09; 要求比较的…

HONOR荣耀MagicBook 15 2021款 锐龙版R5(BMH-WFQ9HN)原厂Windows11预装OEM系统含F10智能还原

链接&#xff1a;https://pan.baidu.com/s/1faYtC5BIDC2lsV_JSMI96A?pwdj302 提取码&#xff1a;j302 原厂系统Windows11.22H2工厂模式安装包,含F10一键智能还原&#xff0c;自带所有驱动、出厂主题壁纸、系统属性专属LOGO标志、Office办公软件、荣耀 电脑管家等预装程序 …

process control 化学工程 需要用到MATLAB的Simulink功能

process control 化学工程 需要用到MATLAB的Simulink功能 所有问题需要的matlab simulink 模型 WeChat: ye1-6688 The riser tube brings in contact the recirculating catalyst with the feed oil, which then vaporizes and splits to lighter components as it flows up th…

服务器下db(数据库)的执行

1、查看 select * from xxxx&#xff08;表名&#xff09; where xxx&#xff08;列表&#xff09;1 and.......正常写就行 2、插入 如果你想要在 SELECT INSERT INTO … SELECT 语句中将部分列保持不变,只改变一两列的值,可以在 语句中直接设置目标列的值,而其他列从源表中…

【代码随想录】算法训练计划31

贪心 1、455. 分发饼干 题目&#xff1a; 假设你是一位很棒的家长&#xff0c;想要给你的孩子们一些小饼干。但是&#xff0c;每个孩子最多只能给一块饼干。 对每个孩子 i&#xff0c;都有一个胃口值 g[i]&#xff0c;这是能让孩子们满足胃口的饼干的最小尺寸&#xff1b;并且…

【C++】类型转换 ③ ( 重新解释类型转换 reinterpret_cast | 指针类型数据转换 )

文章目录 一、重新解释类型转换 reinterpret_cast1、指针数据类型转换 - C 语言隐式类型转换报错 ( 转换失败 )2、指针数据类型转换 - C 语言显示类型强制转换 ( 转换成功 )3、指针数据类型转换 - C 静态类型转换 static_cast ( 转换失败 )4、指针数据类型转换 - C 重新解释类型…

Simulink 的代数环

代数环, 就是由于模型的输出反馈到模块或子系统先的某个输入端, 如果这个输入是直接馈入的, 那么二者在同一个采样点内需得到求解, 但又互相依赖, 哪一方都不能完成求解过程, 使得解算器无法解算导致错误产生, 这样的情况称为代数环。 一旦 Simulink 遇到代数环, 将根据 Confi…

Binlog vs. Redo Log:数据库日志的较劲【高级】

&#x1f38f;&#xff1a;你只管努力&#xff0c;剩下的交给时间 &#x1f3e0; &#xff1a;小破站 Binlog vs. Redo Log&#xff1a;数据库日志的较劲【高级】 前言第一&#xff1a;事务的生命周期事务的生命周期Binlog和Redo Log记录事务的一致性和持久性Binlog的记录过程R…

二叉树--算法题总结

1、利用层序遍历的产生的字符串来创建二叉树 /*** 使用层序遍历的字符串创建二叉树* param treeInfo* return*/public static TreeNode generateTreeNodeSecond(String treeInfo) {LinkedList<TreeNode> treeNodeLinkedList new LinkedList<>();if(StringUtils.is…

Flask 使用Jinja2模板引擎

Jinja2&#xff0c;由Flask框架的创作者开发&#xff0c;是一款功能丰富的模板引擎&#xff0c;以其完整的Unicode支持、灵活性、高效性和安全性而备受推崇。最初受Django模板引擎启发&#xff0c;Jinja2为Flask提供了强大的模板支持&#xff0c;后来也成为其他项目的首选。在本…

@JsonSerialize注解的使用

使用场景 ** 在开发中&#xff0c;有时候某些字段需要特殊处理&#xff0c;比如我们有一个日期字段&#xff0c;当日期为NULL时给前端不返回NULL而返回为其他等信息&#xff0c;就需要自定义字段的序列化。这就是JsonSerialize的用处 ** 1&#xff1a;先写一个指定的处理类 pa…

python环境搭建-yolo代码跑通-呕心沥血制作(告别报错no module named torch)

安装软件 安装过的可以查看有没有添加环境变量 好的! 我们发车! 如果你想方便快捷的跑通大型项目,那么必须安装以下两个软件: 1.pycharm2.anaconda对应作用: pycharm:专门用来跑通python项目的软件,相当于一个编辑器,可以debug调试,可以接受远程链接调试!anaconda:专…

英伟达不同系列GPU介绍

英伟达有以下几个系列的产品线&#xff0c;并介绍它们的特点和主要应用领域&#xff1a; 1. GeForce系列&#xff08;G系列&#xff09;&#xff1a; - 特点&#xff1a;GeForce系列是英伟达主打的消费级GPU产品线&#xff0c;注重提供高性能的图形处理能力和游戏特性。它们…

2023年初中生古诗文大会复选最后6天备考策略和更新的在线模拟题

今天是2023年11月26日&#xff0c;星期日&#xff0c;距离2023年第八届上海市中学生古诗文大会复选&#xff08;复赛&#xff09;还有六天&#xff08;2023年12月2日上午举办&#xff09;&#xff0c;相信各位晋级的小学霸们正在繁忙的学业之余抓紧备考。 为了帮助孩子们更有效…

thinkphp最新开发的物业管理系统 缴费管理、停车管理、收费管理、值班管理

物业费&#xff0c;水电燃气费&#xff0c;电梯费&#xff0c;租金&#xff0c;临时收费等多种收费规则完全自定义&#xff0c;账单自动生成&#xff0c;无需人工计算 实时数据互通&#xff1a;一键报事报修&#xff0c;购买车辆月卡&#xff0c;管理家人信息&#xff0c;参加物…

idea spring initializr创建项目报错

闲来无事就想搞个项目练练手&#xff0c;没想到直接给我卡在项目创建上了&#xff0c;一个个问题最终迎刃而解。 1.上来就给我报了个maven的错 未解析的插件: ‘org.apache.maven.plugins:maven-resources-plugin:3.3.1’ 不慌&#xff0c;应该是maven的路径有问题&#xff0c…

Redis缓存淘汰策略

Redis缓存淘汰策略 1、各种面试题 生产上你们的redis内存设置多少?如何配置、修改redis的内存大小如果内存满了你怎么办&#xff1f;redis清理内存的方式?定期删除和惰性删除了解过吗&#xff1f;redis缓存淘汰策略有哪些?分别是什么?你用哪个?redis的LRU了解过吗?请手…

AMESim与MATLAB联合仿真demo

本文是AMESim与MATLAB联合仿真的demo&#xff0c;记录一下如何进行联合仿真。 AMESim与MATLAB联合仿真可以大幅度提高工作效率。 author&#xff1a;xiao黄 缓慢而坚定的生长 csdn:https://blog.csdn.net/Python_Matlab?typeblog主页传送门 博主的联合仿真环境如下&#xff…

代码随想录二刷 | 哈希表 | 快乐数

代码随想录二刷 &#xff5c; 哈希表 &#xff5c; 快乐数 题目描述解题思路 & 代码实现 题目描述 202.快乐数 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。 …