【数据结构】单链表
链表
- 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启动进入…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...

cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...

用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...

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

Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...

关于easyexcel动态下拉选问题处理
前些日子突然碰到一个问题,说是客户的导入文件模版想支持部分导入内容的下拉选,于是我就找了easyexcel官网寻找解决方案,并没有找到合适的方案,没办法只能自己动手并分享出来,针对Java生成Excel下拉菜单时因选项过多导…...

如何把工业通信协议转换成http websocket
1.现状 工业通信协议多数工作在边缘设备上,比如:PLC、IOT盒子等。上层业务系统需要根据不同的工业协议做对应开发,当设备上用的是modbus从站时,采集设备数据需要开发modbus主站;当设备上用的是西门子PN协议时…...

Redis上篇--知识点总结
Redis上篇–解析 本文大部分知识整理自网上,在正文结束后都会附上参考地址。如果想要深入或者详细学习可以通过文末链接跳转学习。 1. 基本介绍 Redis 是一个开源的、高性能的 内存键值数据库,Redis 的键值对中的 key 就是字符串对象,而 val…...

无头浏览器技术:Python爬虫如何精准模拟搜索点击
1. 无头浏览器技术概述 1.1 什么是无头浏览器? 无头浏览器是一种没有图形用户界面(GUI)的浏览器,它通过程序控制浏览器内核(如Chromium、Firefox)执行页面加载、JavaScript渲染、表单提交等操作。由于不渲…...