C语言中的数据结构--链表的应用2(3)

前言

上一节我们学习了链表的应用,那么这一节我们继续加深一下对链表的理解,我们继续通过Leetcode的经典题目来了解一下链表在实际应用中的功能,废话不多说,我们正式进入今天的学习

单链表相关经典算法OJ题4:合并两个有序链表

https://leetcode.cn/problems/merge-two-sorted-lists/description/

题目详情

该题目与顺序表中的合并两个有序的数组的题目比较类似

题解

我们需要先创建一个新链表,用newHead和newTail分别指向链表的头和尾,再定义两个指针,l1指针用于第一个链表,l2指针用于第二个链表;

我们用l1指针指向的节点的数据大小和l2指针指向的节点的数据大小作比较,若

1.l1指向的节点的数据大于l2指向的节点的数据,则把l2指针指向的节点拿下来到一个新的链表尾插,再让l2指针执行++操作

2..l2指向的节点的数据大于l1指向的节点的数据,则把l1指针指向的节点拿下来到一个新的链表尾插,再让l1指针执行++操作

总的来说就是谁小就拿谁

在我们遍历原链表的过程中,会存在两种结果

1.l1为空,l2不为空

2.l2为空,l1不为空

我们在把第一个节点拿到新链表的时候,newHead和newTail都指向这一个节点,在这种情况下,该节点既为头也为尾

在我们继续向后拿入节点的时候,我们让newTail指向当前节点的位置,而newHead指针保持不动

以此类推,继续循环此操作,直到跳出循环,此时l1或者l2中的一个指针必定是走向了NULL指针

因为链表是升序的,我们此时只需要把没有走完的那个链表的剩余元素全部尾插到新链表就完成任务了

因为原链表可以为空。如果两个链表中的某个链表为空的话,可能就会存在对空指针的解引用,所以我们需要考虑空链表的问题;

根据上述我们梳理的条件,我们可以写出代码如下:

#define _CRT_SECURE_NO_WARNINGS 1

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) 
{
	//判空
	if (list1 == NULL)
	{
		return list2;
	}
	if (list2 == NULL)
	{
		return list1;
	}
	
	ListNode* l1 = list1;
	ListNode* l2 = list2;
	//创建的新链表
	ListNode* newHead, * newTail;
	newHead = newTail = NULL;

	while (l1 && l2)
	{
		if (l1->val < l2->val)
		{
			//尾插l1
			if (newHead == NULL)
			{
				newHead = newTail = l1;
			}
			
			else
			{
				newTail->next = l1;
				newTail = newTail->next;
			}
			l1 = l1->next;
		}
		
		else
		{
			//尾插l2
			if (newHead == NULL)
			{
				newHead = newTail = l2;
			}
			else
			{
				newTail->next = l2;
				newTail = newTail->next;
			}
			l2 = l2->next;
		}
	}
	//跳出循环后有两种情况:要么l1为空,要么l2为空
	if (l2)
	{
		newTail->next = l2;
	}
	if (l1)
	{
		newTail->next = l1;
	}
	return newHead;
}

我们此时在Leetcode的官网运行一下看看是否编写成功:

代码运行没有出现错误,编写成功

优化

我们重新审视一下代码,我们发现存在重复判断,我们每次拿下来节点的时候都需要对判断该节点是不是第一个节点的操作有些麻烦,会拖累程序的运行时间:

if (newHead == NULL)
{
	newHead = newTail = l1;
}

我们有没有什么办法优化一下呢?

这里代码存在重复的原因是因为:新链表是空链表,我们能不能让新链表不是空链表呢?这样就不需要重复的判断了,直接进行尾插就好了

因为刚开始创建新链表的时候,我们把newHead和newTail都设置成了空指针,此时我们可以让它动态申请一个空间,我们在这个新空间里不存储任何的数据

newHead = newTail = (ListNode*)malloc(sizeof(ListNode));

此时的链表就不为空了,头尾指针都指向了一个有效的节点,只不过该节点里面没有存储任何的数据

该节点实际上是链表分类中的一种,叫做带头链表

在两个链表合并结束了以后,我们只需要将newHead的下一个节点返回就行就行

在进行优化后我们的代码如下:

