【线性表】—不带头单向非循环链表的增删查改

news2025/1/18 7:02:52

小菜坤日常上传gitee代码:https://gitee.com/qi-dunyan(所有的原码都放在了我上面的gitee仓库里)

数据结构知识点存放在专栏【数据结构】后续会持续更新
❤❤❤
个人简介:双一流非科班的一名小白,期待与各位大佬一起努力!
推荐数据结构书籍:《大话数据结构》
在这里插入图片描述

目录

  • 前言
  • 接口实现
    • 动态申请节点
    • 尾插与尾删
    • 打印
    • 头插与头删
    • 查找
    • 任意位置插入与删除
    • 销毁
    • 总结

前言

回顾之前的顺序表,我们发现就算是动态扩容,我们也都是成倍的括,也可能存在空间浪费,并且顺序表的头插头删还十分麻烦,需要挪动数据。
而链表的存在就解决了头插头删以及空间浪费这一问题,提到链表,我们脑海中就会浮现出一个链条把东西都链接起来。
在这里插入图片描述

链表

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
。这里所谓的逻辑结构,其实就是为了方便理解,然后加上箭头用来表示关系的,但实际上并不存在箭头。

在这里插入图片描述
我们发现,链式结构其实就是在该节点存放下一个节点的地址,然后通过地址便可以访问到该节点的下一个节点。而上图中的箭头,只是为了方便理解,一个一个连接起来,但实际上是并不存在的。(逻辑结构)

因此,链式结构在逻辑上是连续的(如上图通过箭头链接起来),但在物理地址上却不一定连续。因为每一个节点都是在堆上开辟空间,开辟空间的地址有可能连续,又可能不连续。

链表种类

链表主要分为以下几类:单向与双向、带头与不带头、循环与非循环,而通过这三类的组合,又分为八种形式的链表:带头单向循环链表、带头单向不循环…
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

而我们本次章节研究的就是不带头单向非循环链表。把这一个连接后,后面的其它种类的链表就很好理解与实现了
在这里插入图片描述

接口实现

typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;//数据
	struct SListNode* next;//指向下一个结构体的指针
}SListNode;

动态申请节点

动态申请节点其实就是在堆上malloc出一块空间,并把数据data存放在该节点中。由于后面的插入操作都需要进行开辟新空间,所以这里单独给写了出来,后面用到的时候直接调用即可

//动态申请节点
SListNode* BuySListNode(SLTDateType x)
{
	SListNode*newnode=(SListNode*)malloc(sizeof(SListNode));//malloc空间
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;//将数据放在该节点的data
	newnode->next = NULL;//next置为空(避免野指针)
	return newnode;//返回新节点
}

尾插与尾删

尾插
还是需要进行画图,这样才能更好的理解
在这里插入图片描述
但是这里假如传来的是个空指针,即假如是一个空的链表,那尾插时这个新节点就作为头节点来使用。(特殊情况)

//尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
	//动态申请一个新节点
	SListNode* newnode = BuySListNode(x);
	//空表
	if (*pplist == NULL)
	{
		//这里改变List,传址调用、形参是实参的临时拷贝,形参的修改不会影响实参,所以传List的地址,即用二级指针变量来接收一级指针的地址,这时解引用后就会影响到List
		*pplist = newnode;
	}
	else
	{	//找尾巴
		SListNode* ptail = *pplist;
		//这里修改的是结构体成员变量,所以不用二级指针
		while (ptail->next != NULL)
		{
			ptail = ptail->next;
		}
		//将节点接在尾巴上
		ptail->next = newnode;
	}
}

这里需要注意的是,我是在外面定义的是一个结构体指针,要对此进行修改,必须传址调用,因为传值调用形参的改变不会影响实参。而进行修改后(空表情况下进行尾插),后面的再次尾插其实改变的就不是该变量了,而是该变量的结构体成员next,以及next节点指向的data
在这里插入图片描述
在这里插入图片描述

尾删
画图解决一切。
在这里插入图片描述

这里需要注意的就是,假如只有一个节点的情况下,该节点的next就是空指针,然后再next就形成了空指针的解引用操作(NULL->next)这是错误的,所以我们要考虑到只剩一个节点的特殊情况,另外,还要注意空表状态是不可删除的。

