归并排序 Listnode* vector<int> vector<ListNode*>
加粗样式
ListNode* merge(ListNode* l1,ListNode* l2){ListNode* dummyhead=new ListNode(0);ListNode* cur=dummyhead;while(l1&&l2){if(l1->val>=l2->val){cur->next=l2;l2=l2->next;cur=cur->next;}else if(l1->val<l2->val){cur->next=l1;l1=l1->next;cur=cur->next;}}if(l1){cur->next=l1;}if(l2){cur->next=l2;}return dummyhead->next;}ListNode* fidmid(ListNode* head){ListNode* slow=head;ListNode* fast=head->next;while(fast&&fast->next){slow =slow->next;fast=fast->next->next;}return slow;}ListNode* sortList(ListNode* head) {if(!head||!head->next) return head;ListNode* mid = fidmid(head);ListNode* right1 = mid->next;mid->next = nullptr;ListNode* left=sortList(head);ListNode* right=sortList(right1);return merge(left,right);}
vector<ListNode*> 的归并排序(Merge Sort for Linked List Vector)
归并排序适用于链表,因为链表不支持随机访问,而归并排序的 合并(Merge)操作 仅使用 指针操作,无需额外存储,且时间复杂度为 O(N log K)(N 是总节点数,K 是链表个数)。
归并思路
- 使用分治法(Divide & Conquer):
- 递归地将
vector<ListNode*>分成两半,直到vector只剩一个链表。
- 递归地将
- 合并两个链表:
- 使用
mergeTwoLists()合并两个有序链表。
- 使用
C++ 实现
#include <iostream>
#include <vector>
using namespace std;// 定义链表节点
struct ListNode {int val;ListNode* next;ListNode(int x) : val(x), next(nullptr) {}
};class Solution {
public:// 合并两个有序链表ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {if (!l1) return l2;if (!l2) return l1;if (l1->val < l2->val) {l1->next = mergeTwoLists(l1->next, l2);return l1;} else {l2->next = mergeTwoLists(l1, l2->next);return l2;}}// 归并排序合并 K 个链表ListNode* mergeKLists(vector<ListNode*>& lists) {if (lists.empty()) return nullptr;return mergeKListsHelper(lists, 0, lists.size() - 1);}private:// 递归合并 `lists[left]` 到 `lists[right]`ListNode* mergeKListsHelper(vector<ListNode*>& lists, int left, int right) {if (left == right) return lists[left]; // 只有一个链表,直接返回if (left > right) return nullptr; // 无效区间,返回空int mid = left + (right - left) / 2; // 计算中点ListNode* l1 = mergeKListsHelper(lists, left, mid);ListNode* l2 = mergeKListsHelper(lists, mid + 1, right);return mergeTwoLists(l1, l2); // 合并左右两个部分}
};// 测试代码
void printList(ListNode* head) {while (head) {cout << head->val << " -> ";head = head->next;}cout << "NULL" << endl;
}int main() {// 创建多个升序链表ListNode* list1 = new ListNode(1);list1->next = new ListNode(4);list1->next->next = new ListNode(7);ListNode* list2 = new ListNode(2);list2->next = new ListNode(5);list2->next->next = new ListNode(8);ListNode* list3 = new ListNode(3);list3->next = new ListNode(6);list3->next->next = new ListNode(9);vector<ListNode*> lists = {list1, list2, list3};Solution solution;ListNode* mergedList = solution.mergeKLists(lists);cout << "合并后的链表: ";printList(mergedList);return 0;
}
代码解析
1️⃣ mergeTwoLists()
- 递归合并两个 有序链表,返回合并后的头节点。
- 时间复杂度 O(N)。
2️⃣ mergeKListsHelper()
- 递归分治
vector<ListNode*>:- 递归终止条件:当
left == right时,返回lists[left]。 - 分割:将
lists分为两部分,递归调用mergeKListsHelper()处理。 - 合并:使用
mergeTwoLists()合并两个部分。
- 递归终止条件:当
- 时间复杂度 O(N log K)。
3️⃣ mergeKLists()
- 入口函数,调用
mergeKListsHelper()处理lists[0] ~ lists[K-1]。
时间 & 空间复杂度分析
| 操作 | 时间复杂度 |
|---|---|
| 合并两个链表 | O(N) |
| 递归深度(log K 轮) | O(log K) |
| 总时间复杂度 | O(N log K) |
K是链表个数,N是所有节点数。log K轮,每轮 O(N) 操作,总复杂度 O(N log K)。
空间复杂度: O(log K)(递归栈)。
示例运行
输入
list1: 1 -> 4 -> 7 -> NULL
list2: 2 -> 5 -> 8 -> NULL
list3: 3 -> 6 -> 9 -> NULL
输出
合并后的链表: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> NULL
其他方法
🔹 方法 1:优先队列(最小堆)
利用 priority_queue 维护 K 个链表的头节点,弹出最小值插入结果链表。
ListNode* mergeKLists(vector<ListNode*>& lists) {auto cmp = [](ListNode* a, ListNode* b) { return a->val > b->val; };priority_queue<ListNode*, vector<ListNode*>, decltype(cmp)> pq(cmp);for (auto node : lists) if (node) pq.push(node);ListNode* dummyHead = new ListNode(0), *cur = dummyHead;while (!pq.empty()) {ListNode* tmp = pq.top(); pq.pop();cur->next = tmp; cur = cur->next;if (tmp->next) pq.push(tmp->next);}return dummyHead->next;
}
- 时间复杂度:O(N log K)
- 适用于:K 远小于 N
📌 归并 vs. 优先队列
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 递归归并(分治法) | O(N log K) | O(log K) | 适合 K 大,N 小 |
| 优先队列(堆排序) | O(N log K) | O(K) | 适合 K 小,N 大 |
总结
-
递归归并:
✅ 适用于 K 大、N 小的情况(如K > 10⁵)
✅ 递归深度 O(log K),空间占用少
✅ 时间复杂度 O(N log K),比顺序合并快 -
优先队列(堆):
✅ 适用于 K 小、N 大的情况(如K < 10⁴)
✅ 适合数据流场景,插入新链表时更快
❌priority_queue需要 O(K) 额外空间
🚀 一般情况推荐 优先队列方法,但如果 K 很大,递归归并更优!
C++ vector<int> 归并排序(Merge Sort)
归并排序是一种 分治算法(Divide and Conquer),主要步骤如下:
- 分解(Divide):
- 将
vector<int>拆分成 左右两个子数组,直到每个子数组长度为 1。
- 将
- 合并(Merge):
- 将两个已经排序的子数组 合并为一个有序数组。
1. 递归实现 vector<int> 归并排序
#include <iostream>
#include <vector>
using namespace std;// 合并两个有序子数组
void merge(vector<int>& arr, int left, int mid, int right) {vector<int> leftArr(arr.begin() + left, arr.begin() + mid + 1);vector<int> rightArr(arr.begin() + mid + 1, arr.begin() + right + 1);int i = 0, j = 0, k = left;while (i < leftArr.size() && j < rightArr.size()) {if (leftArr[i] <= rightArr[j]) {arr[k++] = leftArr[i++];} else {arr[k++] = rightArr[j++];}}// 复制剩余元素while (i < leftArr.size()) arr[k++] = leftArr[i++];while (j < rightArr.size()) arr[k++] = rightArr[j++];
}// 递归归并排序
void mergeSort(vector<int>& arr, int left, int right) {if (left < right) {int mid = left + (right - left) / 2;mergeSort(arr, left, mid); // 排序左半部分mergeSort(arr, mid + 1, right); // 排序右半部分merge(arr, left, mid, right); // 合并两个有序子数组}
}// 测试代码
int main() {vector<int> arr = {10, 7, 8, 9, 1, 5};mergeSort(arr, 0, arr.size() - 1);cout << "排序后数组: ";for (int num : arr) cout << num << " ";cout << endl;return 0;
}
2. 代码解析
(1)合并函数 merge()
leftArr和rightArr存储arr[left] ~ arr[mid]和arr[mid+1] ~ arr[right]。- 双指针法 依次取较小的元素,插入
arr[]。 - 复制剩余元素(如果
leftArr或rightArr还有未合并的部分)。
(2)递归排序 mergeSort()
- 递归地将数组拆分 直到只有一个元素(
left == right)。 - 然后合并 这些小数组,最终形成完整排序数组。
3. 时间 & 空间复杂度
| 情况 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 最优情况(Best Case) | O(n log n) | O(n) |
| 最坏情况(Worst Case) | O(n log n) | O(n) |
| 平均情况(Average Case) | O(n log n) | O(n) |
-
时间复杂度 O(n log n):
- 归并排序将数组不断二分,每次分割 O(log n)。
- 合并两个子数组 需要 O(n)。
- 总体复杂度是 O(n log n)。
-
空间复杂度 O(n):
- 临时数组存储左、右子数组,每轮合并占用 O(n) 额外空间。
4. 迭代实现(非递归)
如果想要 避免递归调用栈的额外空间开销,可以使用 迭代方式(非递归):
void mergeSortIterative(vector<int>& arr) {int n = arr.size();vector<int> temp(n);for (int size = 1; size < n; size *= 2) { // 控制子数组大小for (int left = 0; left < n - size; left += 2 * size) {int mid = left + size - 1;int right = min(left + 2 * size - 1, n - 1);// 合并int i = left, j = mid + 1, k = left;while (i <= mid && j <= right) {temp[k++] = (arr[i] <= arr[j]) ? arr[i++] : arr[j++];}while (i <= mid) temp[k++] = arr[i++];while (j <= right) temp[k++] = arr[j++];for (int x = left; x <= right; x++) arr[x] = temp[x]; // 复制回原数组}}
}
迭代思路
- 第一轮:合并 大小为 1 的子数组。
- 第二轮:合并 大小为 2 的子数组。
- 依次倍增,直到 整个数组排序完成。
复杂度
- 时间复杂度 O(n log n)。
- 空间复杂度 O(n)(需要额外
temp[])。
5. 适用场景
| 适用情况 | 不适用情况 |
|---|---|
| 数据量大,要求稳定排序 | 数据量小,快排更快 |
| 适用于链表排序(O(1) 额外空间) | 不适用于内存受限的场景(占用 O(n) 额外空间) |
| 适用于外部排序(磁盘存储数据排序) | 在数组排序时,快排通常更快 |
6. 归并排序 vs 快速排序
| 排序算法 | 时间复杂度 | 空间复杂度 | 是否稳定排序 | 适用场景 |
|---|---|---|---|---|
| 归并排序 | O(n log n) | O(n) | ✅ 稳定 | 适合链表、大数据排序 |
| 快速排序 | O(n log n)(最坏 O(n²)) | O(1) | ❌ 不稳定 | 适合一般数据排序,平均比归并快 |
7. 总结
-
归并排序特点
- 稳定排序:不会改变相同元素的相对顺序。
- 时间复杂度 O(n log n),优于 O(n²) 的排序算法(如冒泡、选择)。
- 空间复杂度 O(n),比快排 O(1) 更高。
-
何时使用归并排序?
- 链表排序(链表归并排序可以优化到 O(1) 额外空间)。
- 需要稳定排序(如数据库排序)。
- 大规模数据排序(外部排序)。
对于大多数数组排序任务,快速排序 通常更快且空间占用更少,但 归并排序在链表或外部排序中表现更优。
🚀 如果数据大且要求稳定排序,推荐归并排序;如果追求更快的排序速度,使用快速排序!
相关文章:
归并排序 Listnode* vector<int> vector<ListNode*>
加粗样式 ListNode* merge(ListNode* l1,ListNode* l2){ListNode* dummyheadnew ListNode(0);ListNode* curdummyhead;while(l1&&l2){if(l1->val>l2->val){cur->nextl2;l2l2->next;curcur->next;}else if(l1->val<l2->val){cur->nextl1…...
深度解析:大模型在多显卡服务器下的通信机制与分布式训练——以DeepSeek、Ollama和vLLM为例
一、引言:大模型与多显卡的必然结合 随着大模型参数规模突破千亿级(如GPT-4、DeepSeek),单显卡的显存容量与算力已无法满足需求。多显卡并行计算成为训练与推理的核心技术,其核心挑战在于高效通信与负载均衡。本文以国…...
鸿蒙NEXT应用App测试-专项测试(DevEco Testing)
注意:大家记得先学通用测试在学专项测试 鸿蒙NEXT应用App测试-通用测试-CSDN博客 注意:博主有个鸿蒙专栏,里面从上到下有关于鸿蒙next的教学文档,大家感兴趣可以学习下 如果大家觉得博主文章写的好的话,可以点下关注…...
达梦数据库学习笔记@1
目录 达梦数据库学习笔记一、表空间管理(一)默认表空间(二)相关数据字典(三)表空间操作(四)临时表空间管理 二、重做日志管理(一)系统视图(二&…...
设计模式| 观察者模式 Observer Pattern详解
目录 一、概述1.1 动机1.2 核心思想1.3 别名 二、角色与实现原理2.1 角色2.2 实现原理2.3 类图 三、经典接口实现3.1 示例3.1.1 观察者接口3.1.2 目标接口3.1.3 具体被观察者3.1.4 具体观察者3.1.5 Client3.1.6 UML时序图 3.2 特点 四、其他实现方式4.1 委托与事件(…...
时间转换(acwing)c/c++/java/python
读取一个整数值,它是工厂中某个事件的持续时间(以秒为单位),请你将其转换为小时:分钟:秒来表示。 输入格式 输入一个整数 NN。 输出格式 输出转换后的时间表示,格式为 hours:minutes:second…...
Rocky8 源码安装 HAProxy
HAProxy 是一款开源的高性能 负载均衡器 和 反向代理 软件,专注于处理高并发流量分发,广泛应用于企业级架构中提升服务的可用性、扩展性和安全性。 一、HAProxy 简介 1.1.HAProxy 是什么? 本质: 基于 C 语言开发 的轻量级工具&a…...
通过AI辅助生成PPT (by quqi99)
作者:张华 发表于:2025-02-23 版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明(http://blog.csdn.net/quqi99) 问题 媳妇需要将一个pdf文件中的某些部分做成PPT课件,我在想是…...
【从0做项目】Java文档搜索引擎(9)烧脑终章!
阿华代码,不是逆风,就是我疯 你们的点赞收藏是我前进最大的动力!! 希望本文内容能够帮助到你!! 目录 文章导读 零:项目结果展示 一:导入 二:问题引入 1:情…...
什么是 Cloud Studio DeepSeek ; 怎么实现Open WebUI快速体验
什么是 Cloud Studio DeepSeek ;怎么实现Open WebUI快速体验 一、概述 欢迎使用 Cloud Studio DeepSeek 工作空间!我们已为您预装并启动了以下服务,等待加载十几秒即可查看效果: Ollama 服务:支持通过 API 调用 DeepSeek 模型。 AnythingLLM 前端服务:提供交互式聊天界…...
rtconfig.cpython-313.pyc 在 .gitignore文件中写入 *.pyc 文件仍然没有被忽略?
在 .gitignore 文件中添加 *.pyc 和 *.*.pyc 规则时,如果 .pyc 文件仍然没有被忽略,可能有以下几种原因: 1. 已经被 Git 跟踪的文件 即使您在 .gitignore 中指定了忽略 .pyc 文件,Git 仍然会跟踪已经被提交到版本库中的文件。如…...
Linux 第二次脚本作业
1、需求:判断192.168.1.0/24网络中,当前在线的ip有哪些,并编写脚本打印出来。 2、设计一个 Shell 程序,在/userdata 目录下建立50个目录,即 user1~user50,并设置每个目录的权限,其中其他用户的权…...
mysql的源码包安装
安装方式一:(编译好的直接安装) 1.添加一块10G的硬盘,给root逻辑卷扩容 (下面安装方式二有,一模一样的装就行,我就不写了,再写的话篇幅就太长了) 2.下载编译好的源码包…...
《论面向对象的建模及应用》审题技巧 - 系统架构设计师
论面向对象的建模及应用写作框架 一、考点概述 本论题“论面向对象的建模及应用”主要考察软件测试工程师对面向对象建模技术的理解和应用能力。具体涵盖以下几个方面: 面向对象建模的基本概念 :这包括理解面向对象编程(OOP)的基…...
#渗透测试#批量漏洞挖掘#畅捷通T+远程命令执行漏洞
免责声明 本教程仅为合法的教学目的而准备,严禁用于任何形式的违法犯罪活动及其他商业行为,在使用本教程前,您应确保该行为符合当地的法律法规,继续阅读即表示您需自行承担所有操作的后果,如有异议,请立即停止本文章读。 目录 一、漏洞概况 二、攻击特征 三、应急处置…...
Sky Hackathon 清水湾的水 AI美食助手
这里写自定义目录标题 视频 视频 video...
【2024 CSDN博客之星】大学四年,我如何在CSDN实现学业与事业的“双逆袭”?
前言: Hello大家好,我是Dream。不知不觉2024年已经过去,自己也马上迈入23岁,感慨时间飞快,从19岁刚入大学加入CSDN,到现在大学毕业已经整整四年了。CSDN陪伴我走过了最青涩的四年大学时光,在这里…...
《AI赋能星际探索:机器人如何开启宇宙新征程!》
在人类对宇宙无尽的探索中,空间探索任务始终充满挑战。从遥远星球的探测,到空间站的维护,每一项任务都需要高精度、高可靠性的操作。人工智能(AI)的迅猛发展,为空间探索机器人带来了革命性的变革࿰…...
06排序 + 查找(D1_排序(D1_基础学习))
目录 学习预热:基础知识 一、什么是排序 二、为什么要排序 三、排序的稳定性 四、排序稳定性的意义 五、排序分类方式 方式一:内外分类 方式二:比较分类 六、排序算法性能评估 1. 算法的时间复杂度 2. 算法的空间复杂度 七、知识小…...
【数据挖掘】深度挖掘
【数据挖掘】深度挖掘 目录:1. 减少样本集的数量知识点示例 2. 对噪声比集剪枝知识点示例建立局部树代码示例(使用 Python 和 scikit - learn 库构建局部决策树)代码解释注意事项 最大超平面定义原理求解方法代码示例(使用 Python…...
【Linux】基于UDP/TCP套接字编程与守护进程
目录 一、网路套接字编程 (一)基础概念 1、源IP地址与目的IP地址 2、端口号 3、TCP与UDP 4、网络字节序 (二)套接字编程接口 1、socket 常见API 2、sockaddr结构 (三)UDP套接字 1、UDP服务器创建…...
C++跳表实现,封装成Skiplist类
跳表 (Skip List) 是由 William Pugh 发明的一种查找数据结构,支持对数据的快速查找,插入和删除。 跳表的期望空间复杂度为O(n) ,跳表的查询,插入和删除操作的期望时间复杂度都为O(logn)。 算法讲解149【扩展】有序表专题2-跳表_哔…...
探索与Cursor协作创建一个完整的前后端分离的项目的最佳实践
探索与Cursor协作创建一个完整的前后端分离的项目的最佳实践 Cursor简介 Cursor在目前代表了AI编程技术的顶峰。在一定程度上可以说是当今AI时代的最强生产力代表。为此,不惜重金开了年费会员来紧跟时代步伐。当然cline、roo code、trae等开源或者免费产品也在紧追不舍。 C…...
【uni-app】对齐胶囊容器组件
代码碎片 <template><div><view :style"{ height: ${statusBarHeight}px }"></view><viewclass"":style"{height: ${menuButtonHeight menuButtonPadding * 2}px,width: ${menuButtonInfo.left}px,}"><slot …...
podman加速器配置,harbor镜像仓库部署
Docker加速器 registries加速器 [rootlocalhost ~]# cat /etc/redhat-release CentOS Stream release 8 [rootlocalhost ~]# cd /etc/containers/ [rootlocalhost containers]# ls certs.d policy.json registries.conf.d storage.conf oci registries.conf re…...
计算机毕业设计SpringBoot+Vue.jst0甘肃非物质文化网站(源码+LW文档+PPT+讲解)
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
使用Python脚本转换YOLOv5配置文件到https://github.com/ultralytics/ultralytics:一个详细的指南
在深度学习领域,YOLO(You Only Look Once)系列模型因其高效和准确性而广受欢迎。然而,随着项目需求的变化,有时我们需要对预训练模型的配置文件进行调整。本文将详细介绍如何使用Python脚本自动转换YOLOv5的配置文件到…...
简识Kafka集群与RocketMQ集群的核心区别
前记:各位潘安、各位子健/各位彦祖、于晏,文字较多,优先看目录。 Kafka集群与RocketMQ集群的核心区别及架构图例说明 一、核心区别对比 特性Kafka 集群RocketMQ 集群设计目标高吞吐量实时日志流系统(如日志收集、大数据流水线&a…...
制造行业CRM选哪家?中大型企业CRM选型方案
在当今竞争激烈的制造行业中,企业对于客户关系管理(CRM)系统的需求日益增强,高效、智能的CRM系统已成为推动企业业务增长、优化客户体验的关键。在制造业 CRM 市场中,纷享销客和销售易都备受关注,且各自有着…...
R 语言科研绘图 --- 散点图-汇总
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...
