C数据结构:双向链表(带头循环)

前言

链表分多种,分别为

不带头不循环单向链表、不带头循环单向链表、带头循环单向链表、带头不循环单向链表

不带头不循环双向链表、不带头循环双向链表、带头循环双向链表、带头不循环双向链表

一共八种

在前一篇博客中完成的单链表即为不带头不循环单向链表

而今天要完成的是带头循环双向链表

 

链表的数据结构

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

单向链表因为是单一指向,所以只需要一个next指针指向下一个节点即可

但现在要实现的是双向链表,所以我们还需要多一个指针指向前一个节点,所以相比单链表来说多了一个prev指向前面一个节点

链表的实现

下面开始链表的实现

链表的初始化

LTNode* LTInit()
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc fail\n");exit(1);}newnode->data = -1;newnode->next = newnode->prev = newnode;return newnode;
}
void LTInit(LTNode** pphead)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc fail\n");exit(1);}newnode->data = -1;newnode->next = newnode->prev = newnode;*pphead = newnode;
}

这里链表有两种初始化的方法,一种方法是在传给函数参数的时候将链表头节点的地址传过来(也就是使用二级指针) ,这样直接在函数内部改变pphead即可

第二种方法就是将我们创建的新节点作为返回值传出去,在调用函数的地方使用一个值接收即可

这里两种方法都好,但是我会选择使用第二种方法

因为下面我们写的打印、尾插、删除等等函数都使用一级指针,如果在这个地方单独的使用一个二级指针会显得有点突兀,所以为了让未来的我们自己或者其他使用者能够简单的使用这个链表,尽量格式都统一了

这里因为是带头的单链表,我们初始化出来的带头节点我们可以把它叫做:哨兵位,哨兵位可以把它理解为一个放哨的,它不存储任何有效值,它只作为头节点

所以这里初始化的时候直接给它赋值为-1

newnode->next = newnode->prev = NULL;

上面这么做是错误的❌

因为我们写的是循环的链表,这样指向空怎么能算循环呢?

应该要让哨兵位的两个指针都指向自己,这就是一个合格的带头循环双向指针

当然为了简化我们后面获取节点的难度,我们可以直接写一个函数封装获取节点的方法

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

 这样我们的初始化代码也可以很好的简化了

LTNode* LTInit()
{//LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));//if (newnode == NULL)//{//	perror("malloc fail\n");//	exit(1);//}//newnode->data = -1;//newnode->next = newnode->prev = newnode;LTNode* newnode = LTBuyNode(-1);return newnode;
}

链表的打印

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

打印每一个节点我们就一定需要循环来解决

但是这个循环条件和单链表的循环条件是不同的

不带头不循环单向链表:

while (pcur != NULL)

 带头循环双向链表:

while (pcur != phead)

单向链表的最后会指向NULL指针,但是带头循环双向链表不会,它最后会循环的指回头节点

所以判断循环结束的条件就是判断pcur是否回到了phead节点

然后我们每经过一个节点就打印一下它的数值即完成了打印函数

链表的尾插

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

 

这里我们需要改变3个节点:头节点,尾节点,新节点

在上面的代码中将创建了新变量ptail指向最后一个节点也就是phead->prev

如果你觉得很乱,我们可以先看newnode

newnode的下一个节点为了循环肯定要指向phead,所以newnode->next = phead

newnode的下一个节点就是2这个节点(也就是ptail),所以newnode->prev = ptail

最后别忘了phead的前一个节点已经不是2而是3了,ptail的下一个节点已经不是phead了而是newnode,这样就完成了尾插

链表的尾删

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

尾删和前面尾插的思路一样,先找到要删除的节点和删除节点的前一个节点

让删除节点的前后节点连接起来,相当于把del松开了,最后free(del)即可

链表的头插

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

 

注:头插不是插在phead的前面 

如果插在phead的前面其实和尾插没有区别,只是我们看起来像是头插了,但是头节点还是phead的情况下其实它是在后面

所以我们还是先找到那三个节点:头节点,头节点的后一个节点,新节点 

让它们一一相连即可

链表的头删

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

头删也不是删哨兵位,而是哨兵位后面的一个节点

 

也是和前面删除一样,把第一个节点分离出去,让哨兵位和第二个节点相连使它变成第一个节点即可 

最后不要忘记了free(del)

链表值查找

LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){//找到了if (pcur->data == x){return pcur;}pcur = pcur->next;}//没有找到return NULL;
}

查找肯定也是要遍历整个链表,循环条件和前面的打印一样

如果后面写的函数都要这样判断,为了让我们的代码通读性更强,我们可以换种写法,使用个函数来返回一下链表中的节点是否为空(链表为空指的是只剩下一个哨兵位) 

