数据结构---双链表
专栏:数据结构
个人主页:HaiFan.
专栏简介:从零开始,数据结构!!
双链表
- 前言
- 双链表各接口的实现
- 为要插入的值开辟一块空间BuyLN
- 初始化LNInit和销毁LNDestory
- 打印链表中的值LNPrint
- 尾插LNPushBack和尾删LNPopBack
- 头插LNPushFron和头删LNPopFront
- 判断链表是否为空LNEmpty
- 查找LNFind
- 任意位置插入 LNInsert
- 任意位置删除LNErase
- 测试结果
- 源代码
前言

双向链表是在结构体内存两个指针,一个指针存下一个结点的地址,另一个指针是存上一个结点的地址,这样,就可以通过一个结点,找到前后两个结点。听起来很复杂,但实现起来,要比单链表简单许多。

双链表:
typedef int LDataType;typedef struct ListNode
{LDataType val;struct ListNode* head;struct ListNode* tail;
}LN;
双链表各接口的实现
LN* BuyLN(LDataType x);//为要插入的值开辟一块空间void LNInit(LN** pphead);//初始化
void LNDestory(LN* phead);//销毁void LNPrint(LN* phead);//打印链表中的值void LNPushBack(LN* phead, LDataType x);//尾插
void LNPopBack(LN* phead);//尾删void LNPushFront(LN* phead, LDataType x);//头插
void LNPopFront(LN* phead);//头删bool LNEmpty(LN* phead);//双链表是否为空LN* LNFind(LN* phead, LDataType x);//查找void LNInsert(LN* pos, LDataType x);//任意位置插入
void LNErase(LN* pos);//任意位置删除
为要插入的值开辟一块空间BuyLN
这个BuyLN是为链表开辟一块空间,为什么要额外写一个这函数,因为在插入值的过程中,需要为这个值开辟一块空间,每次都需要写相同的代码,所以把这个代码写成一个函数,用到式直接调用函数即可
LN* BuyLN(LDataType x)
{LN* sn = (LN*)malloc(sizeof(LN));if (sn == NULL){perror("malloc fail");exit(-1);}sn->val = x;sn->head = NULL;sn->tail = NULL;return sn;
}
初始化LNInit和销毁LNDestory
双链表的初始化,只需要把链表中的head和tail指针都指向自己,形成一个环即可。
void LNInit(LN** pphead)
{(*pphead) = BuyLN(-1);(*pphead)->head = *pphead;(*pphead)->tail = *pphead;
}

空间有开辟就有销毁,销毁之后把空间还给系统
void LNDestory(LN* phead)//ٿռ
{assert(phead);LN* cur = phead->head;while (cur != phead){LN* sn = cur;free(sn);sn = NULL;cur = cur->head;}
}
打印链表中的值LNPrint
头结点是不需要打印的,所以可以先定义一个sn代表头结点指向的下一个元素的位置,然后依次打印,直到sn的地址和头节点的地址相同时,打印结束
void LNPrint(LN* phead)
{assert(phead);LN* sn = phead->head;while (sn != phead){cout << sn->val << ' ';sn = sn->head;}cout << endl;
}
尾插LNPushBack和尾删LNPopBack
单链表的尾插需要先遍历,找到尾,然后进行插入操作,双链表不同于单链表,双链表有两个指针,一个存下一个结点,一个存上一个结点,我们只需要在头节点的左面,也就是让头节点中存上一个结点存要插入的值即可,这样就能完成尾插。

这样就不需要在遍历链表,找尾了。
void LNPushBack(LN* phead, LDataType x)
{LN* newnode = BuyLN(x);LN* prev = phead->tail;prev->head = newnode;newnode->tail = prev;newnode->head = phead;phead->tail = newnode;
}
尾删依旧是把头节点左边的那一个元素给删除即可,当然,表中如果只有头节点,就不要在删除了。。。
void LNPopBack(LN* phead)
{assert(phead->head != phead->tail);LN* prev = phead->tail;prev->tail->head = phead;phead->tail = prev->tail;free(prev);prev = NULL;
}
头插LNPushFron和头删LNPopFront

