【数据结构】顺序表+链表

目录

1.顺序表

1.1初始化顺序表

1.2销毁顺序表

1.3检查容量并扩容

1.4把某个元素插入到下标为pos的位置

1.5头插和尾插

1.6删除下标为pos的元素

1.7头删和尾删

2.顺序表的问题及思考

3.链表

3.1链表的访问

3.2链表的增删查改


1.顺序表

顺序表的本质其实就是一个数组,实际上如果要对数组中某块空间进行删除是非常不方便的,因为如果要删就只能全删掉,或者用后面的覆盖前面的,从效果上来看是删掉了某个元素,那既然顺序表这么不方便,为什么还要使用顺序表?是因为顺序表有一个压倒性的优势就是能够通过下标来访问到我们需要的元素。

顺序表包括静态顺序表和动态顺序表,静态顺序表就是一个固定长度的数组,动态顺序表就是用malloc开辟的一块数组空间存储。实际上我们以前用C语言实现的动态版本的通讯录就充分使用了顺序表,以实现他的增删查改等功能。

下面来演示操作一个顺序表的常用操作

这是一个头文件sl.h,用来声明顺序表的类型与各种函数

接下来完成这些函数功能

1.1初始化顺序表

使用的是动态内存开辟的方式,刚上来a数组能放四个元素

1.2销毁顺序表

1.3检查容量并扩容

a的内存是通过malloc申请的,后面还可能会通过realloc来调整这块空间的大小。因此使用完成之后应该free。

在插入的时候,应该检查顺序表是否已满,如果满了,就扩容

一次性扩容成原来容量的两倍

1.4把某个元素插入到下标为pos的位置

插入之前应该先检查顺序表是否已经满了,为了防止插入的下标合法应该assert一下,当然也可以使用if语句判断,这里我用的是assert

1.5头插和尾插

把某个元素插入到顺序表最开头

可以单独写,就像上图中我注释掉的那段代码一样,先让所有元素往后挪动一位,然后把要插入的元素赋给下标为零的元素覆盖掉原来的值。

当然也可以直接调用刚才我们写的在中间插入元素的函数,头插实际上就是在下标为零的位置插入。

同理尾插就是在下标size的位置插入。

1.6删除下标为pos的元素

先找到这个元素,然后他后面的所有元素往前挪动一位,覆盖掉他,最后size--

1.7头删和尾删

头删和尾删的实现就可以直接调用该函数

头删就是删除下标为0的元素,尾删就是删除下标为size-1的元素

2.顺序表的问题及思考

问题:

1. 中间/头部的插入删除,时间复杂度为O(N)

2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。

3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

思考:如何解决以上问题呢?下面给出了链表的结构来看看。

3.链表

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。

前面管理顺序表的时候,我们定义的结构体需要三个参数,第一个就是一个数组,用来存放数据,第二个是sz,用来记录这个数组中已经有了几个元素了,第三个是capacity,用来记录当前数组的最大容量。定义链表的结构体类型又需要哪些成员变量呢?首先当前内存中应该存着某个数值,而非数组,又因为链表在物理存储上并非连续,要从当前内存找到下一块内存,就需要一个指针,这个指针一般命名为next,而且链表我们,因此链表的结构体类型一般是这样

存着一个数值和一个同种结构体类型的指针,(结构体可以嵌套结构体指针,但是不能嵌套结构体类型,因为这样就会无限套娃而无法计算结构体类型的大小了)

3.1链表的访问

可以这样访问链表

这样的每一个单独的空间就是一个节点,上面的一个节点就包括了一个数值和一个指针,用来指向下一个节点。

在用函数操作链表之前,一定要明确是要修改结构体,还是要修改结构体指针,如果是修改结构体,在函数内应该使用结构体指针,如果是要修改结构体指针(一般是某个节点的地址),就要使用结构体指针的地址,也就是一个二级指针。由于我们经常需要改变某个节点的地址,因此我们会经常传一个结构体指针的地址。比如想要在链表头部插入一个节点,而我们用一个名为phead的结构体指针表示链表头部的地址,指向第一个节点,在我们头插一个节点之后我们显然要改变这个phead,也就是要改变结构体指针,那我们在传参的时候就应该传一个结构体指针的地址。

