当前位置: 首页 > news >正文

【数据结构 -- C语言】 双向带头循环链表的实现

目录

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

2、双向带头循环链表的接口

3、接口实现

3.1 开辟结点

3.2 创建返回链表的头结点

3.3 判断链表是否为空

3.4 打印

3.5 双向链表查找

3.6 双向链表在pos的前面进行插入

3.6.1 头插

3.6.2 尾插

3.6.3 更新头插、尾插写法

3.7 双向链表删除pos位置的节点

3.7.1 头删

3.7.2 尾删

3.7.3 更新头删、尾删写法

3.8 双向链表销毁

4、完整代码

5、功能测试


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

我们将这个题目拆分开来可以提取三个关键字:双向,带头,循环。我们就以这三个关键字来展开介绍一下:

首先是双向:双向就说明了这个结点可以找到自己的前驱和后继,这一点与单链表存在本质的区别;

其次是带头:带头说明链表有一个头结点,这个头结点也可以称为哨兵位的头结点,此结点与其他结点是一样的,只是它的 data 域放的是随机值(有的会存放链表的长度);

最后是循环:循环说明了它的结构是一个环装,头结点的前驱结点存放着尾结点,尾结点的后继结点存放着头结点。

2、双向带头循环链表的接口

双向带头循环链表与单链表的功能是一样的,都是可以增删查改的,但是双向带头循环链表的特性让这么功能写的时候大大提高了我们的效率。

// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{LTDataType data;struct ListNode* next;struct ListNode* prev;
}ListNode;// 创建返回链表的头结点
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

3、接口实现

3.1 开辟结点

我们的链表是动态的链表,因此每次在插入的时候我们都需要 malloc 一个结点,我们将其封装成一个函数,方便后面的代码复用。

ListNode* BuyListNode(LTDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (NULL == newnode){perror("malloc fail:");return NULL;}newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}

3.2 创建返回链表的头结点

这一步是我们给链表创建一个哨兵位的头结点。因为是双向带头循环链表,因此我们让 prev 和 next 指针都指向自己,这样就算是只有一个头结点,我们也是双向循环的结构。

ListNode* ListCreate()
{ListNode* phead = BuyListNode(-1);phead->next = phead;phead->prev = phead;return phead;
}

3.3 判断链表是否为空

这一步是我们为后面的删除时封装的一个判空功能函数,当链表存在的时候不排除链表为空的情况,因此我们封装此函数,以便后面的接口复用。

bool ListEmpty(ListNode* pHead)
{assert(pHead);return pHead->next == pHead;
}

3.4 打印

打印函数我们已经很熟悉了,但是双向带头循环链表的打印终止条件要注意,这里不再是以前的 cur == NULL,而是 cur != pHead ,当 cur 走到 pHead 时就说明我们已经遍历完整个链表了。

void ListPrint(ListNode* pHead)
{assert(pHead);ListNode* cur = pHead->next;printf("guard");while (cur != pHead){printf("<==>%d", cur->data);cur = cur->next;}printf("<==>\n");
}

3.5 双向链表查找

查找不难,但是这里要注意,双向带头链表的第一个结点是哨兵位结点,因此我们要从哨兵位的下一个结点开始遍历查找(cur = pHead->next),循环的终止条件依然是 cur != pHead。

ListNode* ListFind(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* cur = pHead->next;while (cur != pHead){if (cur->data == x)return cur;cur = cur->next;}return NULL;
}

3.6 双向链表在pos的前面进行插入

我们在找到pos位置后,在 pos 位置前进行插入,我们按下面的流程执行:

1、假如我们要在 data3 前插入一个 data4,我们先用定义一个结构体指针变量 prev,先将data3->prev (data2)结点保存在 prev 变量中;

2、再将 prev->next 改为 data4 ,然后将 data4->prev 改为 prev;

3、最后把 data4->next = data3,再把 data3->prev = data4。

void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);ListNode* prev = pos->prev;ListNode* newnode = BuyListNode(x);prev->next = newnode;newnode->prev = prev;newnode->next = pos;pos->prev = newnode;
}

