详解单链表

news2024/11/23 0:17:17

 

 💕十载寒窗无人问,一举成名天下知💕

作者:Mylvzi 

 文章主要内容:程序环境和预处理 

引言:

  我们之前已经学习过顺序表,顺序表是一种线性的存储结构,它在内存中是连续存放的;我们不难发现,顺序表在管理数据时存在一些问题,如进行插入数据时需要挪动大量数据,异地扩容导致内存使用率低,存在大量内存碎片等等,那有没有一种存储方式可以实现“随用随开“的内存使用方式呢?存在,就是我们今天要学的-->链表

链表的基本知识:

  链表是一种管理内存的数据结构,和顺序表一样,都是一种线性表;

单链表(Single List Table)的表示:

(指针域中只有一个地址,所以叫做单链表)

链表与顺序表的区别:

1.链表和顺序表最大的差距在于空间开辟的方式:

链表是有多少就开辟多少(精打细算)

顺序表是直接给你一大片空间,让你使用,不够用了,再给你一大片空间(任性父母)

2.顺序表的空间在内存中是连续的,而链表的空间是一个一个独立的小空间,不连续

单链表的创建: 

 前提准备:

//创建结构体存储单链表的基本信息(数据域 + 指针域):

typedef int SLDataType;
//定义结点
typedef struct SListNode
{
	SLDataType data;//数据域
	struct SListNode* next;//指针域-->存放下一节点的地址
}SLTNode;//SLT-->single list table单链表

单链表的管理:

打印单链表:

逻辑:从头指针(phead)访问下一个结点,打印每个结点的数据域,再把下一个结点的地址给cur,一直到NULL;

//打印链表
void SLTPrint(SLTNode* phead);

//打印链表逻辑
void SLTPrint(SLTNode* phead)
{
	//assert(phead);err
	//断言不合理,因为phead可以是NULL,代表链表中无数据
	//断不断言,要看的传的数据是否合理

	SLTNode* cur = phead;//重新赋头指针
	//while(cur != NULL)
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;

		//++cur err 结点的存储在内存中不是连续的
	}

	printf("NULL");
}

创建新结点: 

逻辑:为新结点动态开辟内存空间,然后再对数据域指针域进行赋值

//创建一个新结点
SLTNode* BuySListNode(SLDataType x);

//创建一个新结点
SLTNode* BuySListNode(SLDataType x)//要存储的数据为x
{
	//为整个结点动态开辟空间
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(-1);
	}

	//初始化
	newnode->data = x;
	newnode->next = NULL;
}

尾插:

 

//尾插-->结点的本质是一种结构体指针,改变结构体指针需要传递结构体指针的地址(二级指针)
void SLTPushBack(SLTNode** pphead, SLDataType x);