#define _CRT_SECURE_NO_WARNINGS 1

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) 
{
	//判空
	if (list1 == NULL)
	{
		return list2;
	}
	if (list2 == NULL)
	{
		return list1;
	}
	
	ListNode* l1 = list1;
	ListNode* l2 = list2;
	//创建的新链表
	ListNode* newHead, * newTail;
	newHead = newTail = (ListNode*)malloc(sizeof(ListNode));

	while (l1 && l2)
	{
		if (l1->val < l2->val)
		{
			//尾插l1
			newTail->next = l1;
			newTail = newTail->next;
			l1 = l1->next;
		}

		else
		{
			//尾插l2
			newTail->next = l2;
			newTail = newTail->next;
			l2 = l2->next;
		}
	}
	//跳出循环后有两种情况:要么l1为空,要么l2为空
	if (l2)
	{
		newTail->next = l2;
	}
	if (l1)
	{
		newTail->next = l1;
	}
	ListNode* ret = newHead->next;
	free(newHead);
	return ret;
}

我们再检验一下代码是否正确:

代码没有错误,优化成功

循环链表经典应用-环形链表的约瑟夫问题

我们首先需要知道循环链表是什么东西

我们知道,链表的最后一个节点指向的是空指针NULL,而循环链表则不同,循环链表的最后一个节点指向的是该链表的第一个节点,这样就让链表成为了一个闭环

故事

著名的Josephus问题:据说著名犹太历史学家 Josephus 有过以下的故事:在罗⻢⼈占领乔塔帕特后,39个犹太⼈与 Josephus及他的朋友躲到⼀个洞中,39个犹太⼈决定宁愿死也不要被⼈抓到,于是决定了⼀个⾃杀⽅式,41个⼈排成⼀个圆圈,由第1个⼈开始报数,每报数到第3⼈该⼈就必须⾃杀,然后再由下⼀ 个重新报数,直到所有⼈都⾃杀⾝亡为⽌。 然⽽Josephus和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与⾃⼰安排在 第16个与第31个位置,于是逃过了这场死亡游戏

题目详情

题解

我们首先需要定义两个指针prev和pcur,其中我们把pcur指针放在第一个节点的位置,让pcur指针去遍历链表,我们把prev指针放到循环链表的最后一个节点,prev指针也随着pcur指针的遍历而向后走,prev指针始终在pcur指针的后一位。(设m=5,n=2)

我们在遍历循环链表的时候,若是找到了顺序为n的节点,我们不能直接释放掉这一个节点,因为要是直接释放掉这个节点,那么顺序为n-1的节点就无法再找到顺序为n+1的节点并且连接起来。

当pcur指针找到了第n个节点时,我们让prev->next指向pcur的下一个节点,然后再把pcur释放掉。此时pcur已经变成了一个野指针,我们现在将pcur赋予原pcur的下一个结点的地址

此时我们重新计数,当pcur再次找到顺序为n的节点时,重复执行之前的操作

以此类推,直到原链表里面的节点个数小于n

此时3的next指针指向了它本身

步骤1:创建带环链表

我们需要先创建头节点,然后根据步骤依次向下创建新的节点,我们首先封装一个函数应用于创建节点,该代码在链表专题中提及过,所以不再做过多的讲解:

//创建节点
ListNode* buyNode(int x)
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (node == NULL)
	{
		exit(1);
	}
	node->val = x;
	node->next = NULL;
	return node;
}

我们封装完创建节点的函数以后,紧接着封装一个创建带环链表的函数:

//创建带环链表
ListNode* creatCircle(int n)
{
	//创建第一个节点
	ListNode* phead = buyNode(1);
	ListNode* ptail = phead;
	//向后创建
	for (int i = 2; i <= n; i++)
	{
		ptail->next = buyNode(i);
		ptail = ptail->next;
	}
	//连接为闭环
	ptail->next = phead;
	return ptail;
}

在该函数中,我们需要返回的是尾节点,因为若是n=1,则需要找到头节点的前一个元素;如果返回的是头节点,那么将找不到头节点之前的尾节点;

步骤2:遍历链表并且计数

在创建完带环链表之后我们需要遍历链表并且计数,此时我们需要创建一个count变量,count变量应该初始化为1

当count的取值等于n的时候,我们此时需要销毁pcur节点

在销毁pcur之前,我们需要先把prev和pcur指向的节点的下一个节点连接起来

根据这些推论,我们可以写出代码如下:

int ysf(int n, int m)
{
	//根据n创建带环链表
	ListNode* prev = creatCircle(n);
	ListNode* pcur = prev->next;
	int count = 1;
	while (pcur->next != pcur)
	{
		if (count == m)
		{
			//销毁pcur节点
			prev->next = pcur->next;
			free(pcur);
			pcur = prev->next;
			count = 1;
		}
		else
		{
			//此时不需要销毁节点
			prev = pcur;
			pcur = pcur->next;
			count++;
		}

	}
	return pcur->val;
}

此时我们的任务就完成了,我们看看代码是否正确:

代码成功运行

该题思路上难度并不大。但是我们要注意细节,不然很有可能会出现错误

单链表相关经典算法OJ题5:分割链表

https://leetcode.cn/problems/partition-list-lcci/

题目详情

题解

方案一:(在原链表上进行修改)

我们定义一个指针pcur指向第一个节点,我们从第一个节点开始遍历;

如果我们遍历到小于3的节点,那么该节点就在原地保持不动;

若是遍历到了大于或者等于3的节点,那么我们先将该节点的前一个节点和后一个节点连接起来,再把该节点尾插到链表的末端;

当我们遍历完全链表的时候,我们的任务也就完成了;

因为该方法需要频繁的将节点断开、连接,而且需要使用多个指针(prev、pcur、ptail、phead)所以实现起来有点麻烦,一般不太推荐使用该方法

方案二:(在新链表上进行修改)

我们先定义pcur指针用来遍历原链表

我们动态申请一个哨兵位,并且定义两个变量newHead和newTail,将这两个变量都指向哨兵位;

若是pcur遍历的第一个节点的值小于x,则把该节点插到哨兵位后面,并且将newTail移动到该节点处;

若是pcur遍历的节点的值小于x,则把该节点头插到新链表中去;

若是pcur遍历的节点的值大于x,则把该节点尾插到新链表中去;

因为题目中说明了我们不需要保留每个分区中各个结点的初始相对位置,所以节点与节点之间的位置可以是随意的;

方案三:(小链表和大链表)

我们创建两个新链表,一个链表仅存放比x小的节点,另外一个链表仅存放比x大的节点;

在小链表中,我们创建两个变量lessHead和lessTail表示小链表的头和尾,我们此时动态申请一个哨兵位,里面不存放任何有效的值,若是原链表中的节点小于x,则该节点直接尾插到小链表中,并且让lessTail指向这个新插入的节点;

在大链表中,我们创建两个变量greaterHead和greaterTail表示小链表的头和尾,我们此时动态申请一个哨兵位,里面不存放任何有效的值,若是原链表中的节点大于x,则该节点直接尾插到小链表中,并且让greaterTail指向这个新插入的节点;

我们遍历完全链表时,大链表和小链表都已经全部插入完成了;

因为大小链表可以为空,我们需要进行判空操作,避免存在对空指针的解引用

我们将小链表的尾节点和大链表的第一个有效的节点(不能和大链表的哨兵位连接在一起)首尾相连,这样我们就完成了任务;

首尾相连以后,如果大链表的尾节点不是原链表的尾节点,那么大链表尾节点指向的节点一定是一个小链表之中的节点,在我们代码运行的时候,大链表最后一个节点本来是应该指向空指针的,但是它不是原链表中的尾节点,例如代码示例中的5,它会指向小链表中的2,此时代码就会进入死循环,所以我们要考虑大链表的尾节点的指向是哪里,要手动把大链表尾节点指向NULL

我们再来考虑一下特殊情况:

若是原链表中只有一个数据1,那么大链表里面仅仅只有一个哨兵位,我们同样按步骤的思路去思考,发现仍然满足题意,所以代码不用进行修改

下面我们来试着实现该代码:

typedef struct ListNode ListNode;

