【数据结构】带头+双向+循环链表(DList)(增、删、查、改)详解
一、带头双向循环链表的定义和结构
1、定义
带头双向循环链表,有一个数据域和两个指针域。一个是前驱指针,指向其前一个节点;一个是后继指针,指向其后一个节点。
// 定义双向链表的节点
typedef struct ListNode
{LTDataType data; // 数据域struct ListNode* prev; // 前驱指针struct ListNode* next; // 后继指针
}ListNode;
2、结构
带头双向循环链表:在所有的链表当中 结构最复杂,一般用在 单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多 优势,实现反而简单了。
二、带头双向循环链表接口的实现
1、创建文件
- test.c(主函数、测试顺序表各个接口功能)
- List.c(带头双向循环链表接口函数的实现)
- List.h(带头双向循环链表的类型定义、接口函数声明、引用的头文件)

2、List.h 头文件代码
// List.h
// 带头+双向+循环链表增删查改实现
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int LTDataType;// 定义双向链表的节点
typedef struct ListNode
{LTDataType data; // 数据域struct ListNode* prev; // 前驱指针struct ListNode* next; // 后继指针
}ListNode;// 动态申请一个新节点
ListNode* BuyListNode(LTDataType x);
// 创建返回链表的头结点
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* plist);
// 双向链表打印
void ListPrint(ListNode* plist);
// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* plist);
// 双向链表头插
void ListPushFront(ListNode* plist, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* plist);
// 双向链表查找
ListNode* ListFind(ListNode* plist, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
// 双向链表的判空
bool ListEmpty(ListNode* phead);
// 获取双向链表的元素个数
size_t ListSize(ListNode* phead);
三、在 List.c 上是西安各个接口函数
1、动态申请一个新结点
// 动态申请一个新节点
ListNode* BuyListNode(LTDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));newnode->data = x;newnode->prev = NULL;newnode->next = NULL;return newnode;
}
2、创建返回链表的头结点(初始化头结点)
// 创建返回链表的头结点
ListNode* ListCreate()
{ListNode* phead = (ListNode*)malloc(sizeof(ListNode)); // 哨兵位头结点phead->next = phead;phead->prev = phead;return phead;
}
也可以用下面这个函数(道理一样):
// 初始化链表
void ListInit(ListNode** pphead)
{*pphead = BuyListNode(-1); // 动态申请一个头节点(*pphead)->prev = *pphead; // 前驱指针指向自己(*pphead)->next = *pphead; // 后继指针指向自己
}
头指针初始指向 NULL,初始化链表时,需要改变头指针的指向,使其指向头节点,所以这里需要传二级指针。
初始化带头双向循环链表,首先动态申请一个头结点,头结点的前驱和后继指针都指向自己,形成一个循环。
3、双向链表的销毁
// 双向链表的销毁
void ListDestroy(ListNode** pphead)
{assert(pphead);assert(*pphead);ListNode* cur = (*pphead)->next;while (cur != *pphead){ListNode* next = cur->next; // 记录cur的直接后继节点free(cur);cur = next;}free(*pphead); // 释放头节点*pphead = NULL; // 置空头指针
}
销毁链表,最后要将头指针 plist 置空,所以用了二级指针来接收。这里也可以用一级指针,但要在函数外面置空 plist 。
一级指针写法:
void ListDestroy(ListNode* phead) {assert(phead);ListNode* cur = phead->next;while (cur != phead){ListNode* next = cur->next;free(cur);cur = next;}free(phead);phead = NULL; }
4、双向链表的打印
// 打印双向链表
void ListPrint(ListNode* phead)
{assert(phead);ListNode* cur = phead->next; // 记录第一个节点printf("head <-> ");while (cur != phead){printf("%d <-> ", cur->data);cur = cur->next;}printf("head\n");
}
5、双向链表的尾插
// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x)
{assert(phead); // 头指针不能为空/* ListNode* newnode = BuyListNode(x); // 动态申请一个节点ListNode* tail = phead->prev; // 记录尾节点tail->next = newnode; // 尾节点的后继指针指向新节点newnode->prev = tail; //2、新节点的前驱指针指向尾节点newnode->next = phead; // 新节点的后继指针指向头节点phead->prev = newnode; // 头节点的前驱指针指向新节点 */ListInsert(phead, x);
}
6、双向链表的尾删
// 双向链表的尾删
void ListPopBack(ListNode* phead)
{assert(phead);assert(phead->next != phead); // 只剩头节点时 链表为空 不能再继续删除/* ListNode* tail = phead->prev; // 记录尾节点ListNode* tailPrev = tail->prev; // 记录尾节点的直接前驱tailPrev->next = phead; // 尾节点的前驱节点的next指针指向头节点phead->prev = tailPrev; // 头节点的prev指针指向尾节点的前驱节点free(tail); // 释放尾节点 */ListErase(pHead->prev);
}

7、双向链表的头插
// 双向链表的头插
void ListPushFront(ListNode* phead, LTDataType x)
{assert(phead);/* ListNode* newnode = BuyListNode(x); // 申请新节点ListNode* pheadNext = phead->next; // 记录第一个节点// 头节点和新节点建立链接phead->next = newnode;newnode->prev = phead;// 新节点和第一个节点建立链接newnode->next = pheadNext;pheadNext->prev = newnode; */ListInsert(phead->next, x);
}
8、双向链表的头删
// 双向链表的头删
void ListPopFront(ListNode* phead)
{assert(phead);assert(phead->next != phead); // 只剩头节点时 链表为空 不能再继续删除/* ListNode* pheadNext = phead->next; // 记录第一个节点// 头节点和第一个节点的后继节点建立链接phead->next = pheadNext->next;pheadNext->next->prev = phead;free(pheadNext); // 头删 */ListErase(phead->next);
}

9、查找双向链表中的元素
// 在双向链表中查找元素,并返回该元素的地址
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; //没找到 返回NULL
}
10、在指定pos位置之前插入元素
// 在指定pos位置之前插入元素
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);ListNode* newnode = BuyListNode(x); // 申请一个节点ListNode* posPrev = pos->prev; // 记录pos的直接前驱// pos的直接前驱和新节点建立链接posPrev->next = newnode;newnode->prev = posPrev;// 新节点和pos建立链接newnode->next = pos;pos->prev = newnode;
}
实现了该函数后,可以尝试改进头插函数(pos相当于链表的第一个节点)和尾插函数(pos相当于链表的头节点),这样写起来更简便。
11、删除指定pos位置的元素
// 删除指定pos位置的元素
void ListErase(ListNode* pos)
{assert(pos);ListNode* posPrev = pos->prev; // 记录pos的直接前驱ListNode* posNext = pos->next; // 记录pos的直接后继// pos的直接前驱和直接后继建立链接posPrev->next = posNext;posNext->prev = posPrev;free(pos); // 释放pos位置的元素//pos = NULL;
}
实现了该函数后,可以尝试改进头删函数(pos相当于链表的第一个节点)和尾删函数(pos相当于链表的最后一个节点),这样写起来更简便。

