归并排序 Listnode* vector<int> vector<ListNode*>

news2025/2/24 23:53:15

加粗样式

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 是链表个数)。

归并思路

  1. 使用分治法(Divide & Conquer)
    • 递归地将 vector<ListNode*> 分成两半,直到 vector 只剩一个链表。
  2. 合并两个链表
    • 使用 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),主要步骤如下:

  1. 分解(Divide)
    • vector<int> 拆分成 左右两个子数组,直到每个子数组长度为 1。
  2. 合并(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()
  • leftArrrightArr 存储 arr[left] ~ arr[mid]arr[mid+1] ~ arr[right]
  • 双指针法 依次取较小的元素,插入 arr[]
  • 复制剩余元素(如果 leftArrrightArr 还有未合并的部分)。
(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) 额外空间)。
    • 需要稳定排序(如数据库排序)。
    • 大规模数据排序(外部排序)

对于大多数数组排序任务,快速排序 通常更快且空间占用更少,但 归并排序在链表或外部排序中表现更优

🚀 如果数据大且要求稳定排序,推荐归并排序;如果追求更快的排序速度,使用快速排序!

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

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

相关文章

鸿蒙NEXT应用App测试-专项测试(DevEco Testing)

注意&#xff1a;大家记得先学通用测试在学专项测试 鸿蒙NEXT应用App测试-通用测试-CSDN博客 注意&#xff1a;博主有个鸿蒙专栏&#xff0c;里面从上到下有关于鸿蒙next的教学文档&#xff0c;大家感兴趣可以学习下 如果大家觉得博主文章写的好的话&#xff0c;可以点下关注…

Rocky8 源码安装 HAProxy

HAProxy 是一款开源的高性能 负载均衡器 和 反向代理 软件&#xff0c;专注于处理高并发流量分发&#xff0c;广泛应用于企业级架构中提升服务的可用性、扩展性和安全性。 一、HAProxy 简介 1.1.HAProxy 是什么&#xff1f; 本质&#xff1a; 基于 C 语言开发 的轻量级工具&a…

【从0做项目】Java文档搜索引擎(9)烧脑终章!

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 文章导读 零&#xff1a;项目结果展示 一&#xff1a;导入 二&#xff1a;问题引入 1&#xff1a;情…

Linux 第二次脚本作业

1、需求&#xff1a;判断192.168.1.0/24网络中&#xff0c;当前在线的ip有哪些&#xff0c;并编写脚本打印出来。 2、设计一个 Shell 程序&#xff0c;在/userdata 目录下建立50个目录&#xff0c;即 user1~user50&#xff0c;并设置每个目录的权限&#xff0c;其中其他用户的权…

mysql的源码包安装

安装方式一&#xff1a;&#xff08;编译好的直接安装&#xff09; 1.添加一块10G的硬盘&#xff0c;给root逻辑卷扩容 &#xff08;下面安装方式二有&#xff0c;一模一样的装就行&#xff0c;我就不写了&#xff0c;再写的话篇幅就太长了&#xff09; 2.下载编译好的源码包…

#渗透测试#批量漏洞挖掘#畅捷通T+远程命令执行漏洞

免责声明 本教程仅为合法的教学目的而准备,严禁用于任何形式的违法犯罪活动及其他商业行为,在使用本教程前,您应确保该行为符合当地的法律法规,继续阅读即表示您需自行承担所有操作的后果,如有异议,请立即停止本文章读。 目录 一、漏洞概况 二、攻击特征 三、应急处置…

【2024 CSDN博客之星】大学四年,我如何在CSDN实现学业与事业的“双逆袭”?

前言&#xff1a; Hello大家好&#xff0c;我是Dream。不知不觉2024年已经过去&#xff0c;自己也马上迈入23岁&#xff0c;感慨时间飞快&#xff0c;从19岁刚入大学加入CSDN&#xff0c;到现在大学毕业已经整整四年了。CSDN陪伴我走过了最青涩的四年大学时光&#xff0c;在这里…

06排序 + 查找(D1_排序(D1_基础学习))

目录 学习预热&#xff1a;基础知识 一、什么是排序 二、为什么要排序 三、排序的稳定性 四、排序稳定性的意义 五、排序分类方式 方式一&#xff1a;内外分类 方式二&#xff1a;比较分类 六、排序算法性能评估 1. 算法的时间复杂度 2. 算法的空间复杂度 七、知识小…