3.2链表的增删查改

如果想要在链表最前面插入一个数据,应该如何做?

只需要让新malloc的那块空间的节点指向原来最前面的那块空间即可。

SLTNode是我们刚才重命名的那个结构体类型,我们要在链表的头部插入一个新的元素,这个新的元素也是一个结构体类型,包含一个数值和一个指针,这个指针指向原来的phead。于是我们就是malloc了一块空间,大小能放一个SLTNode类型的结构体变量(绝对不能直接写成SLTNode*newnode,因为这是一个局部变量,调用完函数之后就销毁了要让新开辟的这块空间一直存在,应该使用malloc),newnode里面就是新开辟的空间的地址,但是这样写虽然开辟的空间一直存在,但是仍然有问题,比如我现在要测试一下

我们的预期应该是打印出来1 2 3 4,但是实际上什么也打印不出来,因为这是一个传值调用,你可能有疑问,这不是传的地址吗?怎么还是传值调用?这个plist是一个结构体指针,指向了某一个结构体,他是我们创建的一个局部变量,在这个TestSlist1函数中由于刚上来这个plist的值是NULL,我们可以认为他是链表的最后一个节点,现在我们要在这个节点的前面插入四个节点,内容分别是1,2,3,4以及下一个节点的地址,虽然plist是一个指针变量,里面存的是一个地址,但是仍然是一个变量,现在直接把plist传给了SLPushFront,当然是传值调用,SLPushFront函数会创建一个形参叫做phead,并把plist里面相同的值拷贝进去,然后SLPushFront函数里面又创建了一个newnode变量,把他和phead一顿操作,那也只是在SLPushFront的栈上进行的操作,根本不会影响plist的值,因此最终plist还是NULL,当然什么也打印不出来。真要传址调用,应该传&plist,我再说的通俗一点,如果我们想要通过函数改变int类型的,我们应该传int*类型的,那么我们现在想要用函数来改变SLTNode*类型的变量plist,我们应该传给函数的参数类型是SLTNode**类型。

回到我们的SLPushFront函数,要想通过他添加节点,并把原来phead改变,形参应该是phead的地址,也就是SLTNode**类型

对SLPushFront进行修改

然后测试的时候传plist的地址

即可实现预期

如果对传址调用的理解还没有那么深刻,那我再举一个尾插函数的例子

首选因为不管是头插还是尾插,都需要动态申请一块内存空间,为了避免代码的冗余,我们利用一个BuyLTNode函数来实现该功能

尾插的函数假如我这么写

首先我们创建了一个局部变量tail并初始化为phead也就是第一个节点的地址,那现在tail指向了第一个节点,要判断tail是否为尾部节点,就是看tail->next是否为NULL,于是利用while循环来找到这个尾部的节点,出循环之后,tail就指向了最后一个节点,我们使用BuyLTNode函数申请一个节点并把这个节点的地址赋给tail,由于使用BuyLTNode申请的节点中next已经被初始化成了NULL,那么当我们把这个地址赋给tail之后,tail就指向了一个节点,这个节点的next是NULL,data被初始化成了x,也就是我们希望插入的数值。此时tail指向了链表最后一个节点,完成了尾插操作。

但事实上这个代码并不能完成尾插的工作,因为我们这里不管是tail也好,还是newnode也好,都是局部变量,出了这个函数之后,这些局部变量就被销毁了,而我们使用BuyLTNode申请的那块空间却还在,但是这块空间却无法找到了,造成了内存泄漏。而且还有一个问题就是,当tail指向最后一个节点的时候,把这个节点的next也就是NULL赋给tail,这时候tail什么也不指向,我们也无法将tail->next修改掉,因此这种写法也无法把链表的节点连接起来。

如果把while循环的判断条件改成tail->next呢?

