【数据结构】单链表的层层实现!! !
关注小庄 顿顿解馋(●’◡’●)
上篇回顾
我们上篇学习了本质为数组的数据结构—顺序表,顺序表支持下标随机访问而且高速缓存命中率高,然而可能造成空间的浪费,同时增加数据时多次移动会造成效率低下,那有什么解决之法呢?这就得引入我们链表这种数据结构
文章目录
- 一.何为链表
- 🏠 链表概念
- 🏠 链表的分类
- 二.单链表的实现
- 🏠 链表的打印
- 🏠 链表的头插和尾插
- 🏠 链表的尾删和头删
- 🏠 链表指定位置的插入和删除
- 🏠 链表的查找
- 🏠 链表的销毁
- `注: 这里要保存好下一个结点地址,销毁后就能继续遍历`
- 三.单链表的分析以及与顺序表的比较
- 🏠 单链表的优缺点
- 🏠 单链表与顺序表的比较
一.何为链表
🏠 链表概念
概念:链表是一种
物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表
中的指针链接次序实现的 。
特点:物理结构不一定连续,逻辑结构连续

我们的链表结构类似我们的火车,有头有尾,中间每个结点被有序链接;与火车不同的是,链表的结点可能不是紧挨着的。

类似这样,我们可以得出:
1.每个结点由数据和下一结点地址两部分组成,而每个结点构成了一个链表。
2.每个结点保存的是下一个结点的地址,这样就能找到下一个结点,最后为空就停止
3.每个结点的地址不是连续的,可以体现出链表的物理结构不一定连续
注:我们的结点一般是在堆区开辟的,因为此时你在程序结束前不free就会一直存在这块空间,同时可根据需要灵活申请结点存数据。
这样我们就可以用一个结构体封装每个结点:
typedef int Datatype;
typedef struct ListNode
{Datatype x;struct ListNode* next;
}Node;
注: 这里我们可以用typedef来重命名我们要存储的数据类型,这样对于不同数据的操作我们只要改typedef即可。
🏠 链表的分类
我们根据链表三个特点:
1.带头不带头 2.单向还是双向 3.循环还是不循环组合成了如上的8种链表
本篇博客,我们要实现的是单向不带头不循环链表(单链表),至于什么是带头,双向,循环我们下回双链表再进行讲解
二.单链表的实现
无头+单向+不循环链表的增删差改
🏠 链表的打印
- 链表数据的打印
这个接口就很好的体现了结点结构保存指针的妙处了~
//链表的打印
void SLTPrint(Node* phead)
{asser(phead);Node* cur = phead;while (cur){printf("%d ", cur->x);cur = cur->next;}
}
🏠 链表的头插和尾插
- 链表的尾插

对于链表的尾插要注意几个问题:1.申请新结点 2.链表是否为空
解决方法:1.对于链表为空,直接让申请的新节点作为头节点 2.对于不为空的链表,首先要
找到尾结点,再进行插入3.对于申请新节点,我们后续的头插也要使用我们可以封装成一个接口,同时结点在堆区申请,调用完接口就不会释放了。
//申请新节点
Node* BuyNode(Datatype x)
{Node* newnode = (Node*)malloc(sizeof(Node));if (newnode == NULL){perror("malloc failed");return;}newnode->next = NULL;newnode->x = x;return newnode;
}void SLTPushBack(Node** pphead, Datatype x)
{assert(pphead);//申请新节点Node* newnode = BuyNode(x);//链表为空if (NULL == *pphead){*pphead = newnode;return;}//链表不为空:1.找尾巴结点2.插入新节点Node* ptail = *pphead;while (ptail->next){ptail = ptail->next;}ptail->next = newnode;
}
思考:这里为什么传二级指针?如果不传二级指针呢?
void SLTNPushBack(Node* pphead, Datatype x)
{//申请新节点Node* newnode = BuyNode(x);//链表为空if (NULL == pphead){pphead = newnode;return;}//链表不为空:1.找尾巴结点2.插入Node* ptail = pphead;while (ptail->next){ptail = ptail->next;}ptail->next = newnode;
}
int main()
{Node* n1 = NULL;SLTNPushBack(n1,1);return 0;
}

经过观察传一级指针版本的调用前后,我们发现n1这个指针变量存储的值并没发生改变,究其原因如下图

- 链表的头插
,
*对于链表的头插要注意的问题:1.申请新节点 2.链表释放为空 *
解决方法:1.链表为空时,申请的新结点作为头结点 2.链表不为空时,让newnode->next指向原来头节点,再让newnode成为新的头节点。
void SLTPushFront(Node** pphead, Datatype x)
{assert(pphead);//申请新节点Node* newnode = BuyNode(x);//链表为空if (NULL == *pphead){*pphead = newnode;return;}newnode->next = *pphead;*pphead = newnode;
}
🏠 链表的尾删和头删
- 链表的尾删

对于链表的尾删,我们需要分三种情况!
1.链表为空时此时删不了直接退出
2.链表只有一个结点时,释放头节点,置phead为空
3.链表有多个结点时,我们需要遍历链表找到尾结点的前置结点,先释放尾节点再将前置结点的next置为空
注:不能先将前置结点的next置为空,再释放尾结点,此时就找不到尾结点的地址了。
//链表的尾删和头删
void SLTPopBack(Node** pphead)
{assert(pphead);assert(*pphead);//判断链表不为空if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;return;}Node* pre = *pphead;while (pre->next->next){pre = pre->next;}free(pre->next);pre->next = NULL;
}
- 链表的头删

对于链表的头删,分为两种情况就可以了,因为有了phead很方便~
1.链表为空时,删不了直接断言下
2.链表不为空时,记录头节点的下一个位置,先释放头节点,再更换phead指向的位置
注:这里也不能先更新phead再释放头结点
void SLTPopFront(Node** pphead)
{assert(pphead);assert(*pphead);Node* pNext = (*pphead)->next;free(*pphead);*pphead = pNext;
}
🏠 链表指定位置的插入和删除

1.链表为空时,无法插入
2.pos位置结点刚好是头结点,直接头插或头删
3.pos位置结点不是头节点,需要找到pos位置的前置结点,记录位置。
void NodeInpos(Node** pphead, Node* pos, Datatype x)
{assert(pphead);//pos不为空 -》 链表一定不能为空assert(pos);assert(*pphead);//建立一个新节点Node* newnode = BuyNode(x);//pos刚好是头结点的情况if (*pphead == pos){//运用头插NodeinFront(pphead,x);return;}//pos刚好不是头结点//1.先找出pos前面的结点pre 2.newnode->next = pre->next 3.pre-<next = newnodeNode* pre = *pphead;while (pre->next != pos){pre = pre->next;}newnode->next = pre->next;pre->next = newnode;
}void NodeDelpos(Node** pphead, Node* pos)
{assert(pphead);assert(pos);assert(*pphead);//pos刚好是第一个结点 执行头删if (*pphead == pos){NodeDelFront(pphead);return;}//pos不是第一个结点 1.先找到那个pos前面结点pre 2.pre->next = pos->next 3.freeNode* pre = *pphead;while (pre->next != pos){pre = pre->next;}pre->next = pos->next;free(pos);pos = NULL;
}
🏠 链表的查找
Node* NodeFind(Node** phead, Datatype x)
{assert(phead);//遍历链表Node* pcur = *phead;while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}//找不到则返回NULL
}
这里建议用一个临时变量来遍历~
🏠 链表的销毁
void NodeDestroy(Node** pphead)
{assert(pphead);assert(*pphead);//1.创一个临时变量存pcur->next的地址Node* pcur = *pphead;while (pcur){Node* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}
注: 这里要保存好下一个结点地址,销毁后就能继续遍历
三.单链表的分析以及与顺序表的比较
🏠 单链表的优缺点
通过实现我们的单链表,我们发现单链表有以下优点
1.单链表不存在空间浪费(根据需求灵活在堆上申请新结点)
2.单链表的任意插入和删除效率高
(分析: 这里是已经确定插入的位置,对于链表只需改变指针的指向就能实现插入和删除,时间复杂度是O(1),而数组插入和删除需要遍历数组,时间复杂度是O(N))
单链表也不是万能的,它在一些应用场景也发挥不出来作用
1.不支持随机访问
2.缓存命中率低
3.查找效率低
总结:链表适用于频繁任意插入和删除的场景,不适用于随机访问和查找
🏠 单链表与顺序表的比较
| 单链表 | 顺序表 |
|---|---|
| 物理空间不一定连续 | 物理空间一定连续 |
| 不支持随机访问 | 支持下标随机访问 |
| 插入和删除效率高 O(1) | 插入和删除效率低 O(N) |
| 缓存命中率低 | 缓存命中率高 |
| 无空间的浪费 | 可能造成数据丢失和空间浪费 |
| 缓存利用率高 |
综上 我们可以根据场景需求选择不同的结构,灵活运用数据~
本次分享到这结束了,下篇我们将讲解双向链表,不妨来个一键三连捏
相关文章:
【数据结构】单链表的层层实现!! !
关注小庄 顿顿解馋(●’◡’●) 上篇回顾 我们上篇学习了本质为数组的数据结构—顺序表,顺序表支持下标随机访问而且高速缓存命中率高,然而可能造成空间的浪费,同时增加数据时多次移动会造成效率低下,那有什么解决之法呢ÿ…...
丰田研究所(TRI)最新成果——可实现全身操控的软体机器人Punyo
文 | BFT机器人 人形机器人在近年的科技浪潮中迅速崛起,成为了各界瞩目的焦点,众多企业纷纷推出自家的机器人模型,但仔细观察,不难发现它们中的许多在操作方式上仍显得颇为相似。这些典型的人形机器人,以其机械臂和抓…...
【PyTorch实战演练】深入剖析MTCNN(多任务级联卷积神经网络)并使用30行代码实现人脸识别
文章目录 0. 前言1. 级联神经网络介绍2. MTCNN介绍2.1 MTCNN提出背景2.2 MTCNN结构 3. MTCNN PyTorch实战3.1 facenet_pytorch库中的MTCNN3.2 识别图像数据3.3 人脸识别3.4 关键点定位 0. 前言 按照国际惯例,首先声明:本文只是我自己学习的理解ÿ…...
MFC中字符串string类型和CString类型互转方法
在Microsoft Foundation Classes (MFC)中,CString是一个非常方便的类,用于处理C风格的字符串。有时,你可能需要在MFC的CString和C标准库中的std::string之间进行转换。下面是如何在两者之间进行转换的方法: CString转std::string…...
Jmeter-使用http proxy代理录制脚本
Jmeter-使用http proxy代理录制脚本 第1步:打卡jmeter工具新增1个线程组 第2步:给线程组添加1个HTTP请求默认值 第3步:设置下HTTP请求默认值第4步:在工作台中新增1个----HTTP代理服务器 第5步:设置HTTP代理服务器 …...
C++训练营:new 运算符
大家好: 衷心希望各位点赞。 您的问题请留在评论区,我会及时回答。 一、new 运算符 new 运算符用于动态分配一片内存空间,并返回这片内存空间的首地址,可将该首地址存入一个指针变量中,主要有以下三种格式。 二、格…...
C# 用Trace.WriteLine输出调试信息无法查看
写程序就会遇见BUG,这时候在代码不同部位输出一些标记的信息对查找错误非常有必要,一般情况下我们都是使用Console.WriteLine()方法来打印信息到控制台窗口,但有时候使用Console.WriteLine()方法会存在不方便的情况,比如鄙人遇到的…...
【Echarts】柱状图上方显示数字以及自定义值,标题和副标题居中,鼠标上显示信息以及自定义信息
欢迎来到《小5讲堂》 大家好,我是全栈小5。 这是《前端》系列文章,每篇文章将以博主理解的角度展开讲解, 特别是针对知识点的概念进行叙说,大部分文章将会对这些概念进行实际例子验证,以此达到加深对知识点的理解和掌握…...
HTML 语义化:构建优质网页的关键
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…...
Flutter入门学习——Flutter和Dart
因为工作的需要,也为了个人发展,现在的话,转战Flutter跨端开发了,虽然目前的项目只发了android端,但是那天尝试了一下Ios的打包流程,也能运行,只是IOS那边的打包稍微复杂一些。 差不多学习了一…...
C++中的内存管理方式
一、C内存管理方式简介 C语言中的内存管理方式在C中可以继续使用,但是在有些地方就无能为力,而且使用起来比较麻烦。因此C中引入了自己的内存管理方式,通过new和delete操作符进行动态内存管理。 二、new语法 new可以申请1个或多个空间&…...
macos m1 arm芯片 使用jpype报错 FileNotFoundError: [Errno 2] JVM DLL not found
startJVM(jpype.getDefaultJVMPath()) 报错 Traceback (most recent call last):File "/Users/thomas990p/PycharmProjects/tuya/volcano-biz-scripts/WenKongFa/FinalCode/java2python/CallJavaAPI.py", line 12, in <module>startJVM(jpype.getDefaultJVMPa…...
Hive中UNION ALL和UNION的区别
1.概述 Hive官方提供了一种联合查询的语法,原名为Union Syntax,用于联合两个表的记录进行查询,此处的联合和join是不同的,join是将两个表的字段拼接到一起,而union是将两个表的记录拼接在一起。 换言之, jo…...
selenium高级应用
常见控件应用 复杂的控件操作1.操作Ajax选项2.滑动滑块操作 WebDriver的特殊操作元素class值包含空格property、attribute、text的区别定位动态id 截图功能页面截图页面截图,返回截图的二进制数据页面截图,返回base64的字符串截取指定元素。先定位元素&a…...
微信小程序重新加载当前页面、刷新当前页面
重新加载页面 使用wx.reLanuch(),url: 路径当前页面跳转, 页面所有数据重新初始化,已配置的数据不会保存 wx.reLaunch({url: /pages/orders/createOrder/createOrder, // 当前页面的路径}) reLanuch()的方法,会有一个…...
如何查找、恢复误清空的 Android 回收站?
“我的回收站里有一些照片。当我点击“恢复”时,没有任何反应。我可以将我的 Android 手机插入我的电脑。这样我就可以手动恢复它们。但我在 Android 上找不到 bin 文件夹。我还可以做些什么?” 随着 Android 手机上的文件数量不断增加,了解…...
Node.js作用
Node.js可以开发应用 开发服务器应用 开发工具类应用 开发桌面端应用...
Web爬虫入门:原理、实现与常见问题解决指南
引言: 在当今数据驱动的时代,网络上蕴藏着无尽的信息宝藏,而爬虫技术则是探索和利用这些宝藏的重要工具。爬虫,简单来说,就是一种自动化程序,它能够模拟人类浏览网页的行为,从中提取所需数据。…...
蓝桥杯练习题——归并排序
1.火柴排队 思路 1.求最小值的时候,可以直接按升序排序,这样得到的值就是最小值 2.求最小交换次数的时候,不能直接排序,因为只能交换相邻的数,只需要知道他们的相对大小,所以可以先用离散化,把…...
C语言--- 指针运算笔试题详解
目录 题目1: 题目2: 题目3: 题目4: 题目5: 题目6: 题目7: 题目1: #include <stdio.h> int main() {int a[5] { 1, 2, 3, 4, 5 };int *ptr (int *)(&a 1);print…...
高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...
在 Spring Boot 中使用 JSP
jsp? 好多年没用了。重新整一下 还费了点时间,记录一下。 项目结构: pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...
论文阅读:Matting by Generation
今天介绍一篇关于 matting 抠图的文章,抠图也算是计算机视觉里面非常经典的一个任务了。从早期的经典算法到如今的深度学习算法,已经有很多的工作和这个任务相关。这两年 diffusion 模型很火,大家又开始用 diffusion 模型做各种 CV 任务了&am…...
在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例
目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码:冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...
【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅!
【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅! 🌱 前言:一棵树的浪漫,从数组开始说起 程序员的世界里,数组是最常见的基本结构之一,几乎每种语言、每种算法都少不了它。可你有没有想过,一组看似“线性排列”的有序数组,竟然可以**“长”成一棵平衡的二…...
前端工具库lodash与lodash-es区别详解
lodash 和 lodash-es 是同一工具库的两个不同版本,核心功能完全一致,主要区别在于模块化格式和优化方式,适合不同的开发环境。以下是详细对比: 1. 模块化格式 lodash 使用 CommonJS 模块格式(require/module.exports&a…...

