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

数据结构篇三:双向循环链表

文章目录

  • 前言
  • 双向链表的结构
  • 功能的解析及实现
    • 1. 双向链表的创建
    • 2. 创建头节点(初始化)
    • 3. 创建新结点
    • 4. 尾插
    • 5. 尾删
    • 6. 头插
    • 7. 头删
    • 8. 查找
    • 9. 在pos位置前插入
    • 10. 删除pos位置的结点
    • 11. 销毁
  • 代码实现
    • 1.ListNode.h
    • 2. ListNode.c
    • 3. test.c
  • 总结

前言

  前面我们学习了单链表的实现,我们发现它在进行从后往前查找的时候有很大的不便,为了解决这个问题,双向链表油然而生。它可以很好的解决单链表无法从后往前查找的困难。

双向链表的结构

在这里插入图片描述

  如图所示,它是有两个指针域,一个指向后一个结点,一个指向前一个结点。它存储了前一个结点的地址与后一个结点的地址,所以可以很方便的进行向前遍历或者向后遍历。同时它还是一个循环链表,可以通过第一个结点直接找到最后一个结点。

功能的解析及实现

1. 双向链表的创建

  就像前文所说,它包含了两个指针域和一个数据域,用来存放它前一个结点的地址和后一个结点的地址以及本身的数据。

typedef struct LTNode
{LTDataType data;struct LTNode* prev;struct LTNode* next;
}LTNode;

2. 创建头节点(初始化)

  此次双向链表的结构我是采用了带头结点的结构,好处就是头节点是malloc出来的,是在堆区上存放,不会因为出了函数就被销毁,也意味着后续的各种操作我们只需要传递一级指针就会有实现单链表时传递二级指针的效果。

LTNode* ListInit()
{LTNode* phead = (LTNode*)malloc(sizeof(LTNode));if (phead == NULL){return NULL;}phead->prev = phead;phead->next = phead;return phead;
}

3. 创建新结点

  每次插入新的数据都需要开辟新的结点,因此把它单独拿出来放到一个函数中实现。

LTNode* BuyListNode(LTDataType x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){return NULL;}newnode->data = x;newnode->prev = NULL;newnode->next = NULL;return newnode;
}

4. 尾插

  因为是循环链表,我们可以通过第一个头节点直接找到尾结点,而在连接的时候,需要将新结点分别连接到头节点的prev以及尾结点的next,同时自身的prev存放尾结点的地址,next存放头节点的地址。如图:
在这里插入图片描述

void ListPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* tail = phead->prev;LTNode* newnode = BuyListNode(x);newnode->data = x;newnode->next = phead;phead->prev = newnode;newnode->prev = tail;tail->next = newnode;
}

5. 尾删

  在创建头节点时,我们是将头节点的prev与next都指向了它自身,因此我们可以通过头节点的next指向的是不是自身来判断是否为存放了数据。(头节点自身不存放数据)。与尾插类似,如图:
在这里插入图片描述

void ListPopBack(LTNode* phead)
{assert(phead);if (phead->next == phead)//判断链表是否存放了数据{return;}LTNode* tail = phead->prev;LTNode* prev = tail->prev;prev->next = phead;phead->prev = prev;free(tail);tail = NULL;
}

6. 头插

  与尾插类似,只不过这个放到了最前面。在尾插是我们是有tail与phead来与新结点连接,头插也一样。先保存当前的第一个结点地址,然后再将新结点连接到头节点与原第一个结点的中间即可。

void ListPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* next = phead->next;//保存当前的第一个结点地址LTNode* newnode = BuyListNode(x);newnode->prev = phead;phead->next = newnode;newnode->next = next;next->prev = newnode;
}

7. 头删

  我们只需要保存第一个结点与第二节结点的地址,然后在将第二个与头节点连接,再释放掉第一个结点即可。同时还需要判断链表是否为空(即有没有元素存放其中)。

void ListPopFront(LTNode* phead)
{//assert(phead->next != phead);  //暴力解决//温和解决if (phead->next == phead){return;}LTNode* prev = phead->next;LTNode* next = prev->next;phead->next = next;next->prev = phead;free(prev);prev = NULL;
}

8. 查找

  依次遍历链表即可,从phead开始,一直到再次遇到phead结束(循环链表)。

LTNode* ListFind(LTNode* phead, LTDataType x)
{LTNode* cur = phead->next;while (cur != phead){if (cur->data == x){return cur;}cur = cur->next;}printf("该元素不存在\n");return NULL;
}

9. 在pos位置前插入

  与头插相似,这里只需要用prev保存pos位置的前一个结点地址,然后将新结点与prev与pos相连接即可。

