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;发现…

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

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

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

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

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

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

面试官:实战中用过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

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

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;例如铁桶僵尸拥有极强的抗…

VRRP虚拟路由实验(华为)

思科设备参考&#xff1a;VRRP虚拟路由实验&#xff08;思科&#xff09; 一&#xff0c;技术简介 VRRP&#xff08;Virtual Router Redundancy Protocol&#xff09;是一种网络协议&#xff0c;用于实现路由器冗余&#xff0c;提高网络可靠性和容错能力。VRRP允许多台路由器…

APP开发_开发一个入门的 H5 APP

1 开发环境的搭建与准备 1.1 安装 Android Studio 下载&#xff1a;首先&#xff0c;从谷歌的安卓开发者网站&#xff08;https://developer.android.google.cn/studio/releases?hlzh-cn&#xff09;下载Android Studio的安装包。在下载页面中&#xff0c;可以根据自己的操作…

llamafactory:unified efficient fine-tuning of 100+ lanuage models

1.introduction llamafactory由三个主要模块组成&#xff0c;Model Loader&#xff0c;Data Worker&#xff0c;Trainer。 2.Efficient fine-tuning techniques 2.1 Efficient Optimization 冻结微调&#xff1a;冻结大部分参数&#xff0c;同时只在一小部分解码器层中微调剩…

【MATLAB源码-第36期】matlab基于BD,SVD,ZF,MMSE,MF,SLNR预编码的MIMO系统误码率分析。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 1. MIMO (多输入多输出)&#xff1a;这是一个无线通信系统中使用的技术&#xff0c;其中有多个发送和接收天线。通过同时发送和接收多个数据流&#xff0c;MIMO可以增加数据速率和系统容量&#xff0c;同时提高信号的可靠性。…

建造者模式:构造复杂对象的艺术

在面向对象的设计中&#xff0c;建造者模式是一种重要的创建型设计模式&#xff0c;专门用来构建复杂的对象。它主要目的是将对象的构造代码与其表示代码分离&#xff0c;使同样的构建过程可以创建不同的表示。本文将详细介绍建造者模式的定义、实现、应用场景以及优缺点&#…

数字乡村创新实践探索农业现代化与乡村振兴新路径:科技赋能农村全面振兴与农民幸福新篇章

随着信息技术的飞速发展&#xff0c;数字乡村成为推动农业现代化与乡村振兴的重要战略举措。科技赋能下的数字乡村创新实践&#xff0c;不仅提升了农业生产的智能化水平&#xff0c;也为乡村治理和农民生活带来了翻天覆地的变化。本文旨在探讨数字乡村创新实践在农业现代化与乡…

Mac环境 llamafile 部署大语言模型LLM

文章目录 Github官网本地部署 llamafile 是一种可在你自己的电脑上运行的可执行大型语言模型&#xff08;LLM&#xff09;&#xff0c;它包含了给定的开放 LLM 的权重&#xff0c;以及运行该模型所需的一切。让人惊喜的是&#xff0c;你无需进行任何安装或配置。 Github https…

年龄与疾病c++

题目描述 某医院想统计一下某项疾病的获得与否与年龄是否有关&#xff0c;需要对以前的诊断记录进行整理&#xff0c;按照0-18岁、19-35岁、36-60岁、61以上&#xff08;含61&#xff09;四个年龄段统计的患病人数以及占总患病人数的比例。 输入 共2行&#xff0c;第一行为过…

ctfshow--web入门--文件上传--web168--web170

web168 法一免杀脚本 还是检查&#xff0c;准备上传图片马 我写的是<?php eval($_POST[a]);?> 上传之后没反应 那么查一下&#xff0c;原来是发现对eval,system还有$_POST和$_GET进行过滤,$_REQUEST还可以用 那么再写一个马&#xff08;免杀脚本&#xff09; <?…

AI 文献综述工具

find sources that support this statement: