双链表——“数据结构与算法”

各位CSDN的uu们你们好呀,今天,小雅兰又回来了,到了好久没有更新的数据结构与算法专栏,最近确实发现自己有很多不足,需要学习的内容也有很多,所以之后更新文章可能不会像之前那种一天一篇或者一天两篇啦,话不多说,下面,让我们进入双链表的世界吧


 之前小雅兰写过单链表的文章:https://xiaoyalan.blog.csdn.net/article/details/130353520?spm=1001.2014.3001.5502

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:

 这八种结构分别为:单向带头循环链表、单向带头非循环链表、单向不带头循环链表、单向不带头非循环链表、双向带头循环链表、双向带头非循环链表、双向不带头循环链表、双向不带头非循环链表

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:

单向不带头非循环链表和双向带头循环链表

  • 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结 构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  • 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都 是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带 来很多优势,实现反而简单了,后面我们代码实现了就知道了。  

下面,我们开始用代码和图来实现一下双向带头循环链表!!!

在后续函数中,有很多函数都需要这样一个功能:创建一个新节点

那么,我们把此功能单独封装成一个函数:

//创建一个新节点
LTNode* BuyLTNode(LTDataType x)
{LTNode* newnode = ((LTNode*)malloc(sizeof(LTNode)));if (newnode == NULL){printf("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, LTDataType x)
{assert(phead);LTNode* tail = phead->prev;//malloc一个新节点LTNode* newnode = BuyLTNode(x);//让tail的下一个结点指向newnode,链接起来tail->next = newnode;//newnode的前一个结点指向tailnewnode->prev = tail;//newnode的下一个结点指向头,形成循环newnode->next = phead;//phead的上一个结点指向newnodephead->prev = newnode;
}

头插:

//头插
//插在哨兵位的头节点的后面
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = BuyLTNode(x);LTNode* first = phead->next;phead->next = newnode;newnode->prev = phead;newnode->next = first;first->prev = newnode;
}

当然,这样的代码可读性是非常高的,可是,有些人就不喜欢定义那么多指针,然后就还有另外一种写法:

//头插
//插在哨兵位的头节点的后面
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = BuyLTNode(x);newnode->next = phead->next;phead->next->prev = newnode;phead->next = newnode;newnode->prev = phead;
}

 但是这样的代码,可读性就显得不是那么的高,所以尽量不要使用这种写法

不是说你写代码多定义了几个指针就说你写的代码没有那些没有定义什么指针的代码差,关键得看可读性!!!

双链表的打印:

//双链表的打印
void LTPrint(LTNode* phead)
{assert(phead);printf("guard<--->");LTNode* cur = phead->next;while (cur != phead){printf("%d<--->", cur->data);cur = cur->next;}printf("\n");
}

 下面,让我们来验证一下头插和尾插的功能

void TestDoubleList1()
{LTNode* plist = LTInit();LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPushBack(plist, 5);LTPrint(plist);LTPushFront(plist, 6);LTPushFront(plist, 7);LTPushFront(plist, 8);LTPushFront(plist, 9);LTPushFront(plist, 10);LTPrint(plist);
}
int main()
{TestDoubleList1();return 0;
}

 尾删:

 首先要判断此链表是否为空:

bool LTEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
}
//尾删
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;
}

头删:

bool LTEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
}
//头删
void LTPopFront(LTNode* phead)
{assert(phead);assert(!LTEmpty(phead));LTNode* first = phead->next;LTNode* second = first->next;phead->next = second;second->prev = phead;free(first);}

头删也有另外一种写法,和上面阐述的一样,也是可以不用定义这么多指针,但是代码的可读性差一些

bool LTEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
}
//头删
void LTPopFront(LTNode* phead)
{assert(phead);assert(!LTEmpty(phead));LTNode* next = phead->next;phead->next = next->next;next->next->prev = phead;free(next);
}

下面,让我们来验证一下头删和尾删的功能

void TestDoubleList2()
{LTNode* plist = LTInit();LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPushBack(plist, 5);LTPrint(plist);LTPushFront(plist, 6);LTPushFront(plist, 7);LTPushFront(plist, 8);LTPushFront(plist, 9);LTPushFront(plist, 10);LTPrint(plist);LTPopBack(plist);LTPopBack(plist);LTPrint(plist);LTPopFront(plist);LTPopFront(plist);LTPrint(plist);
}
int main()
{TestDoubleList2();return 0;
}

在双链表里面查找:

//在双链表里面查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){if (cur->data == x){return cur;}cur = cur->next;}
}

在pos位置之前插入值: 

//在pos位置之前插入
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* prev = pos->prev;LTNode* newnode = BuyLTNode(x);prev->next = newnode;newnode->prev = prev;newnode->next = pos;pos->prev = newnode;
}