void ListInsert(LTNode* pos, LTDataType x)
{LTNode* prevPos = pos->prev;LTNode* newnode = BuyListNode(x);newnode->next = pos;pos->prev = newnode;newnode->prev = prevPos;prevPos->next = newnode;
}

10. 删除pos位置的结点

  保存pos的前一个结点地址与后一个结点地址,然后将彼此相连接,然后free掉pos结点就完成了。

void ListErase(LTNode* pos)
{LTNode* nextPos = pos->next;LTNode* prevPos = pos->prev;nextPos->prev = prevPos;prevPos->next = nextPos;free(pos);pos = NULL;
}

11. 销毁

  动态开辟的结点在最后结束时都需要进行释放,防止出现内存泄漏。用cur保存当前准备要释放的结点,用next保存cur的下一个结点,释放完cur后,再将cur指向next,进行循环。

void ListDestroy(LTNode* phead)
{LTNode* cur = phead;LTNode* next = cur->next;while (cur){free(cur);cur = NULL;if (cur != NULL){cur = next;next = next->next;}}
}

代码实现

1.ListNode.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>typedef int LTDataType;typedef struct LTNode
{LTDataType data;struct LTNode* prev;struct LTNode* next;
}LTNode;// 创建返回链表的头结点.
LTNode* ListInit();// 双向链表销毁
void ListDestroy(LTNode* phead);// 双向链表打印
void ListPrint(LTNode* phead);// 双向链表尾插
void ListPushBack(LTNode* phead, LTDataType x);// 双向链表尾删
void ListPopBack(LTNode* phead);// 双向链表头插
void ListPushFront(LTNode* phead, LTDataType x);// 双向链表头删
void ListPopFront(LTNode* phead);// 双向链表查找
LTNode* ListFind(LTNode* phead, LTDataType x);// 双向链表在pos的前面进行插入
void ListInsert(LTNode* pos, LTDataType x);// 双向链表删除pos位置的节点
void ListErase(LTNode* pos);

2. ListNode.c

#include"ListNode.h"LTNode* BuyListNode(LTDataType x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){return NULL;}newnode->data = x;newnode->prev = NULL;newnode->next = NULL;return newnode;
}
LTNode* ListInit()
{LTNode* phead = (LTNode*)malloc(sizeof(LTNode));if (phead == NULL){return NULL;}phead->prev = phead;phead->next = phead;return phead;
}void ListPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* tail = phead->prev;LTNode* newnode = BuyListNode(x);newnode->data = x;newnode->next = phead;phead->prev = newnode;newnode->prev = tail;tail->next = newnode;
}void ListPrint(LTNode* phead)
{LTNode* cur = phead->next;while (cur != phead){printf("%d ", cur->data);cur = cur->next;}printf("\n");
}void ListPopBack(LTNode* phead)
{assert(phead);if (phead->next == phead){return;}LTNode* tail = phead->prev;LTNode* prev = tail->prev;prev->next = phead;phead->prev = prev;free(tail);tail = NULL;
}void ListPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* next = phead->next;LTNode* newnode = BuyListNode(x);newnode->prev = phead;phead->next = newnode;newnode->next = next;next->prev = newnode;
}void ListPopFront(LTNode* phead)
{//assert(phead->next != phead);  //暴力解决//温和解决if (phead->next == phead){return;}LTNode* prev = phead->next;LTNode* next = prev->next;phead->next = next;next->prev = phead;free(prev);prev = NULL;
}LTNode* ListFind(LTNode* phead, LTDataType x)
{LTNode* cur = phead->next;while (cur != phead){if (cur->data == x){return cur;}cur = cur->next;}printf("该元素不存在\n");return NULL;
}void ListInsert(LTNode* pos, LTDataType x)
{LTNode* prevPos = pos->prev;LTNode* newnode = BuyListNode(x);newnode->next = pos;pos->prev = newnode;newnode->prev = prevPos;prevPos->next = newnode;
}void ListErase(LTNode* pos)
{LTNode* nextPos = pos->next;LTNode* prevPos = pos->prev;nextPos->prev = prevPos;prevPos->next = nextPos;free(pos);pos = NULL;
}void ListDestroy(LTNode* phead)
{LTNode* cur = phead;LTNode* next = cur->next;while (cur){free(cur);cur = NULL;if (cur != NULL){cur = next;next = next->next;}}
}

3. test.c

