【数据结构】单链表
链表
- 1.为什么存在链表
- 2.链表的概念
- 3.单链表的实现
- 4.测试
1.为什么存在链表
我们在学习顺序表的时候,了解到顺序表有一定的缺陷:(1)在中间插入数据和删除数据需要挪动数据,时间复杂度是O(N),效率低下。(2)realloc会异地扩容,需要申请新空间,拷贝数据,释放旧空间。有不小的消耗。(3) realloc扩容后,难免有一定的空间浪费(数据删除后的空间或者扩容后不用的空间)。而链表就能弥补顺序表的缺点。
2.链表的概念
链表是一种物理存储结构上非连续(地址非连续)、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表就是在逻辑结构上就是一条链,链接着一个个节点,每个节点就是一个结构体,包含数据和指向下一个节点的地址。

节点的定义
typedef int SLTDataType;//方便后面更改数据的类型,//比如你存储的数据不是整形而是浮点型就可以在这里修改
typedef struct SListNode//链表的节点
{SLTDataType data;//数据struct SListNode* next;//指向下一个节点的指针
}SLTNode;//重命名:有意义的、简短的名字
3.单链表的实现
- 打印链表(假设现在有一个现成的链表(上图))
void SLTPrint(SLTNode* plist)//plist是头节点
{while (plist!=NULL){printf("%d->", plist->data);plist = plist->next;}printf("NULL\n");
}
结果
1->2->3->4->NULL
疑惑
(1)plist = plist->next;是什么意思?能不能写成plist++;?
a.plist是头节点,指向第一个节点,节点的成员next是指针,指向第二个节点,将next存储的地址赋值给plist,plist就指向第二个节点。以此类推,直到plist指向最后一个节点的空指针。b.不能。plist++跳到物理地址上的下一个结构体。而链表的各节点在物理地址上是不连续的。

(2)循环体的判断条件能否改成whlie(plist->next!=NULL)?
不能,因为最后一个元素没有打印。
- 单链表尾插
(1)首先在找到最后一个节点,然后接上要插入的新节点。
(2)其次,要考虑特殊情况,如果单链表是空链表该如何解决?
void SLTPushBack(SLTNode** pplist, SLTDateType x)//pplist是指向头节点的指针,x是新节点的数据
{assert(pplist);//pplist接收plist的地址,一定不是NULL,就更要断言,防止函数传参传个NULL过来//首先获得一个新节点SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL)//检查是否申请空间失败{perror("malloc");return;}newnode->data = x;//记得把数据放入新节点newnode->next = NULL;//记得将next置为NULL//考虑是空链表if (*pplist == NULL){*pplist = newnode;//直接让头指针接上新的节点就行return;}//不是空链表else{//首先找到链表的尾巴SLTNode* tail = *pplist;while (tail->next != NULL)//当tail指向最后一个节点时停止循环,因为最后一个节点的next是NULL{tail = tail->next;}//然后接上新节点tail->next = newnode;}
}
画图分析

疑惑
(1)在函数开头对pplist进行断言,有没有必要对*pplist断言?
没有,这个链表是空的,我们就是要对其进行尾插,才不为空,断言了就不能对空链表进行尾插。
(2)为什么函数传参要传头节点的地址?
我们都知道形参是实参的临时拷贝,形参的改变不影响实参。如果要改变实参,就是传实参的地址。在单链表不为空时,直接传头节点可以实现尾插,但在单链表为空时,则不能实现,如图。

