链表基础4——带头双向循环链表(c语言实现)

什么是带头双向循环链表

我们直接看图片

定义结点类型 

typedef int LTDataType;//存储的数据类型typedef struct ListNode
{LTDataType data;//数据域struct ListNode* prev;//前驱指针struct ListNode* next;//后继指针
}ListNode;

链表的初始化 

//创建一个新结点
ListNode* BuyListNode(LTDataType x)
{ListNode* node = (ListNode*)malloc(sizeof(ListNode));if (node == NULL){printf("malloc fail\n");exit(-1);}node->data = x;//新结点赋值node->prev = NULL;node->next = NULL;return node;//返回新结点
}//初始化链表
ListNode* ListInit()
{ListNode* phead = BuyListNode(-1);//申请一个头结点,头结点不存储有效数据//起始时只有头结点,让它的前驱和后继都指向自己phead->prev = phead;phead->next = phead;return phead;//返回头结点
}

销毁链表

销毁链表,从头结点的后一个结点处开始向后遍历并释放结点,直到遍历到头结点时,停止遍历并将头结点也释放掉。

//销毁链表
void ListDestroy(ListNode* phead)
{assert(phead);ListNode* cur = phead->next;//从头结点后一个结点开始释放空间ListNode* next = cur->next;//记录cur的后一个结点位置while (cur != phead){free(cur);cur = next;next = next->next;}free(phead);//释放头结点
}

打印双向链表 

打印双向链表时也是从头结点的后一个结点处开始向后遍历并打印,直到遍历到头结点处时便停止遍历和打印(头结点数据不打印)。

//打印双向链表
void ListPrint(ListNode* phead)
{assert(phead);ListNode* cur = phead->next;//从头结点的后一个结点开始打印while (cur != phead)//当cur指针指向头结点时,说明链表以打印完毕{printf("%d ", cur->data);cur = cur->next;}printf("\n");
}

查找元素

给定一个值,在链表中寻找与该值相同的结点,若找到了,则返回结点地址;若没有找到,则返回空指针(NULL)。

//查找元素
ListNode* ListFind(ListNode* phead, LTDataType x)
{assert(phead);ListNode* cur = phead->next;//从头结点的后一个结点开始查找while (cur != phead)//当cur指向头结点时,说明链表已遍历完毕{if (cur->data == x){return cur;//返回目标结点的地址}cur = cur->next;}return NULL;//没有找到目标结点
}

增加结点

头插

 头插,即申请一个新结点,将新结点插入在头结点和头结点的后一个结点之间即可。

//头插
void ListPushFront(ListNode* phead, LTDataType x)
{assert(phead);ListNode* newnode = BuyListNode(x);//申请一个结点,数据域赋值为xListNode* front = phead->next;//记录头结点的后一个结点位置//建立新结点与头结点之间的双向关系phead->next = newnode;newnode->prev = phead;//建立新结点与front结点之间的双向关系newnode->next = front;front->prev = newnode;
}

 尾插

 尾插,申请一个新结点,将新结点插入到头结点和头结点的前一个结点之间即可。因为链表是循环的,头结点的前驱指针直接指向最后一个结点,所以我们不必遍历链表找尾。

//尾插
void ListPushBack(ListNode* phead, LTDataType x)
{assert(phead);ListNode* newnode = BuyListNode(x);//申请一个结点,数据域赋值为xListNode* tail = phead->prev;//记录头结点的前一个结点的位置//建立新结点与头结点之间的双向关系newnode->next = phead;phead->prev = newnode;//建立新结点与tail结点之间的双向关系tail->next = newnode;newnode->prev = tail;
}

在指定位置插入结点

 在直到位置插入结点,准确来说,是在指定位置之前插入一个结点。我们只需申请一个新结点插入到指定位置结点和其前一个结点之间即可

//在指定位置插入结点
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);ListNode* before = pos->prev;//记录pos指向结点的前一个结点ListNode* newnode = BuyListNode(x);//申请一个结点,数据域赋值为x//建立新结点与before结点之间的双向关系before->next = newnode;newnode->prev = before;//建立新结点与pos指向结点之间的双向关系newnode->next = pos;pos->prev = newnode;
}

删除结点

头删

 头删,即释放头结点的后一个结点,并建立头结点与被删除结点的后一个结点之间的双向关系即可。

//头删
void ListPopFront(ListNode* phead)
{assert(phead);assert(phead->next != phead);ListNode* front = phead->next;//记录头结点的后一个结点ListNode* newfront = front->next;//记录front结点的后一个结点//建立头结点与newfront结点之间的双向关系phead->next = newfront;newfront->prev = phead;free(front);//释放front结点
}

尾删

 尾删,即释放最后一个结点,并建立头结点和被删除结点的前一个结点之间的双向关系即可。

//尾删
void ListPopBack(ListNode* phead)
{assert(phead);assert(phead->next != phead);ListNode* tail = phead->prev;//记录头结点的前一个结点ListNode* newtail = tail->prev;//记录tail结点的前一个结点//建立头结点与newtail结点之间的双向关系newtail->next = phead;phead->prev = newtail;free(tail);//释放tail结点
}

删除指定位置结点

删除指定位置结点,释放掉目标结点后,建立该结点前一个结点和后一个结点之间的双向关系即可。