#include"ListNode.h"void test()
{LTNode* phead = ListInit();if (phead == NULL){return;}ListPushBack(phead, 1);//测试:尾插ListPushBack(phead, 2);ListPushBack(phead, 3);ListPushBack(phead, 4);ListPrint(phead);ListPopBack(phead);//测试:尾删ListPopBack(phead);ListPopBack(phead);ListPrint(phead);ListPopBack(phead);//测试:如果链表为空继续删除会不会报错ListPopBack(phead);ListPushBack(phead, 1);//尾插一个数据来对比头插ListPushFront(phead, 1);//测试:头插ListPushFront(phead, 2);ListPushFront(phead, 3);ListPushFront(phead, 4);ListPrint(phead);ListPopFront(phead);//测试:头删ListPopFront(phead);ListPopFront(phead);ListPrint(phead);ListPopFront(phead);//测试:如果链表删除完毕,继续删除会不会报错ListPopFront(phead);ListPopFront(phead);ListPrint(phead);ListPushBack(phead, 1);//插入新元素进行后续测试ListPushBack(phead, 2);ListPushBack(phead, 3);ListPushBack(phead, 4);ListPrint(phead);ListFind(phead, 5);LTNode* pos = ListFind(phead, 2);//测试:在2前面插入数字5ListInsert(pos, 5);ListPrint(phead);pos = ListFind(phead, 2);//测试:删除结点2ListErase(pos);ListPrint(phead);ListDestroy(phead);//测试:销毁链表
}int main()
{test();return 0;
}

总结

  总体而言难度不大,并且双向链表解决了单链表的很多问题,值得好好学习一下。并且在这里总结一下数据结构中形参能对实参产生影响的三种方式:二级指针,头节点(在堆区),返回值。
  双向循环链表就先告一段落了,如果发现文章哪里有问题可以在评论区提出来或者私信我嗷。接下来我会继续学习栈与队列,开启新的篇章,那么本期就到此结束,让我们下期再见!!觉得不错可以点个赞以示鼓励喔!!

相关文章:

数据结构篇三:双向循环链表

文章目录 前言双向链表的结构功能的解析及实现1. 双向链表的创建2. 创建头节点&#xff08;初始化&#xff09;3. 创建新结点4. 尾插5. 尾删6. 头插7. 头删8. 查找9. 在pos位置前插入10. 删除pos位置的结点11. 销毁 代码实现1.ListNode.h2. ListNode.c3. test.c 总结 前言 前面…...

day10 TCP是如何实现可靠传输的

TCP最主要的特点 1、TCP是面向连接的运输层协议。&#xff08; 每一条TCP连接只能有两个端点&#xff08;endpoint&#xff09;&#xff0c;每一条TCP连接只能是点对点的&#xff08;一对一&#xff09;&#xff09; 2、TCP提供可靠交付的服务。 3、TCP提供全双工通信。 4…...

Python | 人脸识别系统 — 背景模糊

本博客为人脸识别系统的背景模糊代码解释 人脸识别系统博客汇总&#xff1a;人脸识别系统-博客索引 项目GitHub地址&#xff1a;Su-Face-Recognition: A face recognition for user logining 注意&#xff1a;阅读本博客前请先参考以下博客 工具安装、环境配置&#xff1a;人脸…...

YOLOv5+单目测量物体尺寸(python)

YOLOv5单目测量尺寸&#xff08;python&#xff09; 1. 相关配置2. 测距原理3. 相机标定3.1&#xff1a;标定方法1&#xff08;针对图片&#xff09;3.2&#xff1a;标定方法2&#xff08;针对视频&#xff09; 4. 相机测距4.1 测距添加4.2 细节修改&#xff08;可忽略&#xf…...

C++异常

C异常 提到异常&#xff0c;大家一定不陌生&#xff0c;在学习new关键字的时候就提到了开空间失败会导致抛异常。其实异常在我们生活中的使用是很多的&#xff0c;有些时候程序发生错误以后我们并不希望程序就直接退出&#xff0c;针对不同的情况&#xff0c;我们更希望有不同的…...

Java中的字符串是如何处理的?

Java中的字符串是通过字符串对象来处理的。字符串是一个类&#xff0c;可以创建一个字符串对象&#xff0c;并在该对象上调用一系列方法来操作该字符串。 Java中的字符串是不可变的&#xff0c;这意味着一旦创建了一个字符串对象&#xff0c;就无法修改它的值。任何对字符串对…...

【热门框架】怎样使用Mybatis-Plus制作标准的分页功能

使用 Mybatis-Plus 实现标准的分页功能需要使用 Page 类来进行分页操作。具体步骤如下&#xff1a; 引入 Mybatis-Plus 依赖 在 Maven 项目中&#xff0c;在 pom.xml 文件中引入 Mybatis-Plus 的依赖&#xff1a; <dependency><groupId>com.baomidou</groupId&g…...