- 单链表头插
同样需要考虑两种情况:单链表为空或者单 链表不为空。
// 单链表的头插
void SLTPushFront(SLTNode** pplist, SLTDateType x)
{assert(pplist);//获得一个新的节点,直接将这个功能封装成一个函数SLTNode* newnode = GetNewNode(x);//先考虑普通,再考虑特殊SLTNode* tmp = *pplist;//加入一个临时变量,保存旧的头节点*pplist = newnode;newnode->next = tmp;//最终我们发现,链表为空也适用,不用考虑特殊情况
}//获得新节点
SLTNode* GetNewNode(SLTDateType* x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc");return;}newnode->data = x;newnode->next = NULL;
}
- 单链表尾删
void SLTPopBack(SLTNode** pplist)
{assert(pplist);//空链表就没必要删除//找到尾巴SLTNode* tail = *pplist;SLTNode* tailFront = tail;同时需要记录尾巴前一个节点while (tail->next)//当tail指向最后一个节点时tailFront指向上一个节点{tailFront = tail;}free(tail);tail = NULL;tailFront->next = NULL;
}
是不是这样做就完成了?并没有,当只有一个节点时,就会这个函数就不能实现尾删。
//正确做法
void SLTPopBack(SLTNode** pplist)
{//空链表就没必要删除assert(*pplist);//找到尾巴SLTNode* tail = *pplist;SLTNode* tailFront = tail;同时需要记录尾巴前一个节点//只有一个节点if (tail->next == NULL){*pplist = NULL;free(tail);tail = NULL;return;}while (tail->next)//当tail指向最后一个节点时tailFront指向上一个节点{tailFront = tail;tail = tail->next;}free(tail);tail = NULL;tailFront->next = NULL;
}
- 单链表头删
void SLTPopFront(SLTNode** pplist)
{//防止为空assert(*pplist);SLTNode* first = *pplist;//记录要删除的头节点*pplist = first->next;//适用于所有特殊情况:包括只有一个节点。free(first);first = NULL;
}
- 单链表查找
SLTNode* SLTFind(SLTNode* plist, SLTDateType x)
{SLTNode* tmp = plist;while (tmp){if (tmp->data == x){return tmp;}tmp = tmp->next;}return tmp;
}
- 单链表在pos之后插入
void SLTInsertAfter(SLTNode* pos, SLTDateType x)
{assert(pos);//首先获得一个新节点SLTNode* newnode = GetNewNode(x);newnode->next = pos->next;pos->next = newnode;
}
疑惑
为什么不在pos位置之前插入?
当pos刚好是第一个节点时,插入新的节点后,由于函数并没有传头节点过来,所以头节点将无法指向新的节点。其实也可以在pos这个位置插入,只要将pos这个位子的数据和插入节点的数据交换一下,然后插入节点继续在pos之后插入,这样数据就像在pos之前的节点插入一样。
- 单链表删除pos位置之后的值
void SListEraseAfter(SLTNode* pos)
{assert(pos);//要考虑pos指向最后一个节点的情况if (pos->next == NULL){return;}SLTNode* tmp = pos->next;pos->next = pos->next->next;free(tmp);tmp = NULL;
}
疑惑
为什么不删除pos位置?
当pos是第一个节点时,被释放后,由于函数没有传头节点,头节点将指向野指针,同时无法指向新的链表
- 单链表的销毁
void SLTDestroy(SLTNode* plist)
{assert(plist);SLTNode* tmp = plist->next ;//用来遍历链表while (tmp){free(plist);plist = tmp;tmp = tmp->next;}free(plist);plist = NULL;
}
分析
当只有一个节点时,tmp = NULL,不进入循环,直接将头节点释放;当有多个节点,tmp进入循环,在tmp = NULL时,plist指向最后一个节点,并没有在while中释放,所以出了循环之后还要释放一次。
4.测试


相关文章:
【数据结构】单链表
链表1.为什么存在链表2.链表的概念3.单链表的实现4.测试1.为什么存在链表 我们在学习顺序表的时候,了解到顺序表有一定的缺陷:(1)在中间插入数据和删除数据需要挪动数据,时间复杂度是O(N)&…...
Windows 右键菜单扩展容器 [开源]
今天给大家分享一个我做的小工具,可以自定义扩展右键菜单的功能来提高工作效率,效果图如下: 如上图,右键菜单多了几个我自定义的菜单: 复制文件路径 复制文件夹路径 我的工具箱 <走配置文件动态创建子菜单&#x…...
爆文制造机!小红书热榜3个方向,告诉你选题诀窍!
我们知道,不论是达人创作内容,还是品牌制定Brief,都需要提前调研筛选海量信息,这时候如果有一个自己的内容素材库,就省事多啦。按照内容需求,我们可以按3个角度划分小红书内容素材:笔记类型、竞…...
【Web安全社工篇】——水坑攻击
作者名:白昼安全主页面链接: 主页传送门创作初心: 以后赚大钱座右铭: 不要让时代的悲哀成为你的悲哀专研方向: web安全,后渗透技术每日鸡汤:努力赚钱不是因为爱钱“水坑攻击”,黑客攻…...
SpringBoot 整合 MongoDB 实现数据的增删改查!
一、介绍在 MongoDB 中有三个比较重要的名词:数据库、集合、文档!数据库(Database):和关系型数据库一样,每个数据库中有自己的用户权限,不同的项目组可以使用不同的数据库集合(Colle…...
VUE前端常问面试题
文章目录一、VUE前端常问面试题二、文档下载地址一、VUE前端常问面试题 1、MVC和MVVM 区别 MVC:MVC全名是 Model View Controller,即模型-视图-控制器的缩写,一种软件设计典范。 Model(模型):是用于处理应用程序数据逻辑部分。通…...
c++中map/unordered_map的不同遍历方式以及结构化绑定
文章目录方式一:值传递遍历方式二:引用传递遍历方式三:使用迭代器遍历方式四:结构化绑定(c17特性)结构化绑定示例(1)元组tuple结构化绑定(2)结构体结构化绑定(3ÿ…...
Kafka系列之:Kraft模式
Kafka系列之:Kraft模式 一、Kraft架构二、Kafka的Kraft集群部署三、初始化集群数据目录四、创建KafkaTopic五、查看Kafka Topic六、创建生产者七、创建消费者一、Kraft架构 Kafka元数据存储在zookeeper中,运行时动态选举controller,由controller进行Kafka集群管理。Kraft模式…...
动态规划:leetcode 139.单词拆分、多重背包问题
leetcode 139.单词拆分leetcode 139.单词拆分给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。说明:拆分时可以重复使用字典中的单词。你可以假设字典中没有重复的单词。示例 1&…...
Stable Diffusion原理详解
Stable Diffusion原理详解 最近AI图像生成异常火爆,听说鹅厂都开始用AI图像生成做前期设定了,小厂更是直接用AI替代了原画师的岗位。这一张张丰富细腻、风格各异、以假乱真的AI生成图像,背后离不开Stable Diffusion算法。 Stable Diffusion…...
webpack高级配置
摇树(tree shaking) 我主要是想说摇树失败的原因(tree shaking 失败的原因),先讲下摇树本身效果 什么是摇树? 举个例子 首先 webpack.config.js配置 const webpack require("webpack");/**…...
jQuery 事件
jQuery 事件 Date: February 28, 2023 Sum: jQuery事件注册、处理、对象 目标: 能够说出4种常见的注册事件 能够说出 on 绑定事件的优势 能够说出 jQuery 事件委派的优点以及方式 能够说出绑定事件与解绑事件 jQuery 事件注册 单个时间注册 语法:…...
【批处理脚本】-2.3-解析地址命令arp
"><--点击返回「批处理BAT从入门到精通」总目录--> 共2页精讲(列举了所有arp的用法,图文并茂,通俗易懂) 目录 1 arp命令解析 1.1 询问当前协议数据,显示当前 ARP 项...
改进 YOLO V5 的密集行人检测算法研究(论文研读)——目标检测
改进 YOLO V5 的密集行人检测算法研究(2021.08)摘 要:1 YOLO V52 SENet 通道注意力机制3 改进的 YOLO V5 模型3.1 训练数据处理改进3.2 YOLO V5 网络改进3.3 损失函数改进3.3.1 使用 CIoU3.3.2 非极大值抑制改进4 研究方案与结果分析4.1 实验…...
Python - Opencv应用实例之CT图像检测边缘和内部缺陷
Python - Opencv应用实例之CT图像检测边缘和内部缺陷 将传统图像处理处理算法应用于CT图像的边缘检测和缺陷检测,想要实现效果如下: 关于图像处理算法,主要涉及的有:灰度、阈值化、边缘或角点等特征提取、灰度相似度变换,主要偏向于一些2D的几何变换、涉及图像矩阵的一些统…...
管理逻辑备数据库(Logical Standby Database)
1. SQL Apply架构概述 SQL Apply使用一组后台进程来应用来自主数据库的更改到逻辑备数据库。 在日志挖掘和应用处理中涉及到的不同的进程和它们的功能如下: 在日志挖掘过程中: 1)READER进程从归档redo日志文件或备redo日志文件中读取redo记…...
【C++】构造函数(初始化列表)、explicit、 Static成员、友元、内部类、匿名对象
构造函数(初始化列表)前提构造函数体赋值初始化列表explicit关键字static成员概念特性(重要)有元友元函数友元类内部类匿名对象构造函数(初始化列表) 前提 前面 六个默认成员对象中我们已经学过什么是构造…...
(六十)再来看看几个最常见和最基本的索引使用规则
今天我们来讲一下最常见和最基本的几个索引使用规则,也就是说,当我们建立好一个联合索引之后,我们的SQL语句要怎么写,才能让他的查询使用到我们建立好的索引呢? 下面就一起来看看,还是用之前的例子来说明。…...
机器学习与目标检测作业(数组相加:形状需要满足哪些条件)
机器学习与目标检测(数组相加:形状需要满足哪些条件)机器学习与目标检测(数组相加:形状需要满足哪些条件)一、形状相同1.1、形状相同示例程序二、符合广播机制2.1、符合广播机制的描述2.2、符合广播机制的示例程序机器学习与目标检…...
CentOS救援模式(Rescue Mode)及紧急模式(Emergency Mode)
当CentOS操作系统崩溃,无法正常启动时,可以通过救援模式或者紧急模式进行系统登录。启动CentOS, 当出现下面界面时,按e进入编辑界面。在编辑界面里,加入参数:systemd.unitrescue.target ,然后Ctrl-X启动进入…...
3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
Python 实现 Web 静态服务器(HTTP 协议)
目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1)下载安装包2)配置环境变量3)安装镜像4)node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1)使用 http-server2)详解 …...