//尾插-->结点的本质是一种结构体指针,改变结构体指针需要传递结构体指针的地址(二级指针)
void SLTPushBack(SLTNode** pphead, SLDataType x)
{
    assert(pphead);//pphead是plist的地址,永远不可能为空,所以要检查;
	//assert(*pphead);err  空链表可以尾插

	//创建一个新结点作尾插用
	SLTNode* newnode = BuySListNode(x);

	//进行尾插-->找到尾结点,使其next指向newnode
	if (*pphead == NULL)
	{
		//如果*phead本事就是NULL,则不存在NULL->next,直接将newnode赋给phead即可
		newnode = *pphead;
	}
	else
	{
		//寻找到尾结点
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

 头插:

//头插
void SLTPushFront(SLTNode** pphead, SLDataType x);

//头插
void SLTPushFront(SLTNode** pphead, SLDataType x)
{
    assert(pphead);
	SLTNode* newnode = BuySListNode(x);

	//头插-->注意顺序
	newnode->next = *pphead;
	*pphead = newnode;
	
	//err
	//*pphead = newnode;
	//newnode->next = *pphead;
}

注意到:头插和尾插的参数都是二级指针,原因在于你改变的是结点,结点本身就是一个结构体指针,改变结构体指针就要传递结构体指针的地址,即二级指针;在顺序表中,我们改变的是一个一个结构体 ,所以传递的是结构体指针

由于形参是实参的临时拷贝,出了函数作用域后会被自动销毁,要改变值,就要传递值的地址!

头插,尾插都要检查pphead,但不用检查*pphead,因为NULL也能插入数据

尾删:

//尾删
void SLTPopBack(SLTNode** pphead);

//尾删
void SLTPopBack(SLTNode** pphead)
{
    assert(pphead);//pphead为空,不合理
	//1.*pphead == NULL  如果是NULL,属于非法删除
	assert(*pphead);

	//2.只有一个节点
	if ((*pphead)->next == NULL)//注意:由于存在优先级问题,*pphead需要添加括号
	{
		free(*pphead);
		*pphead = NULL;
	}
	else//不止一个结点
	{
		//第一种写法
		SLTNode* tail = *pphead;

		//while (tail->next->next != NULL)
		while (tail->next->next)//在倒数第二个结点停止(因为你需要删除最后一个结点)
		{
			tail = tail->next;
		}

		free(tail->next);
		tail->next = NULL;
	}

	第二种写法:双指针法
	//	SLTNode* tailPrev = NULL;//tail结点的上一个结点
	//	SLTNode* tail = *pphead;

	//	while (tail->next)
	//	{
	//		tailPrev = tail;
	//		tail = tail->next;
	//	}
	//	free(tail);
	//	tail = NULL;
	//	//tailPrev->next = NULL;
}

 注意尾删有三种情况

 头删:

//头删
void SLTPopFront(SLTNode** pphead);

//头删
void SLTPopFront(SLTNode** pphead)
{
    assert(pphead);
	//1.空
	assert(*pphead);

	//2.非空
	SLTNode* newhead = (*pphead)->next;//使第二个结点作为头结点
	free(*pphead);
	*pphead = newhead;//这里不是置空,而是置换新结点为头结点
}

寻找结点:

根据输入的值,找到对应的结点

//寻找数据为x的结点
SLTNode* SLTFind(SLTNode* phead, SLDataType x);

SLTNode* SLTFind(SLTNode* phead, SLDataType x)
{
	assert(phead);
	SLTNode* cur = phead;//遍历尽量多定义一个变量

	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}

		cur = cur->next;
	}

	//出循环,遍历到NULL
	return NULL;
}

在pos之前插入x:

逻辑:找到pos之前的prev结点,改变指针域

 

// 在pos之前插入x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLDataType x);

