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

【数据结构】【版本1.1】【线性时代】——单链表



快乐的流畅:个人主页


个人专栏:《算法神殿》《数据结构世界》《进击的C++》

远方有一堆篝火,在为久候之人燃烧!

文章目录

  • 引言
  • 一、顺序表的问题
  • 二、链表的概念
  • 三、单链表的模拟实现
    • 3.1 定义
    • 3.2 打印
    • 3.3 创建新节点
    • 3.4 头插
    • 3.5 尾插
    • 3.6 头删
    • 3.7 尾删
    • 3.8 查找与修改
    • 3.9 指针断言
    • 3.10 指定插入
    • 3.11 指定删除
    • 3.12 销毁

引言

数据结构世界——单链表(Single Linked List)

一、顺序表的问题

让我们回顾一下顺序表:

  1. 中间/头部的插入删除,时间复杂度为O(N)
  2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
  3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

那么,如何解决以上问题呢?

二、链表的概念

链表,是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。


  • 链表的结构跟火车车厢相似,将火车里的某节车厢去掉/加上,不会影响其他车厢,每节车厢都是独立存在的。
  • 车厢是独立存在的,且每节车厢都有车门。想象一下这样的场景,假设每节车厢的车门都是锁上的状态,需要不同的钥匙才能解锁,每次只能携带一把钥匙的情况下如何从车头走到车尾?
  • 最简单的做法:每节车厢里都放一把下一节车厢的钥匙

在链表里,每节“车厢”是怎样的呢?

  • 与顺序表不同的是,链表的每节"车厢"都是独立申请下来的空间,我们称之为结点/节点
  • 节点的组成主要有两个部分:当前节点要保存的数据和保存下一个节点的地址(指针变量)

图中指针变量 plist保存的是第一个节点的地址,我们称plist此时“指向”第一个节点,如果我们希望plist“指向”第二个节点时,只需要修改plist保存的内容为0x0012FFA0


为什么还需要指针变量来保存下一个节点的位置?

链表中每个节点都是独立申请的(即需要插入数据时才去申请一块节点的空间),我们需要通过指针变量来保存下一个节点位置才能从当前节点找到下一个节点

三、单链表的模拟实现

3.1 定义

//单链表
typedef int SLDataType;
typedef struct SListNode
{SLDataType data;struct SListNode* next;
}SLNode;
  • 在结构体内嵌套结构体,一定要表明struct,而不能直接使用typedef后的名称

3.2 打印

先来看看怎么实现链表的打印,这样更有助于我们理解它的结果。

void SLPrint(SLNode* phead)
{SLNode* cur = phead;while (cur != NULL){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}
  • 先用cur指针接受头部地址,当cur不为NULL时,则打印当前节点的数据,并将该节点存储的下一节点的地址赋值给cur
  • 这样cur指针就能一直访问整个链表,直到最后一个节点(该节点存储地址为NULL

3.3 创建新节点

每次插入,要生成新节点,申请空间……一系列操作 ,所以我们可以把它该过程写成一个函数,以增强函数的复用性

SLNode* BuySLNode(SLDataType x)
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL){perror("malloc fail");return;}newnode->data = x;newnode->next = NULL;return newnode;
}
  • malloc动态开辟内存生成一个新节点,再将数据放入新节点中,初始地址为NULL

3.4 头插

void SLPushFront(SLNode** pphead, SLDataType x)
{assert(pphead);SLNode* newnode = BuySLNode(x);newnode->next = *pphead;*pphead = newnode;
}
  • 先创建新节点,再将新节点链接在链表头部
  • 更新链表指针

可能很多人有疑惑,为什么要用二级指针呢?请看接下来的测试:

与顺序表不同的是,我们不再创建结构体,而是创建结构体指针,初始指向NULL。那么,我们要在函数内改变外部指针指向的地址,就要使用二级指针

3.5 尾插