bool LTEmpty(LTNode* phead)
{assert(phead);if (phead->next == phead)return true;elsereturn false;
}

所以最后查找代码就可以这样写

LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);LTNode* pcur = phead->next;while (!LTEmpty(phead)){//找到了if (pcur->data == x){return pcur;}pcur = pcur->next;}//没有找到return NULL;
}

如果我们找到了x指定值,则返回当前节点

若出了循环还没有找到x,则说明链表中没有该值返回NULL即可 

链表的指定节点后插入

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

该插入方法和前面的插入基本相同

找到我们要的三个节点:pos节点,pos节点的下一个节点,新节点

让三者相连即可

链表的指定节点删除

void LTErase(LTNode* pos)
{assert(pos);assert(pos->next != pos);LTNode* next = pos->next;LTNode* prev = pos->prev;prev->next = next;next->prev = prev;free(pos);//函数参数不传二级指针无法修改pos//pos = NULL;
}

删除也和前面的删除都基本相同

找到那三个节点:pos节点,pos的前一个节点,pos的后一个节点

让pos的前一个节点和pos的后一个节点相连即可

最后不要忘了free(pos)

这里pos = NULL是无效的,因为传的是一级指针,是形参,形参的改变无法改变实参,所以我们要置NULL只能在函数外面手动置NULL 

链表的销毁

void LTDestroy(LTNode* phead)
{LTNode* pcur = phead->next;while (LTEmpty(phead)){LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);
}

双链表的销毁和单链表一样,都是遍历整个链表,一个个删除

但是这里这么写循环头节点是无法free()的,所以我们循环走完还要free(phead)最后一块节点

但是这个函数的缺点也是没有传二级指针,形参的改变无法改变实参,所以我们函数使用完成之后只能手动让phead = NULL,否则它将成为野指针

带头循环双向链表完整代码

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>typedef int LTDataType;
typedef struct ListNode
{LTDataType data;struct ListNode* next;struct ListNode* prev;
}LTNode;//带头双向循环链表
//void LTInit(LTNode** pphead);
LTNode* LTInit();void LTPrint(LTNode* phead);bool LTEmpty(LTNode* phead);void LTPushBack(LTNode* phead, LTDataType x);
void LTPopBack(LTNode* phead);void LTPushFront(LTNode* phead, LTDataType x);
void LTPopFront(LTNode* phead);LTNode* LTFind(LTNode* phead, LTDataType x);//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);void LTErase(LTNode* pos);void LTDestroy(LTNode* phead);LTNode* LTBuyNode(LTDataType x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc fail\n");exit(1);}newnode->data = x;newnode->next = newnode->prev = newnode;
}LTNode* LTInit()
{//LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));//if (newnode == NULL)//{//	perror("malloc fail\n");//	exit(1);//}//newnode->data = -1;//newnode->next = newnode->prev = newnode;LTNode* newnode = LTBuyNode(-1);return newnode;
}void LTPrint(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);LTNode* ptail = phead->prev;newnode->next = phead;newnode->prev = ptail;phead->prev = newnode;ptail->next = newnode;
}void LTPopBack(LTNode* phead)
{assert(phead);assert(phead->next != phead);LTNode* del = phead->prev;LTNode* prev = del->prev;prev->next = phead;phead->prev = prev;free(del);del = NULL;
}void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);LTNode* next = phead->next;newnode->next = next;newnode->prev = phead;next->prev = newnode;phead->next = newnode;
}void LTPopFront(LTNode* phead)
{assert(phead);assert(phead->next != phead);LTNode* del = phead->next;LTNode* next = phead->next->next;phead->next = next;next->prev = phead;free(del);del = NULL;
}bool LTEmpty(LTNode* phead)
{assert(phead);if (phead->next == phead)return true;elsereturn false;
}LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);LTNode* pcur = phead->next;while (!LTEmpty(phead)){//找到了if (pcur->data == x){return pcur;}pcur = pcur->next;}//没有找到return NULL;
}void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);LTNode* next = pos->next;newnode->next = next;newnode->prev = pos;pos->next = newnode;next->prev = newnode;
}void LTErase(LTNode* pos)
{assert(pos);assert(pos->next != pos);LTNode* next = pos->next;LTNode* prev = pos->prev;prev->next = next;next->prev = prev;free(pos);//函数参数不传二级指针无法修改pos//pos = NULL;
}void LTDestroy(LTNode* phead)
{LTNode* pcur = phead->next;while (LTEmpty(phead)){LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);
}void TestList()
{LTNode* plist = LTInit();//LTPrint(plist);//int ret = LTEmpty(plist);//if (ret == true)//	printf("链表为空\n");//else//	printf("链表不为空\n");LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPrint(plist);//LTPopBack(plist);//LTPrint(plist);//LTPopBack(plist);//LTPrint(plist);//LTPopBack(plist);//LTPrint(plist);//LTPopBack(plist);//LTPrint(plist);//LTPopBack(plist);//LTPrint(plist);//LTPushFront(plist, 1);//LTPrint(plist);//LTPushFront(plist, 2);//LTPrint(plist);//LTPushFront(plist, 3);//LTPrint(plist);//LTPushFront(plist, 4);//LTPrint(plist);//LTPopFront(plist);//LTPrint(plist);//LTPopFront(plist);//LTPrint(plist);//LTPopFront(plist);//LTPrint(plist);//LTPopFront(plist);//LTPrint(plist);//LTPopFront(plist);//LTPrint(plist);//LTNode* find = LTFind(plist, 4);//if (find == NULL)//	printf("没到了\n");//else//	printf("找到了\n");//LTInsert(find, 20);//LTPrint(plist);//LTErase(find);//LTPrint(plist);LTDestroy(plist);//销毁传一级指针的代价就是需要手动将plist置NULLplist = NULL;
}int main()
{TestList();return 0;
}

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

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