// 在pos之前插入x-->单链表前插效率不高
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLDataType x)
{
	assert(*pphead);

	//pos是*pphead-->头插
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);//头插函数的第一个参数是plist的地址,是二级指针
	}
	else
	{
		//找到pos之前的结点prev
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuySListNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

在pos之后插入x: 

逻辑:找到pos,改变指针域(去观察什么数据发生了改变)

 

// 在pos以后插入x
void SLTInsertAfter(SLTNode* pos, SLDataType x);

// 在pos以后插入x
void SLTInsertAfter(SLTNode* pos, SLDataType x)
{
	assert(pos);//防止传错

	//插入逻辑
	SLTNode* newnode = BuySListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

删除pos位置的结点:

逻辑:找到prev,改变指针域,free(pos)

// 删除pos位置
void SLTErase(SLTNode** pphead, SLTNode* pos);

// 删除pos位置
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
    assert(pphead);
	assert(*pphead);
	assert(pos);

	//头指针-->头删
	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else
	{
		//找到prev
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

删除pos的后一个位置的结点: 

 逻辑:找到posNext,改变指针域,free(posNext);

// 删除pos的后一个位置
void SLTEraseAfter(SLTNode* pos);

// 删除pos的后一个位置
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);//检查pos是否是尾节点

	SLTNode* posNext = pos->next;
	pos->next = posNext->next;

	free(posNext);
	posNext = NULL;
}

总结: 

  单链表是一种链式的线性表,是一种常见的数据结构,但其对数据的管理效率并不高(只有头插,头删的效率还可以),但常常出题考察,在leetcode上非常常见,此外,单链表还经常作为更复杂数据结构的子结构出现,所以还是要掌握好单链表的相关知识,以及他的创建管理!希望这篇文章对你有用,谢谢观看!

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

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

相关文章

《Java-SE-第三十五章》之方法引用

前言 在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!” 博客主页:KC老衲爱尼姑的博客主页 博主的github,平常所写代码皆在于此 共勉:talk is cheap, show me the code 作者是爪哇岛的新手,水平很有限&…

微服务实战项目-学成在线-项目部署

微服务实战项目-学成在线-项目部署 1 什么是DevOps 一个软件的生命周期包括:需求分析阶、设计、开发、测试、上线、维护、升级、废弃。 通过示例说明如下: 1、产品人员进行需求分析 2、设计人员进行软件架构设计和模块设计。 3、每个模块的开发人员…

Vue3实现图片懒加载及自定义懒加载指令

Vue3实现图片懒加载及自定义懒加载指令 前言1.使用vue-lazyload/vue3-lazyload插件2.自定义v-lazy懒加载指令2.1 使用VueUse工具集2.2 使用IntersectionObserver 前言 图片懒加载是一种常见性能优化的方式,它只去加载可视区域图片,而不是在网页加载完毕…

直线电机模组在3C电子行业中的重要应用

直线模组的种类有很多,是自动化行业中必不可少的传动元件,其中丝杆模组和同步带模组的应用率比较高,但随着自动化领域的迅速发展,高精密直线电机模组也得到了广泛的应用,尤其是在电子行业中的应用。 3C电子产品在我们的…

Redis数据一致性问题的三种解决方案

Redis数据一致性问题的三种解决方案 1、首先redis是什么 Redis(Remote Dictionary Server ),是一个高性能的基于Key-Value结构存储的NoSQL开源数据库。大部分公司采用Redis来实现分布式缓存,用来提高数据查询效率。 2、为什么会选Redis 在…

vue全局组件自动注册直接使用,无需单独先引用注册再使用

目录结构: 本案例是在根目录下components文件夹测试的,文件位置项目内任意,确保在main.js挂载路径正确即可 1、新建文件夹(名字随意)zxy_components (放自己组件的地方) 2、在zxy_components文件夹下 !新建…

中科亿海微FIFO使用

引言 FPGA(现场可编程门阵列)是一种可编程逻辑器件,具有灵活性和可重构性,广泛用于数字电路设计和嵌入式系统开发。在FPGA中,FIFO(First-In, First-Out)是一种常见的存储器结构,用于…

Vue3 Props组件简单应用(父组件获取子组件数据)

去官网学习→Props | Vue.js 运行示例&#xff1a; 代码&#xff1a;App.vue <template><img alt"Vue logo" src"./assets/logo.png"><h2>Vue Props数据传递</h2><h4>子组件中的数据&#xff1a;{{ content }}</h4>…

OpenCV实例(九)基于深度学习的运动目标检测(一)YOLO运动目标检测算法

基于深度学习的运动目标检测&#xff08;一&#xff09; 1.YOLO算法检测流程2.YOLO算法网络架构3.网络训练模型3.1 训练策略3.2 代价函数的设定 2012年&#xff0c;随着深度学习技术的不断突破&#xff0c;开始兴起基于深度学习的目标检测算法的研究浪潮。 2014年&#xff0c;…

软考高项-思维导图31-33(计算机高级系统项目管理师)

陆续更新一些软考高项的思维导图&#xff0c;都是一些必背知识点&#xff0c;希望可以帮助大家早日考过高项&#xff0c;早日当上高工&#xff0c;早日成为杭州E类人才。全部完整导图快速获取链接&#xff1a;计算机高级系统项目管理师-思维导图汇总 三十一、范围确认 三十二、…

MySql(干货)

写这篇博客的目的不是为了将介绍原理&#xff0c;而是为了Sql中的代码操作属实太多了&#xff0c;在这里进行一个汇总&#xff0c;方便查阅&#xff01;&#xff01;&#xff01; Sql分类 分类全称说明 DDL Data Definintion Language数据定义语言&#xff0c;用来定义数据库对…

Linux命令200例:pwd用于显示当前工作目录的绝对路径

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌。CSDN专家博主&#xff0c;阿里云社区专家博主&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &…

智能优化算法:白鲨优化算法-附代码

智能优化算法&#xff1a;白鲨优化算法 文章目录 智能优化算法&#xff1a;白鲨优化算法1.白鲨优化算法1.1 初始化1.2 速度更新1.3位置更新1.4鱼群行为 2.实验结果3.参考文献4.Matlab5.python 摘要&#xff1a;WSO 算法是 Braik 等于 2022 年提出一种基于白鲨深海觅食策略的新型…

第二十三章 原理篇:Pix2Seq

大夏天我好像二阳了真是要命啊。 现在找到工作了&#xff0c;感觉很快乐&#xff0c;但是也有了压力。 《论你靠吹牛混进公司后该怎么熬过试用期》 希望自己能保持学习的习惯&#xff01;加油&#xff01; 参考教程&#xff1a; https://arxiv.org/pdf/2109.10852.pdf https://…

动捕系统mockup_optitrack替换为VRPN传递信息

motive&#xff1a;启动→载入已有→layout选择capture→view选择data streming→复选marker右键create刚体→rename刚体→修改local interface为本机ip→勾选vrpn ROS端&#xff1a;roslaunch vrpn_client_ros vrpn_efy.launch 记得修改server地址为motiveip地址 关掉motive…

并查集、树状数组

并查集、树状数组、线段树 并查集树状数组树状数组1 (单点修改&#xff0c;区间查询)树状数组2 (单点查询&#xff0c;区间修改) 并查集 【模板】并查集 题目描述 如题&#xff0c;现在有一个并查集&#xff0c;你需要完成合并和查询操作。 输入格式 第一行包含两个整数 …

GIF制作器-gif动图制作助手、格式转换软件

GIF制作器​​​​​​​-无水印制作gif动态图片&#xff01; 一步轻松制作GIF&#xff0c;将视频、Live Photo、照片轻松转换成GIF动图&#xff0c;超级简单好用~ 【功能简介】 视频转GIFLive Photo转GIF图片转GIFGIF编辑 【编辑工具】 自由裁剪GIF内容调节GIF播放速度添加文…

C++ 网络编程项目fastDFS分布式文件系统(一)

目录 1.项目架构图 1.1 一些概念 1.2 项目架构图 2. 分布式文件系统 2.1 传统文件系统 2.2 分布式文件系统 3. FastDFS 3.1 fastDFS介绍 3.2 fastDFS安装 3.3 fastDFS配置文件 3.4 fastDFS的启动 4. fastDFS状态检测 4.1 对file_id的解释 4. 2上传下载代码实现 …

电流的测量(分流电流表)

在当今的大多数仪器应用中&#xff0c;可以使用两种常见的电流测量方法&#xff1a;分流电流表方法和反馈电流表方法。分流电流表方法通常与通用数字万用表 (DMM)一起使用&#xff0c;用于测量分流电阻器上的电压测量值。该电压测量结果与已知的电阻值相结合&#xff0c;得出电…

ADM2587E在RS485和RS422接口的应用(ADM2587E电路原理图和程序开发)

最近做一个项目使用到ADM2587E&#xff0c;为了解决公司历史遗留的问题&#xff08;ADM2587E芯片发烫&#xff0c;容易烧毁&#xff0c;485设备只能手拉手连接三四个&#xff0c;就通信不正常现象&#xff09;&#xff0c;认真阅读了Datasheet和官网LayOut的一些设计文档&#…