单链表链表专题

1 链表的概念

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

链表的结构跟⽕⻋⻋厢相似,淡季时⻋次的⻋厢会相应减少,旺季时⻋次的⻋厢会额外增加⼏节。只 需要将⽕⻋⾥的某节⻋厢去掉/加上,不会影响其他⻋厢,每节⻋厢都是独⽴存在的。

⻋厢是独⽴存在的,且每节⻋厢都有⻋⻔。想象⼀下这样的场景,假设每节⻋厢的⻋⻔都是锁上的状态,需要不同的钥匙才能解锁,每次只能携带⼀把钥匙的情况下如何从⻋头⾛到⻋尾?

最简单的做法:每节⻋厢⾥都放⼀把下⼀节⻋厢的钥匙。

在链表⾥,每节“⻋厢”是什么样的呢?

与顺序表不同的是,链表⾥的每节"⻋厢"都是独⽴申请下来的空间,我们称之为“结点/节点”节点的组成主要有两个部分:当前节点要保存的数据和保存下⼀个节点的地址(指针变量)。

图中指针变量plist保存的是第⼀个节点的地址,我们称plist此时“指向”第⼀个节点,如果我们希 望plist“指向”第⼆个节点时,只需要修改plist保存的内容为0x0012FFA0。 

为什么还需要指针变量来保存下⼀个节点的位置?

链表中每个节点都是独⽴申请的(即需要插⼊数据时才去申请⼀块节点的空间),我们需要通过指针 变量来保存下⼀个节点位置才能从当前节点找到下⼀个节点。

结合前⾯学到的结构体知识,我们可以给出每个节点对应的结构体代码:

假设当前保存的节点为整型

struct SListNode
{
 int data; //节点数据 
 struct SListNode* next; //指针变量⽤保存下⼀个节点的地址 
};

当我们想要保存⼀个整型数据时,实际是向操作系统申请了⼀块内存,这个内存不仅要保存整型数 据,也需要保存下⼀个节点的地址(当下⼀个节点为空时保存的地址为空)。

当我们想要从第⼀个节点⾛到最后⼀个节点时,只需要在前⼀个节点拿上下⼀个节点的地址(下⼀个 节点的钥匙)就可以了。

给定的链表结构中,如何实现节点从头到尾的打印?

 先打印第一个节点中的数据,即pcur结构体的data成员,pcur结构体的next指针指向下一个节点(pcur结构体的next指针保存着下一个节点的地址)。我们把这个地址重新赋值给pcur,此时pcur就指向了第二个节点。循环这一过程,直到pcur指向空指针时跳出循环。这样就实现了链表的遍历。

2单链表的实现

2.1开辟新节点

ListNode* NewListNote(DataType x)
{
	ListNode* newlistnode = (ListNode*)malloc(sizeof(ListNode));
	if (newlistnode == NULL)
	{
		perror("malloc error");
		exit(1);
	}
	newlistnode->data = x;
	newlistnode->next = NULL;
	return newlistnode;
}

我们对链表进行插入操作时,需要像内存申请一个节点大小的空间,这里我们用到了malloc函数。

申请新空间并且为结构体的成员赋值。

2.2尾插

在链表的尾部插入一个新节点

void LiseTailAdd(ListNode** pphead, DataType x)
{
	assert(pphead);
	ListNode* newlistnode = NewListNote(x);
	if (*pphead == NULL)
	{
		*pphead = newlistnode;
	}
	else
	{
		ListNode* ptail = *pphead;
		while ((ptail)->next != NULL)
		{
			ptail = (ptail)->next;
		}
		(ptail)->next = newlistnode;
	}
}

如果链表中一个节点都没有,就直接插入一个新节点,新节点作为链表的“头节点”。

如果链表中原来就有节点,那我们应该先找到尾节点,然后在尾节点后边插入新节点。

这里为什么传入二级指针呢?

