数据结构——带头双向循环链表
数据结构——带头双向循环链表
- 一、带头双向循环链表的定义
- 二、带头双向循环链表的实现
- 2.1初始化创建带头双向循环链表的节点
- 2.2申请新节点
- 2.3节点的初始化
- 2.4带头双向循环链表的尾插
- 2.5带头双向循环链表的头插
- 2.6判空函数
- 2.7带头双向循环链表的打印函数
- 2.8带头双向循环链表的尾删
- 2.9带头双向循环链表的头删
- 2.11带头双向循环链表的在pos之前插入
- 2.12带头双向循环链表的在pos位置删除
- 2.14带头双向循环链表的销毁
- 三、完整代码
- 3.1LIst.h
- 3.2List.c
- 3.3test.c
一、带头双向循环链表的定义
带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势。
带头双向循环链表包括一个带有哨兵位的头节点,该节点既可以作为链表的第一个节点,也可以作为链表的最后一个节点.
这种链表的特点是每个节点都有两个指针,一个指向前一个节点,一个指向后一个节点,这样就可以实现双向遍历。
同时,链表的最后一个节点的后继指针指向头节点,形成了循环的结构。这样,我们可以在任意一个节点上进行前后移动,插入和删除操作,而不需要像单链表那样遍历整个链表去找到前一个节点。
需要注意的是,带头双向循环链表为空并不意味着没有一个节点,而是只有一个带哨兵位的头节点,所以在使用之前需要对链表进行初始化。
二、带头双向循环链表的实现
2.1初始化创建带头双向循环链表的节点
typedef struct ListNode
{Listdatatype data;struct ListNode* next;struct ListNode* prev;
}LTNode;

在创建带头双向循环链表的节点中比之前单链表节点的创建多了一个struct ListNode* prev;结构体指针,目的在与存储前一个节点的地址,便于将整个链表连在一起。
2.2申请新节点
//创建新节点
LTNode* BuyLTNode(Listdatatype x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc fail");return NULL;}newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}
动态申请内存结点,函数返回的是一个指针类型,用malloc开辟一个LTNode大小的空间,并用node指向这个空间,再判断是否为空,如为空就perror,显示错误信息。反之则把需要存储的数据x存到newnode指向的空间里面,并且把newnode->next,newnode->prev置为空。
2.3节点的初始化
LTNode* LTInit()
{LTNode* phead = BuyLTNode(-1);phead->next = phead;phead->prev = phead;return phead;
}
通过动态内存申请节点,申请了一个头节点。并且将它的phead->next ,phead->prev 都置为phead,得到如下图的头节点。

2.4带头双向循环链表的尾插
void LTPushBack(LTNode* phead, Listdatatype x)
{assert(phead);LTNode* tail = phead->prev;LTNode* newnode = BuyLTNode(x);tail->next = newnode;newnode->prev = tail;newnode->next = phead;phead->prev = newnode;
}
尾插节点的方法:首先通过内存申请一个节点, 然后改变四个指针的指向,便可以完成带头双向循环链表的尾插。


2.5带头双向循环链表的头插
void LTFrontBack(LTNode* phead, Listdatatype x)
{assert(phead);LTNode* newnode = BuyLTNode(x);newnode->next = phead->next;phead->next->prev = newnode;phead->next = newnode;newnode->prev = phead;
}


2.6判空函数
bool LTEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
}
2.7带头双向循环链表的打印函数
//打印
void LTPrint(LTNode* phead)
{LTNode* cur = phead->next;printf("guard<->");while (cur != phead){printf("%d<->", cur->data);cur = cur->next;}printf("\n");
}
2.8带头双向循环链表的尾删
//尾删
void LTPopBack(LTNode* phead)
{assert(phead);assert(! LTEmpty(phead));LTNode* tail = phead->prev;LTNode* tailprev = tail->prev;//改变指针的指向free(tail);tailprev->next = phead;phead->prev = tailprev;
}