//尾删
void SListPopBack(SListNode** pplist)
{
	//空表不可进行删除,所以加个断言
	assert(*pplist);
	//只有一个数据时直接把该节点释放,然后置空即可
	if ((*pplist)->next == NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
		SListNode* ptail = *pplist;//本意即SListNode*ptail=list
		//找到最后一个的前一个
		while (ptail->next->next!=NULL)
		{
			ptail = ptail->next;
		}
		//释放最后一个节点
		free(ptail->next);
		//并把指向最后一个节点的next置空(不置空就是野指针了,因为虽然释放了那块空间,但是它的前一个节点的next依然指向它)
		ptail->next = NULL;
	}
}

打印

这里我们写一个打印的接口,方便我们观察。也很简单,遍历整个链表即可。

//单链表打印
void SListPrint(SListNode* phead)
{
	SListNode* cur = phead;
	//注意这里是cur!=NULL,因为假如是cur->next !=NULL的话,最后一个节点的数据不会被打印
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL");
}

这里我们来测试一下前面的尾插与尾删操作。

void SListTest2()
{
	SListNode* list = NULL;
	//传址调用,解引用后形参的修改会影响实参
	SListPushBack(&list, 0);
	//SListPrint(list);//0->NULL
	SListPushBack(&list, 1);
	SListPushBack(&list, 2);
	SListPushBack(&list, 3);
	SListPushBack(&list, 4);
	//打印
	//SListPrint(list);//0->1->2->3->4->NULL
	//尾删
	SListPopBack(&list);
	//SListPrint(list);//0->1->2->3->NULL
	SListPopBack(&list);
	SListPopBack(&list);
	SListPopBack(&list);
	SListPopBack(&list);
	SListPrint(list);//NULL
	//空表进行删除
	SListPopBack(&list);//报错 error
}

通过测试我们发现一切都在掌控之中,并没有什么问题。接下来实现头插与头删。

头插与头删

头插

单链表的头插最为简单,时间复杂度达到了O(1),还是通过画图从而更好的理解。这里只需要将新节点的next指向目前的头指针,然后头指针再更新为新节点即可。

在这里插入图片描述

//头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
    //注意这里形参是二级指针,因为这里改变的是list,并不是改变list指向的结构体成员,所以传地址,而一级指针的地址,就要用二级指针pplist接收
	SListNode* newnode = BuySListNode(x);
		newnode->next =*pplist;
		*pplist = newnode;
}

我们假定是个空链表,我们发现头插也是适用的。因此就以上代码就够了。
头删

在这里插入图片描述

这里我们需要注意的就是,空表不可进行删除,然后其余的画个图就一目了然,需要注意的是,这里依然是改变的list,所以还是用二级指针。

//头删
void SListPopFront(SListNode** pplist)
{
	assert(*pplist);
	//先保存
	SListNode* next = (*pplist)->next;
	free(*pplist);
	*pplist = next;
}

测试

//头插头删
void SListTest3()
{
	//头插
	SListNode* list = NULL;
	SListPushFront(&list,1);
	//SListPrint(list);//1->NULL
	SListPushFront(&list, 2);
	SListPushFront(&list, 3);
	SListPushFront(&list, 4);
	//SListPrint(list);//4->3->2->1->NULL
	//头删
	SListPopFront(&list);
	//SListPrint(list);//3->2->1->NULL
	SListPopFront(&list);
	SListPopFront(&list);
	SListPopFront(&list);
	SListPrint(list);//NULL
	//SListPopFront(&list);//error(空表不可删除)
}

这里我们通过测试,进行观察发现没有什么问题,接下来是单链表的查找。

查找

查找操作也很简单,无非就是遍历整个链表,然后找到data时返回该节点指针即可,找不到就返回空指针。其实也可以这样来实现:

//单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
	SListNode* cur = plist;
	//while (cur)
	//{
	//	if (cur->data == x)
	//	{
	//		return cur;
	//	}
	//	cur = cur->next;
	//}
	//return NULL;
	//简化版本
	while (cur && cur->data != x)
	{
		cur = cur->next;
	}
	//结束循环的条件,要么就是cur== NULL,说明找不到,或者就是cur->data==x,找到了,这里直接返回cur就行。
	return cur;
}

任意位置插入与删除

pos位置进行插入
思路都在图纸当中,画图会更加容易理解!
在这里插入图片描述

//在pos位置插入
void SListInsert(SListNode** pplist, SListNode* pos, SLTDateType x)
{
	if (*pplist == pos)
	{
		//头插
		SListPushFront(pplist, x);
	}
	else
	{
		SListNode* cur = *pplist;
		//找到pos节点之前的一个
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		//将新节点插入在此
		SListNode* newnode = BuySListNode(x);
		//找到了pos位置之前的了
		cur->next = newnode;
		newnode->next = pos;
	}
}