相关文章

杰发科技AC7840——CAN通信简介(5)_可变波特率设置

0. 简介 设置可变波特率时候&#xff0c;遇到2个坑&#xff0c;在此记录下来 使用该函数即可 can_time_segment_t bitrate2 s_canBitrate[CAN_BITRATE_250K]; CAN_DRV_SetBitrate(instance, &bitrate2); 1. 波特率指针注意不要空 查看设置波特率的接口&#xff0c;发现…

儿童学编程的好处

儿童学习编程是当今社会中越来越受到关注的话题。随着科技的发展&#xff0c;编程已经成为一种基本的技能&#xff0c;而儿童正是未来的希望。因此&#xff0c;让儿童学习编程具有诸多好处。 首先&#xff0c;学习编程可以培养儿童的逻辑思维能力。编程是一门需要严密思维和逻…

nodejs安装常用命令

安装 Node.js 后&#xff0c;你可以在命令行中使用以下常用命令&#xff1a; node&#xff1a;启动 Node.js 的交互式解释器&#xff0c;可以直接在命令行中执行 JavaScript 代码。 npm install <package-name>&#xff1a;安装一个 Node.js 模块&#xff0c;<packag…

吉他弹唱谱怎么制作 Guitar Pro 怎么写弹简谱 Guitar Pro8.02简谱

学习如何制作吉他弹唱谱是提升音乐创作和表现能力的重要一环。借助专业的软件工具如Guitar Pro&#xff0c;可以轻松地将音乐创意转化为可视化的乐谱&#xff0c;使演奏和分享变得更加便捷和高效。下面我们来看看吉他弹唱谱怎么制作&#xff0c;Guitar Pro 怎么写弹简谱的相关内…

el-date-picker调用回车事件

elementui的el-date-picker想要调用回车事件&#xff1a; <el-date-pickerv-model"state.date"type"date"value-format"YYYY-MM-DD HH:mm:ss"placeholder"选择日期"clearablekeydown.enter"handleDown"></el-date-…

threejs--01整体api整理

文章目录 threejs学习01 threejs的基本组成1. threejs的坐标系2. threejs的场景3. threejs相机的类型4. threejs光4.1 threejs的光4.2 threejs的阴影 5. threejs的物体5.1 threejs的曲线5.2 threejs的几何体5.3 threejs的材质5.3 threejs的纹理 6. threejs的动画6.1 threejs的动…

Llama 3下月正式发布,继续开源!

4月10日&#xff0c;Techcrunch消息&#xff0c;Meta在本周伦敦举办的一场活动中确定&#xff0c;下个月将正式发布Llama 3并且继续开源。 Meta全球事务总裁Nick Clegg表示&#xff0c;我们希望在下个月&#xff0c;甚至更短的时间内&#xff0c;正式推出新一代基础模型Llama …

云服务器宝塔ssh:tabby 部署SpringBoot项目

阿里云服务器 ----》 linux 云服务器 &#xff0c; ip , root 密码 tabby -----> ssh 连接工具 &#xff0c;用这个连接云服务器 去操作 云服务器 宝塔 -------》 可视化工具&#xff0c;快速部署 云服务器的 安装环境 &#xff0c;部署项目。 tabby :Tabby Terminal 下载…

【华为OD机试】比赛的冠亚季军(深度优先搜索—JavaPythonC++JS实现)