头插的话,要先找到首元素,然后把要插入的元素和头节点,首元素都双向链接起来即可。
void LNPushFront(LN* phead, LDataType x)
{LN* newnode = BuyLN(x);LN* ne = phead->head;ne->tail = newnode;newnode->head = ne;phead->head = newnode;newnode->tail = phead;
}
头删的话呢,跟上面一样,要先找到首元素,然后让头节点和首元素指向的下一个元素链接起来即可。
void LNPopFront(LN* phead)
{assert(phead->head != phead->tail);LN* ne = phead->head;phead->head = ne->head;ne->head->tail = phead;free(ne);ne = NULL;}

判断链表是否为空LNEmpty
如果链表为空返回true,反之,返回false,当双链表的头指针和尾指针都指向自己的时候,就说明,双链表为空。
bool LNEmpty(LN* phead)
{assert(phead);return phead->head == phead->tail;
}
查找LNFind
查找一个元素,返回这个元素的地址,只需要遍历双链表即可。
LN* LNFind(LN* phead, LDataType x)
{assert(phead);assert(phead->head != phead->tail);LN* cur = phead->head;while (cur != phead){if (x == cur->val){return cur;}cur = cur->head;}return NULL;
}
任意位置插入 LNInsert
再插入之前,要先找到要插入的位置,寻找这个位置的任务就交给了上面写的查找函数,我们只需要传递参数,对该位置进行插入即可,
void LNInsert(LN* pos, LDataType x)
{assert(pos);LN* newnode = BuyLN(x);LN* prev = pos->head;pos->head = newnode;newnode->tail = pos;newnode->head = prev;prev->tail = newnode;
}
任意位置删除LNErase
和上面一样,要删除哪个元素,就要先找到那个元素,然后进行删除操作,找到那个元素的任务依旧交给LNFind函数
void LNErase(LN* pos)
{assert(pos);LN* ne = pos->head;pos->head = ne->head;ne->tail = pos;
}
测试结果
测试代码:
#include "List.h"void TestLN()
{LN* plist = NULL;LNInit(&plist);LNPrint(plist);LNPushBack(plist, 1);LNPushBack(plist, 2);LNPushBack(plist, 3);LNPushBack(plist, 4);LNPushBack(plist, 5);LNPrint(plist);LNPopBack(plist);LNPrint(plist);LNPushFront(plist, -1);LNPushFront(plist, -2);LNPushFront(plist, -3);LNPushFront(plist, -4);LNPushFront(plist, -5);LNPrint(plist);LNPopFront(plist);LNPrint(plist);LN* ret = LNFind(plist, 3);if (ret != NULL){cout << ret->val << ' ' << ret << endl;LNInsert(ret, 10000);LNErase(ret);}LNPrint(plist);LNDestory(plist);
}int main()
{TestLN();return 0;
}