那就要看tail->next什么时候是NULL,我们发现此时tail指向的是最后一个节点,而非NULL,这样如果我们把tail->next改成尾插的节点的地址,就可以实现链表节点的连接。这样是不是就可以了了?

这样其实已经能够完成大部分情况下的尾插了,我们动态申请了一个节点,然后把这个节点的地址放在了newnode里面,又把newnode赋给了tail->next,也就是说本来tail指向的是NULL,现在tail指向的就是动态申请的这块空间,调用结束之后虽然newnode,tail等变量被销毁,但是链表的所有节点都在堆区上,因此所有节点的内容(data和next)都不会被销毁,也就是说内容已经改完了,也已经尾插了一个节点,虽然此时的用来指向链表头部的形参plist已经被销毁,但是好在这个plist传值调用只是链表首地址的一份临时拷贝,他与我们传的实参里面存放的内容是一样的,都是链表头部的地址,既然已经在堆区上完成了尾插的工作,我们当然可以通过传的实参来实现链表的打印。

但是如果刚上来链表是空的,也就是说plist是NULL,如果还是按照上面代码的逻辑,把空指针赋给了tail,然后是while的判断条件,这将会对NULL进行解引用,显然是不对的,那么我们能不能使用if语句把链表为空和非空这两种情况分开来看?也就是说现在改成了这样子

通过测试发现这种方式也不行,这是因为plist刚上来是NULL,我们要尾插一个节点,并让plist指向这个节点,也就是说我们想改变plist,但是我们在调用SListPushBack函数的时候却使用了传值调用,直接把plist的值传了过去,这样是无法改变plist的,要想在函数内改变plist,我们只能在传参的时候使用传址调用,把plist的地址传过去,使用一个SListNode**类型的二级指针来接收这个地址。正确写法如下

我们在传参的时候需要传一个节点的地址也就是&plist,并存放在pplist里面,这个指针就指向了plist,对他进行解引用就找到了plist,如果plist是NULL,那么执行的将会是把newnode赋给*pplist,这个*pplist就不是实参的一份临时拷贝了,而是确确实实是实参所在的单元,通过这个指针就可以改变这个单元的内容,也就当然可以修改plist的内容了。

注:如果要改结构体,需要用结构体指针,如果要改结构体指针,需要用结构体指针的地址,在尾插的函数中,只有链表为空的时候需要修改plist也就是结构体指针,当链表不为空的时候,修改的其实是节点里面的next也就是结构体。

要改变什么,就要用他的地址,并在函数里面对这个地址进行解引用

删除尾部节点

先考虑链表包含一个以上节点的情况,我们要做的是释放掉最后一个节点,并把倒数第二个节点的next置空,这个过程中我们改变的是结构体,应该使用结构体指针。

想要删除尾部节点首先应该找到尾部的节点,当tail指向尾部节点的时候free(tail),就可以把尾部节点这个空间释放掉,在使用free函数之后,通常要把tail置为空指针,实际上tail作为一个局部变量,置空与否都可以,因为出了这个函数tail变量就被销毁了,并不会产生访问他的情况,实际上我们应该把原来倒数第二个节点的next置为NULL,要改变节点,也就是结构体的一部分,我们应该使用结构体指针,于是我们使用了一个指针prev来找到倒数第二个节点并将这个节点的next置空。如图

尾删还有一种写法就是我们直接去找倒数第二个节点,如图

当链表只有一个节点,我们要做的是释放掉这个节点,并让plist置为NULL,因此我们是要改变结构体指针,应该使用结构体指针的地址,这也是我们把函数的形参设计成二级指针的原因。因此完整的尾删功能代码如下

同理头删也需要分三种情况讨论

实际上上面的代码完全可以把链表只有一个节点和链表有多个节点的情况合并起来,如图

只有一个节点也就是*pplist->next是NULL,先把*pplist也就是plist也就是指向唯一节点的这个指针拷贝给tmp,然后把tmp->next也就是NULL赋给*pplist也就是plist,这时候原本指向唯一节点的结构体指针就指向了NULL,与我们期望的逻辑相同。

单链表查找