3.6.1 头插

头插的话我们与在pos位置前插入一致,先将第一个结点保存起来,然后进行插入。

void ListPushFront(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* newnode = BuyListNode(x);ListNode* first = pHead->next;//多写这一个变量下面的插入语句就可以无序写pHead->next = newnode;newnode->next = first;newnode->prev = pHead;first->prev = newnode;
}

3.6.2 尾插

尾插的话先将链表的尾结点保存起来,然后进行插入,还是与在pos位置前插入一样。

void ListPushBack(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* tail = pHead->prev;ListNode* newnode = BuyListNode(x);tail->next = newnode;newnode->prev = tail;newnode->next = pHead;pHead->prev = newnode;
}

3.6.3 更新头插、尾插写法

对比插入代码我们其实不难发现,头插、尾插与在pos位置前插入的代码逻辑是一样的,实现的功能根本上也没有变,那我们就在头插、尾插时直接复用 ListInsert 接口就可以实现。

1> 头插

void ListPushFront(ListNode* pHead, LTDataType x)
{assert(pHead);ListInsert(pHead->next, x);
}

2> 尾插

因为是 pos 位置前插入,链表是双向循环的,所以传的第一个参数就是 pHead ,哨兵位结点前一个结点就是尾结点。

void ListPushFront(ListNode* pHead, LTDataType x)
{assert(pHead);ListInsert(pHead, x);
}

3.7 双向链表删除pos位置的节点

我们在找到 pos 结点后,再将 pos 结点删除,我们按下面的流程执行:

1、定义两个结构体指针变量 posPrev与posNext,将 pos 位置前后结点分别保存在 posPrev与posNext 指针变量中;

2、将posPrev->next = posNext,再将 posNext->prev = posPrev;

3、释放掉 pos 结点,free(pos)。

void ListErase(ListNode* pos)
{assert(pos);ListNode* posPrev = pos->prev;ListNode* posNext = pos->next;posPrev->next = posNext;posNext->prev = posPrev;free(pos);
}

3.7.1 头删

头删的话我们先将 pHead->next结点与pHead->next->next结点保存下来,将哨兵位结点与头结点的下一个结点连接起来,再将头结点释放掉。

void ListPopFront(ListNode* pHead)
{assert(pHead);assert(!ListEmpty(pHead));ListNode* first = pHead->next;ListNode* second = first->next;pHead->next = second;first->prev = pHead;free(first);
}

3.7.2 尾删

尾删的话我们先将 pHead->prev结点与pHead->prev->prev结点保存下来,将哨兵位结点与尾结点的前一个结点连接起来,再将尾结点释放掉。

void ListPopBack(ListNode* pHead)
{assert(pHead);assert(!ListEmpty(pHead));ListNode* tail = pHead->prev;ListNode* tailPrev = tail->prev;tailPrev->next = pHead;pHead->prev = tailPrev;free(tail);
}

总结在头删与尾删前要判断链表是否为空。

3.7.3 更新头删、尾删写法

对比删除代码我们其实不难发现,头删、尾删与删除pos位置结点的代码逻辑是一样的,实现的功能根本上也没有变,那我们就在头删、尾删时直接复用 ListErase 接口就可以实现。

1> 头删

void ListPopFront(ListNode* pHead)
{assert(pHead);assert(!ListEmpty(pHead));ListErase(pHead->next);
}

2> 尾删

void ListPopBack(ListNode* pHead)
{assert(pHead);assert(!ListEmpty(pHead));ListErase(pHead->prev);
}

3.8 双向链表销毁

链表的销毁主要是遍历一遍链表,从 cur = pHead->next 开始,循环终止的条件为 cur != pHead,遍历完再将哨兵位结点释放掉,整个链表就被释放掉了。

void ListDestory(ListNode* pHead)
{assert(pHead);ListNode* cur = pHead->next;while (cur != pHead){ListNode* next = cur->next;free(cur);cur = next;}free(pHead);
}

4、完整代码

完整代码在代码仓库,入口:C语言: C语言学习的代码,多复习 - Gitee.com