//删除指定位置结点
void ListErase(ListNode* pos)
{assert(pos);ListNode* before = pos->prev;//记录pos指向结点的前一个结点ListNode* after = pos->next;//记录pos指向结点的后一个结点//建立before结点与after结点之间的双向关系before->next = after;after->prev = before;free(pos);//释放pos指向的结点
}

链表判空

 链表判空,即判断头结点的前驱或是后驱指向的是否是自己即可。

//链表判空
bool ListEmpty(ListNode* phead)
{assert(phead);return phead->next == phead;//当链表中只有头结点时为空
}

获取链表中的元素个数

 获取链表中的元素个数,即遍历一遍链表,统计结点的个数(头结点不计入)并返回即可。

//获取链表中的元素个数
int ListSize(ListNode* phead)
{assert(phead);int count = 0;//记录元素个数ListNode* cur = phead->next;//从头结点的后一个结点开始遍历while (cur != phead)//当cur指向头结点时,遍历完毕,头结点不计入总元素个数{count++;cur = cur->next;}return count;//返回元素个数
}

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

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

相关文章

什么是反向 ETL?为什么它很有价值?

提取、转换、加载 (ETL) 过程已经成熟并被广泛采用。 它只涉及从各种源系统中获取数据,将其转换为标准化数据模型,然后将其加载到数据仓库中。从那里,您的团队使用其商业智能 (BI) 和分析工具中…

FPGA - ZYNQ Cache一致性问题

什么是Cache? Cache是一种用来提高计算机运行速度的一种技术。它是一种小而快的存储设备,位于CPU与内存之间,用于平衡高速设备与低速设备之间的速度差异。Cache可以存储常用的数据或指令,以便CPU更快地获取,从而减少对…

基于单片机的智能病床呼叫系统设计与仿真

摘 要 本文设计的病床呼叫系统采用单片机作为控制器。该系统具有远程控制、病人的身体情况检测、报警呼叫、显示和执行器运动的功能。远程控制由红外线传感器和矩阵键盘组成,检测电路由温湿度传感器DH22、心率传感器Pulse Sensor、压力传感器MPX4115组成&#x…

操作系统——进程

进程定义 是计算机中已经运行的程序是系统进行资源分配和调度的一个独立单位。 进程的特性 独立性:进程在内存中可以独立寻址,每个进程都有一个独立的堆栈空间。动态性:进程在执行过程中可以申请资源、使用资源、释放资源。并发性&#xf…

OceanBase开发者大会2023届视频及PPT汇总

数据库技术趋势 我眼中的数据库技术 阳振坤OceanBase 首席科学家 观看视频 下载 PDF 未来,中国需要什么样的数据库? 周傲英华东师范大学副校长,CCF 会士 观看视频 下载 PDF 云原生技术趋势解读 Keith ChanCNCF 云原生计算基金会中国区总监 …

批量规范化(batchnormalization)

ˆB 是小批量B的样本均值,σˆ B 是小批量B的样本标准差。应用标准化后,生成的小批量的平均 值为0和单位方差为1。由于单位方差(与其他一些魔法数)是一个主观的选择,因此我们通常包含 拉伸参数(scale&#…

Linux关闭swap分区操作[适用于CDH报警等]

1.查看swap分区挂载路径(没卵用) swapon -s 2.设置配置文件的swap配置 echo “vm.swappiness 0” > /etc/sysctl.conf 3.设置内存中的swap状态。有时候配置文件为0,但集群或服务仍然使用了swap分区,可能原因就是内存没有同步配置 echo “0” > …

【配电网故障定位】基于二进制蝙蝠算法的配电网故障定位 33节点配电系统故障定位【Matlab代码#80】

文章目录 【获取资源请见文章第6节:资源获取】1. 配电网故障定位2. 二进制蝙蝠算法3. 算例展示4. 部分代码展示5. 仿真结果展示6. 资源获取 【获取资源请见文章第6节:资源获取】 1. 配电网故障定位 配电系统故障定位,即在配电网络发生故障的…

微信有关白名单IP

一、商家支付 二、公众号

深入理解与实践“git add”命令的作用

文章目录 **git add命令的作用****git add命令的基本作用****高级用法与注意事项** git add命令的作用 引言: 在Git分布式版本控制系统中,git add命令扮演着至关重要的角色,它是将本地工作区的文件变动整合进版本控制流程的关键步骤。本文旨…

YOLOv9改进策略 | SPPF篇 | 利用RT-DETR的AIFI模块替换SPPFELAN助力小目标检测涨点

一、本文介绍 本文给大家带来是用最新的RT-DETR模型中的AIFI模块来替换YOLOv9中的SPPFELAN。RT-DETR号称是打败YOLO的检测模型,其作为一种基于Transformer的检测方法,相较于传统的基于卷积的检测方法,提供了更为全面和深入的特征理解&#x…

【C++】STL-vector的使用

目录 1、什么是vector? 2、vector的使用 2.1 vector的定义 ​编辑 2.2 遍历修改数据 2.3 迭代器 2.4 vector空间增长问题 2.5 vector的增删查改 3、迭代器失效 3.1 会引起其底层空间改变的操作,都有可能是迭代器失效 3.2 指定位置元素的删除操…