找查的时候并不需要修改链表中的节点或者指针,因此也并不需要传二级指针,唯一需要注意的点就是while循环的判断条件,如果写成cur->next,当它为NULL的时候实际上cur指向了最后一个节点,而我们这时候没有进入while循环,因此就没有判断最后一个节点的data是不是我们要找的内容。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

整合力-整合思维模型和领导力

整合力和领导力是组织成功的两大关键因素。在当今复杂多变的商业环境中,整合力和领导力的结合对于推动组织发展至关重要。本文将探讨整合力和领导力的概念、重要性以及如何有效整合二者以促进组织的成功发展。 ### 整合力的重要性 整合力指的是组织内部各个部门、…

【3GPP】【核心网】【5G】5G核心网协议解析(三)(超详细)

5G协议 NAS协议消息:UE 与 AMF 之间 UE通过5G NAS协议与AMF建立起安全的信令连接,进行用户数据传输和网络服务请求等操作 消息格式:NAS-PDU ------------------------- | Header | NAS Message(s) | ------------------------- Header…

毕业之前准备材料

文章目录 说明第一波截止时间待办事情(共计2)1、读书报告审核(吴海梅老师审核)2、课程学分审核(吴海梅老师审核,曹主216办公室) 第二波外审截止时间待办事情(王移花老师审核&#xf…

Linux下du命令和df命令的使用

du命令作用是估计文件系统的磁盘已使用量,常用于查看文件或目录所占磁盘容量。df命令是统计磁盘使用情况,可以用来查看磁盘已被使用多少空间和还剩余多少空间。du命令语法du [选项] [文件或目录名称]参数:-a:--all, 列…

第七篇:人工智能与机器学习技术VS量测(Measurement)- 我为什么要翻译介绍美国人工智能科技巨头IAB公司 - 它是如何赋能数字化营销生态的?

IAB平台,使命和功能 IAB成立于1996年,总部位于纽约市。 作为美国的人工智能科技巨头社会媒体和营销专业平台公司,互动广告局(IAB- the Interactive Advertising Bureau)自1996年成立以来,先后为700多家媒…

(每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(四)

项目建议与立项申请、初步可行性研究、详细可行性研究、评估与决策是项目投资前使其的四个阶段。在实际工作中,初步可行性研究和详细可行性研究可以依据项目的规模和繁简程度合二为一,但详细可行性研究是不可缺少的。升级改造项目制作初步和详细研究&…

电脑提示“由于仅部分匹配或匹配不明确,因此无法迁移设备”怎么办?

“由于仅部分匹配或匹配不明确,因此无法迁移设备”错误可能会在将较旧的系统更新到较新的系统版本或者安装了双系统之后出现,此外,驱动程序不兼容、系统文件损坏、计算机接口故障、系统不支持出现错误的外接设备等也可能导致该错误出现。了解…

【Selenium】UI自动化|元素定位常见问题

1、报错NoSuchElementException——定位不到元素 分析的可能原因: 页面还没有加载出来,就对页面上的元素进行的操作 元素在iframe中,先要理解下frame的实质,frame中实际上是嵌入了另一个页面,而webdriver每次只能在一…

Cocos Creator 3.8.x 制作模糊效果(比如游戏弹窗需要的模糊效果)

接着上一个讨论的话题,关于3.8.x的后效,今天来分享自定义后效来制作模糊效果,并将他应用到弹窗中做背景,话不多说开整。 一:最终效果 首先咱们来看官网自定义后效怎么搞的,从它的实例开始:自定义后效 二:定义PostProcessSettings给节点提供资源(通过编辑器修改参数的…

两天学会微服务网关Gateway-Gateway路由规则

锋哥原创的微服务网关Gateway视频教程: Gateway微服务网关视频教程(无废话版)_哔哩哔哩_bilibiliGateway微服务网关视频教程(无废话版)共计17条视频,包括:1_Gateway简介、2_Gateway工作原理、3…

vue3 (四)动态组件Vs异步组件

1.动态组件 点击toggle切换2个组件&#xff0c;配合<keep-alive>使用防止切换后数据丢失 <keep-alive><component :is"currentItem"></component> </keep-alive> 2.异步组件 定义方法&#xff1a;app.component(组件名,Vue.defineAs…

vs创建asp.net core webapi发布到ISS服务器

打开服务器创建test123文件夹&#xff0c;并设置共享。 ISS配置信息&#xff1a; 邮件网站&#xff0c;添加网站 webapi asp.net core发布到ISS服务器网页无法打开解决方法 点击ISS Express测试&#xff0c;可以成功打开网页。 点击生成&#xff0c;发布到服务器 找到服务器IP…

第十篇:如何利用人工智能技术做好营销流量整形管理?(Traffic Shaping)- 我为什么要翻译介绍美国人工智能科技巨头IAB公司

IAB平台&#xff0c;使命和功能 IAB成立于1996年&#xff0c;总部位于纽约市​​​​​​​。 作为美国的人工智能科技巨头社会媒体和营销专业平台公司&#xff0c;互动广告局&#xff08;IAB- the Interactive Advertising Bureau&#xff09;自1996年成立以来&#xff0c;先…

每日五道java面试题之mysql数据库篇(六)

目录&#xff1a; 第一题. MySQL中InnoDB引擎的行锁是怎么实现的&#xff1f;第二题. InnoDB存储引擎的锁的算法有三种第三题. 什么是死锁&#xff1f;怎么解决&#xff1f;第四题. 数据库的乐观锁和悲观锁是什么&#xff1f;怎么实现的&#xff1f;第五题. 为什么要使用视图&a…

【MySQL】视图 -- 详解

视图 是一个虚拟表&#xff0c;其内容由查询定义。同真实的表一样&#xff0c;视图包含一系列带有名称的列和行数据。视图的数据变化会影响到基表&#xff0c;基表的数据变化也会影响到视图。 一、基本使用 1、创建视图 create view 视图名 as select 语句; 好处&#xff1a;…

基于NB-IoT的西红柿基地温湿度监测系统

总体硬件架构 在西红柿种植园内&#xff0c;我们为每株作物分配RFID标签&#xff0c;以便在每次照顾作物后记录其生长状况、施肥和灌溉等信息。这些数据将上传至云端&#xff0c;便于用户在线实时监控作物生长情况。 为了确保温湿度的精确控制&#xff0c;我们在作物棚内每隔3米…

#QT(智能家居界面上-图片插入)

1.IDE&#xff1a;QTCreator 2.实验 3.记录 (1)添加图片文件&#xff08;图片资源文件&#xff0c;PNG格式为佳&#xff09; &#xff08;2&#xff09;将图片放入工程文件夹 &#xff08;3&#xff09;按如下步骤将图片加入到工程中&#xff08;pic.qrs文件夹&#xff09; &…

阿里云2核4G服务器支持多少人同时在线?

2核4G服务器支持多少人在线&#xff1f;阿里云服务器网账号下的2核4G服务器支持20人同时在线访问&#xff0c;然而应用不同、类型不同、程序效率不同实际并发数也不同&#xff0c;2核4G服务器的在线访问人数取决于多个变量因素&#xff1a; 2核4G&#xff1a;2核CPU和4G内存对…

2024年【烟花爆竹经营单位主要负责人】试题及解析及烟花爆竹经营单位主要负责人作业模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【烟花爆竹经营单位主要负责人】试题及解析及烟花爆竹经营单位主要负责人作业模拟考试&#xff0c;包含烟花爆竹经营单位主要负责人试题及解析答案和解析及烟花爆竹经营单位主要负责人作业模拟考试练习。安全生…

代码还原之 函数

指令堆里逆向出来的代码有歧义&#xff0c;有三处返回&#xff0c;有嵌套IF语句&#xff0c;故推断出是个函数&#xff1b; #if 0/*27ec: 48 8d 3d 58 39 00 00 lea 0x3958(%rip),%rdi # 614b <_IO_stdin_usedBase0x14b> // rdi"COLUMNS"27f3: e8 e…