【数据结构与算法】单链表的综合运用:1.合并两个有序链表 2.分割链表 3.环形链表的约瑟夫问题

【数据结构与算法】单链表的综合运用:1.合并两个有序链表 2.分割链表 3.环形链表的约瑟夫问题
在这里插入图片描述
🔥小龙报:个人主页
🎬作者简介:C++研发,嵌入式,机器人等方向学习者
❄️个人专栏:《C语言》《【初阶】数据结构与算法》
永远相信美好的事情即将发生
在这里插入图片描述

文章目录


前言

链表是C语言数据结构的核心内容,也是算法面试的高频考点,其灵活的指针操作与逻辑构建对编程思维要求颇高。本文聚焦链表经典实操题型,从合并有序链表、分割链表到环形链表约瑟夫问题,由浅入深拆解解题思路,结合哨兵位、循环计数等实用技巧,通过清晰的算法原理与完整代码实现,帮读者吃透链表操作逻辑,夯实数据结构基础。

一、合并两个有序链表

1.1题目

链接:合并两个有序链表

在这里插入图片描述

1.2 算法原理

核心:判断大小 + 链表为插 + 建立一个哨兵位
和合并两个有序数组的思路一样,定义四个指针
newhead和newtail:新链表的头尾节点
pcur1和pcur2:分别指向两个要合并的链表的头节点
技巧:可以malloc一块空间让newhead和newtail指向这块空间,使其成为首元节点(哨兵位),这样在插入时可以免去链表为空情况的判断,最后返回新链表的下一个节点即可。
哨兵位:数值域不存储有效数据,指针域存储有效地址

1.3代码