void SLPushBack(SLNode** pphead, SLDataType x)
{assert(pphead);//1.空链表//2.非空链表SLNode* newnode = BuySLNode(x);if (*pphead == NULL){*pphead = newnode;}else{SLNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}
  • 如果为空链表,则直接将新节点的地址传给外部的链表指针
  • 如果为非空链表,先申请新节点,再用循环一直向后访问,找到最后一个节点的地址,将新节点链接在最后

3.6 头删

void SLPopFront(SLNode** pphead)
{assert(pphead);assert(*pphead);SLNode* cur = *pphead;*pphead = (*pphead)->next;free(cur);
}
  • 将链表指针的地址改成第二个节点的,并将第一个节点的空间释放

3.7 尾删

void SLPopBack(SLNode** pphead)
{assert(pphead);assert(*pphead);//一个节点//多个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLNode* tail = *pphead;while (tail->next->next){tail = tail->next;}free(tail->next);tail->next = NULL;
}
  • 如果为一个节点,直接释放空间,并赋值为NULL
  • 如果有多个节点,找到倒数第二个节点,解引用将其next指针指向的空间(最后一个节点)释放,再赋值为NULL

3.8 查找与修改

SLNode* SLFind(SLNode* phead, SLDataType x)
{SLNode* cur = phead;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}
  • 找到返回对应节点的地址,找不到返回NULL
  • 能查找,也意味着能修改,因为我们获取了该节点的地址,自然能在外部解引用修改数据,这样,一个函数就有两个功能 ——查找和修改

3.9 指针断言

这里说一下关于assert断言的情况 ,并不是所有函数参数的指针都需要断言,而是要根据实际情况分析而定。


//打印
void SLPrint(SLNode* phead);
//查找
SLNode* SLFind(SLNode* phead, SLDataType x);

打印与查找,则不需要断言,因为空链表也可以打印,也可以查找,就比如你的银行账户没有钱,就不能显示出来看看,不能查询吗?


//头插
void SLPushFront(SLNode** pphead, SLDataType x);
//尾插
void SLPushBack(SLNode** pphead, SLDataType x);

头插与尾插,对于其二级指针需要断言,一级指针不用,因为pphead指向链表指针plist,所以不能为空,而链表指针可为空(即为空链表)。


//头删
void SLPopFront(SLNode** pphead);
//尾删
void SLPopBack(SLNode** pphead);

头删与尾删,其二级指针与一级指针都要断言,除了pphead不能为空,*pphead也不能为空,因为空链表就不能进行删除操作。

3.10 指定插入

这里有两种插入方式,在pos前插入在pos后插入

在pos前插入

void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{assert(pphead);assert(pos);if (pos == *pphead){SLPushFront(pphead, x);}else{SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLNode* newnode = BuySLNode(x);newnode->next = pos;prev->next = newnode;}
}
  • 如果pos为头部地址时,即为头插,可复用头插函数
  • 如果pos不为头部地址时,再找到pos前一个节点的地址,然后创建新节点,最后将它们链接起来

在pos后插入

void SLInsertAfter(SLNode* pos, SLDataType x)
{assert(pos);SLNode* newnode = BuySLNode(x);newnode->next = pos->next;pos->next = newnode;
}
  • 相比于在pos前插入,单链表其实更适合在pos后插入,直接创建新节点,进行链接即可

ps:链接的过程有顺序问题,不能写反了,要不然会环状链接。

3.11 指定删除

这里也有两种删除方式,在pos删除在pos后删除

在pos删除

void SLErase(SLNode** pphead, SLNode* pos)
{assert(pphead);assert(pos);if (pos == *pphead){SLPopFront(pphead);}else{SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);}
}
  • 如果pos为头部地址时,即为头删,可复用头删函数
  • 如果pos不为头部地址时,再找到pos前一个节点的地址,链接pos后一个节点,再释放pos节点空间

在pos后删除

void SLEraseAfter(SLNode* pos)
{assert(pos);assert(pos->next);SLNode* next = pos->next;pos->next = pos->next->next;free(next);
}
  • 相比于在pos位置删除,单链表其实更适合在pos后删除,这里用一个next指针,保存pos下一个节点的地址,在pos链接往后第二个节点后,再对next节点空间释放

3.12 销毁

void SLDestroy(SLNode** pphead)
{assert(pphead);SLNode* cur = *pphead;while (cur){SLNode* next = cur->next;free(cur);cur = next;}*pphead = NULL;
}
  • 在每次循环内临时创建一个新指针next,记录下一个节点的地址,让cur释放所指空间后,还可以找到下一个节点,最后把链表指针解引用置空

当然,这里你也可以传一级指针,不在函数内部把外部的链表指针置为NULL,而在外部手动置空,跟free函数的用法一样,实现半自动。

void SLDestroy(SLNode* phead)
{SLNode* cur = phead;while (cur){SLNode* next = cur->next;free(cur);cur = next;}
}

真诚点赞,手有余香

相关文章:

【数据结构】【版本1.1】【线性时代】——单链表

