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

归并排序 (BM20 数组中的逆序对)

  • 一 、算法实现
  • 二 应用
    • 1 数组中的逆序对
      • 1.1 暴力方法
      • 1.2 归并排序思想
      • 1.2 mid = (left + right )/2 溢出问题
      • 1.3 优化

一 、算法实现

A1: 首先回答一下第一个问题,为什么归并排序需要额外空间?
显然我们知道,归并排序的过程就是,递归划分整个区间为基本相等的左右区间,之间左右区间各只有一个数字,然后就合并两个有序区间。
问题就出在了合并两个有序区间上,需要额外的空间。
为什么呢?
这里我举个例子,比如需要合并的两个有序区间为[3 4] 和 [1 2]
我们需要得到最后的结果为[1 2 3 4], 如果不需要额外的空间的话,是做不到的,
当比较1 和 3 的时候, 1 比 3 小,就会覆盖原来的位置。

A2:回答第二个问题之前,先了解一下归并排序的过程,主要有以下两个操作:

·递归划分整个区间为基本相等的左右两个区间
·合并两个有序区间

#include<iostream>
#include<vector>
using namespace std;void merge_(vector<int>& arr, int left, int mid, int right) {vector<int> temp(right - left +1);//辅助数组,不写长度报错int i = left, j = mid + 1 , k = 0;while (i<= mid && j <= right) //较小值存入tempif (arr[i] > arr[j]) temp[k++] = arr[j++];else temp[k++] = arr[i++];while (i <= mid) //若左侧有剩余,复制到temptemp[k++] = arr[i++];while (j <= right)//若右侧有剩余,复制到temptemp[k++] = arr[j++];for (int i = left, k = 0; i <= right; i++,k++)arr[i] = temp[k];
}void MergeSort(vector<int>& arr, int left, int right) {if (left < right) {int mid = (right + left )/2;//从中间划分子序列;MergeSort(arr, left, mid);  //对左侧子序列进行递归排序;MergeSort(arr, mid + 1, right); //对右侧子序列进行递归排序merge_(arr, left, mid, right); //合并两个有序区间}
}int main() {vector<int> a = {1,4,7,2,5,8,3,6,9,10};MergeSort(a,0,a.size()-1);for(int i = 0; i < 10; i++)cout << a[i] << endl;

在这里插入图片描述

二 应用

1 数组中的逆序对

描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P mod 1000000007

数据范围: 对于50% 的数据, size≤10^4

对于 100% 的数据, size≤10^5

数组中所有数字的值满足 0≤val≤1000000

要求:空间复杂度 O(n),时间复杂度 O(nlogn)
输入描述:
题目保证输入的数组中没有的相同的数字

示例1
输入:[1,2,3,4,5,6,7,0]
返回值:7
(说明,00和前面7个数,组成7个逆序对)

示例2
输入:[1,2,3]
返回值:0

在这里插入图片描述

1.1 暴力方法

对于此题,按住一个arr[i], 依次判断{i+1 … n-1]是否满足条件。n为数组的大小。

int InversePairs(vector<int> data) {const int kmod = 1000000007;int ret = 0;int n = data.size();for (int i = 0; i < n; ++i) {for (int j = i + 1; j < n; ++j) {if (data[i] > data[j]) {ret += 1;ret %= kmod;}}}return ret;}

对于很大的数组会超时

1.2 归并排序思想

明白了归并排序的过程,那么回答问题2.
如果两个区间为[4, 3] 和[1, 2]
那么逆序数为(4,1),(4,2),(3,1),(3,2),同样的如果区间变为有序,比如[3,4] 和 [1,2]的结果是一样的,也就是说区间有序和无序结果是一样的。
但是如果区间有序会有什么好处吗?当然,如果区间有序,比如[3,4] 和 [1,2]
如果3 > 1, 显然3后面的所有数都是大于1, 这里为 4 > 1, 明白其中的奥秘了吧。左区间arr[0]及以后的元素都可以和右区间的arr[2]逆序对,即左区间和右区间的元素arr[2]可以构成(mid-i+1)个逆序对

所以我们可以在合并的时候利用这个规则。

在上述归并排序中,增加了res参数,用于记录,逆序对数量

  void merge_(vector<int>&arr , int left ,int mid ,int right ,int& res);void MergeSort(vector<int>&arr ,int left ,int right, int &res);

具体实现,在下面

 while(i <= mid && j <= right){if(arr[i] > arr[j]) { temp[k++] = arr[j++];res += (mid - i + 1);//比如[3,4] 和 [1,2],当 arr[0]=3, arr[2]=1,此时构成逆序对,//因为各子区间有序,所以左区间arr[0]及以后的元素都可以和右区间的arr[2]逆序对,即左区间和右区间的元素arr[2]可以构成(mid-i+1)个逆序对res %= kmod; //为什么每次都要取余? 直接在最后一次取余不行吗?提交有以下哪个不通过,怀疑是res的累加值非常大超过kmod了,所以最一次性取余效果不一样}else temp[k++] = arr[i++];}
class Solution {
private:const int kmod = 1000000007;      
public:void merge_(vector<int>&arr , int left ,int mid ,int right ,int& res){vector<int> temp(right- left +1);int i = left, j = mid + 1, k = 0;while(i <= mid && j <= right){if(arr[i] > arr[j]) { temp[k++] = arr[j++];res += (mid - i + 1);//比如[3,4] 和 [1,2],当 arr[0]=3, arr[2]=1,此时构成逆序对,//因为各子区间有序,所以左区间arr[0]及以后的元素都可以和右区间的arr[2]逆序对,即左区间和右区间的元素arr[2]可以构成(mid-i+1)个逆序对res %= kmod; //为什么每次都要取余? 直接在最后一次取余不行吗?提交有以下哪个不通过,怀疑是res的累加值非常大超过kmod了,所以最一次性取余效果不一样}else temp[k++] = arr[i++];}while(i <= mid)   temp[k++] = arr[i++];while(j <= right) temp[k++] = arr[j++];for(int i = left, k = 0; i <= right; i++,k++)arr[i] = temp[k];}void MergeSort(vector<int>&arr ,int left ,int right, int &res){if(left < right){//int mid = (left + right )/2; //这种写法和下面一样,但是当数字非常大时时会溢出(超出int范围),鼓将加法改成减法形式计算,int mid = left + (right - left)/2; //从中间划分子序列;MergeSort(arr,left,mid,res);   //对左侧子序列进行递归排序;MergeSort(arr,mid+1,right,res); //对右侧子序列进行递归排序merge_(arr,left,mid,right,res);  //合并两个有序区间}}int InversePairs(vector<int> data) {int res = 0;MergeSort(data,0,data.size() - 1,res);return res;}
};

1.2 mid = (left + right )/2 溢出问题

 //int mid = (left + right )/2; //这种写法和下面一样,但是当数字非常大时时会溢出(超出int范围),鼓将加法改成减法形式计算,
int mid = left + (right - left)/2; //从中间划分子序列;

1.3 优化

上面程序在函数内部(merge函数)开辟额外空间的做法很不好。

因为这样会涉及到频繁的构建 vector 和析构vector,

所以比较好的做法是:直接在最外层开辟一个足够大的数组,然后传引用到函数。

即,把辅助数组temp,作为形参,,,在调用接口函数里申请temp数组:

void merge_(vector<int>&arr , vector<int>&temp, int left ,int mid ,int right ,int& res)void MergeSort(vector<int>&arr ,vector<int>&temp, int left ,int right, int &res)
int InversePairs(vector<int> data) {int res = 0;vector<int> temp(data.size());  // 在最外层开辟辅助数组数组MergeSort(data,temp, 0,data.size() - 1,res);return res;
}

完整实现

class Solution {
private:const int kmod = 1000000007;      
public:void merge_(vector<int>&arr , vector<int>&temp, int left ,int mid ,int right ,int& res){int i = left, j = mid + 1, k = 0;while(i <= mid && j <= right){if(arr[i] > arr[j]) { temp[k++] = arr[j++];res += (mid - i + 1);//比如[3,4] 和 [1,2],当 arr[0]=3, arr[2]=1,此时构成逆序对,//因为各子区间有序,所以左区间arr[0]及以后的元素都可以和右区间的arr[2]逆序对,即左区间和右区间的元素arr[2]可以构成(mid-i+1)个逆序对res %= kmod; //为什么每次都要取余? 直接在最后一次取余不行吗?提交有以下哪个不通过,怀疑是res的累加值非常大超过kmod了,所以最一次性取余效果不一样}else temp[k++] = arr[i++];}while(i <= mid)   temp[k++] = arr[i++];while(j <= right) temp[k++] = arr[j++];for(int i = left, k = 0; i <= right; i++,k++)arr[i] = temp[k];}void MergeSort(vector<int>&arr ,vector<int>&temp, int left ,int right, int &res){if(left < right){//int mid = (left + right )/2; //这种写法和下面一样,但是当数字非常大时时会溢出(超出int范围),鼓将加法改成减法形式计算,int mid = left + (right - left)/2; //从中间划分子序列;MergeSort(arr,temp,left,mid,res);   //对左侧子序列进行递归排序;MergeSort(arr,temp,mid+1,right,res); //对右侧子序列进行递归排序merge_(arr,temp,left,mid,right,res);  //合并两个有序区间}}int InversePairs(vector<int> data) {int res = 0;vector<int> temp(data.size());  // 在最外层开辟辅助数组数组MergeSort(data,temp, 0,data.size() - 1,res);return res;}
};
http://www.dtcms.com/a/613755.html

相关文章:

  • Spring @Around 注解
  • 建设企业网站需要考虑的因素有哪些店铺logo设计免费
  • 50019_基于微信小程序的校园互助系统
  • (120页PPT)ChatGPT与数字化转型的业财融合(附下载方式)
  • Java面试中等测试题
  • 爱站库全栈网站开发工程师
  • docker避免每次sudo方法
  • 计算机图形学·15 计算机视图(Computer Viewing)
  • 使用rufus制作系统盘及Ubantu24.04.3LTS镜像文件下载
  • opencart做视频网站做网站盈利方式
  • Polar MISC(下)
  • DNS基础介绍
  • Spring Boot 3.4 正式发布,结构化日志!
  • Docker安装和使用kkfileview
  • 做超市dm的网站淘宝联盟网站建设不完整
  • 手机终端传输模式深入介绍
  • 深圳工程造价建设信息网站为什么不做网站做公众号
  • 发那科机器人指令详解:从入门到精通
  • 机器人自主导航方案概述
  • HTTPS 究竟比 HTTP 好在哪?
  • 【机器视觉】一文掌握常见图像增强算法。
  • 新能源激光焊接工作站西门子1500系列PLC通过Profinet转CANopen智能网关和机器人进行通讯案例
  • 成品网站w灬源码火龙果企业网站seo怎么做
  • 前端最佳实践
  • 潍坊网站设计深圳设计公司办公室装修设计
  • excel设置下拉框选项?【图文详解】excel怎么设置下拉框选项?数据验证?下拉框选项格式?
  • 大语言模型四大核心技术架构深度解析
  • 图片分类展示网站源码有哪个网站可以做链接
  • 辽宁城乡建设网站优惠券网站怎么搭建
  • 2025.11.14 力扣每日一题