5、功能测试

相关文章:

【数据结构 -- C语言】 双向带头循环链表的实现

目录 1、双向带头循环链表的介绍 2、双向带头循环链表的接口 3、接口实现 3.1 开辟结点 3.2 创建返回链表的头结点 3.3 判断链表是否为空 3.4 打印 3.5 双向链表查找 3.6 双向链表在pos的前面进行插入 3.6.1 头插 3.6.2 尾插 3.6.3 更新头插、尾插写法 3.7 双向链…...

自然语言处理与其Mix-up数据增强方法报告

自然语言处理与其Mix-up数据增强方法 1绪论1.课题背景与意义1.2国内外研究现状 2 自然语言经典知识简介2.1 贝叶斯算法2.2 最大熵模型2.3神经网络模型 3 Data Augmentation for Neural Machine Translation with Mix-up3.1 数据增强3.2 对于神经机器翻译的软上下文的数据增强3.…...

Vue(组件化编程:非单文件组件、单文件组件)

一、组件化编程 1. 对比传统编写与组件化编程&#xff08;下面两个解释图对比可以直观了解&#xff09; 传统组件编写&#xff1a;不同的HTML引入不同的样式和行为文件 组件方式编写&#xff1a;组件单独&#xff0c;复用率高&#xff08;前提组件拆分十分细致&#xff09; 理…...

【MATLAB数据处理实用案例详解(22)】——基于BP神经网络的PID参数整定

目录 一、问题描述二、算法仿真2.1 BP_PID参数整定初始化2.2 优化PID2.3 绘制图像 三、运行结果四、完整程序 一、问题描述 基于BP神经网络的PID控制的系统结构如下图所示&#xff1a; 考虑仿真对象&#xff0c;输入为r(k)1.0&#xff0c;输入层为4&#xff0c;隐藏层为5&…...

第11章 项目人力资源管理

文章目录 项目人力资源管理 过程11.2.1 编制项目人力资源计划的工具与技术&#xff08;1&#xff09;层次结构图&#xff08;工作、组织、资源 分解结构&#xff09;&#xff08;2&#xff09;矩阵图&#xff08;责任分配矩阵&#xff0c;RAM&#xff09;&#xff08;3&#xf…...

07-Vue技术栈之(组件之间的通信方式)

目录 1、组件的自定义事件1.1 绑定自定义事件&#xff1a;1.1.1 第一种方式1.1.2 第二种方式1.1.3 自定义事件只触发一次 1.2 解绑自定义事件1.3绑定原生DOM事件1.4 总结 2、全局事件总线&#xff08;GlobalEventBus&#xff09;2.1 应用全局事件总线 3、 消息订阅与发布&#…...

度量学习Metirc Learning和基于负例的对比学习Contrastive Learning的异同点思考

参考&#xff1a;对比学习&#xff08;Contrastive Learning&#xff09;:研究进展精要 - 知乎 参考&#xff1a;对比学习论文综述【论文精读】_哔哩哔哩_bilibili 参考&#xff1a;度量学习DML之Contrastive Loss及其变种_对比损失的变种_胖胖大海的博客-CSDN博客 参考&…...

3.编写油猴脚本之-helloword

3.编写油猴脚本之-helloword Start 通过上一篇文章的学习&#xff0c;我们安装完毕了油猴插件。今天我们来编写一个helloword的脚步&#xff0c;体验一下油猴。 1. 开始 点击油猴插件>添加新脚本 默认生成的脚本 // UserScript // name New Userscript // name…...

openwrt的openclash提示【更新失败,请确认设备闪存空间足够后再试】

网上搜索了一下&#xff0c;问题应该是出在“无法从网络下载内核更新包”或者“无法识别内核的版本号” 解决办法&#xff1a;手动下载&#xff08;我是只搞了DEV内核就搞定了TUN和Meta没有动&#xff09; --> 上传到路由器上 --> 解压缩 --> 回到openclash界面更新配…...

torch.nn.Module

它是所有的神经网络的根父类&#xff01; 你的神经网络必然要继承 可以看一下这篇文章...