快乐的流畅:个人主页 个人专栏:《算法神殿》《数据结构世界》《进击的C》 远方有一堆篝火,在为久候之人燃烧! 文章目录 引言一、顺序表的问题二、链表的概念三、单链表的模拟实现3.1 定义3.2 打印3.3 创建新节点3.4 头插3.5 尾插3…...

【计算机毕业设计】258基于微信小程序的课堂点名系统

🙊作者简介:拥有多年开发工作经验,分享技术代码帮助学生学习,独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。🌹赠送计算机毕业设计600个选题excel文件,帮助大学选题。赠送开题报告模板&#xff…...

common.js和es6中模块引入的区别

common.js CommonJS 是一种模块系统,主要用于 Node.js 环境。它使用 require 函数来引入模块,并使用 module.exports 来导出模块。 语法: 导出模块: // moduleA.js const name Jo; module.exports name;// 或者导出一个对象…...

关于对pagination.js源代码进行修改且引入项目使用

实现效果 使用定时器对组件进行每秒请求&#xff0c;每过固定时间之后&#xff0c;进行下一页项目请求&#xff0c;进行到最后一页请求的时候返回第一页。 首先引入js插件 <script src"./js/pagination.js" type"text/javascript"></script>…...

《思考总结》

思考总结 ==标题==:卷积操作的作用1. **特征提取**2. **参数共享**3. **降维和数据压缩**4. **提升计算效率**5. **平滑和去噪**卷积操作示例输入图像卷积核卷积过程总结==标题==:上卷积什么是上卷积(反卷积/转置卷积)上卷积的作用上卷积的实现1. **最近邻插值(Nearest Ne…...

使用QT绘制简单的动态数据折线图

两个核心类时QChart和QLineSeries 下面这个示例代码中&#xff0c;定时器每隔一段时间将曲线图中的数据点向右移动 一个单位&#xff0c;同时调整横坐标轴的范围&#xff0c;实现了一次滚动对应移动一个数据点的效果。 QLineSeries最多容纳40961024个点 #include <QtWidg…...

Linux-centos7 nvm使用

NVM下载使用 文件夹创建拉取nvm包在~/.bashrc的末尾&#xff0c;添加如下语句验证nvm是否安装成功 文件夹创建 mkdir /root/home/software/拉取nvm包 cd /root/home/software/ wget https://github.com/nvm-sh/nvm/archive/refs/tags/v0.38.0.tar.gz tar xvzf v0.38.0.tar.g…...

【Linux】Linux环境基础开发工具_6

文章目录 四、Linux环境基础开发工具gdb 未完待续 四、Linux环境基础开发工具 gdb 我们已经可以写代码了&#xff0c;也能够执行代码了&#xff0c;但是代码错了该如何调试呢&#xff1f;Linux中可以使用 gdb 工具进行调试。 我们写一个简单的程序&#xff1a; 但是我们尝试…...

Redis宣布商用后,Redis国产化替代方案有那些?

一、背景 Redis作为使用最为广泛的开源缓存中间件&#xff0c;现已成为IT开发中必不可少的核心组件。官方修改协议印证了“开源”不意味着“无偿使用”&#xff0c;相关限制或将对基于开源Redis提供中间件产品的厂商&#xff0c;及提供Redis服务的云厂商产生一定影响。 二、国…...

Go API

Go语言提供了大量的标准库&#xff0c;因此 google 公司也为这些标准库提供了相应的API文档&#xff0c;用于告诉开发者如何使用这些标准库&#xff0c;以及标准库包含的方法。官方位置&#xff1a;https://golang.org Golang中文网在线标准库文档: https://studygolang.com/p…...

基于STM32的简易智能家居设计(嘉立创支持)

一、项目功能概述 1、OLED显示温湿度、空气质量&#xff0c;并可以设置报警阈值 2、设置4个继电器开关&#xff0c;分别控制灯、空调、开关、风扇 3、设计一个离线语音识别系统&#xff0c;可以语音控制打开指定开关、并且可以显示识别命令词到OLED屏上 4、OLED实时显示&#…...

【YOLOv5/v7改进系列】改进池化层为RT-DETR的AIFI

一、导言 Real-Time DEtection TRansformer&#xff08;RT-DETR&#xff09;&#xff0c;是一种实时端到端目标检测器&#xff0c;克服了Non-Maximum Suppression&#xff08;NMS&#xff09;对速度和准确性的影响。通过设计高效的混合编码器和不确定性最小化查询选择&#xf…...

使用Python和Matplotlib绘制复杂数学函数图像

本文介绍了如何使用Python编程语言和Matplotlib库来绘制复杂的数学函数图像。通过引入NumPy库的数学函数,我们可以处理包括指数函数在内的各种复杂表达式。本文详细讲解了如何设置中文字体以确保在图像中正确显示中文标题和标签,并提供了一个完整的代码示例,用户可以通过输入…...

淘宝/1688获得店铺的所有商品(商品列表)

通过以下步骤&#xff0c;可以获得淘宝或1688店铺的所有商品。请注意&#xff0c;具体步骤可能会因为平台的更新而有所改变&#xff0c;可以根据实际情况进行操作。 更多API调用展示以及获取Key和secret请移步 返回数据格式&#xff1a; {"user": null,"ite…...

【MySQL】锁机制

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 给大家跳段街舞感谢支持&#xff01;ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ …...

LangChain入门学习笔记(一)——Hello World

什么是LangChain LangChain是一个开源&#xff08;github repo&#xff09;的大语言模型应用开发框架&#xff0c;提供了一整套的工具、方法和接口去帮助程序员构建基于大语言模型的端到端应用。LangChain是长链&#xff08;long chain&#xff09;的意思&#xff0c;它的一个…...

[ROS 系列学习教程] 建模与仿真 - 使用 Arbotix 控制机器人

ROS 系列学习教程(总目录) 本文目录 一、Arbotix 简介二、安装Arbotix三、配置Arbotix控制器四、配置launch启动文件五、数据交互接口六、在rviz中仿真控制机器人6.1 直接发topic控制6.2 使用键盘控制6.3 编写代码控制机器人移动 前面讲了机器人的建模&#xff0c;是静态的&…...

java:使用JSqlParser给sql语句增加tenant_id和deleted条件

# 示例代码 【pom.xml】 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-core</artifactId><version>3.4.3.1</version> </dependency>【MyJSqlParserTest.java】 package com.chz.myJSqlParser;pu…...

华三HCL模拟器安装及华三防火墙配置

0、前言 最近跟模拟器杠上了&#xff0c;主要是需要运行防火墙&#xff0c;目前已经成功模拟出华为、山石防火墙&#xff0c;而且模拟出来的设备能与物理网络环境进行互联。现在我又盯上华三防火墙了。 首先下载模拟器&#xff1a; 下载地址&#xff1a;H3C网络设备模拟器官方免…...

MySQL基础---库的操作和表的操作(配着自己的实操图,简单易上手)

绪论​ 勿问成功的秘诀为何&#xff0c;且尽全力做您应该做的事吧。–美华纳&#xff1b;本章是MySQL的第二章&#xff0c;本章主要写道MySQL中库和表的增删查改以及对库和表的备份处理&#xff0c;本章是基于上一章所写若没安装mysql可以查看Linux下搭建mysql软件及登录和基本…...

C++:std::is_convertible

C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器

一.自适应梯度算法Adagrad概述 Adagrad&#xff08;Adaptive Gradient Algorithm&#xff09;是一种自适应学习率的优化算法&#xff0c;由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率&#xff0c;适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql

智慧工地管理云平台系统&#xff0c;智慧工地全套源码&#xff0c;java版智慧工地源码&#xff0c;支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求&#xff0c;提供“平台网络终端”的整体解决方案&#xff0c;提供劳务管理、视频管理、智能监测、绿色施工、安全管…...

大语言模型如何处理长文本?常用文本分割技术详解

为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

el-switch文字内置

el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...

高等数学(下)题型笔记(八)空间解析几何与向量代数

目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

视频字幕质量评估的大规模细粒度基准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用&#xff0c;因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型&#xff08;VLMs&#xff09;在字幕生成方面…...

【笔记】WSL 中 Rust 安装与测试完整记录

#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统&#xff1a;Ubuntu 24.04 LTS (WSL2)架构&#xff1a;x86_64 (GNU/Linux)Rust 版本&#xff1a;rustc 1.87.0 (2025-05-09)Cargo 版本&#xff1a;cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...

AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别

【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而&#xff0c;传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案&#xff0c;能够实现大范围覆盖并远程采集数据。尽管具备这些优势&#xf…...

【JVM】Java虚拟机(二)——垃圾回收

目录 一、如何判断对象可以回收 &#xff08;一&#xff09;引用计数法 &#xff08;二&#xff09;可达性分析算法 二、垃圾回收算法 &#xff08;一&#xff09;标记清除 &#xff08;二&#xff09;标记整理 &#xff08;三&#xff09;复制 &#xff08;四&#xff…...