数据结构预算法--链表(单链表,双向链表)
1.链表
目录
1.链表
1.1链表的概念及结构
1.2 链表的分类
2.单链表的实现(不带哨兵位)
2.1接口函数
2.2函数的实现
3.双向链表的实现(带哨兵位)
3.1接口函数
3.2函数的实现
1.1链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
现实中 数据结构中
1.2 链表的分类
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
1. 单向或者双向
2. 带头或者不带头
3. 循环或者非循环
1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
2.单链表的实现(不带哨兵位)
2.1接口函数
// 1、无头+单向+非循环链表增删查改实现
typedef int SLTDateType;
typedef struct SListNode
{SLTDateType data;struct SListNode* next;
}SListNode;
// 动态申请一个结点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos);
//销毁单链表
void SLTDestroy(SLNode** pphead);
2.2函数的实现
1. 动态申请一个结点
SListNode* BuySListNode(SLTDateType x) {SListNode* new = (SListNode*)malloc(sizeof(SListNode));if (new == NULL){perror(malloc);exit(-1);}new->data =x;new->next = NULL;return new; }动态申链表,并给新malloc出的空间传值。
2.单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x) {assert(pphead);SListNode* newlist = BuySListNode(x);if (*pplist == NULL){*pplist = newlist;}else {SListNode* tail = NULL;tail = *pplist;while (tail->next != NULL){tail = tail->next;}tail->next = newlist;} }1. 创建一个新节点,并为其分配内存空间。 2. 将新节点的数据赋值为要插入的数据。 3. 将新节点的指针域(next)设置为NULL,表示它是链表的最后一个节点。 4. 如果链表为空,将头指针指向新节点;否则,找到链表的最后一个节点,将其指针域指向新节点。
3.单链表的尾删
void SListPopBack(SListNode** pplist) {assert(pphead);assert(*pplist);if ((*pplist)->next == NULL){free(*pplist);*pplist = NULL;}else {SListNode* tail = *pplist;SListNode* prve = NULL;while (tail->next != NULL){ prve = tail;tail = tail->next;}free(tail);tail = NULL;//不置空也没问题,出作用域自动销毁prve->next = NULL;}}1. 如果链表为空,直接返回空链表。 2. 如果链表只有一个节点,释放该节点的内存空间,并将头指针指向NULL。 3. 遍历链表,找到倒数第二个节点。 4. 将倒数第二个节点的指针域设置为NULL,表示它是链表的最后一个节点。 5. 释放最后一个节点的内存空间。
3.单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x) {assert(pphead);SListNode* newlist = BuySListNode(x);newlist->next = *pplist;*pplist = newlist; }1. 创建一个新节点,并为其分配内存空间。 2. 将新节点的数据赋值为要插入的数据。 3. 将新节点的指针域指向当前的头节点。 4. 将头指针指向新节点。
4.单链表头删
void SListPopFront(SListNode** pplist) {assert(pphead);assert(*pplist);SListNode* tmp = (*pplist)->next;free(*pplist);*pplist = tmp; }1. 如果链表为空,直接返回空链表。 2. 将头指针指向第二个节点。 3. 释放第一个节点的内存空间。
5.单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x) {SListNode* find = plist;while (find != NULL){if (find->data == x){return find;break;}find = find->next;}return NULL; }1. 如果链表为空,返回NULL。 2. 遍历链表,逐个比较节点的数据与目标值。 3. 如果找到匹配的节点,返回该节点的指针;否则,返回NULL。
6.在pos节点后插入
void SListInsertAfter(SListNode* pos, SLTDateType x) {assert(pphead);if (pos == NULL) {return;}SListNode* newlist = BuySListNode(x);newlist->next = pos->next;pos->next = newlist; }1.判断了指定位置是否为NULL,如果为NULL,则直接返回,不进行插入操作。2.我们创建一个新节点,并将新节点的数据赋值为要插入的数据。3.我们将新节点的指针域指向指定位置节点原来的下一个节点,然后将指定位置节点的指针域指向新节点,完成插入操作。
7.删除pos节点后的值
void SListEraseAfter(SListNode* pos) {assert(pphead);if (pos == NULL || pos->next == NULL) {return;}SListNode* temp = pos->next;pos->next = temp->next;free(temp); }1.判断了指定位置是否为NULL或者指定位置的下一个节点是否为NULL,如果是,则直接返回,不进行删除操作。2.我们创建一个临时指针temp,指向指定位置节点的下一个节点。3.我们将指定位置节点的指针域指向temp节点的下一个节点,然后释放temp节点的内存空间,完成删除操作。
8.销毁单链表
先保存下一个的地址,在销毁当前节点。void SLTDestroy(SLNode** pphead) {assert(pphead);SLNode* cur = *pphead;while (cur){SLNode* next = cur->next;free(cur);cur = next;}*pphead = NULL; }
9.分析思考为什么不在pos位置之前插入?为什么不删除pos位置?
这是因为单链表的节点只有一个指针指向下一个节点,没有指向前一个节点的指针。那该如何解决呢?请看后面双向链表的实现。
3.双向链表的实现(带哨兵位)
3.1接口函数
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int LTDataType;
typedef struct ListNode
{struct ListNode* next;struct ListNode* prev;LTDataType data;
}LTNode;LTNode* BuyLTNode(LTDataType x);
LTNode* LTInit();
void LTPrint(LTNode* phead);
void LTPushBack(LTNode* phead, LTDataType x);
void LTPopBack(LTNode* phead);void LTPushFront(LTNode* phead, LTDataType x);
void LTPopFront(LTNode* phead);int LTSize(LTNode* phead);LTNode* LTFind(LTNode* phead, LTDataType x);// pos֮ǰx
void LTInsert(LTNode* pos, LTDataType x);
// ɾposλ
void LTErase(LTNode* pos);
3.2函数的实现
我们可以注意到,在单链表和双线链表出参的不同,单链表传的是二级指针,而双向链表传的是一级指针。实际上这是有无哨兵位造成的,当没有哨兵位时,我们需要用二级指针去保存链表第一个节点的地址,此时改变的是结构体的指针,因此需要用结构体的二级指针,而带哨兵位,我们只需要改变哨兵位后面的节点(结构体),此时改变的是结构体,因此只需要用结构体的一级指针。
双向链表相较于单链表实际上就多了头指针域,这样就能找到当前节点的上一个节点,也就是可以轻松的做到在任意节点前插入。双向链表做到了“首尾呼应”,自然为节点不用指向NULL。
这样很多操作就变的简单,快捷,高效。
1.动态申请一个结点
LTNode* BuyLTNode(LTDataType x) {LTNode* node = (LTNode*)malloc(sizeof(LTNode));if (node == NULL){perror("malloc fail");exit(-1);}node->data = x;node->next = NULL;node->prev = NULL;return node; }
2.头节点(哨兵位)的初始化
LTNode* LTInit() {LTNode* phead = BuyLTNode(0);phead->next = phead;phead->prev = phead;return phead; }
这里涉及到改变结构体的值的操作,当然也可以写成接收二级指针的形式,档期当前的方式当然也是可行的。这里的操作就是对哨兵位的初始化,我们可以看到,我们将头节点的前,后指针域都指向了自己,这样就保持了循环的效果。
3.双向链表尾插
void LTPushBack(LTNode* phead, LTDataType x) {assert(phead);LTNode* tail = phead->prev;LTNode* newnode = BuyLTNode(x);newnode->prev = tail;tail->next = newnode;newnode->next = phead;phead->prev = newnode; }
![]()
双向链表要实现尾删是非常便捷的,不用循环找到尾节点,因为头节点的前指针域就指向了尾节点,所以我们只需要一步就能找到尾了。然后我们只需尾节点 指向新节点,然后让新节点指向尾节点,之后再让新节点指向头节点,最后让头节点指向新节点就好了。
4.尾删
void LTPopBack(LTNode* phead) {assert(phead);assert(phead->next != phead);LTNode* tail = phead->prev;LTNode* tailPrev = tail->prev;free(tail);tailPrev->next = phead;phead->prev = tailPrev;}要删除尾节点,首先要找到尾节点和尾节点的前一个节点,然后释放掉尾节点,让新的尾节点指向头,再让头指向尾。
5.打印双向链表
void LTPrint(LTNode* phead) {assert(phead);assert(phead->next!=phead);printf("phead<=>");LTNode* cur = phead->next;while (cur != phead){printf("%d<=>", cur->data);cur = cur->next;}printf("\n"); }
当我们打印双向链表时,只存在头节点就不要打印了,所以我们可以加上第二句断言。
打印的时候我们从头节点的下一个节点开始打印,最后走一圈遍历到头的时候就停止打印。
这里我就省略头插,头删,求节点个数和查找了,因为操作大同小异,十分简单,最后会奉上完整代码。
6.pos节点前插入
void LTInsert(LTNode* pos, LTDataType x) {assert(pos);LTNode* posPrev = pos->prev;LTNode* newnode = BuyLTNode(x);posPrev->next = newnode;newnode->prev = posPrev;newnode->next = pos;pos->prev = newnode; }
想要在pos节点的前面插入,那么只需要找到pos节点和pos节点前面的节点就可以了,找pos节点我们可以配合查找函数来使用,找到想要的pos节点就可以了。
7.删除pos节点
void LTErase(LTNode* pos) {assert(pos);LTNode* posPrev = pos->prev;LTNode* posNext = pos->next;free(pos);posPrev->next = posNext;posNext->prev = posPrev; }
想要删除pos节点,只需要找到pos节点的前一个和pos节点的后一个,free掉pos节点,然后让pos节点的前一个和pos节点的后一个连接就好了。
这就是链表的全部内容了,希望对各位老铁有帮助,接下来我会更新链表的OJ题目,希望各位老铁,多多支持!!!
相关文章:
数据结构预算法--链表(单链表,双向链表)
1.链表 目录 1.链表 1.1链表的概念及结构 1.2 链表的分类 2.单链表的实现(不带哨兵位) 2.1接口函数 2.2函数的实现 3.双向链表的实现(带哨兵位) 3.1接口函数 3.2函数的实现 1.1链表的概念及结构 概念:链表是一种物理存储结…...
数据结构线性表——栈
前言:哈喽小伙伴们,今天我们将一起进入数据结构线性表的第四篇章——栈的讲解,栈还是比较简单的哦,跟紧博主的思路,不要掉队哦。 目录 一.什么是栈 二.如何实现栈 三.栈的实现 栈的初始化 四.栈的操作 1.数据入栈…...
自定义 springboot 启动器 starter 与自动装配原理
Maven 依赖 classpath 类路径管理 Maven 项目中的类路径添加来源分为三类 自定义 springboot starter starter 启动器定义的规则自定义 starter 示例 自动装配 文章链接...
16 _ 二分查找(下):如何快速定位IP对应的省份地址?
通过IP地址来查找IP归属地的功能,不知道你有没有用过?没用过也没关系,你现在可以打开百度,在搜索框里随便输一个IP地址,就会看到它的归属地。 这个功能并不复杂,它是通过维护一个很大的IP地址库来实现的。地址库中包括IP地址范围和归属地的对应关系。 当我们想要查询202…...
vb.net圣经带快捷键,用原装的数据库
Imports System.Data.SqlServerCe Imports System.Text.RegularExpressions Imports System.Data.OleDbPublic Class Form1Dim jiuyue As String() {"创", "出", "利", "民", "申", "书", "士", "…...
Unity中Shader的雾效
文章目录 前言一、Unity中的雾效在哪开启二、Unity中不同种类雾的区别1、线性雾2、指数雾1(推荐用这个,兼具效果和性能)3、指数雾2(效果更真实,性能消耗多) 三、在我们自己的Shader中实现判断,是…...
企业微信开发教程一:添加企微应用流程图解以及常见问题图文说明
最近在前辈的基础上新添加了一个企微应用,过程中遇到了一些卡点,这里一一通过图片标注与注释的方式记录一下,希望能给后来人提供一些清晰明了的帮助,话不多说,大家直接看图吧。 (文中包括一些本项目独有的配…...
【LeetCode】67. 二进制求和
67. 二进制求和 难度:简单 题目 给你两个二进制字符串 a 和 b ,以二进制字符串的形式返回它们的和。 示例 1: 输入:a "11", b "1" 输出:"100"示例 2: 输入:a "…...
【LeetCode刷题笔记】二叉树(一)
102. 二叉树的层序遍历 解题思路: 1. BFS广度优先遍历 ,使用队列,按层访问 解题思路: 2. 前序遍历 , 递归 ,在递归方法参数中,将 层索引...
NativeScript开发ios应用,怎么生成测试程序?
在 NativeScript 中,要部署 iOS 应用程序,你需要遵循以下一般步骤: 1、确保开发环境: 确保你的开发环境中已经安装了 Xcode,并且你有一个有效的 Apple 开发者账号。 2、构建 iOS 应用: 在你的 NativeScri…...
Js面试题:说一下js的模块化?
作用: 一个模块就是实现某个特定功能的文件,在文件中定义的变量、函数、类都是私有的,对其他文件不可见。 为了解决引入多个js文件时,出现 命名冲突、污染作用域 等问题 AMD: 浏览器端模块解决方案 AMD即是“异步模块定…...
媒体转码软件Media Encoder 2024 mac中文版功能介绍
Media Encoder 2024 mac是一款媒体转码软件,它可以将视频从一种格式转码为另一种格式,支持H.265、HDR10等多种编码格式,同时优化了视频质量,提高了编码速度。此外,Media Encoder 2024还支持收录、创建代理和输出各种格…...
整治PPOCRLabel中cv2文件读取问题(更新中)
PPOCRLabel 使用PPOCRLabel对ocr预标注结果进行纠正由于PaddleOCR代码库十分混乱,路径经常乱调pip和代码库的代码(pip库和源码冲突),经常报错,因此paddleocr和ppocrlabel都是使用pip包;PPOCRLabel中使用了cv2进行图片数据的读取,…...
网络运维Day09-补充
文章目录 rsync增量同步scp与rsync的区别rsync常用选项 rsync本地实验rsync远程同步实验练习上传练习下载 总结 rsync增量同步 rsync是增量同步的一种工具,可以实现本地目录之间数据同步,也可以实现远程跨主机之间数据同步 scp与rsync的区别 scp属于全…...
【C++】【Opencv】minMaxLoc()函数详解和示例
minMaxLoc()函数 是 OpenCV 库中的一个函数,用于找到一个多维数组中的最小值和最大值,以及它们的位置。这个函数对于处理图像和数组非常有用。本文通过参数和示例详解,帮助大家理解和使用该函数。 参数详解 函数原型…...
用Go实现网络流量解析和行为检测引擎
1.前言 最近有个在学校读书的迷弟问我:大德德, 有没有这么一款软件, 能够批量读取多个抓包文件,并把我想要的数据呈现出来, 比如:源IP、目的IP、源mac地址、目的mac地址等等。我说:“这样的软件你要认真找真能找出不少开源软件, 但毕竟没有你自己的灵魂在里面,要不…...
Mysql数据备份 — mysqldump
一 备份类型 - 逻辑备份(mysqldump): - 优点: - 恢复简单,可以使用管道将他们输入到mysql。 - 与存储引擎无关,因为是从MySQL服务器中提取数据而生成的,所以消除了底层数据…...
vue使用Echarts5实现词云图
先上官网 词云图有些特殊,它属于Echarts 的扩展,需要额外安装Echarts-wordcloud包。 Echarts 官网 Echarts-wordcloud 词云图官网 先安装 npm install echarts npm install echarts-wordcloud再引入 echarts选一个引入就行;4或5版本都可以 …...
带有密码的Excel只读模式,如何取消?
Excel文件打开之后发现是只读模式,想要退出只读模式,但是只读模式是带有密码的,该如何取消带有密码的excel只读文件呢? 带有密码的只读模式,是设置了excel文件的修改权限,取消修改权限,我们需要…...
Linux下基本操作命令
一、基础命令 1. pwd 命令 pwd命令用于显示当前所在的工作目录的全路径名称。该命令无需任何参数,只需在终端窗口中输入 pwd 命令即可使用。 2. cd 命令 cd命令用于更改当前工作目录。该命令需要一个参数:目标目录名称。例如,若要进入 Do…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...
算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
2025年- H71-Lc179--39.组合总和(回溯,组合)--Java版
1.题目描述 2.思路 当前的元素可以重复使用。 (1)确定回溯算法函数的参数和返回值(一般是void类型) (2)因为是用递归实现的,所以我们要确定终止条件 (3)单层搜索逻辑 二…...
深度解析云存储:概念、架构与应用实践
在数据爆炸式增长的时代,传统本地存储因容量限制、管理复杂等问题,已难以满足企业和个人的需求。云存储凭借灵活扩展、便捷访问等特性,成为数据存储领域的主流解决方案。从个人照片备份到企业核心数据管理,云存储正重塑数据存储与…...
C/Python/Go示例 | Socket Programing与RPC
Socket Programming介绍 Computer networking这个领域围绕着两台电脑或者同一台电脑内的不同进程之间的数据传输和信息交流,会涉及到许多有意思的话题,诸如怎么确保对方能收到信息,怎么应对数据丢失、被污染或者顺序混乱,怎么提高…...
开疆智能Ethernet/IP转Modbus网关连接鸣志步进电机驱动器配置案例
在工业自动化控制系统中,常常会遇到不同品牌和通信协议的设备需要协同工作的情况。本案例中,客户现场采用了 罗克韦尔PLC,但需要控制的变频器仅支持 ModbusRTU 协议。为了实现PLC 对变频器的有效控制与监控,引入了开疆智能Etherne…...









