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

深入理解链表(SList)操作

目录:

  • 一、 链表介绍
    • 1.1、 为什么引入链表
    • 1.2、 链表的概念及结构
    • 1.3、 链表的分类
  • 二、 无头单向非[循环链表](https://so.csdn.net/so/search?q=循环链表&spm=1001.2101.3001.7020)的实现
    • 2.1、 [单链表](https://so.csdn.net/so/search?q=单链表&spm=1001.2101.3001.7020)的定义
    • 2.2、 动态申请一个节点
    • 2.3、 销毁(释放)所有节点
    • 2.4、 打印单链表
    • 2.5、 单链表尾插
    • 2.6、 单链表头插
    • 2.7、 单链表尾删
    • 2.8、 单链表头删
    • 2.9、 在单链表中查找指定值的节点
    • 2.10、 单链表在pos位置之后插入
    • 2.11、 单链表删除指定pos位置的节点
    • 2.12、 单链表删除指定pos位置之后的节点
    • 2.13、 求单链表长度
    • 2.14、 判断单链表是否为空
  • 三、 总结

学习链表之前,建议先学习下顺序表,下面是顺序表的直达链接:

深入理解顺序表(SeqList)

一、 链表介绍

1.1、 为什么引入链表

  • 学习链表之前,先让我们来思考一个问题:

为什么有了顺序表,还需要有链表这样的数据结构呢?

  • 顺序表存在的一些问题:
  1. 顺序表在中间/头部的插入删除,要挪动很多数据,时间复杂度为O(N),效率太低了。
  2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
  3. 增容一般是一次增长2倍,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们
    再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间SeqList。
  • 为了更好的解决上述问题,引入了链表。

1.2、 链表的概念及结构

  • 概念

前面学习的顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,而链表是一种物理存储结构上不连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的,可以实现更加灵活的动态内存管理。

  • 链表的组成

链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。

每个结点包括两个部分:

1、数据域:存储数据元素

2、指针域:存储下一个结点地址

  • 链表的物理结构

可以看到,4个节点的地址并不是连续的,链表在物理结构上不一定是线性的,而在逻辑结构上是线性的

image-20210824110616542

  • 链表的逻辑结构(想象出来的)

image-20210822225322883

  • 注意

1、链式结构在逻辑上是连续的,但在物理上不一定连续

2、链表的节点是在堆上申请出来的

1.3、 链表的分类

链表的结构非常多样化

  • 单向、双向

image-20210827235931988

  • 带头结点、不带头节点(哨兵位的头节点,不存储有效数据)

image-20210828002001494

  • 非循环、循环

image-20210827235906305

  • 常用的两种结构

image-20210828000023080

二、 无头单向非循环链表的实现


2.1、 单链表的定义

image-20210831212953194

typedef int SLTDataType;//定义单链表节点
typedef struct SListNode
{SLTDataType data;        //数据域struct SListNode* next;  //指针域
}SLTNode;

2.2、 动态申请一个节点

//动态申请一个节点
SLTNode* BuySLTNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL)  //检查是否开辟成功{perror("malloc fail");return;}newnode->data = x;newnode->next = NULL;return newnode;
}

2.3、 销毁(释放)所有节点

//销毁单链表中所有节点
void SLTDestory(SLTNode** pphead)
{assert(pphead);SLTNode* cur = *pphead;while (cur != NULL)  //遍历链表{SLTNode* next = cur->next;  //保存cur的下一个节点free(cur);  //释放节点cur = next;}*pphead = NULL;  //z
}
  • 作用:

销毁单链表中所有节点,指向头节点的指针置空,防止内存泄露等问题


2.4、 打印单链表

//打印单链表
void SLTPrint(SLTNode* phead)
{SLTNode* ptr = phead;while (ptr != NULL){printf("%d->", ptr->data);ptr = ptr->next;}printf("NULL\n");
}