一般查找和在pos位置之前插入值是搭配使用的!!!

下面,让我们来测试一下此功能:

void TestDoubleList3()
{LTNode* plist = LTInit();LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPushBack(plist, 5);LTPrint(plist);LTPushFront(plist, 6);LTPushFront(plist, 7);LTPushFront(plist, 8);LTPushFront(plist, 9);LTPushFront(plist, 10);LTPrint(plist);LTPopBack(plist);LTPopBack(plist);LTPrint(plist);LTPopFront(plist);LTPopFront(plist);LTPrint(plist);LTNode* pos = LTFind(plist, 3);if (pos != NULL){LTInsert(pos, 30);}LTPrint(plist);}
int main()
{TestDoubleList3();return 0;
}

 在pos位置之前插入值这个功能,可以很好地解决之前头插和尾插的功能,头插和尾插可以直接复用此代码:

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{LTInsert(phead->next, x);
}
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTInsert(phead, x);
}

删除pos位置的值: 

//删除pos位置的值
void LTErase(LTNode* pos)
{assert(pos);LTNode* posPrev = pos->prev;LTNode* posNext = pos->next;posPrev->next = posNext;posNext->prev = posPrev;free(pos);
}

验证一下该删除功能:

void TestDoubleList4()
{LTNode* plist = LTInit();LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPushBack(plist, 5);LTPrint(plist);LTPushFront(plist, 6);LTPushFront(plist, 7);LTPushFront(plist, 8);LTPushFront(plist, 9);LTPushFront(plist, 10);LTPrint(plist);LTPopBack(plist);LTPopBack(plist);LTPrint(plist);LTPopFront(plist);LTPopFront(plist);LTPrint(plist);LTNode* pos = LTFind(plist, 3);if (pos != NULL){LTInsert(pos, 30);}LTPrint(plist);pos = LTFind(plist, 1);if (pos != NULL){LTErase(pos);}LTPrint(plist);
}
int main()
{TestDoubleList4();return 0;
}

 

这个功能也很神奇,删除pos位置的值,之前我们写的头删和尾删一样可以复用此代码的功能

//尾删
void LTPopBack(LTNode* phead)
{assert(phead);assert(!LTEmpty(phead));LTErase(phead->prev);
}
//头删
void LTPopFront(LTNode* phead)
{assert(phead);assert(!LTEmpty(phead));LTErase(phead->next);
}

 双链表的销毁:

//双链表的销毁
void LTDestroy(LTNode* phead)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){LTNode* next = cur->next;free(cur);cur = next;}free(phead);
}

如果不销毁双链表,就会有内存泄漏的问题!!!


源代码如下:

List.h的内容:

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int LTDataType;
typedef struct ListNode
{struct ListNode* prev;struct ListNode* next;LTDataType data;
}LTNode;
//双链表的初始化
LTNode* LTInit();
//双链表的打印
void LTPrint(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);
//在双链表里面查找
LTNode* LTFind(LTNode* phead, LTDataType x);
//在pos位置之前插入
void LTInsert(LTNode* pos, LTDataType x);
//删除pos位置的值
void LTErase(LTNode* pos);
//双链表的销毁
void LTDestroy(LTNode* phead);

List.c的内容:

