【数据结构】【版本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;}
}

真诚点赞,手有余香

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/348368.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot + EasyExcel + SqlServer 进行批量处理数据

前言 在日常开发和工作中&#xff0c;我们可能要根据用户上传的文件做一系列的处理&#xff0c;本篇文章就以Excel表格文件为例&#xff0c;模拟用户上传Excel文件&#xff0c;讲述后端如何高效的进行数据的处理。 一.引入 EasyExcel 依赖 <!-- https://mvnrepository.com/…

centos7.6使用飞鱼FlyFish的docker镜像

参考教程&#xff1a; 飞鱼的docker镜像使用教程&#xff1a; doc/FlyFish_docker镜像使用指南.md 云智慧/FlyFish - Gitee.com centos的docker安装教程&#xff1a; CentOS下 Docker、Docker Compose 的安装教程_centos安装docker-compose_centos 安装docker-compose-CSDN…

深度学习(四)——torchvision中数据集的使用

1. 参数详解 torchvision中每个数据集的参数都是大同小异的&#xff0c;这里只介绍CIFAR10数据集 该数据集的数据格式为PIL格式 class torchvision.datasets.CIFAR10(root:str,train:boolTrue,transform:Optional[Callable]None,target_transform:Optional[Callable]None,do…

服务器数据恢复—EMC Isilon存储中被误删的虚拟机数据恢复案例

服务器存储数据恢复环境&#xff1a; EMC Isilon S200集群存储&#xff0c;共三个节点&#xff0c;每节点配置12块SATA硬盘。 服务器存储故障&#xff1a; 工作人员误操作删除虚拟机&#xff0c;虚拟机中数据包括数据库、MP4、AS、TS类型的视频文件等。需要恢复数据的虚拟机通…

大众点评全国美食POI采集780万家-2024年5月底

大众点评全国美食POI采集780万家-2024年5月底 店铺POI点位示例&#xff1a; 店铺id H8kTSRz3kLUQ2WtU 店铺名称 幸福西饼生日蛋糕(布心店) 十分制服务评分 8.2 十分制环境评分 8.4 十分制划算评分 8.3 人均价格 75 评价数量 119033 店铺地址 金稻田路1068号边防布心住…

如何察觉自己或者家人是否出现了听力问题?

如何察觉自己或者家人是否出现了听力问题呢&#xff1f;可以从以下两个方面观察&#xff1a; 一&#xff0e;社交方面 • 是不是经常需要别人重复刚说的话才能理解&#xff1f; • 多人对话中是否感到吃力&#xff1f; • 觉得别人讲话含糊不清&#xff1f; • 在人多嘈杂…

【APP移动端自动化测试】第二节.Appium介绍和常用命令代码实现

文章目录 前言一、Appium介绍和安装二、python代码功能实现 2.1 hello appium 参数详解 2.2 在脚本内启动其他app 2.3 获取app的包名和界面名 2.4 关闭app和驱动对象 2.5 安装和卸载以及是否安装app 2.6 将应用置于后台总结 前言 一、Appium介绍…

【perl】基本语法 /备忘录/

分享 perl 语言学习资源 Perl 教程|极客教程 (geek-docs.com) Perl [zh] (runebook.dev) Perl 运算符 | 菜鸟教程 (runoob.com) Perl Documentation - Perldoc Browser Search the CPAN - metacpan.org 当然还有一些经典书籍&#xff0c;不再列举。 1、数字 1.1、数字表…

火车头采集怎么使用GPT等AI原创文章

火车头采集官方并没有GPT、百度文心一言AI、阿里通义千问AI、Kimi大模型等AI功能&#xff0c;但支持接入插件&#xff0c;可以编写相应人工智能AI原创文章插件&#xff08;火车头采集支持PHP和c#这2种语言的插件编写&#xff09;&#xff0c;或者导入第三方封装好的GPT等AI原创…

大文件word生成的处理与解决策略

前言 对于简单word文档的生成导出&#xff0c;java已经有着很多技术来进行处理&#xff0c;在有着相对固定的格式样板下&#xff0c;采用word模板导出相对会是比较好的选择。但是当数据量且包含大量图片后&#xff0c;采用模板导出就显得无力了&#xff0c;模板的缺点是无法应…

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

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

Mysql查询分析工具Explain的使用

一、前言 作为一名合格的开发人员&#xff0c;与数据库打交道是必不可少的&#xff0c;尤其是在业务规模和数据体量大规模增长的条件下&#xff0c;应用系统大部分请求读写比例在10:1左右&#xff0c;而且插入操作和一般的更新操作很少出现性能问题&#xff0c;遇到最多的&…

遇到Windows无法启动时不要担心,这里有解决办法

序言 如果有一天你打开电脑,Windows拒绝启动,你该怎么办?其实“Windows无法启动”是一种常见症状,原因多种多样,因此你需要进行一些故障排除。 现代版本的Windows更善于从这种情况中自动恢复,而Windows XP遇到此问题时可能会停止在运行的地方,现代版本的Windows将尝试…

论文解读——《I2EDL: Interactive Instruction Error Detection and Localization》

一、研究背景 视觉与语言导航&#xff08;VLN&#xff09;是一个AI领域的研究任务&#xff0c;旨在开发能够按照自然语言指令在三维空间中导航到指定位置的智能体。这项任务与人类的日常活动——如按照口头指示到达某个地点——十分相似&#xff0c;对于推动人机交互的自然性和…

为什么总选不到合适的安全数据交换系统?解决问题重点在这

安全数据交换系统对于企业而言&#xff0c;重要性不言而喻。企业业务开展离不开数据交换&#xff0c;只有数据流动起来&#xff0c;才能真正发挥价值&#xff0c;但数据流动的过程&#xff0c;涉及多个系统、多种环境、多个人员角色&#xff0c;因此&#xff0c;有较大的风险。…

Gi标签管理

文章目录 前言理解标签创建标签操作标签总结 前言 理解标签 标签&#xff0c;可以理解为对某次commit的一次标识&#xff0c;相当于起起了一个别名。 例如&#xff0c;在项目发布某个版本时候&#xff0c;针对最后一次commit起一个v1.0这样的标签来标识里程碑的意义。 这有什…

【Linux】线程(一)

谈论之前需要先谈论一些线程的背景知识 其中就有进程地址空间&#xff0c;又是这个让我们又爱又恨的东西 目录 背景知识&#xff1a;地址空间&#xff1a; 背景知识&#xff1a; 地址空间&#xff1a; 说在前边&#xff0c;OS通常分为4个核心模块&#xff1a;执行流管理&…

Flutter项目开发模版,开箱即用

前言 当前案例 Flutter SDK版本&#xff1a;3.22.2 每当我们开始一个新项目&#xff0c;都会 引入常用库、封装工具类&#xff0c;配置环境等等&#xff0c;我参考了一些文档&#xff0c;将这些内容整合、简单修改、二次封装&#xff0c;得到了一个开箱即用的Flutter开发模版…

喜讯!云起无垠入选《2024中国AI大模型产业图谱1.0版》

近日&#xff0c;数据猿与上海大数据联盟联合策划并启动了“2024全年度三大策划活动”&#xff0c;经过数月的精心筹备和严格筛选&#xff0c;通过直接申报交流、深入访谈调研、外部咨询评价以及匿名访谈等多维度交叉验证的方式&#xff0c;最终完成了《2024中国AI大模型产业图…