2.5、 单链表尾插

  • 先来看一种错误写法:这个也是弄懂单链表的关键

传一级指针的值,用一级指针接收

  • 这种写法会导致一个问题:

因为当链表为空时,我们需要改变 plist 的指向,使其指向第一个节点。

而初始 plist 和 phead 都指向 NULL,调用函数后,phead 指向了新的节点,而 plist 还是指向 NULL 的。

  • 如何解决呢:

plist 是指向第一个节点的指针,想要在函数中改变 plist 的值(指向),必须要把 plist指针的地址 作为实参传过去,形参用 二级指针 接收,这样在函数中对二级指针解引用得到 plist 的值,就可以改变 plist 的值(指向)了

记住:在函数里面要改变 int,则要传 int* ,要改变 int* ,则要传 int**

  • 注意区分开这几个代表的意思哦

平时一般不用解引用两层,没啥意义,一般是解引用一层,来改变外面 plist 的指向。

  • **正确写法:**通过二级指针改变外面 plist 的指向

单链表为空时,plist 直接指向新节点;

单链表不为空时,先找到单链表的尾节点,然后将尾节点的next指向新节点

//单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);  //检查参数是否传错SLTNode* newnode = BuySListNode(x);  //动态申请一个节点if (*pphead == NULL)  //单链表中没有节点时{*pphead = newnode;  //plist指向新节点}else if (*pphead != NULL)  //单链表中已经有节点时{SLTNode* tail = *pphead;while (tail->next != NULL)  //找到单链表中的最后一个节点{tail = tail->next;}tail->next = newnode;  //最后一个节点的next指向新节点}
}
  • 运行结果如下:

在这里插入图片描述


2.6、 单链表头插

  • 图解头插操作

image-20210824230705865

  • 代码如下

因为要改变外部 plist 的指向,所以用二级指针接收指针 plist 的地址

//单链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);  //检查参数是否传错SLTNode* newnode = BuySListNode(x);  //动态申请一个节点newnode->next = *pphead;  //新节点的next指针指向plist指向的位置*pphead = newnode;  //plist指向头插的新节点
}
  • 运行结果如下:

在这里插入图片描述


2.7、 单链表尾删

  • 图解尾删操作

image-20210824224625868

  • 代码如下

单链表只有一个节点时,删除节点,plist 指向 NULL;

单链表有多个节点时,先找到单链表尾节点的上一个节点,删除尾节点,然后将该节点的next指向 NULL;

因为可能要改变外部 plist 的指向,所以用二级指针接收指针 plist 的地址。

//单链表尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead);   //检查参数是否传错assert(*pphead);  //断言,链表不能为空SLTNode* tail = *pphead;if ((*pphead)->next == NULL)  //链表只有一个节点{free(*pphead);   //删除节点*pphead = NULL;  //plist置空}else  //链表中有多个节点{while (tail->next->next != NULL)  //找到链表的尾节点的上一个节点{tail = tail->next;}free(tail->next);   //删除尾节点tail->next = NULL;  //置空/*思路2:SLTNode* prev = *pphead;while (tail->next)  //找到链表的尾节点和它的上一个节点{prev = tail;tail = tail->next;}free(tail);         //删除尾节点prev->next = NULL;  //置空*/}
}
  • 运行结果如下:

在这里插入图片描述


2.8、 单链表头删

  • 图解头删操作

image-20210825001225011

  • 代码如下

因为可能要改变外部 plist 的指向,所以用二级指针接收指针 plist 的地址。

//单链表头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead);   //检查参数是否传错assert(*pphead);  //链表不能为空SLTNode* cur = *pphead;  //保存头节点的地址*pphead = cur->next;  //plist指向头节点的下一个节点free(cur);  //删除头节点
}
  • 运行结果如下:

在这里插入图片描述


2.9、 在单链表中查找指定值的节点

如果查找到,返回该节点的地址;没有查找到,返回 NULL。