本文收录于专栏:算法之翼 本专栏所有题目均包含优质解题思路,高质量解题代码(Java&Python&C++&JS分别实现),详细代码讲解,助你深入学习,深度掌握! 文章目录 一. 题目二.解题思路三.题解代码Python题解代码JAVA题解代码C/C++题解代码JS题解代码四.代码讲解(Ja…

ubuntu netplan 设置dns

ubuntu netplan 设置dns 要配置netplan&#xff0c;请/etc/netplan/使用.yaml扩展名&#xff08;例如/etc/netplan/config.yaml&#xff09;保存配置文件&#xff0c;然后运行sudo netplan apply。此命令解析配置并将其应用于系统。 配置文件格式 参照 这里. eth0:dhcp4: noa…

机器学习和深度学习--李宏毅(笔记与个人理解)Day9

Day9 Logistic Regression&#xff08;内涵&#xff0c;熵和交叉熵的详解&#xff09; 中间打了一天的gta5&#xff0c;图书馆闭馆正好npy 不舒服那天天气不好&#xff0c;哈哈哈哈哈总之各种理由吧&#xff0c;导致昨天没弄起来&#xff0c;今天补更&#xff01; 这里重点注意…

git修改某个远端服务器的地址的方式以及4种remote(git remote set-url origin xxx、git remote -v)

假设本地有1个远端仓库&#xff0c;默认一般叫origin&#xff0c;原来对应的git url是&#xff1a;gitxxx.git # 查看方式&#xff1a; git remote -v# 修改方式&#xff1a; git地址url指定远程仓库&#xff1a;&#xff08;常用&#xff09; git remote set-url origin gitn…

面试官:实战中用过CountDownLatch吗?详细说一说,我:啊这...

写在开头 在很多的面经中都看到过提问 CountDownLatch 的问题&#xff0c;正好我们最近也在梳理学习AQS&#xff08;抽象队列同步器&#xff09;&#xff0c;而CountDownLatch又是其中典型的代表&#xff0c;我们今天就继续来学一下这个同步工具类&#xff01; CountDownLatc…

机器学习第34周周报VBAED

文章目录 week34 VBAED摘要Abstract一、文献阅读1. 题目2. abstract3. 网络架构3.1 序列问题阐述3.2 变分模态分解3.3 具有 BiLSTM 和双向输入注意力的编码器3.4 具有 BiLSTM 和双向时间注意力的解码器 4. 文献解读4.1 Introduction4.2 创新点4.3 实验过程4.3.1 数据集数据预处…

Redis为什么会变慢

链接: https://pan.baidu.com/s/1PNzhDMqkFBdKUu9_FxA5zw 提取码: euvk

软考中级网络工程师-计算机基础理论与安全-第三节存储技术基础

关于进程与线程的描述中&#xff0c;正确的是&#xff1a;( ) A 进程与线程是相同的概念 B 进程需要存储空间 C 一个线程可包含多个进程 D 线程不需要分时器切换 试题答案 正确答案&#xff1a; B 答案解析 一个运行的程序对应一个进程&#xff0c;需要相应的存储空间。在实际中…

若依 ruoyi-vue 维护Ancestors字段 树转换成List

迁移部门表&#xff0c;没有ancestors字段&#xff0c;若依部门权限没办法做&#xff0c;写了一段代码维护ancestors字段。 调用的若依框架组成树的方法&#xff0c;拼接ancestors public AjaxResult tree() {List<SysDept> depts deptService.selectDeptTree();for (Sy…

以太网数据量大小字符串生成方法(可变单位)

0 前言 当我们想显示以太网数据量大小时&#xff0c;往往有个头疼的单位需要处理&#xff0c;单位取小了不一目了然&#xff0c;单位取大了精度太低。本例设计一个函数&#xff0c;将根据以太网数据量大小自动生成单位可变的字符串&#xff08;KB、MB、GB、TB、PB&#xff09;…

雷军 30 年前的病毒论文,结语最后一句是亮点

在 2023 雷军年度「成长」主题演讲中&#xff0c;他分享了自己在武汉大学设立的三个目标&#xff1a; 两年修完大学所有学分成为优秀的程序员在学报上发论文 对于彼时的在校生来说&#xff0c;在一级学报上发表论文是十分困难的事情&#xff0c;但雷军并没有因困难而畏惧、放…

【植物大战僵尸融合机器学习】+源码

上期回顾&#xff1a; 今天给大家推荐一个Gtihub开源项目&#xff1a;PythonPlantsVsZombies&#xff0c;翻译成中就是植物大战僵尸。 《植物大战僵尸》是一款极富策略性的小游戏。可怕的僵尸即将入侵&#xff0c;每种僵尸都有不同的特点&#xff0c;例如铁桶僵尸拥有极强的抗…