源代码
.h文件
#pragma once#include <iostream>
#include <assert.h>
#include <stdlib.h>using namespace std;typedef int LDataType;typedef struct ListNode
{LDataType val;struct ListNode* head;struct ListNode* tail;
}LN;LN* BuyLN(LDataType x);//为要插入的值开辟一块空间void LNInit(LN** pphead);//初始化
void LNDestory(LN* phead);//销毁void LNPrint(LN* phead);//打印链表中的值void LNPushBack(LN* phead, LDataType x);//尾插
void LNPopBack(LN* phead);//尾删void LNPushFront(LN* phead, LDataType x);//头插
void LNPopFront(LN* phead);//头删bool LNEmpty(LN* phead);//双链表是否为空LN* LNFind(LN* phead, LDataType x);//查找void LNInsert(LN* pos, LDataType x);//任意位置插入
void LNErase(LN* pos);//任意位置删除
.cpp文件
#include "List.h"LN* BuyLN(LDataType x)
{LN* sn = (LN*)malloc(sizeof(LN));if (sn == NULL){perror("malloc fail");exit(-1);}sn->val = x;sn->head = NULL;sn->tail = NULL;return sn;
}void LNPrint(LN* phead)
{assert(phead);LN* sn = phead->head;while (sn != phead){cout << sn->val << ' ';sn = sn->head;}cout << endl;
}void LNInit(LN** pphead)//ʼ
{(*pphead) = BuyLN(-1);(*pphead)->head = *pphead;(*pphead)->tail = *pphead;
}void LNDestory(LN* phead)//ٿռ
{assert(phead);LN* cur = phead->head;while (cur != phead){LN* sn = cur;free(sn);sn = NULL;cur = cur->head;}
}void LNPushBack(LN* phead, LDataType x)
{LN* newnode = BuyLN(x);LN* prev = phead->tail;prev->head = newnode;newnode->tail = prev;newnode->head = phead;phead->tail = newnode;
}void LNPopBack(LN* phead)
{assert(phead->head != phead->tail);LN* prev = phead->tail;prev->tail->head = phead;phead->tail = prev->tail;free(prev);prev = NULL;
}void LNPushFront(LN* phead, LDataType x)
{LN* newnode = BuyLN(x);LN* ne = phead->head;ne->tail = newnode;newnode->head = ne;phead->head = newnode;newnode->tail = phead;
}void LNPopFront(LN* phead)
{assert(phead->head != phead->tail);LN* ne = phead->head;phead->head = ne->head;ne->head->tail = phead;free(ne);ne = NULL;}bool LNEmpty(LN* phead)
{assert(phead);return phead->head == phead->tail;
}LN* LNFind(LN* phead, LDataType x)
{assert(phead);assert(phead->head != phead->tail);LN* cur = phead->head;while (cur != phead){if (x == cur->val){return cur;}cur = cur->head;}return NULL;
}void LNInsert(LN* pos, LDataType x)
{assert(pos);LN* newnode = BuyLN(x);LN* prev = pos->head;pos->head = newnode;newnode->tail = pos;newnode->head = prev;prev->tail = newnode;
}void LNErase(LN* pos)
{assert(pos);LN* ne = pos->head;pos->head = ne->head;ne->tail = pos;
}
test.cpp文件
#define _CRT_SECURE_NO_WARNINGS 1#include "List.h"void TestLN()
{LN* plist = NULL;LNInit(&plist);LNPrint(plist);LNPushBack(plist, 1);LNPushBack(plist, 2);LNPushBack(plist, 3);LNPushBack(plist, 4);LNPushBack(plist, 5);LNPrint(plist);LNPopBack(plist);LNPrint(plist);LNPushFront(plist, -1);LNPushFront(plist, -2);LNPushFront(plist, -3);LNPushFront(plist, -4);LNPushFront(plist, -5);LNPrint(plist);LNPopFront(plist);LNPrint(plist);LN* ret = LNFind(plist, 3);if (ret != NULL){cout << ret->val << ' ' << ret << endl;LNInsert(ret, 10000);LNErase(ret);}LNPrint(plist);LNDestory(plist);
}int main()
{TestLN();return 0;
}
相关文章:
数据结构---双链表
专栏:数据结构 个人主页:HaiFan. 专栏简介:从零开始,数据结构!! 双链表前言双链表各接口的实现为要插入的值开辟一块空间BuyLN初始化LNInit和销毁LNDestory打印链表中的值LNPrint尾插LNPushBack和尾删LNPop…...
Windows 环境安装Scala详情
为了进一步学习Spark,必须先学习Scala 编程语言。首先开始Scala 环境搭建。温馨提示:本文是基于Windows 11 安装Scala 2.13.1 版本第一步:确保本机已经正确安装JDK1.8 环境第二步:Scala 官网下载我们所属scala版本文件。Scala 官网…...
C++ Qt自建网页浏览器
C Qt自建网页浏览器如需安装运行环境或远程调试,见文章底部个人QQ名片,由专业技术人员远程协助!前言这篇博客针对<<C Qt自建网页浏览器>>编写代码,代码整洁,规则,易读。 学习与应用推荐首选。文…...
Flink从入门到精通系列(四)
5、DataStream API(基础篇) Flink 有非常灵活的分层 API 设计,其中的核心层就是 DataStream/DataSet API。由于新版本已经实现了流批一体,DataSet API 将被弃用,官方推荐统一使用 DataStream API 处理流数据和批数据。…...
Nginx 配置实例-反向代理案例一
实现效果:使用nginx反向代理,访问 www.suke.com 直接跳转到本机地址127.0.0.1:8080 一、准备工作 Centos7 安装 Nginxhttps://liush.blog.csdn.net/article/details/125027693 1. 启动一个 tomcat Centos7安装JDK1.8https://liush.blog.csdn.net/arti…...
为什么北欧的顶级程序员数量远超中国?
说起北欧,很多人会想到寒冷的冬天,漫长的极夜,童话王国和圣诞老人,但是如果我罗列下诞生于北欧的计算机技术,恐怕你会惊掉下巴。Linux:世界上最流行的开源操作系统,最早的内核由Linus Torvalds开…...
vuex getters的作用和使用(求平均年龄),以及辅助函数mapGetters
getters作用:派生状态数据mapGetters作用:映射getters中的数据使用:方法名自定义,系统自动注入参数:state,每一个方法中必须有return,其return的结果被该方法名所接收。在state中声明数据listst…...
20230311给Ubuntu18.04下的GTX1080M安装驱动
20230311给Ubuntu18.04下的GTX1080M安装驱动 2023/3/11 12:50 2. 安装GTX1080驱动 安装 Nvidia 驱动 367.27 sudo add-apt-repository ppa:graphics-drivers/ppa 第一次运行出现如下的警告: Fresh drivers from upstream, currently shipping Nvidia. ## Curren…...
2023腾讯面试真题:
【腾讯】面试真题: 1、Kafka 是什么?主要应用场景有哪些? Kafka 是一个分布式流式处理平台。这到底是什么意思呢? 流平台具有三个关键功能: 消息队列:发布和订阅消息流,这个功能类似于消息…...
23种设计模式-建造者模式(Android应用场景介绍)
什么是建造者模式 建造者模式是一种创建型设计模式,它允许您使用相同的创建过程来生成不同类型和表示的对象。在本文中,我们将深入探讨建造者模式的Java实现,并通过一个例子来解释其工作原理。我们还将探讨如何在Android应用程序中使用建造者…...
English Learning - L2 语音作业打卡 双元音 [ʊə] [eə] Day17 2023.3.9 周四
English Learning - L2 语音作业打卡 双元音 [ʊə] [eə] Day17 2023.3.9 周四💌发音小贴士:💌当日目标音发音规则/技巧:🍭 Part 1【热身练习】🍭 Part2【练习内容】🍭【练习感受】🍓元音 [ʊə…...
【动态规划】多重背包问题,分组背包问题
Halo,这里是Ppeua。平时主要更新C语言,C,数据结构算法......感兴趣就关注我吧!你定不会失望。 🌈个人主页:主页链接 🌈算法专栏:专栏链接 我会一直往里填充内容哒! &…...
JAVA面向对象特征之——封装
4.封装 private关键字 是一个权限修饰符 可以修饰成员(成员变量和成员方法) 作用是保护成员不被别的类使用,被private修饰的成员只在本类中才能访问 针对private修饰的成员变量,如果需要被其他类使用,提供相应的操作 提供 “get变量名()…...
【数据结构】二叉树相关OJ题
文章目录一、单值二叉树二、检查两颗树是否相同三、判断一棵树是否为另一颗树的子树四、对称二叉树五、二叉树的前序遍历六、二叉树中序遍历七、二叉树的后序遍历八、二叉树的构建及遍历一、单值二叉树 单值二叉树 题目描述 如果二叉树每个节点都具有相同的值,那…...
Windows安装Hadoop
当初搭建Hadoop、Hive、HBase、Flink等这些没有截图写文,今为分享特重装。下载Hadoop下载地址:https://mirrors.tuna.tsinghua.edu.cn/apache/hadoop/以管理员身份运行cmd切换到所在目录执行start winrar x -y hadoop-3.3.4.tar.gz,解压。配置…...
ICG-Hydrazide,吲哚菁绿-酰肼,ICG-HZ结构式,溶于二氯甲烷等部分有机溶剂,
ICG-Hydrazide,吲哚菁绿-酰肼 中文名称:吲哚菁绿-酰肼 英文名称:ICG-Hydrazide 英文别名:ICG-HZ 性状:粉末或固体 溶剂:溶于二氯甲烷等部分有机溶剂 稳定性:-20℃密封保存、置阴凉干燥处、防潮 分子…...
【论文阅读】浏览器扩展危害-Helping or Hindering? How Browser Extensions Undermine Security
本文来源于ACM CCS 2022; https://dl.acm.org/doi/10.1145/3548606.3560685 摘要 “浏览器扩展”是轻量级的浏览器附加组件,使用各个浏览器特定的功能丰富的JavaScript api,为用户提供了额外的Web客户端功能,如改进网站外观和与…...
线性和非线性最小二乘问题的常见解法总结
线性和非线性最小二乘问题的各种解法 先看这篇博客,非常好:线性和非线性最小二乘问题的各种解法 1. 线性最小二乘问题有最优解 但是面对大型稀疏矩阵的时候使用迭代法效率更好。 迭代法 有Jacobi迭代法、 Seidel迭代法及Sor法 【数值分析】Jacobi、Se…...
数据库知识点
数据库是指按照一定规则存储、组织和管理数据的系统。在现代化的信息化社会中,数据库已经成为了各种应用系统中不可或缺的一部分。因此,对于数据库的知识掌握不仅是计算机专业人员必备的技能,也是各个行业从业者必须具备的基本素质之一。 数…...
Maven打包构建Docker镜像并推送到仓库
Maven打包构建Docker镜像并推送到仓库 文章目录Maven打包构建Docker镜像并推送到仓库一,服务器Docker配置二,本地项目maven配置2.1 pom.xml2.2 dockerfile2.3 验证2.4 统一dockerfile对于开发完成的服务要发布至服务器Docker时,我刚学习了解D…...
终极指南:5分钟搭建SillyTavern AI聊天前端,解锁个性化角色对话体验
终极指南:5分钟搭建SillyTavern AI聊天前端,解锁个性化角色对话体验 【免费下载链接】SillyTavern LLM Frontend for Power Users. 项目地址: https://gitcode.com/GitHub_Trending/si/SillyTavern 想要创建专属的AI聊天伙伴,体验深度…...
物理网卡down了?虚拟机还能通信吗?看teaming策略就够了
在ESXi虚拟化运维中,物理网卡(vmnic)故障、网线松动、网卡损坏导致网卡down(宕机),是常见的硬件故障场景。很多新手遇到这种情况,会下意识认为所有虚拟机都会断网,但实际并非如此。核…...
从用户态到内核态:Linux Hook技术的全景实践与攻防解析
1. Linux Hook技术入门:从概念到实践 第一次接触Hook技术是在十年前的一个安全分析项目中,当时需要监控某个可疑进程的行为。那时候我才明白,原来Linux系统里藏着这么多可以"截胡"程序执行的秘密通道。简单来说,Hook技术…...
FPGA边缘视觉方案解析:从芯片选型到多传感器融合实战
1. 项目概述:单芯片FPGA嵌入式视觉与融合分析方案 最近在梳理一些老项目的技术文档时,翻到了Altera(现在已是Intel PSG的一部分)和Eutecus在2015年左右合作推出的一套方案,当时在EE Times上被称作“Single-Chip FPGA-B…...
MATLAB roots函数实战:5分钟搞定高阶系统稳定性判断(附完整代码)
MATLAB roots函数实战:高阶系统稳定性分析的黄金法则 在控制工程和自动化领域,系统稳定性分析是每个工程师的必修课。面对复杂的高阶系统特征方程,传统的手工计算方法不仅耗时耗力,还容易出错。而MATLAB的roots函数配合简单的可视…...
Fast-GitHub:3个技巧让国内开发者告别GitHub龟速时代
Fast-GitHub:3个技巧让国内开发者告别GitHub龟速时代 【免费下载链接】Fast-GitHub 国内Github下载很慢,用上了这个插件后,下载速度嗖嗖嗖的~! 项目地址: https://gitcode.com/gh_mirrors/fa/Fast-GitHub 你是否曾经因为Gi…...
从零到一:Lmbench 性能测试实战与结果深度解读
1. 为什么你需要Lmbench性能测试 第一次听说Lmbench时,我也和大多数新手一样困惑:系统性能测试工具那么多,为什么非要选这个老古董?直到在服务器部署项目时连续遇到三次性能瓶颈,我才真正理解它的价值。那次我们用某款…...
自动化测试系统开关架构与继电器选型指南
1. 自动化测试系统中的开关架构选择在自动化测试系统中,开关架构的选择直接影响着测试效率、信号完整性和系统成本。根据测试需求和被测设备(DUT)特性,我们可以将开关架构分为四种基本类型。1.1 无开关架构无开关架构是最直接的连接方式,每个…...
Nevis‘22基准:评估持续学习模型的计算效率与知识迁移能力
1. 项目概述:为什么我们需要一个全新的终身学习基准?在计算机视觉乃至整个机器学习领域,我们正面临一个日益尖锐的矛盾:一方面,我们希望模型能够像人类一样,在漫长的时间里持续学习新知识,不断进…...
别再只会用IP核了!手把手教你用Verilog从零实现一个16阶FIR滤波器(附完整代码)
从零构建16阶FIR滤波器:Verilog实战指南与工程思维解析 在FPGA开发领域,FIR(有限脉冲响应)滤波器是数字信号处理的基础模块,但大多数工程师习惯直接调用厂商提供的IP核,这就像只会开自动挡汽车的司机——虽…...