论文解析-基于 Unity3D 游戏人工智能的研究与应用

1.重写 AgentAction 方法 1.1 重写 AgentAction 方法 这段代码是一个重写了 AgentAction 方法的方法。以下是对每行代码解释&#xff1a; ①public override void AgentAction(float[] vectorAction) 这行代码声明了一个公共的、重写了父类的 AgentAction 方法的方法。它接受…...

6、Flutterr聊天界面网络请求

一、准备网络数据 1.1 数据准备工作 来到网络数据制造的网址,注册登录后,新建仓库,名为WeChat_flutter;点击进入该仓库,删掉左侧的示例接口,新建接口. 3. 接着点击右上角‘编辑’按钮,新建响应内容,类型为Array,一次生成50条 4. 点击chat_list左侧添加按钮,新建chat_list中的…...

Java 8 腰斩!Java 17 暴涨 430%!!(文末福利)

New Relic 最新发布了一份 “2023 年 Java 生态系统状况报告”&#xff0c;旨在提供有关当今 Java 生态系统状态的背景和见解。该报告基于从数百万个提供性能数据的应用程序中收集的数据&#xff0c;对生产中使用最多的版本、最受欢迎的 JDK 供应商、容器的兴起等多方面进行了调…...

如何手写一个支持H.265的高清播放器

概述 音视频编解码技术在当前的互联网行业中十分热门&#xff0c;特别是高清视频播放器的开发&#xff0c;其中包括4K、8K等超高清分辨率的播放器&#xff0c;具有极高的市场需求和广泛的应用场景。H265编码技术更是实现高清视频压缩的重要手段之一。如果想要掌握音视频编解码…...

Day 1 认识软件测试——(软件测试定义、目的、原则)

Day 1 认识软件测试——(软件测试定义、目的、原则) 文章目录 Day 1 认识软件测试——(软件测试定义、目的、原则)软件测试的定义软件测试的目的软件测试的经济学问题黑盒测试白盒测试软件测试原则小结所谓软件测试,就是一个过程或一系列过程,用来确定计算机代码完成了其…...

Docker Harbor

目录 一、Docker Harbor概述 1、Harbor的优势 2、Harbor知识点 3、Docker私有仓库架构 二、Harbor构建Docker私有仓库 1、环境配置 2、案例需求 3、部署docker-compose服务 4、部署harbor服务 5、启动harbor ① 访问 ② 添加项目并填写项目名称 ③ 通过127.0.0.1来…...

第三十四章 Unity人形动画(上)

在我们DirectX课程中&#xff0c;我们讲过一个模型最少拥有网格和材质&#xff0c;可以没有动画。游戏场景中的静态物体就可以是这样的模型&#xff0c;例如花草树木&#xff0c;建筑物等等&#xff0c;他们通过MeshRenderer就可以渲染。对于一个带有动画的FBX文件&#xff0c;…...

计算机图形学-GAMES101-7

引言 场景中有很多的三角形&#xff0c;如果实现可见性和遮挡呢&#xff1f;  一个简单的想法是&#xff0c;从远到近画&#xff0c;近处的物体自然会覆盖掉远处的物体&#xff0c;这种画法也叫画家算法。  但是实际绘制中物体的顺序是不容易确定的&#xff0c;比如如下图绘制…...

AndroidAuto 解决PCTS NF7

