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

【数据结构】带头+双向+循环链表(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,初始化链表时,需要改变头指针的指向,使其指向头节点,所以这里需要传二级指针。 


初始化带头双向循环链表,首先动态申请一个头结点头结点的前驱和后继指针都指向自己,形成一个循环

image-20210903220045099


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、定义 带头双向循环链表&#xff0c;有一个数据域和两个指针域。一个是前驱指针&#xff0c;指向其前一个节点&#xff1b;一个是后继指针&#xff0c;指向其后一个节点。 // 定义双向链表的节点 typedef struct ListNode {LTDataType dat…...

接口自动化测试平台

下载了大神的EasyTest项目demo修改了下<https://testerhome.com/topics/12648 原地址>。也有看另一位大神的HttpRunnerManager<https://github.com/HttpRunner/HttpRunnerManager 原地址>&#xff0c;由于水平有限&#xff0c;感觉有点复杂~~~ 【整整200集】超超超…...

【物联网】微信小程序接入阿里云物联网平台

微信小程序接入阿里云物联网平台 一 阿里云平台端 1.登录阿里云 阿里云物联网平台 点击进入公共实例&#xff0c;之前没有的点进去申请 2.点击产品&#xff0c;创建产品 3.产品名称自定义&#xff0c;按项目选择类型&#xff0c;节点类型选择之恋设备&#xff0c;联网方式W…...

PKG内容查看工具:Suspicious Package for Mac安装教程

Suspicious Package Mac版是一款Mac平台上的查看 PKG 程序包内信息的应用&#xff0c;Suspicious Package Mac版支持查看全部包内全部文件&#xff0c;比如需要运行的脚本&#xff0c;开发者&#xff0c;来源等等。 suspicious package mac使用简单&#xff0c;只需在选择pkg安…...

第16节:R语言医学分析实例:肺切除手术的Apriori关联规则分析

关联规则 肺切除手术的Apriori关联规则分析。 分析的目的是确定患有肺癌并需要接受肺切除术的患者的共病症状。 了解哪些症状是共病的可以帮助改善患者护理和药物处方。 分析类型是关联规则学习,通过探索变量之间的关联或频繁项集,尝试在大型数据集中找到见解和隐藏关系(H…...

ChatGPT+MidJourney 3分钟生成你的动画故事

chatgpt是真的火了&#xff0c;chatgpt产生了一个划时代的意义——自chatgpt起&#xff0c;AI是真的要落地了。 chatgpt能做的事情太多了&#xff0c;多到最初开发模型的程序员自己&#xff0c;也没法说得清楚chatgpt都能做啥&#xff0c;似乎只要你能想得到&#xff0c;它都有…...

在CSDN学Golang云原生(Kubernetes Pod调度)

一&#xff0c;NodeSelector定向调度 在 Kubernetes 中&#xff0c;可以使用 NodeSelector 字段来指定 Pod 调度到哪些节点上运行。NodeSelector 是一个键值对的 map&#xff0c;其中键是节点的标签名&#xff0c;值是标签值。具体步骤如下&#xff1a; 在节点上添加标签 首…...

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流量&#xff0c;并将1024字节读入缓冲区b。 import ( "fmt" "net&qu…...

【HarmonyOS】API6使用storage实现轻量级数据存储

写在前面 本篇内容基于API6 JS语言进行开发&#xff0c;通过结合轻量级数据存储开发指导的文档&#xff0c;帮助大家完成一个实际的代码案例&#xff0c;通过这个小案例&#xff0c;可以实现简单数据的存储。 参考文档&#xff1a;文档中心 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:特例化

目录 模板的形式&#xff1a; 一.模板的多参数应用&#xff1a; 例&#xff1a; 错误使用1&#xff1a;使用不标准的模板形参表 ​编辑 错误使用2&#xff1a;使用变量作为实参传递给函数模板 二.模板的特例化&#xff1a; 类模板&#xff1a; 针对模板的特化步骤&am…...

Python Web开发技巧VII

目录 装饰器inject_serializer 装饰器atomic rebase git 清理add的数据 查看git的当前工作目录 makemigrations文件名称 action(detailTrue, methods["GET"]) 如何只取序列化器的一个字段进行返回 Response和JsonResponse有什么区别 序列化器填表和单字段如…...

LaTex4【下载模板、引入文献】

下载latex模板&#xff1a;&#xff08;模板官网一般都有&#xff0c;去找&#xff09; 我这随便找了一个&#xff1a; 下载得到一个压缩包&#xff0c;然后用overleaf打开&#x1f447;&#xff1a; (然后改里面的内容就好啦) 另外&#xff0c;有很多在线的数学公式编辑器&am…...

【VSCode部署模型】导出TensorFlow2.X训练好的模型信息

参考tensorflow2.0 C加载python训练保存的pb模型 经过模型训练及保存&#xff0c;我们得到“OptimalModelDataSet2”文件夹&#xff0c;模型的保存方法(.h5或.pb文件)&#xff0c;参考【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是搜索引擎&#xff0c;是常见的搜索工具之一。 Kibana 是一个开源的分析和可视化平台&#xff0c;旨在与 Elasticsearch 合作。Kibana 提供搜索、查看和与存储在 Elasticsearch 索引中的数据进行交互的功能。开发者或运维人员可以轻松地执行高级数据分析…...

记一次安装nvm切换node.js版本实例详解

最后效果如下&#xff1a; 背景&#xff1a;由于我以前安装过node.js&#xff0c;后续想安装nvm将node.js管理起来。 问题&#xff1a;nvm-use命令行运行成功&#xff0c;但是nvm-list显示并没有成功。 原因&#xff1a;因为安装过node.js&#xff0c;所以原先的node.js不收n…...

生态共建丨YashanDB与构力科技完成兼容互认证

近日&#xff0c;深圳计算科学研究院崖山数据库系统YashanDB V22.2与北京构力科技有限公司BIMBase云平台完成兼容性互认证。经严格测试&#xff0c;双方产品完全兼容、运行稳定。 崖山数据库系统YashanDB是深算院自主研发设计的新型数据库系统&#xff0c;融入原创理论&#xf…...

React从入门到实战-react脚手架,消息订阅与发布

创建项目并启动 全局安装 npm install -g create-react-app切换到想创建项目的目录&#xff0c;使用命令&#xff1a;create-react-app 项目名称 ​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存中…(iQ6hEUgAABpQAAAD1CAYAAABeIRZoAAAAAXNSR0IArs4c6QAAIABJREFUe…...

从零构建深度学习推理框架-1 简介和Tensor

源代码作者&#xff1a;https://github.com/zjhellofss 本文仅作为个人学习心得领悟 &#xff0c;将原作品提炼&#xff0c;更加适合新手 什么是推理框架&#xff1f; 深度学习推理框架用于对已训练完成的神经网络进行预测&#xff0c;也就是说&#xff0c;能够将深度训练框…...

JavaSec-RCE

简介 RCE(Remote Code Execution)&#xff0c;可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景&#xff1a;Groovy代码注入 Groovy是一种基于JVM的动态语言&#xff0c;语法简洁&#xff0c;支持闭包、动态类型和Java互操作性&#xff0c…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查

在对接支付宝API的时候&#xff0c;遇到了一些问题&#xff0c;记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

逻辑回归:给不确定性划界的分类大师

想象你是一名医生。面对患者的检查报告&#xff08;肿瘤大小、血液指标&#xff09;&#xff0c;你需要做出一个**决定性判断**&#xff1a;恶性还是良性&#xff1f;这种“非黑即白”的抉择&#xff0c;正是**逻辑回归&#xff08;Logistic Regression&#xff09;** 的战场&a…...

Go 语言接口详解

Go 语言接口详解 核心概念 接口定义 在 Go 语言中&#xff0c;接口是一种抽象类型&#xff0c;它定义了一组方法的集合&#xff1a; // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的&#xff1a; // 矩形结构体…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

生成 Git SSH 证书

&#x1f511; 1. ​​生成 SSH 密钥对​​ 在终端&#xff08;Windows 使用 Git Bash&#xff0c;Mac/Linux 使用 Terminal&#xff09;执行命令&#xff1a; ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" ​​参数说明​​&#xff1a; -t rsa&#x…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)

参考官方文档&#xff1a;https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java&#xff08;供 Kotlin 使用&#xff09; 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...