当前位置: 首页 > news >正文

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

在这里插入图片描述

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

相关文章:

  • 《Effective C++》 笔记
  • 滑线变阻器的工作原理是什么?
  • 香港国际金融市场的多元化投资与风险管理策略
  • 基于fpga技术的脉冲信号源设计(论文+源码)
  • 【电子元件】光通量和色温 (欧司朗LED灯珠 KW3 CGLNM1.TG命名规则)
  • 某ai gpt的bug
  • 在线图片翻译有哪些?快来试试这5款
  • 大华智能云网关注册管理平台 doLogin SQL注入漏洞复现(CNVD-2024-38747)
  • Bitcoin全节点搭建
  • 又进入火坑了,该何去何从
  • PyQt 程序使用 Inno Setup 打包成 Setup 安装包教程
  • 【zlm】h264 vp9 尝试研究
  • 探讨程序搭建
  • 学习AJAX请求(初步)24.10.21-10.23
  • PCC Net模型实现行人数量统计
  • casa天文软件全代码记录
  • vue 页面导出gif图片 img 导出gif 超简单~ 可修改播放速度
  • 重构复杂简单变量之状态与策略模式
  • 就是这个样的粗爆,手搓一个计算器:BMI计算器
  • python 爬虫抓取百度热搜
  • 100种算法【Python版】第4篇——回溯法
  • 台湾精锐APEX减速机AB系列特点解析
  • vcruntime140.dll无法继续执行代码-解决方案
  • Java项目-基于springboot框架的校园志愿者管理系统项目实战(附源码+文档)
  • 羽毛球场馆预约小程序,提高场馆便捷性、利用率
  • 南京某大厂 渗透测试工程师实习面试分享