struct ListNode* partition(struct ListNode* head, int x) 
{
	//判空
	if (head == NULL)
	{
		return head;
	}
	//创建两个带头链表
	ListNode* lessHead, * lessTail;
	ListNode* greaterHead, * greaterTail;
	lessHead = lessTail = (ListNode*)malloc(sizeof(ListNode));
	greaterHead = greaterTail = (ListNode*)malloc(sizeof(ListNode));
	//遍历原链表,将原链表的节点尾插到大小链表中
	ListNode* pcur = head;
	while (pcur)
	{
		if (pcur->val < x)
		{
			//尾插到小链表
			lessTail->next = pcur;
			lessTail = lessTail->next;
		}
		else
		{
			//尾插到大链表中
			greaterTail->next = pcur;
			greaterTail = greaterTail->next;
		}
		pcur = pcur->next;
	}
	//处理大链表尾节点next指针指向+next指针初始化
	greaterTail->next = NULL;
	//小链表大链表首尾相连
	lessTail->next = greaterHead->next;
	//释放
	ListNode* ret = lessHead->next;
	free(lessHead);
	free(greaterHead);
	lessHead = greaterHead = NULL;
	return ret;
}

我们试着运行一下代码:

代码成功运行,编写成功

结尾

本节我们同样是学习链表的应用,通过这两节的学习,我们对链表的理解更加深入了,那么关于链表的所有内容到了本节为止就暂时告一段落了,下一节我们将学习双向链表,谢谢您的浏览!!!

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

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

相关文章

【分享】各大框架都在使用的Unsafe类

前言 几乎每个使用 Java开发的工具、软件基础设施、高性能开发库都在底层使用了sun.misc.Unsafe&#xff0c;比如Netty、Cassandra、Hadoop、Kafka等。 Unsafe类在提升Java运行效率&#xff0c;增强Java语言底层操作能力方面起了很大的作用。但Unsafe类在sun.misc包下&#x…

大型语言模型有什么用?

大型语言模型有什么用&#xff1f; 大型语言模型识别、总结、翻译、预测、生成文本和其他内容。 AI 应用程序正在总结文章、撰写故事和进行长时间对话——而大型语言模型正在承担繁重的工作。 大型语言模型或 LLM 是一种深度学习算法&#xff0c;可以根据从海量数据集中获得…

linux进阶篇:下载工具wget的安装以及应用

1 wget工具介绍 wget是一个下载文件的工具&#xff0c;它用在命令行下。对于Linux用户是必不可少的工具&#xff0c;我们经常要下载一些软件或从远程服务器恢复备份到本地服务器。 wget支持HTTP&#xff0c;HTTPS和FTP协议&#xff0c;可以使用HTTP代理。所谓的自动下载是指&a…

【C语言基础】:文件操作详解(后篇)

文章目录 一、文件的顺序读写1.1 顺序函数读写函数介绍1.2 fgetc函数和fputc函数1.3 fputs函数和fgets函数1.4 fprintf函数和fscanf函数1.5 fwrite函数和fread函数 二、文件的随机读写2.1 fseek函数2.2 ftell函数2.3 rewind函数 三、文件读取结束的判定3.1 feof函数 四、文件缓…

Linux mmap

目录 内存映射概念&#xff1a; 函数原型&#xff1a; 内存映射步骤&#xff1a; 主要功能 &#xff1a; 系统调用mmap用于共享内存的两种方式&#xff1a; 使用普通文件提供的内存映射&#xff1a; 使用特殊文件提供匿名内存映射&#xff1a; 注意事项 &#xff1a; …

【漏洞复现】潍微科技-水务信息管理平台 ChangePwd SQL注入漏洞

0x01 产品简介 潍微科技-水务信息管理平台主要帮助水务企业实现水质状态监测、管网运行监控、水厂安全保障、用水实时监控以及排放有效监管,确保居民安全稳定用水、环境有效保护,全面提升水务管理效率。 0x02 漏洞概述 潍微科技-水务信息管理平台 ChangePwd 接口存在SQL注…

YOLOv8改进 | 检测头篇 | 自研超分辨率检测头HATHead助力超分辨率检测(混合注意力变换器检测头)

一、本文介绍 本文给大家带来的改进机制是由由我本人利用HAT注意力机制&#xff08;超分辨率注意力机制&#xff09;结合V8检测头去掉其中的部分内容形成一种全新的超分辨率检测头。混合注意力变换器&#xff08;HAT&#xff09;的设计理念是通过融合通道注意力和自注意力机制…

Windows Edge 兼容性问题修复:提升用户体验的关键步骤

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

