【数据结构】单链表(二)

news2025/1/15 16:31:08

目录

1.查找数据

2.指定位置插入和删除节点

2.1 指定位置之前插入节点

2.2 指定位置之后插入节点

2.3 删除指定位置节点

2.4 删除指定位置之后的节点

3.销毁链表


我们接着上一篇【数据结构】单链表(一)-CSDN博客 来继续实现单链表

1.查找数据

SList.h中进行函数的声明

SLNode* SLfind(SLNode* pps, Type x);//查找

返回值是一个地址,如果找到,就返回这个数的地址,如果没找到,就返回NULL,参数就是链表首节点地址和要查找的数

SList.c中进行函数的实现

首先我们可以再定义一个指针存放首节点地址,这样的话在后面的遍历链表时就不会改变pps的指向了

SLNode* SLfind(SLNode* pps, Type x)//查找
{
	SLNode* pcur = pps;//新定义一个指针,指向首节点
}

然后就是循环遍历

SLNode* SLfind(SLNode* pps, Type x)//查找
{
	SLNode* pcur = pps;//新定义一个指针,指向首节点
	while (pcur)//pcur不能为空
	{
		if (pcur->data == x) //找到
			return pcur;//直接返回地址
		pcur = pcur->next;//没找到往后找
	}
}

当跳出while循环时,证明没找到,此时pcur为空,我们直接返回NULL

SLNode* SLfind(SLNode* pps, Type x)//查找
{
	SLNode* pcur = pps;//新定义一个指针,指向首节点
	while (pcur)//pcur不能为空
	{
		if (pcur->data == x) //找到
			return pcur;//直接返回地址
		pcur = pcur->next;//没找到往后找
	}
	return NULL;//没找到
}

test.c中测试一下

void SListtest3()
{
	SLNode* plist = NULL;//空链表
	SLPushBack(&plist, 1);//尾插
	SLPushBack(&plist, 2);
	SLPushHead(&plist, 6);//头插
	SLPushHead(&plist, 7);
	SLPrint(plist);//打印
	SLNode* find = SLfind(plist, 2);
	if (find == NULL)
		printf("没找到\n");
	else
		printf("找到了\n");
}
int main()
{
	//SListtest1();
	//SListtest2();
	SListtest3();
	return 0;
}

 自己测试的时候可以多测几次

2.指定位置插入和删除节点

上一篇我们说了头部尾部的插入和删除数据,现在我们来实现一下指定位置的插入和删除数据

2.1 指定位置之前插入节点

SList.h中进行函数的声明

void SLInsert(SLNode** pps, SLNode* pos, Type x);//指定之前插

参数有三个:链表首节点的地址,指定的位置,要插入的数据

SList.c中进行函数的实现

 现在我们要在节点3前面插入一个节点,就要让节点2里面的next指向新节点,新节点里面的next指向节点3

我们先找pos的前一个结点 ,用循环遍历

void SLInsert(SLNode** pps, SLNode* pos, Type x)//指定之前插
{
	assert(pps && *pps);
	assert(pos);
	SLNode* prev = *pps;//再定义一个指针变量初始指向首节点
	while (prev->next != pos)
	{
		prev = prev->next;
	}
}

跳出循环后此时prev指向pos前一个节点,然后让这些节点“手牵手”

void SLInsert(SLNode** pps, SLNode* pos, Type x)//指定之前插
{
	assert(pps && *pps);
	assert(pos);
	SLNode* newnode = SLBuyNode(x);//插入的数据
	SLNode* prev = *pps;//再定义一个指针变量初始指向首节点
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	newnode->next = pos;
    prev->next = newnode;
}

代码写到这里我们在分析一下pos为1时可不可行

 这种情况下prev会一直往后走,直到走到最后一个节点,上面的代码在次情况下行不通

我们再分析一下pos为最后一个节点时可不可行

依旧是让节点3里面的next指向新节点,新节点里面的next指向节点4

经分析,上面的代码在这种情况下可行,所以不可行的就是pos为1的情况,我们单独把这种情况列出来,其实pos为1时也就是头插的情况

void SLInsert(SLNode** pps, SLNode* pos, Type x)//指定之前插
{
	assert(pps && *pps);
	assert(pos);
	SLNode* newnode = SLBuyNode(x);//插入的数据
	if (pos == *pps)
	{
		SLPushHead(pps, x);//直接调用头插代码
	}
	else//其他位置
	{
		SLNode* prev = *pps;//再定义一个指针变量初始指向首节点
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		newnode->next = pos;
        prev->next = newnode;
	}
}

这就是完整的代码