pos位置进行删除
在这里插入图片描述

//删除pos位置
void SListErase(SListNode** pplist, SListNode* pos)
{
	assert(pos);
	//假如pos指向list
	if (pos == *pplist)
	{
		SListPopFront(pplist);
	}
	else
	{
		SListNode* cur = *pplist;
		//找到pos位置之前的
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = pos->next;
		free(pos);
	}
}

销毁

最后便是单链表的销毁,因为我们知道,malloc、realloc、calloc这几个函数都是与free成对出现的。不然会造成内存泄漏。
这里我们也是需要进行遍历每一个节点,然后进行删除,不过需要注意的是在删除该节点之前。要先记住下一个节点。

在这里插入图片描述

//单链表销毁
void SListDestroy(SListNode** pplist)
{
	SListNode* cur = *pplist;
	while(cur != NULL)
	{
		SListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pplist = NULL;//一定要置空,不然后面打印就是野指针的访问
}

总结

在这里,一定要多画图,根据图形来理清思路,然后再进行写代码,同时一定要考虑考虑特殊情况,比如空表状态下能不能删除,比如free的时候会不会存在野指针, 并且还建议大家边写边调试,不要一口气从尾插写完,要逐步进行,慢即是快!


end
生活原本沉闷,但跑起来就会有风!❤

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

dns网络服务器未响应是什么原因(如果各自方法都尝试后无法使用,请尝试重启猫)

事件起因:周六晚上,看法国对丹麦世界杯,突然网页浏览器无法工作。 1.然后尝试修改dns路由修改为114.114.114.114,又还原system32/driver/hosts仍然无法使用 2.查看是否有浏览器代理,查询无 3.查看是否有可疑进程&…

游戏开发24课 cocoscreator scrollview优化

分享一个 ScrollView 优化组件 增加一个 在线演示地址 显得高大上一点 image.png32762116 263 KB 目前支持的功能 水平/垂直滑动 可变尺寸动态更新 平滑滚动到底部 Grid 正序排列、倒序排列 无限循环滚动 单向、双向 下拉刷新 (只是这么叫 实际上就是两种类型…

深度学习第四课——卷积神经网络(week 2)

目录 二、深度卷积网路 2.1 经典网络结构 2.1.1 LeNet - 5 2.1.2 AlexNet 2.1.3 VGG - 16 2.2 残差网络(ResNets - Residual Networks) 2.3 残差网络为什么有用 2.4 网络中的网络及11卷积 2.5 Inception网络 2.5.1 介绍 2.5.2 应用 2.6 使用…

24. [Python GUI] PyQt5中的模型与视图框架-表格部件QTableWidget

PyQt5的表格部件QTableWidget QTableWidget 类继承自 QTableView,该类是一个由 Qt 实现的标准的表格部件,该类的数据项由 QTableWidgetItem 类管理。 当前单元格(或当前项目)与当前索引或当前选择是相同的,即可以同时选择多个单元格&#x…

Spark 3.0 - 7.LR 多分类实现影评预测电影评分与指标评测

目录 一.引言 二.LR 多分类分析 三.LR 多分类实战 1.数据准备 Comment -> RDD -> DF 2.数据处理 JieBaTokenizer -> HashingVector 3.模型训练 LR 4.模型评估 Metrics 5.人工校验 DIY 四.总结 一.引言 Spark 3.0 - 5.ML Pipeline 实战之电影影评情感分析 通…

浅析数据采集工具Flume

title: Flume系列 第一章 Flume基础理论 1.1 数据收集工具产生背景 Hadoop 业务的一般整体开发流程: 任何完整的大数据平台,一般都会包括以下的基本处理过程: 数据采集 数据 ETL 数据存储 数据计算/分析 数据展现 其中,数据…

Nacos注册中心和服务方式

目录 一、服务治理介绍 常见的注册中心 二、Nacos注册中心介绍 三、运用Nacos搭建环境 四、DiscoveryClient实现负载均衡 五、Ribbon实现负载均衡 六、基于Feign实现服务调用 七、Feign传参 一、服务治理介绍 通过上一章的操作,我们已经可以实现微服务之间的调…

【Android +Tensroflow Lite】实现从基于机器学习语音中识别指令讲解及实战(超详细 附源码)

需要源码和配置文件请点赞关注收藏后评论区留言~~~ 一、基于机器学习的语音推断 Tensorflow基于分层和模块化的设计思想,整个框架以C语言的编程接口为界,分为前端和后端两大部分 Tensorflow框架结构如下图 二、Tensorflow Lite简介 虽然Tensorflow是一…

WMS类图结构分析-android12

为什么要分析类图? WMS是一个复杂的模块,就像一个很大的家族,里面有各种角色,认识类图就像是认识WMS模块中的各个角色,不先把人认清楚了,怎么更好的理解他们之间的交互? 我觉得,这…

【MATLAB教程案例47】基于双目相机拍摄图像的三维重建matlab仿真

欢迎订阅《FPGA学习入门100例教程》、《MATLAB学习入门100例教程》 本课程学习成果预览: 目录 1.软件版本 2.基于双目相机拍摄图像的三维重建原理概述

GII全球创新指数2013-2020

1、数据来源:世界知识产权组织发布的《2021年全球创新指数报告》 2、时间跨度:2013-2020 3、区域范围:全球 4、指标说明: 全球创新指数(Global Innovation Index,GII)是世界知识产权组织、康…

20221127-1Spring_day01(资料来自黑马程序)

Spring_day01 今日目标 掌握Spring相关概念完成IOC/DI的入门案例编写掌握IOC的相关配置与使用掌握DI的相关配置与使用 1,课程介绍 对于一门新技术,我们需要从为什么要学、学什么以及怎么学这三个方向入手来学习。那对于Spring来说: 1.1 为什么要学? …

Reactive UI -- 反应式编程UI框架入门学习(一)

反应式编程 反应式编程是一种相对于命令式的编程范式,由函数式的组合声明来构建异步数据流。要理解这个概念,可以简单的借助Excel中的单元格函数。 上图中,A1B1C1,无论B1和C1中的数据怎么变化,A1中的值都会自动变化&a…

Kafka - 08 Kafka Broker工作流程 | 节点服役 | 节点退役

文章目录1. Kafka Broker 工作流程2. Kafka 节点服役1. 增加一个Kafka节点2. 执行负载均衡操作3. Kafka 节点退役1. Kafka Broker 工作流程 Kafka上下线时Zookeeper中的数据变化: [zk: localhost:2181(CONNECTED) 9] ls / [zookeeper, kafka_cluster][zk: localhost…

使用nw.js快速开发一个基于浏览器的小型桌面端(适用于高校学生实验作业)

首先讲下退坑事项,节约读者时间 生成的exe会依赖SDK文件夹下的一些dll,所以不能简单的交付这个exe,需要使用额外的软件进行打包,如Enigma Virtual Box、inno setup自定义可执行文件的icon也要额外软件,如Resource Hac…

SCDM 实例教程:基本几何建模

作者 | 张杨 ANSYS SpaceClaim Direct Modeler(简称 SCDM)是基于直接建模思想的新一代3D建模和几何处理软件。SCDM可以显著地缩短产品设计周期,大幅提升CAE分析的模型处理质量和效率,为用户带来全新的产品设计体验。 本文将主要…

常用 CMD 命令

前言 作为一个程序员,可能更多的是在 Linux 中使用命令来操作。但在日常使用 Windows 的过程中,或多或少会使用到命令提示符窗口,也就是 Windows 中的 CMD。这个时候,掌握一些常用的命令就尤为重要了,一方面方便自己使…

排序-指标解读

一、ROC ROC曲线全称是(receiver operating characteristic cure)受试者工作特征曲线。 首先大家看到这里肯定会好奇,为啥名字这么奇怪,来一波背景介绍先。 “ROC起先应用于军事领域,据说在第二次世界大战期间,ROC 曲线最先是由…

几分钟快速学会Linux开启启动服务

背景 最近在银行遇到一个部署问题,uat、prod 两个环境的ECS中的服务要求制作好基础镜像,上环境的时候只需要在对应的ECS中选择更换系统即可,不允许传统连接SSH上去安装,这就要求我们就得提前把需要运行的服务内置到系统中&#x…

债券数据集:绿色债券数据集、历时新发、发行债券、DCM定价估值四大指标数据

1、绿色债券数据集 1、数据来源:wind 2、时间跨度:2016.01-2021.11年 3、区域范围:全国 4、指标说明: 部分指标如下: 数据截图如下: 2、历史新发债券数据库 1、数据来源:wind 2、时间跨度…