Java方法引用:提高代码可读性和可维护性

前言 在Java 8中&#xff0c;可以使用方法引用&#xff08;Method Reference&#xff09;来简化Lambda表达式。方法引用是一种更简洁易懂的语法形式&#xff0c;可以通过指定方法的名称代替Lambda表达式。 本文将介绍方法引用的用法和实现原理&#xff0c;并结合代码案例详细…...

如何使用CSS和JS实现一个响应式的滚动时间轴

随着互联网的发展&#xff0c;网站的界面设计越来越重要。吸引用户的关注、提高用户体验已经成为了许多网站的目标。而在实现各种复杂的界面效果中&#xff0c;CSS与JS的组合无疑是开发者的得力工具。本文将介绍如何使用CSS和JS实现一个响应式的滚动时间轴。 1.需求分析 在开…...

Feign组件的使用及开发中使用方式

在微服务的服务集群中服务与服务之间需要调用暴露的服务.那么就需要在服务内部发送http请求&#xff0c; 我们可以使用较为老的HttpClient实现&#xff0c;也可以使用SpringCloud提供的RestTemplate类调用对应的方法来发送对应的请求。 说明&#xff1a; 现在有两个微服务一个是…...

html css 面试题

1. 如何理解HTML语义化 1&#xff0c;可读性&#xff0c;易读性 2&#xff0c;seo搜索引擎更容易读懂 2&#xff0c;哪些是块元素&#xff0c;哪些是内联元素 1&#xff1a;div&#xff0c;h1&#xff0c;table&#xff0c;ul&#xff0c;p 2&#xff1a;span&#xff0c; img…...

LeetCode_双指针_中等_24.两两交换链表中的节点

目录 1.题目2.思路3.代码实现&#xff08;Java&#xff09; 1.题目 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 示例 1&a…...

【openGauss实战11】性能报告WDR深度解读

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…...

Vue3实现打字机效果

typeit 介绍 typeit是一款轻量级打字机特效插件。该打印机特效可以设置打字速度&#xff0c;是否显示光标&#xff0c;是否换行和延迟时间等属性&#xff0c;它可以打印单行文本和多行文本&#xff0c;并具有可缩放、响应式等特点。官方文档 安装 # npm npm install typeit # …...

maven无法依赖spring-cloud-stater-zipkin如何解决?

当 Maven 无法依赖 spring-cloud-starter-zipkin 时&#xff0c;您可以尝试以下方法解决&#xff1a; 确保拼写正确&#xff1a;请检查项目中的 pom.xml 文件&#xff0c;确保依赖的拼写正确。正确的依赖名称应为&#xff1a;spring-cloud-starter-zipkin。添加 Spring Cloud …...

实战踩坑---MFC---CreateEvent

