数据结构---双链表
专栏:数据结构
个人主页: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…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...
R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...
STM32---外部32.768K晶振(LSE)无法起振问题
晶振是否起振主要就检查两个1、晶振与MCU是否兼容;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容(CL)与匹配电容(CL1、CL2)的关系 2. 如何选择 CL1 和 CL…...