直接上代码 public void handleNavigationFocusRequest(int focusType) {// Always grant requested focus in this example.-mGal.galReceiver.sendNavigationFocusState(focusType);+mGal.galReceiver.sendNavigationFocusState...

GPT:你知道这五年我怎么过的么?

时间轴 GPT 首先最初版的GPT&#xff0c;来源于论文Improving Language Understanding by Generative Pre-Training&#xff08;翻译过来就是&#xff1a;使用通用的预训练来提升语言的理解能力&#xff09;。GPT这个名字其实并没有在论文中提到过&#xff0c;后人将论文名最后…...

Python一行命令搭建HTTP服务器并外网访问 - 内网穿透

文章目录 1.前言2.本地http服务器搭建2.1.Python的安装和设置2.2.Python服务器设置和测试 3.cpolar的安装和注册3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 转载自远程内网穿透的文章&#xff1a;【Python】快速简单搭建HTTP服务器并公网访问「cpolar内网穿透…...

TypeScript5-泛型

泛型是 TS 中一个重要的概念&#xff0c;它可以创建可复用的组件&#xff0c;同时保持对类型信息的一致性。 泛型提供了一种方式使得类型可以被参数化&#xff0c;这样就可以创建可以适用于各种数据类型的函数或类&#xff0c;而不仅仅限于一个数据类型。 一、泛型 先来看一…...

IMX6ULL裸机篇之DDR3的时钟配置

一. MMDC 控制器 对于 I.MX6U 来说&#xff0c;有 DDR 内存控制器&#xff0c;否则的话它怎么连接 DDR 呢&#xff1f;MMDC控制器 就是 I.MX6U 的 DDR内存控制器。 MMDC 外设包含一个内核(MMDC_CORE)和 PHY(MMDC_PHY)&#xff0c;内核和 PHY 的功能如下&#xff1a; MMDC 内…...

PBDB Data Service:Specimens and measurements(标本和测量)

Specimens and measurements&#xff08;标本和测量&#xff09; 描述摘要1. [Single specimen&#xff08;单个标本&#xff09;](https://blog.csdn.net/whitedrogen/article/details/130685099)2. [Add specimen records or update existing records&#xff08;添加标本记录…...

Zookeeper(一)

简介 设计模式角度 Zookeeper&#xff1a;是一个基于观察者模式设计的分布式服务管理框架&#xff0c;它负责存储和管理大家都关心的数据&#xff0c;然后接受观察者的注册&#xff0c;一旦这些数据的状态发生变化&#xff0c;Zookeeper就将负责通知已经在Zookeeper上注册的那…...

Maven(五):Maven的使用——依赖的测试

Maven&#xff08;五&#xff09;&#xff1a;Maven的使用——依赖的测试 前言一、实验六&#xff1a;测试依赖的范围1、依赖范围1.1 compile 和 test 对比1.2 compile 和 provided 对比1.3 结论 二、实验七&#xff1a;测试依赖的传递性1、依赖的传递性1.1 概念1.2 传递的原则…...

超级独角兽 Databricks 的崛起之路

在数据扩张以及 AI 兴起的时代&#xff0c;数据存储和分析平台拥有巨大价值和能量。 随着互联网数据的爆炸性增长&#xff0c;数据已经成为企业的新型资源&#xff0c;犹如石油般重要。越来越多的企业希望利用各种结构化和非结构化数据来发挥自己的优势。 然而&#xff0c;他…...

python 3.8 + tensorflow 2.4.0 + cuda11.0 的问题

版本匹配 &#x1f517;从源代码构建 | TensorFlow 报错&#xff1a;Could not load dynamic library ‘cupti64_110.dll’; dlerror: cupti64_110.dll not found 是因为我电脑中的 cuda 版本以前是 10&#xff0c;现在是 11.4 &#xff0c;所以需要安装对应版本的 cudatoolk…...

华为杯”研究生数学建模竞赛2021 年中国研究生数学建模竞赛 E 题: 信号干扰下的超宽带(UWB)精确定位问题-参考思路

一、背景 UWB ( Ultra-Wideband )技术也被称之为“超宽带”,又称之为脉冲无线电技术。这是一 种无需任何载波,通过发送纳秒级脉冲而完成数据传输的短距离范围内无线通信技术,并且信 号传输过程中的功耗仅仅有几十 W 。 UWB 因其独有的特点,使其在军事、物联网等各个领…...

Java 中的访问修饰符有什么区别?

Java 中的访问修饰符用于控制类、类的成员变量和方法的访问权限&#xff0c;主要有以下四种&#xff1a; public&#xff1a;公共访问修饰符&#xff0c;可以被任何类访问。public 修饰的类、成员变量和方法可以在任何地方被访问到。 protected&#xff1a;受保护的访问修饰符…...