test.c中测试一下

void SListtest3()
{
	SLNode* plist = NULL;//空链表
	SLPushBack(&plist, 1);//尾插
	SLPushBack(&plist, 2);
	SLPushHead(&plist, 6);//头插
	SLPushHead(&plist, 7);
	SLPrint(plist);//打印
	SLNode* find = SLfind(plist, 2);//找2
	SLInsert(&plist, find, 11);//直接插在2前面
	SLPrint(plist);//打印
}
int main()
{
	SListtest3();
	return 0;
}

看结果

其他情况有疑惑的话一定要自己测试运行一下

2.2 指定位置之后插入节点

SList.h中进行函数的声明

void SLAfter(SLNode* pos, Type x);//指定之后插

这里只有两个参数,一个是指定位置,一个是要插入的值,这里我们不需要知道头节点,因为可以通过pos找到下一个节点,在指定位置之前插入数据的函数需要头节点是因为我们不能通过pos找到pos的前一个节点

SList.c中进行函数的实现

void SLAfter(SLNode* pos, Type x)//指定之后插
{
	assert(pos);
	SLNode* newnode = SLBuyNode(x);//插入的数据
	newnode->next = pos->next;
	pos->next = newnode;
}

注意:  newnode->next = pos->next;   pos->next = newnode;这两句代码的顺序不可以交换,交换后是错的

test.c中测试一下

void SListtest3()
{
	SLNode* plist = NULL;//空链表
	SLPushBack(&plist, 1);//尾插
	SLPushBack(&plist, 2);
	SLPushHead(&plist, 6);//头插
	SLPushHead(&plist, 7);
	SLPrint(plist);//打印
	SLNode* find = SLfind(plist, 2);//找2
	SLInsert(&plist, find, 11);//直接插在2前面
	SLPrint(plist);//打印
	SLAfter(find, 5);//插在2后面
	SLPrint(plist);//打印
}
int main()
{
	SListtest3();
	return 0;
}

代码没有问题

2.3 删除指定位置节点

SList.h中进行函数的声明

void SLErase(SLNode** pps, SLNode* pos);//删除pos节点

参数是二级指针,接收首节点地址,还有一个参数是要删除的节点

SList.c中进行函数的实现

我们要让pos的前一个节点指向pos的后一个节点,然后把pos这个节点销毁

既然要找pos的前一个节点,我们依旧是定义一个指针prev,初始为*pps,往后一个一个找,直到找到pos前一个节点 

void SLErase(SLNode** pps, SLNode* pos)//删除pos节点
{
	assert(pps && *pps);
	assert(pos);
	SLNode* prev = *pps;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	prev->next = pos->next;
	free(pos);
	pos = NULL;
}

如果此时链表只有一个节点,上面的代码可行吗?来分析一下

发现代码走不通,其实这种情况就是头删的情况,我们直接调用头删的代码就可以了