【数据挖掘】深度挖掘

【数据挖掘】深度挖掘 目录&#xff1a;1. 减少样本集的数量知识点示例 2. 对噪声比集剪枝知识点示例建立局部树代码示例&#xff08;使用 Python 和 scikit - learn 库构建局部决策树&#xff09;代码解释注意事项 最大超平面定义原理求解方法代码示例&#xff08;使用 Python…

【Linux】基于UDP/TCP套接字编程与守护进程

目录 一、网路套接字编程 &#xff08;一&#xff09;基础概念 1、源IP地址与目的IP地址 2、端口号 3、TCP与UDP 4、网络字节序 &#xff08;二&#xff09;套接字编程接口 1、socket 常见API 2、sockaddr结构 &#xff08;三&#xff09;UDP套接字 1、UDP服务器创建…

C++跳表实现,封装成Skiplist类

跳表 (Skip List) 是由 William Pugh 发明的一种查找数据结构&#xff0c;支持对数据的快速查找&#xff0c;插入和删除。 跳表的期望空间复杂度为O(n) &#xff0c;跳表的查询&#xff0c;插入和删除操作的期望时间复杂度都为O(logn)。 算法讲解149【扩展】有序表专题2-跳表_哔…

【uni-app】对齐胶囊容器组件

代码碎片 <template><div><view :style"{ height: ${statusBarHeight}px }"></view><viewclass"":style"{height: ${menuButtonHeight menuButtonPadding * 2}px,width: ${menuButtonInfo.left}px,}"><slot …

计算机毕业设计SpringBoot+Vue.jst0甘肃非物质文化网站(源码+LW文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

制造行业CRM选哪家?中大型企业CRM选型方案

在当今竞争激烈的制造行业中&#xff0c;企业对于客户关系管理&#xff08;CRM&#xff09;系统的需求日益增强&#xff0c;高效、智能的CRM系统已成为推动企业业务增长、优化客户体验的关键。在制造业 CRM 市场中&#xff0c;纷享销客和销售易都备受关注&#xff0c;且各自有着…

R 语言科研绘图 --- 散点图-汇总

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…

从零开始的网站搭建(以照片/文本/视频信息通信网站为例)

本文面向已经有一些编程基础&#xff08;会至少一门编程语言&#xff0c;比如python&#xff09;&#xff0c;但是没有搭建过web应用的人群&#xff0c;会写得尽量细致。重点介绍流程和部署云端的步骤&#xff0c;具体javascript代码怎么写之类的&#xff0c;这里不会涉及。 搭…

Elasticsearch Open Inference API 增加了对 Jina AI 嵌入和 Rerank 模型的支持

作者&#xff1a;Hemant Malik 及 Joan Fontanals Martnez 探索如何使用 Elasticsearch Open Inference API 访问 Jina AI 模型。 我们在 Jina AI 的朋友们将 Jina AI 的嵌入模型和重新排名产品的原生集成添加到 Elasticsearch 开放推理 API 中。这包括对行业领先的多语言文本嵌…

Unity学习part4

1、ui界面的基础使用 ui可以在2d和矩形工具界面下操作&#xff0c;更方便&#xff0c;画布与游戏窗口的比例一般默认相同 如图所示&#xff0c;图片在画布上显示的位置和在游戏窗口上显示的位置是相同的 渲染模式&#xff1a;屏幕空间--覆盖&#xff0c;指画布覆盖在游戏物体渲…

进程概念、PCB及进程查看

文章目录 一.进程的概念进程控制块&#xff08;PCB&#xff09; 二.进程查看通过指令查看进程通过proc目录查看进程的cwd和exe获取进程pid和ppid通过fork()创建子进程 一.进程的概念 进程是一个运行起来的程序&#xff0c;而程序是存放在磁盘的&#xff0c;cpu要想执行程序的指…

php session数据存储位置选择

PHP session 数据的存储位置可以通过配置文件或者代码来进行设置。默认情况下&#xff0c;session 数据是存储在服务器的文件系统中的。你可以将 session 数据存储在其他地方&#xff0c;例如数据库、缓存等。 基础概念 PHP session默认情况下将数据存储在服务器端的临时文件中…