//在单链表中查找指定值节点
SListNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* cur = phead;//遍历链表while (cur != NULL){if (cur->data == x){return cur;  //找到了,返回该节点的地址}cur = cur->next;}return NULL;  //未找到,返回NULL
}

2.10、 单链表在pos位置之后插入

  • 图解插入操作

image-20210827234957091

  • 分析思考为什么不在pos位置之前插入?

单链表不适合在pos位置之前插入,因为需要遍历链表找到pos位置的前一个节点,

单链表更适合在pos位置之后插入,如果在后面插入,只需要知道pos位置就行,会简单很多

C++官方库里面单链表给的也是在之后插入

  • 代码如下
//单链表在指定pos位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);  //给的pos位置不能为空SLTNode* newnode = BuySListNode(x);  //动态申请一个节点newnode->next = pos->next; //新节点的next指针指向pos位置后一个节点pos->next = newnode;       //pos位置的next指向新节点
}
  • 作用:

先调用 SLTFind 函数在单链表中查找指定值的节点,查找到了,返回该节点的地址,也即是我们指定的 pos 位置,然后将其传给 SLTInsertAfter 函数。


2.11、 单链表删除指定pos位置的节点

  • 图解删除操作

image-20210827235619437

  • 代码如下

要考虑到两种情况:pos位置为单链表的第一个节点,pos位置为中间节点;

因为可能要改变外部 plist 的指向,所以用二级指针接收指针 plist 的地址。

//单链表删除指定pos位置的节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(*pphead); //链表不能为空assert(pos);     //给的pos位置不能为空//pos位置为第一个节点,相当于头删if (pos == *pphead){SLTPopFront(pphead);}//pos位置为中间节点else{SLTNode* prev = *pphead;while (prev->next != pos)  //找到pos位置的前一个节点{prev = prev->next;}prev->next = pos->next;  //pos位置的前一个节点指向pos位置的后一个节点free(pos);  //释放pos节点pos = NULL; //置空}
}
  • 补充一下,assert 断言

assert 放在函数里面检查参数,一方面是为了安全,另一方面也是为了防止有人调用该函数时,不正确的使用,错误传入参数,好及时提醒到他,写代码时一定要考虑到有人不正确的使用该函数时的场景,来进行避免

  • 测试其功能

先调用 SLTFind 函数在单链表中查找指定值的节点,查找到了,返回该节点的地址,也即是我们指定的 pos 位置,然后将其传给 SLTErase 函数。


2.12、 单链表删除指定pos位置之后的节点

  • 图解删除操作

在这里插入图片描述

  • 代码如下
//单链表删除指定pos位置之后的节点
void SLTEraseAfter(SLTNode* pos)
{assert(pos);       //给的pos位置不能为空assert(pos->next); //给的pos位置不能是尾节点SLTNode*del = pos->next;  //保存pos位置的后一个节点pos->next = pos->next->next;free(del);  //释放pos位置的后一个节点del=NULL;
}

-运行结果如下:

在这里插入图片描述


2.13、 求单链表长度

//求单链表长度
int SLTSize(SLTNode* phead)
{int size = 0;SLTNode* cur = phead;while (cur != NULL)  //遍历链表{size++;cur = cur->next;}return size;
}

2.14、 判断单链表是否为空

plist为空,返回 1 (true),非空,返回 0 (false)

//单链表判空
bool SLTEmpty(SLTNode* phead)
{//plist为空,返回1(true),非空,返回0(false)return phead == NULL;/*写法2:return phead == NULL ? true : false;*/
}

三、 总结

单链表是一种简单且高效的数据结构,适用于需要频繁插入和删除操作的场景。通过掌握单链表的创建、插入、删除、查找、修改和遍历等操作,我们可以更好地理解和应用这一数据结构,从而提高程序的效率和可靠性。

相关文章:

深入理解链表(SList)操作

