【数据结构之带头双向循环链表的实现】
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"…...
微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...
并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...
C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...
Linux系统部署KES
1、安装准备 1.版本说明V008R006C009B0014 V008:是version产品的大版本。 R006:是release产品特性版本。 C009:是通用版 B0014:是build开发过程中的构建版本2.硬件要求 #安全版和企业版 内存:1GB 以上 硬盘…...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...
通过MicroSip配置自己的freeswitch服务器进行调试记录
之前用docker安装的freeswitch的,启动是正常的, 但用下面的Microsip连接不上 主要原因有可能一下几个 1、通过下面命令可以看 [rootlocalhost default]# docker exec -it freeswitch fs_cli -x "sofia status profile internal"Name …...