2.9带头双向循环链表的头删
void LTPopFront(LTNode* phead)
{assert(phead);assert(!LTEmpty(phead));LTNode* first = phead->next;LTNode* firstnext = first->next;free(first);phead->next = firstnext;firstnext->prev = phead;
}

2.11带头双向循环链表的在pos之前插入
void LTInsert(LTNode* pos, Listdatatype x)
{assert(pos);LTNode* newnode = BuyLTNode(x);LTNode* posprev = pos->prev;posprev->next = newnode;newnode->prev = posprev;newnode->next = pos;pos->prev = newnode;
}

2.12带头双向循环链表的在pos位置删除
void LTErase(LTNode* pos)
{assert(pos);LTNode* posprev = pos->prev;LTNode* posnext = pos->next;posprev->next = posnext;posnext->prev = posprev;free(pos);
}

2.14带头双向循环链表的销毁
//销毁
LTNode* LTDestory(LTNode* phead)
{LTNode* cur = phead->next;while (cur != phead){LTNode* next = cur->next;free(cur);cur = next;}free(phead);
}
三、完整代码
3.1LIst.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int Listdatatype;
typedef struct ListNode
{Listdatatype data;struct ListNode* next;struct ListNode* prev;
}LTNode;//初始化
LTNode* LTInit();//尾插
void LTPushBack(LTNode* phead, Listdatatype x);//尾删
void LTPopBack(LTNode* phead);//头插
void LTFrontBack(LTNode* phead, Listdatatype x);//头删
void LTPopFront(LTNode* phead);//打印
void LTPrint(LTNode* phead);//判空
bool LTEmpty(LTNode* phead);//在pos之前插入
void LTInsert(LTNode* pos, Listdatatype x);//在pos之前删除
void LTErase(LTNode* pos);//寻找
LTNode* LTFind(LTNode* phead, Listdatatype x);//销毁
LTNode* LTDestory(LTNode* phead);
3.2List.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"
//创建新节点
LTNode* BuyLTNode(Listdatatype x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc fail");return NULL;}newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}LTNode* LTInit()
{LTNode* phead = BuyLTNode(-1);phead->next = phead;phead->prev = phead;return phead;
}
//尾插
void LTPushBack(LTNode* phead, Listdatatype x)
{assert(phead);LTNode* tail = phead->prev;LTNode* newnode = BuyLTNode(x);tail->next = newnode;newnode->prev = tail;newnode->next = phead;phead->prev = newnode;
}//头插
void LTFrontBack(LTNode* phead, Listdatatype x)
{assert(phead);LTNode* newnode = BuyLTNode(x);newnode->next = phead->next;phead->next->prev = newnode;phead->next = newnode;newnode->prev = phead;
}//判空
bool LTEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
}//打印
void LTPrint(LTNode* phead)
{LTNode* cur = phead->next;printf("guard<->");while (cur != phead){printf("%d<->", cur->data);cur = cur->next;}printf("\n");
}//尾删
void LTPopBack(LTNode* phead)
{assert(phead);assert(! LTEmpty(phead));LTNode* tail = phead->prev;LTNode* tailprev = tail->prev;//改变指针的指向free(tail);tailprev->next = phead;phead->prev = tailprev;
}//头删
void LTPopFront(LTNode* phead)
{assert(phead);assert(!LTEmpty(phead));LTNode* first = phead->next;LTNode* firstnext = first->next;free(first);phead->next = firstnext;firstnext->prev = phead;
}//寻找
LTNode* LTFind(LTNode* phead, Listdatatype x)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}//在pos之前插入
void LTInsert(LTNode* pos, Listdatatype x)
{assert(pos);LTNode* newnode = BuyLTNode(x);LTNode* posprev = pos->prev;posprev->next = newnode;newnode->prev = posprev;newnode->next = pos;pos->prev = newnode;
}在pos之前删除
//void LTErase(LTNode* pos)
//{
// assert(pos);
// LTNode* posprev = pos->prev;
// free(posprev);
// posprev->prev->next = pos;
// pos->prev = posprev->prev;
//
//}//在pos位置删除
void LTErase(LTNode* pos)
{assert(pos);LTNode* posprev = pos->prev;LTNode* posnext = pos->next;posprev->next = posnext;posnext->prev = posprev;free(pos);
}//销毁
LTNode* LTDestory(LTNode* phead)
{LTNode* cur = phead->next;while (cur != phead){LTNode* next = cur->next;free(cur);cur = next;}free(phead);
}
3.3test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"
//void test1()
//{
// LTNode* plist = LTInit();
// LTPushBack(plist, 1);
// LTPushBack(plist, 2);
// LTPushBack(plist, 3);
// LTPushBack(plist, 4);
// LTPrint(plist);
// LTPopBack(plist);
// LTPrint(plist);//}
void test2()
{LTNode* plist = LTInit();LTFrontBack(plist, 1);LTFrontBack(plist, 2);LTFrontBack(plist, 3);LTFrontBack(plist, 4);LTPrint(plist);LTErase(3);LTPrint(plist);
}
int main()
{//test1();test2();return 0;
}
相关文章:
数据结构——带头双向循环链表
数据结构——带头双向循环链表 一、带头双向循环链表的定义二、带头双向循环链表的实现2.1初始化创建带头双向循环链表的节点2.2申请新节点2.3节点的初始化2.4带头双向循环链表的尾插2.5带头双向循环链表的头插2.6判空函数2.7带头双向循环链表的打印函数2.8带头双向循环链表的尾…...
MySQL大数据量高速迁移,500GB只需1个小时
在上篇「快、准、稳的实现亿级别MySQL大表迁移」的文章中,介绍了NineData在单张大表场景下的迁移性能和优势。但在大部分场景中,可能遇到的是多张表构成的大数据量场景下的数据搬迁问题。因为搬迁数据量较大,迁移的时长、稳定性及准确性都受到…...
kafka复习:(25)kafka stream
一、java代码: package com.cisdi.dsp.modules.metaAnalysis.rest.kafka2023;import org.apache.kafka.common.serialization.Serdes; import org.apache.kafka.streams.KafkaStreams; import org.apache.kafka.streams.StreamsBuilder; import org.apache.kafka.s…...
接口自动化测试总结
一、什么项目适合做自动化测试? 软件需求变动不频繁 测试脚本的稳定性决定了自动化测试的维护成本。如果软件需求变动过于频繁,测试人员需要根据变动的需求来更新测试用例以及相关的测试脚本,而脚本的维护本身就是一个代码开发的过程&#x…...
【Redis】Lua脚本在Redis中的基本使用及其原子性保证原理
文章目录 背景一、Eval二、EvalSHA三、Redis 对 Lua 脚本的管理3.1 script flush3.2 script exists3.3 script load3.4 script kill 四、Lua在Redis中原子性执行的原理 背景 Lua 本身是一种轻量小巧的脚本语言,在Redis2.6版本开始引入了对Lua脚本的支持。通过在服务…...
汇编--int指令
中断信息可以来自CPU的内部和外部, 当CPU的内部有需要处理的事情发生的时候,将产生需要马上处理的中断信息,引发中断过程。在http://t.csdn.cn/jihpG,我们讲解了中断过程和两种内中断的处理。 这一章中, 我们讲解另一种…...
生成式AI的JavScript技术栈
如果不使用新的软件基础设施技术,就很难理解它们。 至少,a16z 基础设施团队发现了这一点,而且因为我们中的许多人都是以程序员的身份开始职业生涯的,所以我们经常通过实践来学习。 尤其是生成式AI浪潮的情况尤其如此,它…...
从零开始学习软件测试-第39天笔记
接口测试 http消息结构 请求报文 请求行 请求方式 url 协议版本请求头空行请求体响应报文 响应行 协议版本 状态码 状态消息响应头空行响应体 请求参数类型 path参数 写在路径中的 https://xxx.xxx.com/参数值query参数 写在url问号后面,以键值对形式存在 h…...
【多思路附源码】2023高教社杯 国赛数学建模C题思路 - 蔬菜类商品的自动定价与补货决策
赛题介绍 在生鲜商超中,一般蔬菜类商品的保鲜期都比较短,且品相随销售时间的增加而变差, 大部分品种如当日未售出,隔日就无法再售。因此, 商超通常会根据各商品的历史销售和需 求情况每天进行补货。 由于商超销售的蔬…...
Vue2+Vue3基础入门到实战项目(六)——课程学习笔记
镇贴!!! day07 vuex的基本认知 使用场景 某个状态 在 很多个组件 来使用 (个人信息) 多个组件 共同维护 一份数据 (购物车) 构建多组件共享的数据环境 1.创建项目 vue create vuex-demo 2.创建三个组件, 目录如下 |-components |--Son1.…...
QT—基于http协议的网络文件下载
1.常用到的类 QNetworkAccessManager类用于协调网络操作,负责发送网络请求,创建网络响应 QNetworkReply类表示网络请求的响应。在QNetworkAccessManager发送一个网络请求后创建一个网络响应。它提供了以下信号: finished():完成…...
SpringBoot-配置优先级
配置 SpringBoot项目支持的三种格式的配置文件 application.properties:这是最常用的配置文件类型,使用键值对的形式来配置应用程序的属性。可以在该文件中配置应用程序的端口号、数据库连接信息、日志级别等。 application.yml:这是一种更…...
科普初步了解大模型
目录 一、大模型的简单认知 (一)官方定义 (二)聚焦到大语言模型 (三)大模型的应用举例 二、如何得到大模型 (一)整体的一般步骤 训练自己的模型 使用预训练模型 选择适当的…...
Nginx 和 网关的关系是什么
分析&回答 Nginx也可以实现网关,可以实现对api接口的拦截,负载均衡、反向代理、请求过滤等。网关功能可以进行扩展,比如:安全控制,统一异常处理,XXS,SQL注入等;权限控制,黑白名…...
解决springboot项目中的groupId、package或路径的混淆问题
对于像我一样喜欢跳跃着学习的聪明人来说,肯定要学springboot,什么sevlet、maven、java基础,都太老土了,用不到就不学。所以古代的聪明人有句话叫“书到用时方恨少”,测试开源项目时,编译总是报错ÿ…...
Vmware 网络恢复断网和连接
如果你的 虚拟机无法联网了,比如: vmware 无法将网络更改为桥接状态: 没有未桥接的主机网络适配器 等各种稀奇古怪的问题; 按照下面操作 还远默认设置 包你解决各种问题!...
学生来看!如何白嫖内网穿透?点进来!
文章目录 前言本教程解决的问题是:按照本教程方法操作后,达到的效果是前排提醒: 1 搭建虚拟机1.1 下载文件vmvare虚拟机安装包1.2 安装VMware虚拟机:1.3 解压虚拟机文件1.4 虚拟机初始化1.5 没有搜索到解决方式:1.6 虚…...
C++中的stack和queue
文章目录 1. stack的介绍和使用1.1 stack的介绍1.2 stack的使用 2. queue的介绍和使用2.1 queue的介绍2.2 queue的使用 3 priority_queue的介绍和使用3.1 priority_queue的介绍3.2 priority_queue的使用 4. 容器适配器4.1 什么是适配器4.2 STL标准库中stack和queue的底层结构4.…...
Ubuntu-22.04通过RDP协议连接远程桌面
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、RDP是什么?二、配置1.打开远程桌面功能2.验证服务3.防火墙配置4.测试效果 总结 前言 由于一些特殊需要,我需要通过远程桌面连接到U…...
20230908java面经整理
1.cpp和java的区别 cpp可以多重继承,对表java中的实现多个接口 cpp支持运算符重载、goto、默认函数参数 cpp自动强转,导致不安全;java必须显式强转 java提供垃圾回收机制,自动管理内存分配,当gc要释放无用对象时调用f…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...
uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...