/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ typedef structListNode ListNode;structListNode*mergeTwoLists(structListNode* list1,structListNode* list2){ ListNode* newhead =(ListNode*)malloc(sizeof(ListNode)); ListNode* pcur1 = list1;; ListNode* pcur2 = list2; ListNode*newtail = newhead; newtail->next = NULL;while(pcur1 && pcur2){if(pcur1->val <= pcur2->val){ newtail->next = pcur1; newtail = newtail->next; pcur1 = pcur1->next;}else{ newtail->next = pcur2; newtail = newtail->next; pcur2 = pcur2->next;}}//为遍历完的链表直接接上新链表尾部节点if(pcur1) newtail->next = pcur1;if(pcur2) newtail->next = pcur2;return newhead->next;}

注:大家也可以free掉我们手动开辟的节点空间来养成良好的编程习惯,因为这是算法题,在程序执行完后会会自动回收笔者主要讲解思路,就不主动free但大家在写工程时不要的空间要及时释放养成良好编程习惯

二、分割链表

2.1题目

链接:分割链表

在这里插入图片描述

2.2 算法原理

创建两个新链表,list1和list2分别存放小于x和大于或等于x的节点,最后让list1的尾指针指向list2的第一个元素即可。
:list2作为新链表的后半部分,最后一个节点的next要及时置NILL,防止死循环
技巧:依旧可以使用哨兵位,并把哨兵位的next初始化为NULL可以避免合并时一条链表为空的特殊判断造成要写大量特判代码。

2.3代码

/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ typedef structListNode ListNode;structListNode*partition(structListNode* head,int x){if(head == NULL)return head;//指向小于x链表的头节点 ListNode* list1 =(ListNode*)malloc(sizeof(ListNode)); ListNode* tail1 = list1;//指向大于x链表的头节点 ListNode* list2 =(ListNode*)malloc(sizeof(ListNode)); ListNode* tail2 = list2;//防止单个节点链表出现非法解引用 list1->next = NULL; list2->next = NULL; ListNode* pcur = head;while(pcur){if(pcur->val < x){ tail1->next = pcur; tail1 = tail1->next;}else{ tail2->next = pcur; tail2 = tail2->next;} pcur = pcur->next;} tail1->next = list2->next;//防止死循环 tail2->next = NULL;return list1->next;}

注:大家也可以free掉我们手动开辟的节点空间来养成良好的编程习惯,因为这是算法题,在程序执行完后会会自动回收笔者主要讲解思路,就不主动free但大家在写工程时不要的空间要及时释放养成良好编程习惯

三、环形链表的约瑟夫问题

3.1题目

链接:环形链表的约瑟夫问题

在这里插入图片描述

3.2 算法原理

核心:利用循环链表 + 循环计数
定义一个指针v指尾节点,一个指针指向头结点,利用一个变量s来循环技术,当s == m时让h指向的当前元素出队即可

在这里插入图片描述

3.3代码

typedef structListNode ListNode;//创建节点 ListNode*BuyNode(int x){ ListNode* newnode =(ListNode*)malloc(sizeof(ListNode)); newnode->val = x; newnode->next = NULL;return newnode;}//创建环形链表 ListNode*CyclicList(int n){ ListNode* head =BuyNode(1); ListNode* tail = head;for(int i =2;i <= n;i++){ tail->next =BuyNode(i); tail = tail->next;} tail->next = head;return tail;}intysf(int n,int m ){ ListNode* prev =CyclicList(n); ListNode* phead = prev->next;int count =1;//计数while(phead != prev){if(count == m){ prev->next = phead->next;free(phead); phead = prev->next; count =1;}else{ prev = phead; phead = phead->next; count++;}}return phead->val;}

总结与每日励志

✨本文系统讲解了链表操作的三大经典题型:合并有序链表通过哨兵位简化插入逻辑,分割链表采用双链表分类处理,环形链表约瑟夫问题巧妙结合循环计数。每种解法均配有清晰的算法图解和完整代码实现,帮助读者深入理解链表操作的核心逻辑与实用技巧。掌握这些题型不仅能提升面试竞争力,更能培养扎实的数据结构思维。坚持每日练习,保持对算法的热情与专注,终将在编程之路上收获成长与突破。永远相信美好的事情即将发生!

在这里插入图片描述

Read more

Python 进程池(ProcessPoolExecutor)全面使用教程

Python 进程池(ProcessPoolExecutor)全面使用教程

一、进程池概述 进程池(ProcessPoolExecutor)是 Python 中用于并行执行任务的强大工具,尤其适合CPU密集型操作。与传统的多进程编程相比,它提供了更简单、更高级的接口。 适用场景: 1. CPU密集型任务(数学计算、图像处理等) 2. 需要并行处理独立任务的情况 3. 需要限制并发进程数量的场景 4. 需要获取任务执行结果的场景 二、基本使用 from concurrent.futures import ProcessPoolExecutor import time # CPU密集型计算函数defcalculate_square(n):print(f"计算 {n} 的平方...") time.sleep(1)# 模拟耗时计算return n * n # 使用进程池with ProcessPoolExecutor(max_

By Ne0inhk
Python的前世今生,如何与AI大模型结下不解之缘

Python的前世今生,如何与AI大模型结下不解之缘

从一门诞生于圣诞假期的“小众脚本语言”,到如今AI大模型时代的“第一开发语言”,Python走过了三十余年的迭代之路。 这段跨越三十余年的征程,既是Python“顺势而为、自我革新”的成长史,更是其与时代需求同频共振的必然。而它与AI大模型结下的不解之缘,从来都不是偶然相遇,而是Python自身的核心特性,与AI大模型开发需求深度契合、双向奔赴的结果——正是这份契合,让Python在AI浪潮中脱颖而出,成为大模型开发的首选语言。 一、Python的前世:偶然诞生,默默沉淀 1989年末,荷兰程序员吉多·范罗苏姆在阿姆斯特丹的研究中心,为解决当时ABC语言扩展性不足、脚本编写繁琐的痛点,闲暇时敲出了Python的第一行代码,语言命名源于他喜爱的电视喜剧《巨蟒剧团之飞翔的马戏团》。 1991年,Python 0.9正式发布,自带列表、字典等核心数据结构与异常处理功能;2000年Python 2.0推出,新增Unicode支持与垃圾回收优化;2008年Python 3.0发布,清理冗余代码、完善Unicode支持,完成向现代编程语言的跨越;此后数十年持续迭代,截至2023年,Pyth

By Ne0inhk
【面试专栏|JVM虚拟机】JVM垃圾回收入门:对象死亡判断的底层逻辑

【面试专栏|JVM虚拟机】JVM垃圾回收入门:对象死亡判断的底层逻辑

🍃 予枫:个人主页 📚 个人专栏: 《Java 从入门到起飞》《读研码农的干货日常》《Java 面试刷题指南》 💻 Debug 这个世界,Return 更好的自己! 引言 作为Java程序员,JVM垃圾回收机制是面试绕不开的高频考点,而“如何判断对象死亡”更是基础中的基础。很多人只记结论却不懂底层逻辑,面试时被面试官追问就慌了阵脚。本文将从原理出发,拆解对象死亡的2种核心判断方式,补充面试高频追问,帮你吃透考点、轻松应答,建议收藏备用! 文章目录 * 引言 * 一、为什么要判断对象死亡? * 二、对象死亡判断的2大核心机制 * 2.1 引用计数法(已淘汰) * 原理 * 示例(伪代码) * 致命缺陷:循环引用问题 * 2.2 可达性分析算法(主流核心) * 原理 * 第一步:明确GC Roots的组成(面试高频考点)

By Ne0inhk