因为链表的头节点的地址可能会因为新节点的插入而发生改变(原链表中一个节点都没有视情况下)。想要头节点的地址发生改变,就必须传二级指针。即函数的传址调用。

2.3头插

在链表的头部插入一个新节点

void LiseHeadAdd(ListNode** pphead, DataType x)
{
	assert(pphead);
	ListNode* newlistnode = NewListNote(x);
	newlistnode->next = *pphead;
	*pphead = newlistnode;
}

建一个新节点,再让新节点的next指针指向头节点,然后让这个新节点作为链表的“头节点”。

2.4头删

删除链表的“头节点”

void LiseHeadDel(ListNode** pphead)
{
	assert(pphead);
	ListNode* tmp = (*pphead)->next;
	free(*pphead);
	*pphead = tmp;
}

直接释放头节点,让它的下一个节点作为链表的新的头节点,但是直接free头节点后,我们就找不到了它的下一个节点,所以要在释放之前用一个变量tmp把他下一个节点的地址保存下来。再让*pphead指向tmp。

2.5尾删

删除链表的尾节点

void LiseTailDel(ListNode** pphead)
{
	assert(pphead);
	ListNode* per = *pphead;
	ListNode* ptail = *pphead;
	while (ptail->next)
	{
		per = ptail;
		ptail = ptail->next;
	}
	per->next = NULL;
	//free(ptail);
	//ptail = NULL;
}

要删除尾节点,我们应该把尾节点释放掉,并且让为节点的前一个节点的next指针指向空。所以我们要先找到尾节点和尾节点的前一个节点。找尾节点的方法和尾插找尾的方法类似。找到尾后释放即可。

2.6查找数据

ListNode* FindNote(ListNode* phead, DataType x)
{
	while (phead)
	{
		if (phead->data == x)
		{
			printf("找到了\n");
			return phead;
		}
		phead = phead->next;
	}
	printf("没找到");
}

遍历链表,查找某个数据是否存在,如果存在就返回对应节点的地址。不存在的话就打印没找到。

2.7在指定位置之前插入

void PopNoteFrontAdd(ListNode** pphead, ListNode* pop, DataType x)
{
	assert(pphead);
	if (*pphead == pop)
	{
		LiseHeadAdd(pphead, x);
	}
	else
	{
		ListNode* per = *pphead;
		while (per->next != pop)
		{
			per = per->next;
		}
		ListNode* newlistnode = NewListNote(x);
		newlistnode->next = pop;
		per->next = newlistnode;
	}
}

 在指定位置之前插入,我们需要找到这个位置之前的节点,让这个节点的next指针指向新节点,然后新节点的next指针在指向pop。

所以要遍历链表,通过头节点找到这个位置之前的节点。

2.8在指定位置之后插入

void PopNoteBehindAdd(ListNode* pop, DataType x)
{
	ListNode* newlistnode = NewListNote(x);
	newlistnode->next = pop->next;
	pop->next = newlistnode;
}

在指定位置之后插入,只需要让新节点的next指针指向pop的next节点,再让pop的next指针指向新节点。

注意这两条指令的顺序不能颠倒,因为如果先让pop的next指针先发生改变,就找无法找到原来的pop的next节点。

2.9删除pop之后位置节点

void DelPopBehindNode(ListNode* pop)
{
	assert(pop && pop->next);
    ListNode* tmp = pop->next;
	pop->next = tmp->next;
    free(tmp);
    tmp = NULL;
}

释放pop之后位置的节点,但释放之后无法找到这个位置的下一个节点,所以释放之前要用变量tmp把pop->next先存起来。

2.10删除pop位置的节点

void DelPopNode(ListNode* pop, ListNode** pphead)
{
	assert(pphead);
	if (pop == *pphead)
	{
		LiseHeadDel(pphead);
	}
	else
	{
		ListNode* per = *pphead;
		while (per->next != pop)
		{
			per = per->next;
		}
		per->next = pop->next;
		free(pop);
		pop = NULL;
	}
}