使用事件CreateEvent注意事项 HANDLECreateEvent( LPSECURITY_ATTRIBUTESlpEventAttributes,// 安全属性 BOOLbManualReset,// 复位方式 BOOLbInitialState,// 初始状态 LPCTSTRlpName // 对象名称 );[1] 参数 lpEventAttributes[输入] 一个指向SECURITY_ATTRIBUTES结构…...

JavaWeb学习------jQuery

JavaWeb学习------jQuery jQuery函数库下载 jQuery函数库下载官网&#xff1a;Download jQuery | jQuery配套资料&#xff0c;免费下载 链接&#xff1a;https://pan.baidu.com/s/1aXBfItEYG4uM53u6PUEMTg 提取码&#xff1a;6c9i 然后下载&#xff1f; 来到官网&#xf…...

米哈游测开岗 【一面总结】

目录 1.黑盒测试与白盒测试的区别 2.测试一个下单功能 3.get与post的区别 4.一次get请求产生几个数据包 5.常用的linux命令 6.进程与线程的区别 7.数据库查询如何去重 8.MySql怎么连接两张表&#xff0c;有什么区别 9.说说索引 10.cookie 和 session 的区别 (会话管…...

微服务 Spring Boot 整合Redis 实现优惠卷秒杀 一人一单

文章目录 一、什么是全局唯一ID ⛅全局唯一ID ⚡Redis实现全局唯一ID 二、环境准备 三、实现秒杀下单 四、库存超卖问题 ⏳问题分析 ⌚ 乐观锁解决库存超卖 ✅Jmeter 测试 五、优惠卷秒杀 实现一人一单 ⛵小结 一、什么是全局唯一ID ⛅全局唯一ID 在分布式系统中,经常需要使用…...

构建OVS网络

构建OVS网络 1. 配置虚拟机环境 &#xff08;1&#xff09;配置虚拟机交换机 1 创建一个名为br-xd的虚拟交换机。 # ovs-vsctl add-br br-xd 2 查询虚拟交换机。 # ovs-vsctl show 5a1cd870-fc31-4820-a7f4-b75c19450582 Bridge br-xd Port br-xd …...

5分钟部署Qwen3-VL-8B:MacBook也能跑的视觉语言模型,零基础上手

5分钟部署Qwen3-VL-8B&#xff1a;MacBook也能跑的视觉语言模型&#xff0c;零基础上手 1. 为什么选择Qwen3-VL-8B-Instruct-GGUF 1.1 轻量级多模态模型的突破 Qwen3-VL-8B-Instruct-GGUF是阿里通义实验室最新推出的视觉语言模型&#xff0c;它最大的特点就是小身材大能量。…...

手把手教你配置:用微型纵向加密搞定IEC-104协议的风光数据安全上传

新能源场站IEC-104协议安全传输实战&#xff1a;微型纵向加密配置全指南 在新能源场站的自动化系统中&#xff0c;IEC-104协议作为电力行业标准通信规约&#xff0c;承担着风机、光伏逆变器与升压站之间关键运行数据传输的重任。然而&#xff0c;传统光纤环网中的明文传输方式存…...

【Django 实验三】个人主页开发实战

【Django 实验三】个人主页开发实战 作者&#xff1a;刘静怡 | 学号&#xff1a;F23016208 | 完成日期&#xff1a;2026年3月29日 目录 环境准备项目创建数据模型设计视图函数编写模板系统Admin 后台配置页面美化功能完善总结 一、环境准备 1.1 环境要求 Python: 3.10Django…...

大语言模型,视觉模型,全模态模型,语音模型和向量模型的区别和使用

1. 大语言模型&#xff08;Large Language Model, LLM&#xff09;定义&#xff1a;以文本为输入&#xff0c;生成文本的模型。特点&#xff1a;输入输出都是自然语言&#xff08;或包含少量结构化的 prompt&#xff09;。擅长对话、写作、推理、代码生成等任务。在 LangChain …...

互联网一线大厂最新版 Java面试八股文(含答案,万字总结,精心打磨,建议收藏)

Java 面试 Java 面试随着时间的改变而改变。在过去的日子里&#xff0c;当你知道 String 和 StringBuilder 的区别就能让你直接进入第二轮面试&#xff0c;但是现在问题变得越来越高级&#xff0c;面试官问的问题也更深入。 在我初入职场的时候&#xff0c;类似于 Vector 与 A…...

CentOS8网络管理大变革:从network.service到NetworkManager的全面解析

CentOS8网络管理架构深度解析&#xff1a;从传统命令到NetworkManager的进化之路 如果你是一位长期使用CentOS的系统管理员&#xff0c;最近升级到CentOS8后可能会遇到一个令人困惑的问题&#xff1a;当你习惯性地输入systemctl restart network命令时&#xff0c;系统却无情地…...

Matlab与VeriStand无缝集成:开发环境配置全攻略

1. 环境准备&#xff1a;软件安装与版本匹配 搞过Matlab和VeriStand集成的朋友都知道&#xff0c;最头疼的不是写代码&#xff0c;而是环境配置。我当年第一次尝试时&#xff0c;光软件版本兼容性问题就折腾了两天。这里分享几个血泪教训&#xff1a; 首先Matlab和VeriStand的版…...

多无人机协同打击任务分配方法

随着无人机技术的不断成熟和完善&#xff0c;其军事应用的优势日益显现&#xff0c;近年来其在军事冲突中 所发挥的作用更使人们认识到&#xff0c;无人机在未来战争中将成为重要的军事装备。随着无人机在军 事中的大量应用&#xff0c;无人机集群协同执行任务将成为典型的应用…...

Loop:Mac窗口管理的优雅革命,开源免费的全新体验

Loop&#xff1a;Mac窗口管理的优雅革命&#xff0c;开源免费的全新体验 【免费下载链接】Loop MacOS窗口管理 项目地址: https://gitcode.com/GitHub_Trending/lo/Loop 你是否曾在多窗口工作中迷失方向&#xff1f;Loop作为一款开源的macOS窗口管理工具&#xff0c;通过…...

从入门到精通:用OmenSuperHub打造专属惠普游戏本性能方案

从入门到精通&#xff1a;用OmenSuperHub打造专属惠普游戏本性能方案 【免费下载链接】OmenSuperHub 项目地址: https://gitcode.com/gh_mirrors/om/OmenSuperHub &#x1f50d; 问题发现&#xff1a;官方游戏控制中心的五大痛点 作为惠普OMEN游戏本用户&#xff0c;你…...