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

在这里插入图片描述

📢博客主页: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.rhkb.cn/news/454661.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

爬虫日常实战

爬取美团新闻信息&#xff0c;此处采用两种方法实现&#xff1a; 注意点&#xff1a;因为此处的数据都是动态数据&#xff0c;所以一定要考虑好向下滑动数据包会更新的情况&#xff0c;不然就只能读取当前页即第一页数据&#xff0c;方法一通过更新ajax数据包网址页数&#xf…

vscode 预览markdown 文件

1. 点击左边扩展 2. 搜索“Markdown Preview Enhanced” 3. 选第一个安装即可 4. 重启vscode 5. 打开一个markdown 文件 6. 点击右上角的预览按钮

[mysql]mysql的全部单行函数

单行函数 几乎我们认识的语言都会对一些常用的功能进行,封装,有些叫函数,有些叫方法(Java),后期我们还可以自定义函数. 现在我们就当大家是没有语言基础,我们来从头开始讲.不过大家肯定接触过,中学说的函数,yf(x)f代表的就是function的缩写,这里其y2x1fx代表的就是封装的内容…

FileLink内外网文件交换——致力企业高效安全文件共享

随着数字化转型的推进&#xff0c;企业之间的文件交流需求日益增加。然而&#xff0c;传统的文件传输方式往往无法满足速度和安全性的双重要求。FileLink作为一款专注于跨网文件交换的工具&#xff0c;致力于为企业提供高效、安全的文件共享解决方案。 应用场景一&#xff1a;项…

C++大沥2019年真题——数字圈

Hi&#xff01;大家好&#xff01;Im#张亿&#xff0c;今天来讲C大沥2019年真题——数字圈 题目描述 当我们写数字时会发现有些数字有封闭区域&#xff0c;有的数字没有封闭区域。 数字 0 有一个封闭区域&#xff0c;数字 1、2、 3 都没有封闭区域&#xff0c;数字 4 有一个封…

word中的内容旋转90度

在vsto、Aspose.Words 中&#xff0c;默认没有直接的 API 可以让表格整体旋转 90 度。然而&#xff0c;我们可以通过一些方式来实现类似的效果&#xff0c;具体思路如下&#xff1a; 将表格插入到一个形状&#xff08;Shape&#xff09;或文本框中&#xff0c;然后旋转该形状。…

《RECONX: RECONSTRUCT ANY SCENE FROM SPARSEVIEWS WITH VIDEO DIFFUSION MODEL》论文阅读

论文地址&#xff1a;https://arxiv.org/pdf/2408.16767 项目地址&#xff1a;GitHub - liuff19/ReconX: ReconX: Reconstruct Any Scene from Sparse Views with Video Diffusion Model ---------------------------------------------------------------------------------…

2019年计算机网络408真题解析

第一题&#xff1a; 解析&#xff1a;OSI参考模型第5层完成的功能 首先&#xff0c;我们需要对OSI参考模型很熟悉&#xff1a;从下到上依次是&#xff1a;物理层-数据链路层-网络层- 运输层-会话层-表示层-应用层&#xff0c;由此可知&#xff0c;题目要问的是会话层的主要功能…

什么是感知与计算融合?

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

基于SSM品牌银饰售卖系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;促销活动管理&#xff0c;饰品管理&#xff0c;我的收藏管理&#xff0c;系统管理&#xff0c;订单管理 用户账号功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;…

新书速览|Android智能座舱开发:从源码到实践

《Android智能座舱开发:从源码到实践》 本书内容 《Android智能座舱开发:从源码到实践》是一本专注于Android智能座舱系统开发与优化的实战指南。《Android智能座舱开发:从源码到实践》共9章&#xff0c;第1章从搭建源码编译环境开始&#xff0c;详细指导读者如何下载和编译An…

活体人脸识别技术总结及实践

文章目录 1、背景2、人脸反伪装技术2.1 活体人脸识别常见模式2.2 学术上反伪装研究 3、工程实现3.1 Silent-Face3.2 Silent-Face模型转rknn3.3 Silent-Face模型的限制 1、背景 1.1 什么是活体检测&#xff1f; 在人脸识别之前&#xff0c;先判断一下屏幕前摄像头捕捉到的人脸是…

深度解析RLS(Recursive Least Squares)算法

目录 一、引言二、RLS算法的基本思想三、RLS算法的数学推导四、RLS算法的特点五、RLS算法的应用场景六、RLS算法的局限性七、总结 一、引言 在自适应滤波领域&#xff0c;LMS&#xff08;Least Mean Squares&#xff09;算法因其计算简单、实现方便而广受欢迎。然而&#xff0…

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

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

【数据结构与算法】时间、空间复杂度详解

大家有没有遇到过&#xff0c;为什么有些程序跑得飞快&#xff0c;而有些程序却慢得让人抓狂&#xff1f;我们可能都是这样认为的&#xff1a;他写的程序效率高等等&#xff0c;确实如此。但这背后隐藏着两个重要的概念&#xff1a;时间复杂度和空间复杂度。它们就像程序的“效…

算法题总结(十九)——图论

图论 DFS框架 void dfs(参数) { if (终止条件) {存放结果;return; }for (选择&#xff1a;本节点所连接的其他节点) {处理节点;dfs(图&#xff0c;选择的节点); // 递归回溯&#xff0c;撤销处理结果 } }深搜三部曲 确认递归函数&#xff0c;参数确认终止条件处理目前搜索节…

Windows系统启动MongoDB报错无法连接服务器

文章目录 发现问题解决办法 发现问题 1&#xff09;、先是发现执行 mongo 命令&#xff0c;启动报错&#xff1a; error: MongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017&#xff1b; 2&#xff09;、再检查 MongoDB 进程 tasklist | findstr mongo 发现没有进程&a…

爬虫基础--requests模块

1、requests模块的认识 requests模块的认识请跳转到 requests请求库使用_使用requests库-CSDN博客 2、爬取数据 这里我们以b站动漫追番人数为例。 首先进去b站官网 鼠标右键点击检查或者键盘的F12&#xff0c;进入开发者模式。&#xff08;这里我使用的是谷歌浏览器为例&#…

【JVM】—深入理解G1回收器—回收过程详解

深入理解G1回收器—回收过程详解 ⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记链接&#x1f449;https://github.com/A-BigTree/Code_Learning ⭐⭐⭐⭐⭐⭐ 如果可以&#xff0c;麻烦各位看官顺手点个star~&#x1f60a; 文章目录 深入理解G1回收…

基于PERL语言的MS中CASTEP模块批量提交计算脚本

在现代科学研究中&#xff0c;高效的计算工具对于推动科研进步具有不可估量的价值。为了满足广大科研工作者在材料科学、化学、物理等领域日益增长的计算需求&#xff0c;我们特别推出了一款基于Perl语言的MS CASTEP模块批量提交计算脚本。 一、批量提交&#xff0c;高效处理 …