void SLErase(SLNode** pps, SLNode* pos)//删除pos节点
{
	assert(pps && *pps);
	assert(pos);
	if (pos == *pps)//一个节点
	{
		SLPopHead(pps);
	}
	else//多个节点
	{
		SLNode* prev = *pps;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

test.c中测试一下

void SListtest3()
{
	SLNode* plist = NULL;//空链表
	SLPushBack(&plist, 1);//尾插
	SLPushBack(&plist, 2);
	SLPushHead(&plist, 6);//头插
	SLPushHead(&plist, 7);
	SLPrint(plist);//打印
	SLNode* find = SLfind(plist, 7);//找7
	SLErase(&plist, find);//删除指定位置节点
	SLPrint(plist);//打印
}
int main()
{
	SListtest3();
	return 0;
}

删除成功

2.4 删除指定位置之后的节点

SList.h中进行函数的声明

void SLPushAfter(SLNode* pos);//删除pos之后的节点

pos的后一个节点我们可以直接通过pos找到,就不需要头节点地址,所以一个参数就好了

 在SList.c中进行函数的实现

 

还是先让pos这个节点找到它的下下个节点,然后再销毁pos后面的节点

 这里呢我们需要一个临时变量存放pos->next的地址,然后再连接节点

 我们先写一下代码,让pos和pos下下个节点相连

void SLPushAfter(SLNode* pos)//删除pos之后的节点
{
	assert(pos);
	assert(pos->next);
	SLNode* temp = pos->next;
	pos->next = temp->next;
}

 然后销毁pos下一个节点并置空

void SLPushAfter(SLNode* pos)//删除pos之后的节点
{
	assert(pos);
	assert(pos->next);
	SLNode* temp = pos->next;//临时变量
	pos->next = temp->next;//连接
	free(temp);//销毁
	temp = NULL;//置空
}

test.c中测试一下

void SListtest3()
{
	SLNode* plist = NULL;//空链表
	SLPushBack(&plist, 1);//尾插
	SLPushBack(&plist, 2);
	SLPushHead(&plist, 6);//头插
	SLPushHead(&plist, 7);
	SLPrint(plist);//打印
	SLNode* find = SLfind(plist, 7);//找7
	SLPushAfter(find);//删除指定位置后一个节点
    SLPrint(plist);//打印
}
int main()
{
	SListtest3();
	return 0;
}

3.销毁链表

跟顺序表一样,链表使用完之后也要销毁,链表由一个一个节点组成,所以也要一个一个销毁

SList.h中进行函数的声明

void SLDestroy(SLNode** pps);//销毁

参数就是首节点地址

SList.c中进行函数的实现

我们在销毁当前节点之前要把下一个节点的信息存起来

 销毁空间

pcur后移到提前保存的next处

 

然后next后移,把当前的pcur销毁

就这样一直往后,直到pcur为空

代码来实现一下


void SLDestroy(SLNode** pps)//销毁
{
	assert(*pps && pps);
	SLNode* pcur = *pps;
	while (pcur)
	{
		SLNode* next = pcur->next;//存下节点信息
		free(pcur);//释放
		pcur = next;//往后走
	}
	*pps = NULL;//不要忘了头节点此时没有置空,要置空
}

test.c中测试一下

void SListtest3()
{
	SLNode* plist = NULL;//空链表
	SLPushBack(&plist, 1);//尾插
	SLPushBack(&plist, 2);
	SLPushHead(&plist, 6);//头插
	SLPushHead(&plist, 7);
	SLPrint(plist);//打印
	SLDestroy(&plist);//销毁
	SLPrint(plist);//打印
}
int main()
{
	SListtest3();
	return 0;
}

可以自己通过调试看结果,能看到更详细,打印出来看也可以

单链表实现就分享到这里,拜拜~ 

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

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

相关文章

【Shell语言学堂】数组练习题

数组练习 1、使用数组和循环实现冒泡排序2、将冒泡排序的代码重构为2个函数,2个关系是a函数调用b函数自定义数组参数: 3、声明一个存储的全整数数组,对其中的每一个值进行10处理4、对硬盘使用空间占比的排序5、对当前目录的文件大小进行排序 …

Vue3 ts环境下的PropType

简介 在Typscript中,我们可以使用PropType进行类型的推断与验证。在日常的开发中我们常常会遇到下面这样的场景: 我们通过request请求从服务端获取了一条数据,数据是个Array的格式,Array中的每个元素又是一个对象,像下…

BFS宽度优先搜索例题(蓝桥杯)——逃跑的牛

问题描述: 农夫John的一头牛逃跑了,他想要将逃跑的牛找回来。现假设农夫John和牛的位置都在一条直线上,农夫John的初始位置为N(0≤N≤100,000),牛的初始位置为K(0≤K≤100,000)。农夫…

MySOL之旅--------MySQL数据库基础( 3 )

本篇碎碎念:要相信啊,胜利就在前方,要是因为一点小事就停滞不前,可能你也不适合获取胜利,成功的路上会伴有泥石,但是走到最后,你会发现身上的泥泞皆是荣耀的勋章! 今日份励志文案: 凡是发生皆有利于我 目录 查询(select) 1.全列查询 2.指定列查询 3.查询字段为表达式 ​编…

数据库被rmallox勒索病毒加密,如何还原?

近年来,网络安全问题日益严峻,勒索病毒作为其中的一种恶意软件,已成为网络安全领域的一大难题。其中,rmallox勒索病毒以其高度的隐蔽性和破坏性,给不少企业和个人带来了严重损失。本文将从rmallox勒索病毒的特点、传播…

【2024年MathorCup数模竞赛】C题赛题与解题思路

2024年MathorCup数模竞赛C题 题目 物流网络分拣中心货量预测及人员排班背景求解问题 解题思路问题一问题二问题三问题四 本次竞赛的C题是对物流网络分拣中心的货量预测及人员排班问题进行规划。整个问题可以分为两个部分,一是对时间序列进行预测,二是对人…

C++初阶:模板进阶

非类型模板参数 模板参数分为类型形参与非类型形参 。 类型形参即:出现在模板参数列表中,跟在 class 或者 typename 之类的参数类型名称 。 非类型形参,就是用一个常量作为类 ( 函数 ) 模板的一个参数,在类 ( 函数 ) 模板中可将…

7、configMap

1、configMap是什么 类似与pod的配置中心,不会因为pod的创建销毁,相关配置发生改变 pod定义硬编码意味着需要有效区分⽣产环境与开发过程中的pod 定义。为了能在多个环境下复⽤pod的定义,需要将配置从pod定义描 述中解耦出来。 2、向容器中…

HarmonyOS实战开发-证书管理、如何实现对签名数据进行校验功能。

介绍 本示例使用了ohos.security.certManager相关接口实现了对签名数据进行校验的功能。 实现场景如下: 1)使用正确的原始数据和签名数据进行签名校验场景:模拟服务端对签名数据进行校验,验证客户端身份和原始数据完整性。 2&…

智慧污水井物联网远程监控案例

智慧污水井物联网远程监控案例 在当今数字化转型的浪潮中,智慧水务已成为城市基础设施建设的重要组成部分。其中,基于物联网技术的智慧污水井远程监控系统以其高效、精准、实时的特性,在提升污水处理效能、保障城市水环境安全、实现精细化管…

数据资产可能是个伪命题?

✅作者简介:《数据运营:数据分析模型撬动新零售实战》作者、《数据实践之美》作者、数据科技公司创始人、多次参加国家级大数据行业标准研讨及制定、高端企培合作讲师。 🌸公众号:风姑娘的数字视角,免费分享数据应用相…

【分享】跨境虾皮Shopee各区域商品详情API返回值(商品,订单,面单等)♥

虾皮(shopee)是一个亚洲区域的电商平台,主要在东南亚地区提供电商服务。虾皮提供了丰富的电商数据,包括商品数据、订单数据、会员数据、评价数据等。 虾皮Shopee♥♥​​​​​​​♥​​​​​​​♥​​​​​​​♥​​​​​​​♥ 1.授权 ​ 接口…

Matlab 将数据写入excel文件

Matlab 将数据写入excel文件 函数:writematrix 功能:将矩阵写入文件 语法 writematrix(A) writematrix(A,filename) writematrix(___,Name,Value) 说明 writematrix(A) 将同构数组 A 写入以逗号分隔的文本文件。文件名为数组的工作区变量名称&…

点动、电子凸轮、电子齿轮

一、常见的运动模式有三种 1、点位运动:进队终点的位置有要求,不在乎运动轨迹。要求定位速度快。可分为JOG电动、MOVE寸动、和VMOVE持续运动三类。 2、连续轨迹运动:也称为插补,系统在高速运动的情况下,既要保证轮廓…

中文分词,c++应用,想到jieba分词,结果还的自己封装。探索中

一、研究背景 随着互联网的快速发展,信息也呈了爆炸式的增长趋势。在海量的信息中,我们如何快速抽取出有效信息成为了必须要解决的问题。由于信息处理的重复性,而计算机又善于处理机械的、重复的、有规律可循的工作,因此自然就…

技术必备:接口自动化测试数据校验神器【JSonPath】

我们今天不讲如何开发一款自定义开发校验规则库,而是给大家分享一款在开发自定义校验规则库或者常规的接口自动化测试时,经常会用到的一款数据提取神器:JSonPath。 1. JSonPath介绍 JSonPath是一种简单的方法来提取给定JSON文档的部分内容。…

Visual Components对重型机械工业的影响 衡祖仿真

一、重型机械行业面临的挑战 此行业制造商面临着许多挑战,首先是世界各地实施的环境法规不断增多,可持续建筑实践、改善空气质量和减少排放已成为产品设计和开发背后的主要驱动力,健康和安全标准也在不断发展,给已经面临熟练劳动…

Mongodb入门--头歌实验MongoDB 复制集 分片

一、MongoDB之副本集配置 1.1MongoDB主从复制 主从复制是MongoDB最早使用的复制方式, 该复制方式易于配置,并且可以支持任意数量的从节点服务器,与使用单节点模式相比有如下优点: 在从服务器上存储数据副本,提高了数…

机器学习——模型融合:Boosting算法

机器学习——模型融合:Boosting算法 1. Boosting核心思想 Boosting算法是一种集成学习方法,其核心思想是通过组合多个弱学习器(即准确率略高于随机猜测的学习器)来构建一个强学习器(即准确率较高的学习器&#xff09…

【C++】——list的介绍及使用 模拟实现

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 文章目录 前言 一、list的介绍及使用 1.1 list的介绍 1.2 list的使用 1.2.1 list的构造 1.2.2 list iterator的使用 1.2.3 list capacity 1.2.4 list element access 1.…