如果pop是头节点,直接调用头删函数。

如果pop不是头节点,那应该找到pop的前一个结点,并让其next指针指向pop的下一个节点,最后释放pop节点。

2.11链表销毁

void SListDesTroy(ListNode** pphead)
{
	assert(pphead && *pphead);
	while (*pphead != NULL)
	{
		ListNode* next = (*pphead)->next;
		free(*pphead);
		*pphead = next;
	}
}

遍历链表,逐个节点释放。但是直接释放的话会找不到下一个节点,所以要有一个变量next将下一个节点的地址存起来。

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

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

相关文章

Redis实现延迟任务的几种方案

🏷️个人主页:牵着猫散步的鼠鼠 🏷️系列专栏:Java全栈-专栏 🏷️个人学习笔记,若有缺误,欢迎评论区指正 目录 1.前言 2.Redis如何实现延迟任务? 3.代码实现 3.1. 过期键通知事…

技术速递|为 .NET iOS 和 .NET MAUI 应用程序添加 Apple 隐私清单支持

作者:Gerald Versluis 排版:Alan Wang Apple 正在推出一项隐私政策,将隐私清单文件包含在针对 App Store 上的 iOS、iPadOS 和 tvOS 平台的新应用程序和更新应用程序中。请注意,至少目前 macOS 应用程序被排除在外。 隐私清单文件…

这部经典之作,时隔六年迎来重磅升级!

🍅 作者简介:哪吒,CSDN2021博客之星亚军🏆、新星计划导师✌、博客专家💪 🍅 哪吒多年工作总结:Java学习路线总结,搬砖工逆袭Java架构师 🍅 技术交流:定期更新…

Niobe WiFi IoT开发板OpenHarmony内核编程开发——Semaphore

本示例将演示如何在Niobe WiFi IoT开发板上使用cmsis 2.0 接口进行信号量开发 Semaphore API分析 osThreadNew() osThreadId_t osThreadNew(osThreadFunc_t func, void *argument,const osThreadAttr_t *attr )描述: 函数osThreadNew通过将线程添加到活动线程列表…

TG-12F使用SDK对接阿里生活物联网平台

文章目录 前言一、注意二、准备1. 安装Ubuntu(版本20.04 X64)程序运行时库。按顺序逐条执行命令:2. 安装Ubuntu(版本20.04 X64)依赖软件包。按照顺序逐条执行命令:3. 安装Python依赖包。按照顺序逐条执行命…

vscode 打代码光标特效

vscode 打代码光标特效 在设置里面找到settings 进入之后在代码最下方加入此代码 "explorer.confirmDelete": false,"powermode.enabled": true, //启动"powermode.presets": "fireworks", // 火花效果// particles、 simple-rift、e…

FFmpeg: 自实现ijkplayer播放器--04消息队列设计

文章目录 播放器状态转换图播放器状态对应的消息: 消息对象消息队列消息队列api插入消息获取消息初始化消息插入消息加锁初始化消息设置消息参数消息队列初始化清空消息销毁消息启动消息队列终止消息队列删除消息 消息队列,用于发送,设置播放…

eclipse导入maven项目与配置使用本地仓库

前言 本人润国外了,发现不能用收费软件IDEA了,需要使用eclipse,这个免费。 但是早忘了怎么用了,在此总结下。 一、eclipse导入本地项目 1.选这个:open projects from file system… 2.找到项目文件夹,…

如何编写易于访问的技术文档 - 最佳实践与示例

当你为项目或工具编写技术文档时,你会希望它易于访问。这意味着它将为全球网络上的多样化受众提供服务并可用。 网络无障碍旨在使任何人都能访问网络内容。设计师、开发人员和撰写人员有共同的无障碍最佳实践。本文将涵盖一些创建技术内容的最佳实践。 &#xff0…

KIVY 学习1