目录: 一、 链表介绍1.1、 为什么引入链表1.2、 链表的概念及结构1.3、 链表的分类 二、 无头单向非[循环链表](https://so.csdn.net/so/search?q循环链表&spm1001.2101.3001.7020)的实现2.1、 [单链表](https://so.csdn.net/so/search?q单链表&spm1001.2…...

03. prometheus 监控 Linux 主机

文章目录 一、prometheus 监控 Linux 主机二、防火墙打开端口1. 方式一:使用 iptables 添加白名单(推荐使用):2. 方式二:重载防火墙 一、prometheus 监控 Linux 主机 1. 官网下载 node_exporter 官网:htt…...

AI占据2024诺贝尔两大奖项,是否预示着未来AI即一切?

本次诺贝尔物理学和学奖的获得者都与AI息息相关,可谓是“AI领域的大丰收”。 2024年诺贝尔物理学奖揭晓:瑞典皇家科学院公布了2024年诺贝尔物理学奖的获得者。他们是美国的约翰霍普菲尔德(John J. Hopfield),以及加拿…...

[已解决] Install PyTorch 报错 —— OpenOccupancy 配环境

目录 关于 常见的初始化报错 环境推荐 torch, torchvision & torchaudio cudatoolkit 本地pip安装方法 关于 OpenOccupancy: 语义占用感知对于自动驾驶至关重要,因为自动驾驶汽车需要对3D城市结构进行细粒度感知。然而,现有的相关基准在城市场…...

6. PH47 代码框架硬件开发环境搭建

概述 PH47代码框架的硬件开发环境搭建同样简单, 建立基本的 PH47 框架学习或二次开发的硬件开发环境所需设备如下: BBP 飞控板及相关软硬件: BBP飞控板,或者至少一块Stm32F411核心板(WeAct Studio)Stm32程序烧录工具…...

package.json配置

package.json配置 描述配置文件配置脚本配置依赖配置发布配置系统配置第三方配置 描述配置 name : 项目名称,第三方包可以通过npm install 包名安装 "name":"react"version : 项目版本,项目版本号 "version" : "18.2…...

视频怎么转gif动图?5个简单转换方法快来学(详细教程)

相信大家在社交平台上会经常看到一些有趣的gif动图表情包,有些小伙伴就会问:这些GIF动图是如何制作的呢?一般GIF动图表情包可以用视频来制作,今天小编就来给大家分享几个视频转成GIF动图的方法,相信通过以下的几个方法…...

10月更新:优维EasyOps®需求解决更彻底,功能体验再升级

升 级 不 止 步 欢迎来到 需求至上,功能完善 的 \ EasyOps 7.5版本 / 👇 >> 联动架构视图:深度融合监控与资源拓扑 传统上,依赖监控态势感知系统固有的分层拓扑结构虽有其优势,但在处理复杂系统尤其是核心数…...

黑马javaWeb笔记重点备份1:三层架构、IOC、DI

来自:【黑马程序员JavaWeb开发教程,实现javaweb企业开发全流程(涵盖SpringMyBatisSpringMVCSpringBoot等)】 https://www.bilibili.com/video/BV1m84y1w7Tb/?p75&share_sourcecopy_web&vd_source9332b8fc5ea8d349a54c398…...

大坝渗流监测设备——渗压计

渗压计是一种用于监测大坝等水工建筑物渗流压力的重要设备,其准确性和可靠性对于保障大坝安全运行至关重要。南京峟思将为大家详细介绍渗压计的工作原理、安装方法及其在大坝渗流监测中的应用。 渗压计主要利用振弦频率的变化来测量渗透水压力。设备由透水部件、感应…...

Pikachu-Sql Inject-宽字节注入

基本概念 宽字节是相对于ascII这样单字节而言的;像 GB2312、GBK、GB18030、BIG5、Shift_JIS 等这些都是常说的宽字节,实际上只有两字节 GBK 是一种多字符的编码,通常来说,一个 gbk 编码汉字,占用2个字节。一个…...

如何制作低代码开发的视频教程?

如何制作低代码开发的视频教程? 随着数字化转型的加速,越来越多的企业和组织开始采用低代码开发平台来加速应用程序的构建。对于许多开发者和业务人员来说,学习如何使用这些平台可以显著提高工作效率。因此,创建一份清晰、实用且…...

Flink学习地址

--基础概念 概览 | Apache Flink --应用系列 如何学习Flink:糙快猛的大数据之路(图文并茂)_flink 学习-CSDN博客 --Python系列 pyflink实时接收kafka数据至hive_pyflink下kafka数据经过处理后插入hive-CSDN博客 Pyflink教程(一)&#…...

05_23 种设计模式之《建造者模式》

文章目录 一、建造者模式基础知识建造者模式的结构建造者模式的应用场景 一、建造者模式基础知识 建造者模式(Builder Pattern)是一种创建型设计模式,它提供了一种优雅的方式来创建复杂对象,同时隐藏其构建过程。这种模式允许你通…...

详细分析Spring Security OAuth2中的JwtAccessTokenConverter基本知识(附Demo)

目录 前言1. 基本知识2. Demo3. 实战 前言 java框架 零基础从入门到精通的学习路线 附开源项目面经等(超全)【Java项目】实战CRUD的功能整理(持续更新) 1. 基本知识 JwtAccessTokenConverter 是 Spring Security OAuth2 中的一…...

python习题2

1、输出一个年份,判断其是不是闰年 #输入一个年份,判断其是否是闰年 y eval(input()) if y%4 0 and y%100 ! 0:print("是") elif y%4000:print("是") else:print("不是") 2、模拟智能客服: 按1查询账户余额…...

CVSS 4.0 学习笔记

通用漏洞评分系统(CVSS)捕获了主要技术软件、硬件和固件漏洞的特征。其输出包括数字分数,表明与其他漏洞。 以下因素可能包括但不限于:监管要求、客户数量受影响、因违约造成的金钱损失、生命或财产受到威胁,或潜在漏洞的声誉影响。这些因素在CVSS评估范围之外。 CVSS的好…...

解决 GPTQ 模型导入后推理生成 Tokens 速度很慢的问题(从源码重新安装 Auto-GPTQ)

这里解决的是使用 Auto-GPTQ 或者 Transformers 导入 GPTQ 模型后推理速度很慢的问题。 值得注意的是,这个问题很有可能是因为安装不正确,所以 GPTQ 无法正确使用 GPU 进行推理,也就是说无法进行加速,即便 print(model.device) 显…...

NDC美国药品编码目录数据库查询方法

NDC(National Drug Code)翻译为“国家药品代码”,是美国食品药品监督管理局(FDA)制定的一种药品标识系统,用于唯一标识药品。这个编码系统主要目的是为精准识别和追踪不同药品而建设,行业人员和…...

vue3的v-model使用

vue3的v-model使用 单个绑定值 子组件 props: [‘modelValue’], emits: [‘update:modelValue’], 注&#xff1a;modelValue是默认的&#xff0c;如果只有一个需要绑定v-model&#xff0c;可使用modelValue 此时父组件写法 <CustomInput v-model"searchText"…...

开源物联网网关openclaw-gateway:架构解析与本地化智能家居部署实践

1. 项目概述与核心价值最近在折腾一些物联网和智能家居项目&#xff0c;发现一个挺有意思的东西&#xff0c;叫openclaw-gateway。这名字听起来有点“机械感”&#xff0c;claw是爪子&#xff0c;gateway是网关&#xff0c;合起来像是一个“开放爪子的网关”。乍一看可能有点摸…...

ssm图书在线商城(10044)

有需要的同学&#xff0c;源代码和配套文档领取&#xff0c;加文章最下方的名片哦 一、项目演示 项目演示视频 二、资料介绍 完整源代码&#xff08;前后端源代码SQL脚本&#xff09;配套文档&#xff08;LWPPT开题报告/任务书&#xff09;远程调试控屏包运行一键启动项目&…...

构建交互式工程实验场:从算法可视化到技术原型设计

1. 项目概述&#xff1a;一个交互式工程实验场的诞生 如果你和我一样&#xff0c;是个喜欢在代码里“瞎折腾”的工程师&#xff0c;那你肯定也经历过这样的场景&#xff1a;脑子里突然蹦出一个关于算法、数据结构或者某个系统设计的奇思妙想&#xff0c;想快速验证一下。这时候…...

从“玩原神不”到AC:手把手教你用概率DP解决湘潭邀请赛F题(期望计算避坑指南)

从队友闲聊到AC代码&#xff1a;概率DP在算法竞赛中的实战拆解 "玩原神不~"——这句看似随意的队友闲聊&#xff0c;竟成了解决湘潭邀请赛F题的关键灵感。在算法竞赛中&#xff0c;概率与期望DP问题往往让选手望而生畏&#xff0c;但通过这道题的完整解析&#xff0…...

API Key认证系统设计:企业级API开放平台实践

API Key认证系统设计&#xff1a;企业级API开放平台实践 摘要&#xff1a;当AI应用从内部工具转向对外开放时&#xff0c;如何确保接口安全、防止滥用并实现精细化权限控制&#xff1f;本文基于一个真实的跑步教练AI项目&#xff0c;详细解析如何构建一套生产级的API Key认证系…...

小白程序员必看!收藏这份AI学习指南,从0到1逆袭高薪职业(内含经验分享)

作者原UI设计师&#xff0c;因职业瓶颈被辞退后转行AI领域。文章分享了学习AI的动机、遇到的困难、心得体会以及成功转行后的薪资提升经历。强调主动拥抱变化的重要性&#xff0c;建议多练习、多总结&#xff0c;并感谢老师们的耐心指导。最后&#xff0c;作者表示将继续深耕AI…...

自托管信息聚合器FeedMe:全栈部署与高效信息管理实践

1. 项目概述&#xff1a;一个“喂饱”你的信息聚合器最近在折腾一个挺有意思的小项目&#xff0c;叫 FeedMe。这名字起得挺直白&#xff0c;翻译过来就是“喂我”。它的核心目标&#xff0c;就是帮你把散落在互联网各个角落的信息源——比如你关注的博客、技术论坛、新闻网站、…...

Swagger2Word终极指南:3种方法实现API文档自动化转换

Swagger2Word终极指南&#xff1a;3种方法实现API文档自动化转换 【免费下载链接】swagger2word 项目地址: https://gitcode.com/gh_mirrors/swa/swagger2word 还在为手动编写API文档而烦恼吗&#xff1f;Swagger2Word为你提供了一站式自动化解决方案&#xff0c;将Swa…...

深度解析:Performance-Fish如何通过四级缓存架构实现《环世界》400%性能优化

深度解析&#xff1a;Performance-Fish如何通过四级缓存架构实现《环世界》400%性能优化 【免费下载链接】Performance-Fish Performance Mod for RimWorld 项目地址: https://gitcode.com/gh_mirrors/pe/Performance-Fish Performance-Fish是《环世界》&#xff08;Rim…...

别再死记硬背了!用MATLAB手把手教你画根轨迹图(附代码与避坑指南)

MATLAB实战&#xff1a;从零绘制根轨迹图的完整指南与避坑技巧 在控制系统的设计与分析中&#xff0c;根轨迹图是理解系统动态特性的重要工具。传统教学中&#xff0c;学生往往被要求死记硬背绘制规则&#xff0c;却难以理解其实际应用价值。本文将彻底改变这一现状——通过MAT…...