#include"List.h"
//创建一个新节点
LTNode* BuyLTNode(LTDataType x)
{LTNode* newnode = ((LTNode*)malloc(sizeof(LTNode)));if (newnode == NULL){printf("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 LTPrint(LTNode* phead)
{assert(phead);printf("guard<--->");LTNode* cur = phead->next;while (cur != phead){printf("%d<--->", cur->data);cur = cur->next;}printf("\n");
}
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* tail = phead->prev;//malloc一个新节点LTNode* newnode = BuyLTNode(x);//让tail的下一个结点指向newnode,链接起来tail->next = newnode;//newnode的前一个结点指向tailnewnode->prev = tail;//newnode的下一个结点指向头,形成循环newnode->next = phead;//phead的上一个结点指向newnodephead->prev = newnode;
}
//头插
//插在哨兵位的头节点的后面
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = BuyLTNode(x);LTNode* first = phead->next;phead->next = newnode;newnode->prev = phead;newnode->next = first;first->prev = newnode;
}
头插
插在哨兵位的头节点的后面
//void LTPushFront(LTNode* phead, LTDataType x)
//{
//	assert(phead);
//	LTNode* newnode = BuyLTNode(x);
//	newnode->next = phead->next;
//	phead->next->prev = newnode;
//	phead->next = newnode;
//	newnode->prev = phead;
//}
头插
//void LTPushFront(LTNode* phead, LTDataType x)
//{
//	LTInsert(phead->next, x);
//}
尾插
//void LTPushBack(LTNode* phead, LTDataType x)
//{
//	assert(phead);
//	LTInsert(phead, x);
//}
bool LTEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
}
//尾删
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* next = phead->next;
//	phead->next = next->next;
//	next->next->prev = phead;
//	free(next);
//}
//头删
void LTPopFront(LTNode* phead)
{assert(phead);assert(!LTEmpty(phead));LTNode* first = phead->next;LTNode* second = first->next;phead->next = second;second->prev = phead;free(first);
}
尾删
//void LTPopBack(LTNode* phead)
//{
//	assert(phead);
//	assert(!LTEmpty(phead));
//	LTErase(phead->prev);
//}
头删
//void LTPopFront(LTNode* phead)
//{
//	assert(phead);
//	assert(!LTEmpty(phead));
//	LTErase(phead->next);
//}
//在双链表里面查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){if (cur->data == x){return cur;}cur = cur->next;}
}
//在pos位置之前插入
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* prev = pos->prev;LTNode* newnode = BuyLTNode(x);prev->next = newnode;newnode->prev = prev;newnode->next = pos;pos->prev = newnode;
}
//删除pos位置的值
void LTErase(LTNode* pos)
{assert(pos);LTNode* posPrev = pos->prev;LTNode* posNext = pos->next;posPrev->next = posNext;posNext->prev = posPrev;free(pos);
}
//双链表的销毁
void LTDestroy(LTNode* phead)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){LTNode* next = cur->next;free(cur);cur = next;}free(phead);
}

好啦,小雅兰今天的学习内容就到这里啦,还要继续加油噢!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/269349.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

红帆OA 多处 SQL注入漏洞复现

0x01 产品简介 红帆iOffice.net从最早满足医院行政办公需求(传统OA),到目前融合了卫生主管部门的管理规范和众多行业特色应用,是目前唯一定位于解决医院综合业务管理的软件,是最符合医院行业特点的医院综合业务管理平台,是成功案例最多的医院综合业务管理软件。 0x02 漏…

网络安全: Kali Linux 使用 docker-compose 部署 openvas

目录 一、实验 1.环境 2.Kali Linux 安装docker与docker-compose 3.Kali Linux 使用docker-compose方式部署 openvas 4. KaliLinux 使用openvas 二、问题 1. 信息安全漏洞库 2.信息安全漏洞共享平台 3.Windows 更新指南与查询 4.CVE 查询 5.docker-compose 如何修改o…

哪些型号的高速主轴适合PCB分板机

在选择适合PCB分板机的高速主轴时&#xff0c;SycoTec品牌提供了丰富的型号选择&#xff0c;主要型号包括4025 HY、4033 AC&#xff08;电动换刀&#xff09;、4033 AC-ESD、4033 DC-T和4041 HY-ESD等。 那么如何选择合适的PCB分板机高速主轴型号呢&#xff1f;在选择适合PCB分…

LZO索引文件失效说明

在hive中创建lzo文件和索引时&#xff0c;进行查询时会出现问题.hive的默认输入格式是开启小文件合并的&#xff0c;会把索引也合并进来。所以要关闭hive小文件合并功能&#xff01;

day03_Vue_Element

文章目录 01.Ajax1.1 Ajax 概述1.2 同步异步1.3 原生Ajax 2. Axios2.1 Axios的基本使用2.2 Axios快速入门2.3请求方法的别名2.4 案例 3 前后台分离开发3.1 前后台分离开发介绍 04 YAPI4.1 YAPI介绍4.2 接口文档管理 05 前端工程化5.1 前端工程化介绍5.2 前端工程化入门5.2.1 环…

小程序学习

1、小程序体验 2、注册账号 小程序 (qq.com) 3、开发工具下载 下载 / 稳定版更新日志 (qq.com) 4、目录结构 "navigationBarBackgroundColor": "#00b26a" 配置头部背景色 4、wxml模板介绍 5、wxss 6、js文件 7、宿主环境 1、通信主体 2、运行机制 3、…

网工学习 DHCP配置-接口模式

网工学习 DHCP配置-接口模式 学习DHCP总是看到&#xff0c;接口模式、全局模式、中继模式。理解起来也不困难&#xff0c;但是自己动手操作起来全是问号。跟着老师视频配置啥问题没有&#xff0c;自己组建网络环境配置就是不通&#xff0c;悲催。今天总结一下我学习接口模式的…

动手学深度学习—循环神经网络RNN详解

循环神经网络 循环神经网络的步骤&#xff1a; 处理数据 将数据按照批量大小和时间步数进行处理&#xff0c;最后得到迭代器&#xff0c;即每一个迭代的大小是批量大小时间步数&#xff0c;迭代次数根据整个数据的大小决定&#xff0c;最后得出处理的数据&#xff08;参照第三…