环境 python 3.6 3.7 对应Kivy 1.11.1版本各依赖 python -m pip install docutils pygments pypiwin32 kivy_deps.sdl20.1.22 kivy_deps.glew0.1.12 这是一个用于安装Python包的命令,它会安装一些特定的包。具体来说,这个命令会安装以下包: …

Python的国际化和本地化【第162篇—国际化和本地化】

👽发现宝藏 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 随着全球化的发展,多语言支持在软件开发中变得越来越重要。Python作为一种流行的…

Springboot+Vue项目-基于Java+Mysql的网上订餐系统(附源码+LW+演示录像)

大家好!我是程序猿老A,感谢您阅读本文,欢迎一键三连哦。 💞当前专栏:Java毕业设计 精彩专栏推荐👇🏻👇🏻👇🏻 🎀 Python毕业设计 &…

游戏实践:扫雷

一.游戏介绍 虽然很多人玩过这个游戏,但还是介绍一下。在下面的格子里,埋的有10颗雷,我们通过鼠标点击的方式,点出你认为不是雷的地方,等到把所有没有雷的格子点完之后,及视为游戏胜利。 上面的数字的意思…

Linux第90步_异步通知实验

“异步通知”的核心就是信号&#xff0c;由“驱动设备”主动报告给“应用程序”的。 1、添加“EXTI3.c” #include "EXTI3.h" #include <linux/gpio.h> //使能gpio_request(),gpio_free(),gpio_direction_input(), //使能gpio_direction_output(),gpio_get_v…

数据资产管理制度探索——浙江篇

在行政事业单位数据资产管理领域&#xff0c;浙江省以创新性思维与高质量发展的战略眼光&#xff0c;积极探索并构建了具有前瞻性和实效性的数据资产管理制度。作为财政部数据资产管理试点省份&#xff0c;浙江省财政厅与省标准化研究院强强联合&#xff0c;充分运用数据溯源、…

40.原子累加器

java8之后&#xff0c;新增了专门用于计数的类&#xff0c;LongAccumulator,LongAdder的性能高于AtomicLong。 LongAdder 性能 > AtomicLong 性能 性能高的原因&#xff1a;如果都往一个共享变量上面进行累加&#xff0c;那么比较重试的次数肯定就多&#xff1b;如果分成几…

KubeSphere中间件部署

中间件部署实战 语雀 RuoYi-Cloud部署实战 语雀 https://www.bilibili.com/video/BV13Q4y1C7hS?p79 应用部署三要素 应用的部署方式&#xff08;Deployment、StatefulSet、DaemonSet&#xff09; 应用的数据挂载&#xff08;数据、配置文件&#xff09; 应用的可访问性…

【AngularJs】前端使用iframe预览pdf文件报错

<iframe style"width: 100%; height: 100%;" src"{{vm.previewUrl}}"></iframe> 出现报错信息&#xff1a;Cant interpolate: {{vm.previewUrl}} 在ctrl文件中信任该文件就可以了 vm.trustUrl $sce.trustAsResourceUrl(vm.previewUrl);//信任…

二期 1.3 Spring Cloud Alibaba微服务组件Nacos注册中心介绍

文章目录 一、注册中心有什么用?二、注册中心对比三、Nacos是什么?3.1 Nacos 基本概念3.2 Nacos 主要功能3.3 Nacos 优势一、注册中心有什么用? 谈起微服务架构,总会提到注册中心,它是微服务架构必不可少的组件之一,那么注册中心作用到底是什么? 话说微服务架构下 服务…

AI大模型探索之路-应用篇9:Langchain框架LangSmith模块-AI模型监控神器

目录 前言 一、概述 二、准备工作 三、代码实践 二、监控查看 总结 前言 在经过前面多个篇章的学习后&#xff0c;我们已经了解到Langchain框架是一个为开发人员提供的全方位服务方案。从模型封装调用、提示词模板封装、Chain链式操作、检索增强&#xff0c;再到上线部署…