【随笔】Git 基础篇 -- 远程仓库 git clone(二十五)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

FTP所有操作

产生告警原理&#xff1a; 告警中出现 FTP STOP 关键字。Hawkeye keylogger木马名

iPad 无法解锁?修复 iPad 滑动解锁不起作用的 9 个解决方案

“我的 iPad Pro 一整天都工作正常&#xff0c;直到 20 分钟前。当我解锁它时&#xff0c;它不让我向上滑动。屏幕有响应&#xff0c;但我的 iPad 无法解锁。是否有其他人遇到过这种情况并找到了解决方法&#xff1f;解决方案&#xff1f;” ——来自 Apple 支持社区 iPad 屏幕…

前端开发攻略---根据音频节奏实时绘制不断变化的波形图。深入剖析如何通过代码实现音频数据的可视化。

1、演示 2、代码分析 逐行解析 JavaScript 代码块&#xff1a; const audioEle document.querySelector(audio) const cvs document.querySelector(canvas) const ctx cvs.getContext(2d)这几行代码首先获取了 <audio> 和 <canvas> 元素的引用&#xff0c;并使用…

Quartz + SpringBoot 实现分布式定时任务

文章目录 前言一、分布式定时任务解决方案二、Quartz是什么&#xff1f;1.quartz简介2.quartz的优缺点 二、Quartz分布式部署总结 前言 因为应用升级&#xff0c;由之前的单节点微服务应用升级为集群微服务应用&#xff0c;所以之前的定时任务Spring Scheduled不再适用了&…

进程等待waitwaitpid

文章目录 进程等待进程等待的必要性进程等待的方法waitwaitpidstatus 非阻塞等待 进程等待 任何子进程&#xff0c;在退出的情况下&#xff0c;一般必须要被父进程等待 进程等待的必要性 1.父进程通过等待&#xff0c;解决子进程退出的僵尸问题&#xff0c;回收系统资源。 2.…

基于springboot实现知识管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现知识管理系统演示 摘要 随着信息互联网信息的飞速发展&#xff0c;无纸化作业变成了一种趋势&#xff0c;针对这个问题开发一个专门适应师生作业交流形式的网站。本文介绍了知识管理系统的开发全过程。通过分析企业对于知识管理系统的需求&#xff0c;创建了…

012:vue结合纯CSS实现蛇形流程图/步骤条

文章目录 1. 实现效果2. 实现代码 1. 实现效果 2. 实现代码 <template><div class"container"><div v-for"(item, index) in list" class"grid-item"><div class"step">step{{index1}}</div></div&…

大厂Java笔试题之对完全数的处理

题目&#xff1a;完全数&#xff08;Perfect number&#xff09;&#xff0c;又称完美数或完备数&#xff0c;是一些特殊的自然数。 它所有的真因子&#xff08;即除了自身以外的约数&#xff09;的和&#xff08;即因子函数&#xff09;&#xff0c;恰好等于它本身。 例如&…

赋能力量,幸福花开 ——罗湖区懿米阳光开启全职妈妈社工培育计划

最美人间四月天&#xff0c;不负春光不负卿。 四月&#xff0c;迎来了全国社会工作师考试报名的日子&#xff0c;罗湖区全职妈妈妇联与罗湖区阳光妈妈妇联在服务过程中发现&#xff0c;全职妈妈们有获得社会工作师职业资格证的需求&#xff0c;为了更好地针对这一需求&#xf…

YOLOv5原创优化 : loss优化 | 一种新的自适应阈值焦点损失函数loss,增强目标特征,助力红外小目标暴力涨点

💡💡💡问题点:注意到红外小目标图像中目标与背景之间存在极大的不平衡,这使得模型更加关注背景特征而不是目标特征 💡💡💡解决对策:提出了一种新的自适应阈值焦点损失函数,该函数将目标和背景解耦,并利用自适应机制来调整损失权重,迫使模型将更多的注意力分配…

vs调试教程

官网链接&#xff1a; Microsoft Learn&#xff1a;培养开拓职业生涯新机遇的技能通过文档和培训习得技术技能、获得认证并与社区建立联系https://learn.microsoft.com/zh-cn/调试教程 调试器文档 - Visual Studio (Windows) | Microsoft Learn浏览文档&#xff0c;以帮助你使…