12、双向链表的判空
// 双向链表的判空
bool ListEmpty(ListNode* phead)
{ assert(phead);return phead->next == phead; //为空 返回ture 否则返回false
}
13、获取双向链表的元素个数
// 获取双向链表的元素个数
size_t ListSize(ListNode* phead)
{assert(phead);size_t size = 0;ListNode* cur = phead->next; // 记录第一个节点while (cur != phead){size++;cur = cur->next;}return size;
}
四、代码整合
// List.c
#include "List.h"// 动态申请一个新节点
ListNode* BuyListNode(LTDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));newnode->data = x;newnode->prev = NULL;newnode->next = NULL;return newnode;
}// 创建返回链表的头结点
ListNode* ListCreate()
{ListNode* phead = (ListNode*)malloc(sizeof(ListNode)); // 哨兵位头结点phead->next = phead;phead->prev = phead;return phead;
}// 双向链表的销毁
void ListDestroy(ListNode** pphead)
{assert(pphead);assert(*pphead);ListNode* cur = (*pphead)->next;while (cur != *pphead){ListNode* next = cur->next; // 记录cur的直接后继节点free(cur);cur = next;}free(*pphead); // 释放头节点*pphead = NULL; // 置空头指针
}// 打印双向链表
void ListPrint(ListNode* phead)
{assert(phead);ListNode* cur = phead->next; // 记录第一个节点printf("head <-> ");while (cur != phead){printf("%d <-> ", cur->data);cur = cur->next;}printf("head\n");
}// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x)
{assert(phead); // 头指针不能为空ListInsert(phead, x);
}// 双向链表的尾删
void ListPopBack(ListNode* phead)
{assert(phead);assert(phead->next != phead); // 只剩头节点时 链表为空 不能再继续删除ListErase(pHead->prev);
}// 双向链表的头插
void ListPushFront(ListNode* phead, LTDataType x)
{assert(phead);ListInsert(phead->next, x);
}// 双向链表的头删
void ListPopFront(ListNode* phead)
{assert(phead);assert(phead->next != phead); // 只剩头节点时 链表为空 不能再继续删除ListErase(phead->next);
}// 在双向链表中查找元素,并返回该元素的地址
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; //没找到 返回NULL
}// 在指定pos位置之前插入元素
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);ListNode* newnode = BuyListNode(x); // 申请一个节点ListNode* posPrev = pos->prev; // 记录pos的直接前驱// pos的直接前驱和新节点建立链接posPrev->next = newnode;newnode->prev = posPrev;// 新节点和pos建立链接newnode->next = pos;pos->prev = newnode;
}// 删除指定pos位置的元素
void ListErase(ListNode* pos)
{assert(pos);ListNode* posPrev = pos->prev; // 记录pos的直接前驱ListNode* posNext = pos->next; // 记录pos的直接后继// pos的直接前驱和直接后继建立链接posPrev->next = posNext;posNext->prev = posPrev;free(pos); // 释放pos位置的元素//pos = NULL;
}// 双向链表的判空
bool ListEmpty(ListNode* phead)
{ assert(phead);return phead->next == phead; //为空 返回ture 否则返回false
}// 获取双向链表的元素个数
size_t ListSize(ListNode* phead)
{assert(phead);size_t size = 0;ListNode* cur = phead->next; // 记录第一个节点while (cur != phead){size++;cur = cur->next;}return size;
}
相关文章:
【数据结构】带头+双向+循环链表(DList)(增、删、查、改)详解
一、带头双向循环链表的定义和结构 1、定义 带头双向循环链表,有一个数据域和两个指针域。一个是前驱指针,指向其前一个节点;一个是后继指针,指向其后一个节点。 // 定义双向链表的节点 typedef struct ListNode {LTDataType dat…...
接口自动化测试平台
下载了大神的EasyTest项目demo修改了下<https://testerhome.com/topics/12648 原地址>。也有看另一位大神的HttpRunnerManager<https://github.com/HttpRunner/HttpRunnerManager 原地址>,由于水平有限,感觉有点复杂~~~ 【整整200集】超超超…...
【物联网】微信小程序接入阿里云物联网平台
微信小程序接入阿里云物联网平台 一 阿里云平台端 1.登录阿里云 阿里云物联网平台 点击进入公共实例,之前没有的点进去申请 2.点击产品,创建产品 3.产品名称自定义,按项目选择类型,节点类型选择之恋设备,联网方式W…...
PKG内容查看工具:Suspicious Package for Mac安装教程
Suspicious Package Mac版是一款Mac平台上的查看 PKG 程序包内信息的应用,Suspicious Package Mac版支持查看全部包内全部文件,比如需要运行的脚本,开发者,来源等等。 suspicious package mac使用简单,只需在选择pkg安…...
第16节:R语言医学分析实例:肺切除手术的Apriori关联规则分析
关联规则 肺切除手术的Apriori关联规则分析。 分析的目的是确定患有肺癌并需要接受肺切除术的患者的共病症状。 了解哪些症状是共病的可以帮助改善患者护理和药物处方。 分析类型是关联规则学习,通过探索变量之间的关联或频繁项集,尝试在大型数据集中找到见解和隐藏关系(H…...
ChatGPT+MidJourney 3分钟生成你的动画故事
chatgpt是真的火了,chatgpt产生了一个划时代的意义——自chatgpt起,AI是真的要落地了。 chatgpt能做的事情太多了,多到最初开发模型的程序员自己,也没法说得清楚chatgpt都能做啥,似乎只要你能想得到,它都有…...
在CSDN学Golang云原生(Kubernetes Pod调度)
一,NodeSelector定向调度 在 Kubernetes 中,可以使用 NodeSelector 字段来指定 Pod 调度到哪些节点上运行。NodeSelector 是一个键值对的 map,其中键是节点的标签名,值是标签值。具体步骤如下: 在节点上添加标签 首…...
Rust vs Go:常用语法对比(七)
题图来自 Go vs Rust: Which will be the top pick in programming?[1] 121. UDP listen and read Listen UDP traffic on port p and read 1024 bytes into buffer b. 听端口p上的UDP流量,并将1024字节读入缓冲区b。 import ( "fmt" "net&qu…...
【HarmonyOS】API6使用storage实现轻量级数据存储
写在前面 本篇内容基于API6 JS语言进行开发,通过结合轻量级数据存储开发指导的文档,帮助大家完成一个实际的代码案例,通过这个小案例,可以实现简单数据的存储。 参考文档:文档中心 1、页面布局 首先我们编写一个简单…...
Python Flask构建微信小程序订餐系统 (十二)
🔥 创建切换商品分类状态的JS文件 🔥 ; var food_act_ops={init:function(){this.eventBind();},eventBind:function(){//表示作用域var that = this;$(".wrap_search select[name=status]").change(function(){$(".wrap_search").submit();});$(&qu…...
C++——模板的作用2:特例化
目录 模板的形式: 一.模板的多参数应用: 例: 错误使用1:使用不标准的模板形参表 编辑 错误使用2:使用变量作为实参传递给函数模板 二.模板的特例化: 类模板: 针对模板的特化步骤&am…...
Python Web开发技巧VII
目录 装饰器inject_serializer 装饰器atomic rebase git 清理add的数据 查看git的当前工作目录 makemigrations文件名称 action(detailTrue, methods["GET"]) 如何只取序列化器的一个字段进行返回 Response和JsonResponse有什么区别 序列化器填表和单字段如…...
LaTex4【下载模板、引入文献】
下载latex模板:(模板官网一般都有,去找) 我这随便找了一个: 下载得到一个压缩包,然后用overleaf打开👇: (然后改里面的内容就好啦) 另外,有很多在线的数学公式编辑器&am…...
【VSCode部署模型】导出TensorFlow2.X训练好的模型信息
参考tensorflow2.0 C加载python训练保存的pb模型 经过模型训练及保存,我们得到“OptimalModelDataSet2”文件夹,模型的保存方法(.h5或.pb文件),参考【Visual Studio Code】c/c部署tensorflow训练的模型 其中“OptimalModelDataSet2”文件夹保…...
windows环境下,安装elasticsearch
目录 前言准备安装 jdk 安装nodejsElasticSearch下载ElasticSearch-head 下载 安装ElasticSearch安装ElasticSearch-head插件设置用户名密码访问ElasticSearch 默认用户名和密码参考 前言 win10elasticsearch 8.9.0 准备 安装 jdk ElasticSearch 是基于lucence开发的&#…...
Elasticsearch入门笔记(一)
环境搭建 Elasticsearch是搜索引擎,是常见的搜索工具之一。 Kibana 是一个开源的分析和可视化平台,旨在与 Elasticsearch 合作。Kibana 提供搜索、查看和与存储在 Elasticsearch 索引中的数据进行交互的功能。开发者或运维人员可以轻松地执行高级数据分析…...
记一次安装nvm切换node.js版本实例详解
最后效果如下: 背景:由于我以前安装过node.js,后续想安装nvm将node.js管理起来。 问题:nvm-use命令行运行成功,但是nvm-list显示并没有成功。 原因:因为安装过node.js,所以原先的node.js不收n…...
生态共建丨YashanDB与构力科技完成兼容互认证
近日,深圳计算科学研究院崖山数据库系统YashanDB V22.2与北京构力科技有限公司BIMBase云平台完成兼容性互认证。经严格测试,双方产品完全兼容、运行稳定。 崖山数据库系统YashanDB是深算院自主研发设计的新型数据库系统,融入原创理论…...
React从入门到实战-react脚手架,消息订阅与发布
创建项目并启动 全局安装 npm install -g create-react-app切换到想创建项目的目录,使用命令:create-react-app 项目名称 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存中…(iQ6hEUgAABpQAAAD1CAYAAABeIRZoAAAAAXNSR0IArs4c6QAAIABJREFUe…...
从零构建深度学习推理框架-1 简介和Tensor
源代码作者:https://github.com/zjhellofss 本文仅作为个人学习心得领悟 ,将原作品提炼,更加适合新手 什么是推理框架? 深度学习推理框架用于对已训练完成的神经网络进行预测,也就是说,能够将深度训练框…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...








