【数据结构之带头双向循环链表的实现】
1.链表的分类
链表的结构有多种多样,以下情况组合起来就有8种(2x2x2)链表结构:
虽然有这么多的链表结构,但是我们实际中最常用的还是两种结构:单链表和双向带头循环链表。
- 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶.图的邻接表等等。另外这种结构在笔试面试中出现很多。
- 带头双向循环链表:结构最为复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然复杂,但是用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现就知道了。
2.带头双向循环链表的实现
和前面的数据结构的实现一样我们需要用三个文件,List.h(链表结构的声明以及链表的函数的声明),List.c(函数功能的实现),test.c(函数功能的测试文件)。
2.1 List.h(链表结构的定义以及链表的函数的声明)
在这里我们需要理解双向是啥意思,双向在结构中体现不仅有指向前一个节点的指针,而且有指向下一个节点的指针。初次之外通过这两个指针让链表也构成了循环。示意图如下:
故而带头双向链表在定义的时候需要用一个包含三个变量的结构变量,具体定义如下:
注:和前面一样在这里我们操作的数据类型可变,所以对类型重命名的方法。
#include <stdio.h> #include <stdlib.h> #include <assert.h> #include <stdbool.h>//带头双向链表的结构的定义 以及所要实现的函数的声明 typedef int LTDatatype; typedef struct ListNode {LTDatatype data;struct ListNode* prev;struct ListNode* next; }LTNode;//打印链表 void LTPrint(LTNode* phead);//初始化 需要创建哨兵位 这样插入和删除的时候就不要考虑链表为空了 void LTInit(LTNode** pphead); //因此双向链表为空的情况就是链表中只有一个哨兵位//插入 //头插 因为有了哨兵位就并不会影响头结点,故传一级指针即可 void LTPushBack(LTNode* phead, LTDatatype x); //头插 void LTPushFront(LTNode* phead, LTDatatype x);//对链表进行判空 bool* LTEmpty(LTNode* phead);//删除 //尾删 void LTPopBack(LTNode* phead); //头删 void LTPopFront(LTNode* phead);//数据的查找 LTNode* LTFind(LTNode* phead, LTDatatype x);//指定位置的操作 //指定位置之后数据的插入 void* LTInsert( LTNode* pos, LTDatatype x);//指定位置的数据的删除 void LTErase( LTNode* pos);//链表的销毁 void LTDestroy(LTNode* phead);
2.2 List.c(函数功能的实现) 需要包含头文件#include "List.h"
2.2.1 链表的初始化
LTNode* LTBuyNode(LTDatatype x) {LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc fail");exit(1);}newnode->data = x;newnode->prev = newnode;newnode->next = newnode;return newnode; } void LTInit(LTNode** pphead) //需要改变头结点(影响头结点)所以传二级指针 {*pphead=LTBuyNode(-1); }
实现思路:
- 在初始化之前我们得先实现一个申请新节点的函数。实现该函数需要用到malloc函数申请一个节点的空间,再进行判断看是否申请成功。如果申请成功就将数据保存在newnode->data中,并且将两个指针变量newnode->pre和newnode->next都指向新节点。最后返回新节点的地址。
- 因为我们实现的是带头的链表,这个头是方便插入和删除(就相当于哨兵位),所以在初始化时只需要创建一个携带无效数据的节点即可。
2.2.2 链表的打印
//链表的打印 void LTPrint(LTNode* phead) {LTNode* pcur = phead->next;if(pcur->next!=phead)printf("plist->");while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("plist\n"); }
实现思路:
我们需要遍历链表,对链表中的数据进行打印即可。但是需要注意的是,遍历链表的时候需要从头结点的下一个节点开始遍历,遍历结束条件是当前节点所指向的下一个节点 不能是头结点(哨兵位)。
2.2.3 链表的数据的插入
//插入 //尾插 因为有了哨兵位就并不会影响头结点,故传一级指针即可 void LTPushBack(LTNode* phead, LTDatatype x) {assert(phead);LTNode* newnode = LTBuyNode(x);//改变 phead->prev phead->prev->next newnode->prev newnode->nextnewnode->prev = phead->prev;newnode->next = phead;phead->prev->next = newnode;phead->prev = newnode; } //头插 void LTPushFront(LTNode* phead, LTDatatype x) {assert(phead);LTNode* newnode = LTBuyNode(x);//改变 phead phead->prev phead->next phead->next->prev的指向关系newnode->next = phead->next;newnode->prev = phead;phead->next->prev = newnode;phead->next = newnode; }
实现思路:
在实现头插和尾插时都需要先判断所传值的有效性,以及先申请到一个新节点。
- 尾插: 先将新节点的prev指向目前链表的尾节点,再将新节点的next指向头结点。除此之外,我们还需要将链表的尾节点的next指向新节点,将头结点的prev指向新节点。
- 头插: 先将新节点的next指向头结点的下一个节点,再将新节点的prev指向头结点。除此之外我们还需要将头节点的下一个节点的prev指向新节点,再将头结点的next指向新节点。
2.2.4 链表的判空
//对链表进行判空 bool LTEmpty(LTNode* phead) {assert(phead);return phead->next == phead; }
实现思路:
返回值为bool类型,先判断值的有效性。然后将判断头结点的下一个指针是否为它本身返回即可。
2.2.5 链表的数据的删除
//删除 //尾删 void LTPopBack(LTNode* phead) {assert(phead);assert(!LTEmpty(phead));//改变phead-prev->prev的next phead->prev的指向LTNode* del = phead->prev;LTNode* delprev = del->prev;phead->prev = delprev;delprev->next = phead;free(del);del = NULL; } //头删 void LTPopFront(LTNode* phead) {assert(phead);assert(!LTEmpty(phead));//改变phead->next->next的prev phead的nextLTNode* del = phead->next;LTNode* delnext = del->next;phead->next = delnext;delnext->prev = phead;free(del);del = NULL; }
实现思路:
在实现尾删和头删时都需要先进行对所传值的有效性进行判断,再进行判断链表是否为空(除头结点外是否还有其他的有效节点)。
- 尾删: 先将要删除的节点,即尾节点先保存下来;同时将尾节点的前一个节点保存下来delprev。(要不然直接释放掉就找不到了) 然后将头结点的prev指向delprev尾节点的前一个节点,将delprev的next指向头节点。最后释放掉del,并将del置为空。
- 头删: 先将要删除的节点,即头节点先保存下来;同时将尾节点的下一个节点保存下来delnext。(要不然直接释放掉就找不到了) 然后将头节点的nedxt指向delnext,再将delnext的prev指向头结点。 最后释放掉要删除的节点,并将其置为空。
注:因为每个节点都是通过动态内存申请的空间,所以在删除节点的时候是通过free,切记释放完需置为空,避免对野指针的使用。
2.2.6 链表中数据的查找
//数据的查找 LTNode* LTFind(LTNode* phead, LTDatatype x) {assert(phead);assert(!LTEmpty(phead));LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x)return pcur;pcur = pcur->next;}return NULL; }
实现思路:
先判断所传phead 的有效性,再进行判空。然后再进行链表的遍历,如果当前节点的数据刚好为所要查找的数据,则就返回当前节点的位置,否则就返回空指针。
2.2.7 链表中指定位置的操作
// 指定位置的操作 //指定位置之后数据的插入 void* LTInsert(LTNode* pos, LTDatatype x) {assert(pos);LTNode* newnode = LTBuyNode(x);newnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode; }//指定位置的数据的删除 void LTErase(LTNode* pos) {assert(pos);pos->prev->next= pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL; }
实现思路:
指定位置之后的插入和指定位置的删除都需要先对所要操作的位置的有效性进行判断。
- 指定位置之后的插入: 申请一个新节点,将新节点的next指向pos的下一个节点,将新节点的prev指向pos。将pos 的下一个节点的prev指向新节点,将pos的next指向新节点。
- 指定位置的删除: 先将pos之前的节点的next指向pos的下一个节点,再将pos的下一个节点的prev指向pos的前一个节点。
2.2.8 链表的销毁
//链表的销毁 void LTDestroy(LTNode* phead) {assert(phead);LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = pcur = NULL; }
实现思路:
先判断所传值的有效性,因为每个节点都是通过动态内存申请的空间,所以链表的销毁需要通过free函数,通过遍历逐一释放节点, 释放完需要将当前节点和头结点置为空。
但是值得注意的是:
- 在释放当前节点时,得先将当前节点的下一个节点保存下来。
- 释放加点时,应该从头结点的下一个节点开始销毁(即从有效节点开始释放)。
2.3 test.c(函数功能的测试文件)
#include "List.h" void test() {LTNode* lt = NULL;LTInit(<);//LTPrint(lt);LTPushBack(lt, 1);LTPushFront(lt, 29);LTPushFront(lt, 25);LTPushFront(lt, 2);LTPushFront(lt, 33);LTPrint(lt);LTPopBack(lt);//LTPushBack(lt, 1);//LTPopBack(lt);LTPrint(lt);/*LTPopFront(lt);LTPopFront(lt);LTPopFront(lt);LTPopFront(lt);LTPrint(lt);*/LTNode*ret=LTFind(lt, 25);if (ret)printf("ҵ%d\n",ret->data);elseprintf("δҵ\n");LTInsert(ret, 56);LTErase(ret);LTPrint(lt);LTDestroy(lt);LTPrint(lt); } int main() {test();return 0; }
如有错误,还望改正!!!
关注博主,优质内容不断更新!
相关文章:

【数据结构之带头双向循环链表的实现】
1.链表的分类 链表的结构有多种多样,以下情况组合起来就有8种(2x2x2)链表结构: 虽然有这么多的链表结构,但是我们实际中最常用的还是两种结构:单链表和双向带头循环链表。 无头单向非循环链表:结…...

【docker】docker数据卷与网络部署服务
Docker 网络模式 选择网络模式 Host Mode (主机模式) 特点: 容器与宿主机共享网络命名空间操作: docker run --nethost ... Container Mode (容器模式) 特点: 容器与指定容器共享网络命名空间操作: docker run --netcontainer:<container-id-or-name> ... None Mode (无…...

Spring MVC框架学习笔记
学习视频:10001 Spring MVC概述_哔哩哔哩_bilibili~11005 请求映射方式_哔哩哔哩_bilibili 目录 1.概述 Java EE三层架构 Spring MVC在三层架构中的位置 编辑 Spring MVC在表现层的作用 Spring MVC的特点 2.Spring MVC入门程序 代码实现 Spring MVC工作原理 Spring …...

LeetCode 100道题目和答案(面试必备)(一)
1.两数之和 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。 你可以按…...

OpenGL投影矩阵
OpenGL Projection Matrix OpenGL投影矩阵...

Linux中的`make`与`Makefile`:项目自动化构建工具
Linux中的make与Makefile:项目自动化构建工具 在Linux及类Unix系统中,make是一种广泛使用的自动化构建工具,它通过读取和执行Makefile(或makefile,文件名不区分大小写)中的指令来自动化编译和构建程序。Ma…...

GitHub开源项目精选:轻量级预约/预订日历组件,用React和TypeScript构建
在日常开发中,我们经常需要在项目中添加预约或预订功能。今天给大家推荐一个超级轻量级的预约/预订日历组件,它是用React和TypeScript构建的,非常适合那些需要简单易用的日历解决方案的开发者。 安装方法: 你可以选择使用npm或者y…...

闲钱放在哪里?收益稳定且又高!
家庭理财,最大的问题就是,手里这点闲钱,说多不多,但打理起来,还真的很”挠头“。 放银行,存款利率接二连三下调,利息又又又要变少了! 投资出去,看着到处的雷声隆隆&…...

【Linux】简易线程池项目
线程池是一个可以巩固一些线程相关接口 && 加强理解的一个小项目。 注意:这里的线程池使用的线程并不是Linux原生接口,而是经过封装的,具体请看线程封装,为什么不使用原生接口? 因为原生接口一旦进行pthread…...

基于vue框架的NBA球星管理系统1878g(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
系统程序文件列表 项目功能:用户,球员,球员数据,榜单类型,联盟榜单,重要比赛回放,精彩时刻视频,视频专栏,本赛季赛程,十佳球,投票信息,投票结果 开题报告内容 基于Vue框架的NBA球星管理系统 开题报告 一、选题背景 随着互联网的普及和体育产业的蓬勃发展&#x…...

【docker】Dockerfile练习
1、overlay文件系统原理测试 cd /mnt mkdir A B C worker merged echo "From A">./A/a.txt echo "From A">./A/b.txt echo "From A">./A/c.txt echo "From B">./B/a.txt echo "From B">./B/d.txt echo &quo…...

数据可视化的魔法:Python Matplotlib库的奇妙之旅
标题:数据可视化的魔法:Python Matplotlib库的奇妙之旅 在数据科学和分析领域,数据可视化是一种将复杂数据转换为图形表示的强有力工具,它可以帮助我们更直观地理解数据。Python中的Matplotlib库是进行数据可视化的瑞士军刀&…...

Python数据科学的秘密武器:Pandas库的深度解析
标题:Python数据科学的秘密武器:Pandas库的深度解析 Python作为数据科学领域的宠儿,其强大的数据处理能力离不开Pandas库的加持。Pandas是一个开源的数据分析和操作库,它提供了快速、灵活和表达力强的数据结构,旨在使…...

云计算实训24——python基本环境搭建、变量和数据类型、数据集合、py脚本
一、python环境搭建 确保拥有阿里云镜像 查看python环境 [rootpython ~]# yum list installed | grep python 查看epel是否安装 [rootpython ~]# yum list installed | grep epel 安装epel [rootpython ~]# yum -y install epel-release.noarch 查看是否安装python3 [rootpyt…...

深入了解网络性能监控(NPM):优化网络性能的关键
目录 网络性能监控(NPM)是什么? 关键网络性能指标 案例分享:如何利用NPM优化网络性能 实用技巧:如何高效运维你的网络 结论 随着企业依赖于互联网和内部网络进行业务运营,网络的稳定性和性能显得尤为重…...

Vue引入使用iconfont字体图标
由于element-ui或element-plus提供的图标有时候并不能满足日常需求,所以这篇介绍一下前端引入阿里巴巴矢量图标库使用,不止是vue使用,不限于vue2、vue3,html或是其他框架也是同样的道理,只要引入都是同样可以使用的。 1. 首先进入阿里巴巴矢量图标库官网 官网:https://…...

Doc2Vec
Doc2Vec 是一种扩展自 Word2Vec 的算法,它不仅可以生成词向量,还可以生成句子或文档的向量。下面是一个使用 Doc2Vec 比较两个句子的具体过程: 步骤 1: 训练 Doc2Vec 模型 首先,你需要有一个训练好的 Doc2Vec 模型。训练过程大致…...

MES生产过程透明管理,实施掌握生产每个环节
MES(制造执行系统)生产过程透明管理,旨在通过集成多种技术手段和管理模块,实现对生产过程的实时监控和精准掌握,确保每个生产环节都能被清晰地记录和追踪。以下是对MES生产过程透明管理的详细阐述: 一、MES…...

Java解析压缩包,并根据指定文件夹上传文件
方法 public Multimap<String, String> getCodeBucketMultimap(HttpServletRequest request)throws IOException {MultipartHttpServletRequest multiRequest (MultipartHttpServletRequest) request;// 基于servlet获取文件流List<MultipartFile> multipartFile…...

【HTML】纯前台字符验证码
效果图: 大致思路: 1.在<canvas>画布里写出几个字符; 2.给字符一个随机的角度和颜色; 3.给字符上画出一些干扰线和干扰点。 <canvas width"100" height"30" id"canvasRef" click"…...

如何在 Vue.js 项目中动态设置页面标题
目录 方法 1:使用 Vue Router 的元信息(meta) 步骤 1: 配置路由元信息 步骤 2: 使用路由守卫设置标题 方法 2:在组件内设置标题 在组件挂载时设置标题 使用响应式数据动态更新标题 在开发 Vue.js 应用时,设置动态页面标题是常见需求,尤其当应用包含多个页面时,为每…...

Eval绕过限制参数限制
PHP Eval函数参数限制在16个字符 PHP代码 <?php$param $_REQUEST[param]; if (strlen($param) < 17 && stripos($param, eval) false && stripos($param, assert) false){eval($param);}?># 部署环境属于ubuntu系统 通过GET传参绕过 由于是…...

计算机网络408考研 2021
2021 计算机网络408考研2021年真题解析_哔哩哔哩_bilibili 1 1 11 1 1 11...

element table表格树形数据展示
element table表格树形数据展示 1、效果 2、代码 <el-table ref"pointMultipleTable" border class"table-box" :data"[damActiveObj]"row-key"id" :tree-props"{ children: children }" :expand-row-keys"expand…...

Ubuntu 安装 Snipaste
一、下载 Snipaste 下载Snipastehttps://zh.snipaste.com/ 二、在/opt 创建 Snipaste 目录,创建 bin 和 icon 子目录,将 Snipaste.AppImage 移动到 bin 目录 三、创建快捷键图标 1. 创建桌面图标,右键→允许运行 yammiemy-pc >/home/y…...

NET8环境WebAPI实现文件的压缩及下载
目录 1、文件下载的原理2、具体实现2.1 提前准备2.2 服务器端的实现2.3 请求端的实现 3、代码下载4、更多特性4.1 单独压缩文件4.2 解析4.2.1 整体解析4.2.2 单个文件解析 4.3 其他4.3.1 设置压缩级别4.3.2 密码保护4.3.3 进度反馈 5、参考资料 1、文件下载的原理 在实际应用环…...

Ubuntu 18 使用NVIDIA上的HDMI输出声音
前言 在未做修改之前,Settings -> Sound -> Output 里面只有 Digital Output(S/PDIF) - Built-in Audio 不显示HDMI的输出设备检查当前存在的音频设备 sudo lspci -v | grep -A7 -i "audio"输出: 从输出可以看出来是有两个设备的 00:1…...

C#模拟量线性变换小程序
1、一步步建立一个C#项目 一步步建立一个C#项目(连续读取S7-1200PLC数据)_s7协议批量读取-CSDN博客文章浏览阅读1.7k次,点赞2次,收藏4次。本文详细介绍了如何使用C#构建一个项目,通过S7net库连接并连续读取S7-1200 PLC的数据,包括创建窗体应用、配置存储位置、安装S7net库…...

跟《经济学人》学英文:2024年08月10日这期 How AI models are getting smarter
How AI models are getting smarter Deep neural networks are learning diffusion and other tricks 原文: Type in a question to ChatGPT and an answer will materialise. Put a prompt into DALL-E 3 and an image will emerge. Click on TikTok’s “for y…...

Spring Web MVC入门(上)
1. Spring Web MVC Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从⼀开始就包含在 Spring 框架中。它的正式名称“Spring Web MVC”来⾃其源模块的名称(Spring-webmvc),但它通常被称为“spring MVC”; 什么是Servlet呢? Servlet…...