基于SpringBoot的物业管理系统

** &#x1f345;点赞收藏关注 → 私信领取本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345;** 一 、设计说明 1.1 研究…

Elasticsearch:使用 Streamlit、语义搜索和命名实体提取开发 Elastic Search 应用程序

作者&#xff1a;Camille Corti-Georgiou 介绍 一切都是一个搜索问题。 我在 Elastic 工作的第一周就听到有人说过这句话&#xff0c;从那时起&#xff0c;这句话就永久地印在了我的脑海中。 这篇博客的目的并不是我出色的同事对我所做的相关陈述进行分析&#xff0c;但我首先…

Python的PrettyTable模块

Python的PrettyTable模块 1.PrettyTable介绍与基本使用 ​ 在使用Python查询表格数据的时候&#xff0c;直接print出来的话。数据杂乱无章&#xff0c;这个使用就可以使用PrettyTable模块来解决这个问题。如下图: 这样在输出的窗口可以很清晰看到所需要的信息。那么类似这种表…

更换个人开发环境后,pycharm连接服务器报错Authentication failed

原因&#xff1a;服务器中更换个人开发环境后&#xff0c;密码变了。 解决&#xff1a;在pycharm中修改服务器开发环境密码即可。 1 找到Tools-Depolyment-Configuration 2 点击SSH Configuration后的省略号 3 修改这里面的Password即可

autodock分子对接操作步骤完整版

对接完整步骤具体操作 设置工作目录 保证工作目录下必须要有这五个文件&#xff1a; 对蛋白质的操作 打开蛋白质 去水&#xff0c;结构周围的小红点。 加氢 将蛋白质设为受体 点击确定进行保存 进行下一步小分子 小分子具体操作 打开小分子 对小分子进行加氢 将小分子设定为配…

第三篇【传奇开心果系列】Python的自动化办公库技术点案例示例:深度解读Pandas股票市场数据分析

传奇开心果博文系列 系列博文目录Python的自动化办公库技术点案例示例系列 博文目录前言一、Pandas进行股票市场数据分析常见步骤和示例代码1. 加载数据2. 数据清洗和准备3. 分析股票价格和交易量4. 财务数据分析 二、扩展思路介绍1. 技术指标分析2. 波动性分析3. 相关性分析4.…

HTML5:七天学会基础动画网页7

CSS3高级特效 2D转换方法 移动:translate() 旋转:rotate() 缩放:scale() 倾斜:skew() 属性:transform 作用:对元素进行移动,旋转,缩放,倾斜。 2D移动 设定元素从当前位置移动到给定位置(x,y) 方法 说明 translate(x,y) 2D转换 沿X轴和Y轴移…

【李沐论文精读】Transformer精读

论文&#xff1a;Attention is All You Need 参考&#xff1a;李沐视频【Transformer论文逐段精读】、Transformer论文逐段精读【论文精读】、李沐视频精读系列 一、摘要 主流的序列转换(sequence transduction)模型都是基于复杂的循环或卷积神经网络&#xff0c;这个模型包含一…

云计算,用价格让利换创新空间?

文 | 智能相对论 作者 | 李源 ECS&#xff08;云服务器&#xff09;最高降36%、OSS&#xff08;对象存储&#xff09;最高降55%、RDS&#xff08;云数据库&#xff09;最高降40%…… 阿里云惊人的降幅&#xff0c;一次性把国内云计算厂商的价格战推到了白热化阶段。 这次能…

LVS负载均衡集群的基本介绍

目录 一、LVS集群基本介绍 1、什么是集群 2、集群的类型 2.1 负载均衡群集&#xff08;Load Balance Cluster) 2.2 高可用群集(High Availiablity Cluster) 2.3 高性能运算群集(High Performance Computing Cluster) 3、负载均衡集群的结构 4、LVS集群类型中的术语 5、…

【javaEE-唠嗑局】如何用jconsole观察进程里的多线程情况

&#x1f4e2;编程环境&#xff1a;idea 如何用jconsole观察进程里的多线程情况 1. 打开jdk2. 打开jconsole3. 查看每个线程的情况 以下面这段代码为例&#xff1a;代码运行时&#xff0c;包括一个进程&#xff0c;该进程中有两个线程。 package thread; public class Demo1 …

【algorithm】算法基础课---排序算法(附笔记 | 建议收藏)

&#x1f680;write in front&#x1f680; &#x1f4dd;个人主页&#xff1a;认真写博客的夏目浅石. &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd; &#x1f4e3;系列专栏&#xff1a;AcWing算法学习笔记 &#x1f4ac;总结&#xff1a;希望你看完…