【算法】归并排序概念及例题运用

news2024/10/23 15:19:14

在这里插入图片描述

📢博客主页:https://blog.csdn.net/2301_779549673
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 CSDN🙉
📢未来很长,值得我们全力奔赴更美好的生活✨

在这里插入图片描述

在这里插入图片描述

文章目录

  • 🏳️‍🌈归并排序概念
  • 🏳️‍🌈原理与实现
  • 🏳️‍🌈性能分析
    • ❤️(一)时间复杂度
    • 🧡(二)空间复杂度
    • 💛(三)稳定性
  • 🏳️‍🌈例题分析(4题)
    • ❤️链接: [912. 排序数组](https://leetcode.cn/problems/sort-an-array/description/)
    • 🧡链接: [LCR 170. 交易逆序对的总数](https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/description/)
    • 💛链接: [315. 计算右侧小于当前元素的个数](https://leetcode.cn/problems/count-of-smaller-numbers-after-self/description/)
    • 💚链接: [493. 翻转对](https://leetcode.cn/problems/reverse-pairs/description/)
  • 👥总结


🏳️‍🌈归并排序概念

在这里插入图片描述
归并排序是一种高效稳定的基于分治思想的排序算法,适用于多种场景。

归并排序(Merge sort)是建立在归并操作上的一种有效、稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。它将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

当有 n 个记录时,需进行 logn 轮归并排序,每一轮归并,其比较次数不超过 n,元素移动次数都是 n。因此,归并排序的时间复杂度为 O (nlogn)。归并排序时需要和待排序记录个数相等的存储空间,所以空间复杂度为 O (n)。

归并排序适用于数据量大,并且对稳定性有要求的场景。例如,在处理大量数据的数据库系统中,归并排序可以保证数据的稳定性,确保相同值的数据在排序前后的相对位置不变。同时,在一些需要对数据进行多次排序的应用中,归并排序的稳定性也能保证结果的准确性。

在归并排序的过程中,基本的操作是合并两个已排序的数组。取两个输入数组 A 和 B,一个输出数组 C,以及三个计数器 i、j、k,它们初始位置置于对应数组的开始端。A [i] 和 B [j] 中较小者拷贝到 C 中的下一个位置,相关计数器向前推进一步。当两个输入数组有一个用完时候,则将另外一个数组中剩余部分拷贝到 C 中。

总的来说,归并排序以其高效性和稳定性,在各种数据处理场景中都有着广泛的应用。


🏳️‍🌈原理与实现

在这里插入图片描述
归并排序的原理基于分治思想,其主要步骤包括分割和合并。

  • 首先,将待排序的数组不断地分割成较小的子数组,直到每个子数组只包含一个元素。此时,每个子数组都是有序的。
  • 然后,开始进行合并操作。在合并过程中,将两个已排序的子数组合并成一个更大的有序数组。具体来说,比较两个子数组的首元素,将较小的元素放入新的数组中,然后移动相应子数组的指针。
  • 重复这个过程,直到其中一个子数组被遍历完。
  • 最后,将另一个子数组中剩余的元素直接复制到新数组中。通过不断地进行分割和合并操作,最终可以得到一个完全有序的数组。

在这里插入图片描述
此外,由于归并排序是先排中间,然后再排两边的,所以可以近似看成二叉树的后序遍历,也就是先遍历左子树和右子树,再是根节点。

🏳️‍🌈性能分析

❤️(一)时间复杂度

归并排序的时间复杂度始终为 O(nlogn),这意味着无论数据的初始状态如何,归并排序的运行时间都与数据规模 n 和对数函数 logn 的乘积成正比。

归并排序采用分治法,将问题不断分解为规模更小的子问题进行求解。假设我们需要对一个包含 n个数的序列使用归并排序,并且使用的是递归的实现方式,那么过程如下:

1.递归的第一层,将 n 个数划分为 2 个子区间,每个子区间的数字个数为 n/2
2.递归的第二层,将 n 个数划分为 4个子区间,每个子区间的数字个数为 n/4.
3.递归的第三层,将 n 个数划分为8个子区间,每个子区间的数字个数为 n/8
4.递归的第 logn 层,将 n 个数划分为 n 个子区间,每个子区间的数字个数为 1。

归并排序的总时间 T(n)= 2T(n/2)+o(n)。假设解决最后的子问题用时为常数 c,则对于 n个待排序记录来说整个问题的规模为 cn 。从递归树可以看出,第一层时间代价为 cn,第二层时间代价为 cn/2 + cn/2= cn…每一层代价都是 cn,总共有 logn+1 层。所以总的时间代价为 cn*(logn+1),时间复杂度是 O(nlogn)

在不同规模数据下,归并排序的表现相对稳定。无论是小规模数据还是大规模数据,其时间复杂度都保持在 O(nlogn)。对于小规模数据,虽然归并排序可能会显得有些“大材小用”,但其时间复杂度不会急剧上升。而对于大规模数据,归并排序的优势更加明显,相比一些时间复杂度较高的排序算法,如冒泡排序、选择排序等,归并排序能够在更短的时间内完成排序任务。

🧡(二)空间复杂度

归并排序需要额外的 O(n)空间来保存临时数组。在归并过程中,需要创建临时数组来存储合并后的结果。这意味着归并排序的空间开销与数据规模 成正比。

这种空间开销对算法性能有一定的影响。一方面,额外的空间需求可能会在处理大规模数据时占用较多的内存资源。特别是在内存有限的环境中,可能会导致内存不足的问题。另一方面,创建临时数组和进行数据复制也会带来一定的时间开销。然而,归并排序的稳定性和高效的时间复杂度在很多情况下可以弥补其空间复杂度较高的不足。在一些对稳定性要求较高的场景中,归并排序仍然是一个不错的选择。

💛(三)稳定性

归并排序是稳定排序算法,这意味着在排序过程中,相等元素的相对位置不会改变。

归并排序之所以是稳定的,原因在于其合并过程。在合并两个已排序的子数组时,如果两个元素相等,归并排序会先将位于前面子数组中的元素放入新的数组中,从而保证了相等元素的相对位置不变。

在实际应用中,稳定性具有重要意义。例如,在对学生成绩进行排序时,如果有多个学生得分相同,稳定排序可以保持他们原来的顺序,这对于后续的数据分析和处理非常重要。另外,在一些需要保持数据原有顺序关系的场景中,归并排序的稳定性可以确保排序结果的准确性和可靠性。

🏳️‍🌈例题分析(4题)

❤️链接: 912. 排序数组

在这里插入图片描述

  1. 明确我们的目的,是通过使用归并排序算法对给定的整数向量进行排序
  2. 对左右两边的数组进行排序
  3. 创建临时变量数组,通过双指针,使左右数组合并有序
class Solution {
    int tmp[50010];
public:
    vector<int> sortArray(vector<int>& nums) {
        int n = nums.size();
        mergesort(nums, 0, n-1);
        return nums;
    }
    void mergesort(vector<int>& nums, int l, int r){

        // 1.获取中间点坐标
        if(l >= r) return;
        int mid = l + ((r - l) >> 1);
        
        // 2. 利用归并排序自身对左右范围的数组进行,是左右两边都有序
        mergesort(nums, l, mid);
        mergesort(nums, mid + 1, r);

        // 3.创建临时变量数组,同时利用双指针,将左右有序数组合并
        int index = l;
        int cur1 = l, cur2 = mid + 1;
        while(cur1 <= mid && cur2 <= r){
            if(nums[cur1] < nums[cur2]) tmp[index++] = nums[cur1++];
            else tmp[index++] = nums[cur2++];
        }

        // 4.使左右两边的数组充分排序进入临时数组
        while(cur1 <= mid) tmp[index++] = nums[cur1++];
        while(cur2 <= r) tmp[index++] = nums[cur2++];

        // 5.将临时数组中的元素返回,使原数组有序
        for(int i = l; i <= r; ++i) nums[i] = tmp[i];
    }
};

🧡链接: LCR 170. 交易逆序对的总数

在这里插入图片描述
因为归并排序具有较好的稳定性,所以我们利用归并排序排升序的同时,计算每次右边数组大于左边数组元素的个数,并返回

class Solution {
int tmp[50010];
public:
    int reversePairs(vector<int>& record) {
        return mergesort(record, 0, record.size() - 1);
    }   

    int mergesort(vector<int>& record, int l, int r){
        // 1.获取中间点坐标
        if (l >= r) return 0;
        int mid = l + ((r - l) >> 1);
        int cur1 = l, cur2 = mid + 1, ret = 0;

        // 2. 利用归并排序自身对左右范围的数组进行,是左右两边都有序
        ret += mergesort(record, l, mid);
        ret += mergesort(record, mid+1, r);
        
        // 3.利用双指针合并数组
        // 4.利用升序数组的特性,计算当前两组元素中符合前一天的股价高于后一天的股价的组合个数
        int index = 0;
        while(cur1 <= mid && cur2 <= r){
            if(record[cur1] > record[cur2]) ret += mid - cur1 + 1, tmp[index++] = record[cur2++];
            else tmp[index++] = record[cur1++];
        }
        while(cur1 <= mid) tmp[index++] = record[cur1++];
        while(cur2 <= r) tmp[index++] = record[cur2++];

        // 5.将临时数组中的元素返回,使原数组有序
        for(int i=0; i<index; ++i) record[l+i] = tmp[i];
        return ret;
    }
};

💛链接: 315. 计算右侧小于当前元素的个数

在这里插入图片描述
整体做法和上题如出一辙,不过这里需要返回对应的元素个数组成的vector,所以我们需要提前建立可以用来标记元素下标和记录满足元素个数数量的返回数组

class Solution {
    // 返回值
    vector<int> ret;
    // 记录原始下标
    vector<int> index;
    // 临时变量数组
    int tmpnums[100010];
    // 临时下标数组
    int tmpindex[100010];

public:
    vector<int> countSmaller(vector<int>& nums) {
        int n = nums.size();
        ret.resize(n);
        index.resize(n);
        // 获取原始下标
        for (int i = 0; i < n; ++i)
            index[i] = i;
        mergesort(ret, index, nums, 0, n - 1);
        return ret;
    }
    void mergesort(vector<int>& ret, vector<int>& index, vector<int>& nums, int left, int right) {
        if (left >= right)
            return;
        int mid = left + ((right - left) >> 1);

        mergesort(ret, index, nums, left, mid);
        mergesort(ret, index, nums, mid + 1, right);

        int cur1 = left, cur2 = mid + 1, i = 0;
        while (cur1 <= mid && cur2 <= right) {
            if (nums[cur2] >= nums[cur1]) {
                tmpindex[i] = index[cur2];
                tmpnums[i++] = nums[cur2++];
            } else {
                ret[index[cur1]] += right - cur2 + 1;
                tmpindex[i] = index[cur1];
                tmpnums[i++] = nums[cur1++];
            }
        }
        while (cur1 <= mid)
            tmpindex[i] = index[cur1], tmpnums[i++] = nums[cur1++];
        while (cur2 <= right)
            tmpindex[i] = index[cur2], tmpnums[i++] = nums[cur2++];

        for (int j = left; j <= right; ++j)
            index[j] = tmpindex[j - left], nums[j] = tmpnums[j - left];
    }
};

💚链接: 493. 翻转对

在这里插入图片描述
大体规则相同,当这里用降序更容易实现

class Solution {
    int tmp[50010];

public:
    int reversePairs(vector<int>& nums) {
        int n = nums.size();
        return mergesort(nums, 0, n - 1);
    }

    int mergesort(vector<int>& nums, int left, int right) {
        if (left >= right)
            return 0;
        int ret = 0;
        int mid = (left + right) >> 1;

        ret += mergesort(nums, left, mid);
        ret += mergesort(nums, mid + 1, right);

        int cur1 = left, cur2 = mid + 1, i = left;
        while (cur1 <= mid) {
            while (cur2 <= right && nums[cur2] >= nums[cur1] / 2.0)
                cur2++;
            if (cur2 > right)
                break;
            ret += right - cur2 + 1;
            cur1++;
        }

        
        cur1 = left, cur2 = mid + 1;
        while (cur1 <= mid && cur2 <= right) {
            tmp[i++] = nums[cur1] <= nums[cur2] ? nums[cur2++] : nums[cur1++];
        }
        while (cur1 <= mid)
            tmp[i++] = nums[cur1++];
        while (cur2 <= right)
            tmp[i++] = nums[cur2++];

        for (int j = left; j <= right; ++j)
            nums[j] = tmp[j];
        return ret;
    }
};

👥总结

本篇博文对 归并排序概念及例题运用 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

请添加图片描述

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

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

相关文章

小程序视频SDK解决方案,提供个性化开发和特效定制设计

美摄科技作为视频处理技术的领航者&#xff0c;深知在这一变革中&#xff0c;每一个细微的创新都能激发无限可能。因此&#xff0c;我们精心打造了一套小程序视频SDK解决方案&#xff0c;旨在满足不同行业、不同规模客户的多元化需求&#xff0c;携手共创视频内容的璀璨未来。 …

这几次比赛题解

因为考虑到再看&#xff0c;所以将所有题目都做成了pdf格式 梦熊十三连测 T1 这道题其实什么也不用想&#xff0c;就按照题目给的意思来打代码就行&#xff0c;这就有40分可以拿。懒人做法 #include<bits/stdc.h> using namespace std; typedef long long ll; ll read…

中航资本:股票显示缺口什么意思啊?股票有缺口一定会补吗?

股票显现缺口什么意思啊&#xff1f; 股票显现缺口是指股票在运行进程中&#xff0c;忽然上涨或许下跌使股价远离上一个交易日收盘价的状况&#xff0c;也便是股票当天的开盘价格和股票前一个交易日的收盘价格违背崎岖很大。在K线图中&#xff0c;缺口表现为股价在持续动摇中有…

MT-Pref数据集:包含18种语言的18k实例,涵盖多个领域。实验表明它能有效提升Tower模型在WMT23和FLORES基准测试中的翻译质量。

2024-10-10&#xff0c;由电信研究所、里斯本大学等联合创建MT-Pref数据集&#xff0c;它包含18种语言方向的18k实例&#xff0c;覆盖了2022年后的多个领域文本。通过在WMT23和FLORES基准测试上的实验&#xff0c;我们展示了使用MT-Pref数据集对Tower模型进行对齐可以显著提高翻…

React实现购物车功能

今日学习React的useReducer&#xff0c;实现了一个购物车功能 文章目录 目录 效果展示 逻辑代码 CSS代码 效果展示 逻辑代码 import {useReducer} from "react"; import ./index.css; import { message} from antd;export function ShoppingCount(){// 初始化购…

钡铼技术边缘计算2DIN2DO工业无线路由器R40A

R40A不仅具备了传统工业无线路由器的基本功能&#xff0c;如4G网络连接、稳定的数据传输等&#xff0c;还创新性地整合了可编程逻辑控制器&#xff08;PLC&#xff09;功能、多种工业协议转换能力以及数据采集终端的功能。 强大的边缘计算能力 随着物联网技术的发展&#xff…

STM32_实验5_中断实验

通过外部中断来检测四个按键按下的状态&#xff1a; WK_UP 控制蜂鸣器响和停 KEY0 控制 LED_R 互斥点亮 KEY1 控制 LED_G 互斥点亮 KEY2 控制 LED_B 互斥点亮。 中断的基本概念&#xff1a; 中断请求&#xff08;IRQ&#xff09;&#xff1a; 当发生某个特定事件&#xff08;例…

如何通过谷歌外推占据搜索引擎首页?

外贸企业在推广过程中&#xff0c;如何在谷歌搜索引擎中占据有利位置&#xff0c;获取更多曝光&#xff0c;GLB谷歌霸屏服务就可以派上用场。它通过高效的品牌外推策略&#xff0c;可以让你的企业信息在谷歌中实现“霸屏”效果&#xff0c;特别是长尾关键词的全面覆盖 很多企业…

如何实现安川MP3300运动控制器与西门子1200系列PLC进行ModbusTCP通讯

在工业自动化中&#xff0c;实现不同品牌、不同型号设备之间的通讯是确保生产流程顺畅、高效运行的关键。本文详细介绍了安川MP3300运动控制器与西门子1200系列PLC进行ModbusTCP通讯的具体方法。 一&#xff0e;软硬件需求 1.一台安川MP3300CPU301&#xff0c;其IP地址是192.…

android11 usb摄像头添加多分辨率支持

部分借鉴于&#xff1a;https://blog.csdn.net/weixin_45639314/article/details/142210634 目录 一、需求介绍 二、UVC介绍 三、解析 四、补丁修改 1、预览的限制主要存在于hal层和framework层 2、添加所需要的分辨率&#xff1a; 3、hal层修改 4、frameworks 5、备…

OceanBase 首席科学家阳振坤:大模型时代的数据库思考

2024年 OceanBase 年度大会 即将于10月23日&#xff0c;在北京举行。 欢迎到现场了解更多“SQL AI ” 的探讨与分享&#xff01; 近期&#xff0c;2024年金融业数据库技术大会在北京圆满举行&#xff0c;聚焦“大模型时代下数据库的创新发展”议题&#xff0c;汇聚了国内外众多…

Java的评论大冒险:用代码征服API数据

在一个充满数字奥秘的虚拟世界里&#xff0c;Java勇士正准备踏上他的新征程&#xff1a;获取商品评论的API数据。这不仅是一次技术的挑战&#xff0c;更是一次与时间赛跑的较量。Java勇士&#xff0c;这位编程界的探险家&#xff0c;打开了他的IDE&#xff0c;准备开始这场冒险…

什么是感知与计算融合?

感知与计算融合&#xff08;Perception-Computing Fusion&#xff09;是指将感知技术&#xff08;如传感器、摄像头等&#xff09;与计算技术&#xff08;如数据处理、人工智能等&#xff09;有机结合&#xff0c;以实现对环境的更深层次理解和智能反应的过程。该技术广泛应用于…

进程间通信大总结Linux

目录 进程间通信介绍 进程间通信目的 进程间通信发展 进程间通信分类 管道 System V IPC POSIX IPC 管道 什么是管道 匿名管道 用fork来共享管道原理 站在文件描述符角度-深度理解管道 管道读写规则 管道特点 命名管道 创建一个命名管道 匿名管道与命名管道的区…

【leetcode|哈希表、动态规划】最长连续序列、最大子数组和

目录 最长连续序列 解法一&#xff1a;暴力枚举 复杂度 解法二&#xff1a;优化解法一省去二层循环中不必要的遍历 复杂度 最大子数组和 解法一&#xff1a;暴力枚举 复杂度 解法二&#xff1a;贪心 复杂度 解法三&#xff1a;动态规划 复杂度 最长连续序列 输入输…

长短期记忆网络(Long Short-Term Memory,LSTM)

简介&#xff1a;个人学习分享&#xff0c;如有错误&#xff0c;欢迎批评指正。 长短期记忆网络&#xff08;Long Short-Term Memory&#xff0c;简称LSTM&#xff09;是一种特殊的循环神经网络&#xff08;Recurrent Neural Network&#xff0c;简称RNN&#xff09;架构&#…

网络安全中的日志审计:为何至关重要?

在数字化时代&#xff0c;网络安全已成为企业和组织不可忽视的重要议题。随着网络攻击手段的不断进化&#xff0c;保护信息系统和数据安全变得日益复杂和具有挑战性。在这种背景下&#xff0c;日志审计作为一种关键的信息安全和网络管理工具&#xff0c;发挥着至关重要的作用。…

RHCE——例行性工作 at、crontab

一.单一执行的列行型工作&#xff1a;仅处理执行一次就结束了 1.at命令的工作过程 &#xff08;1&#xff09;/etc/at.allow&#xff0c;写在该文件的人可以使用at命令 &#xff08;2&#xff09;/etc/at.deny&#xff0c;黑名单 &#xff08;3&#xff09;两个文件如果都…

【Spring篇】Spring的Aop详解

&#x1f9f8;安清h&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;【计算机网络】【Mybatis篇】【Spring篇】 &#x1f6a6;作者简介&#xff1a;一个有趣爱睡觉的intp&#xff0c;期待和更多人分享自己所学知识的真诚大学生。 目录 &#x1f3af;初始Sprig AOP及…

SVM(支持向量机)

SVM&#xff08;支持向量机&#xff09; 引言 支持向量机(Support Vector Machine,SVM)&#xff0c;可以用来解答二分类问题。支持向量(Support Vector)&#xff1a;把划分数据的决策边界叫做超平面&#xff0c;点到超平面的距离叫做间隔。在SVM中&#xff0c;距离超平面最近…