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

【分治思想】归并排序 与 逆序对

归并排序

归并排序是一种分治算法,怎么分,怎么治?

  • 分:通过递归不断把数组分成两半,直到每个子数组只剩 1 个元素(天然有序)
  • 治:把两个已经排好序的子数组合并成一个有序数组。

把问题分解为简单子问题,就体现在每个数组只剩一个元素时,那就天然有序了。

模板题

P1177 【模板】排序
在这里插入图片描述

归并排序代码实现

#include<iostream>using namespace std;const int N = 1e5 + 10;int n, a[N], tmp[N]; //a存数据,tmp辅助归并排序时合并两个数组 void merge_sort(int left, int right)
{if(left >= right) return; //left == right只有一个元素,有序,返回 //1.数组一分为二 int mid = (left + right) / 2;//2.将左右数组都排好序 merge_sort(left, mid);merge_sort(mid + 1, right);//3.合并左右有序数组到tmp中 int cur1 = left, cur2 = mid + 1, i = left; //i帮助写入临时数组 while(cur1 <= mid && cur2 <= right){if(a[cur1] <= a[cur2]) tmp[i++] = a[cur1++];else tmp[i++] = a[cur2++];}while(cur1 <= mid) tmp[i++] = a[cur1++];while(cur2 <= right) tmp[i++] = a[cur2++];//4.将tmp中的[left,right]区间转移到a的[left,right]区间 for(int j=left;j<=right;j++) a[j] = tmp[j]; //最容易错的一步,要知道在merge_sort函数中,我们是对[left, right]区间进行排序而不是[0, n] 
}int main()
{cin >> n;for(int i=1;i<=n;i++) cin >> a[i];merge_sort(1,n);for(int i=1;i<=n;i++) cout << a[i] << " ";return 0;
}

时间复杂度

不断地将数组一分为二,直到只剩下一个元素为止,时间复杂度为 logn;
将 tmp 数组复制到 a 数组,时间复杂度为 n。
总的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

逆序对

什么是逆序对?

逆序对(Inversion)是指在一个序列中,如果前面的数比后面的数大,那么这两个数就构成一个逆序对。

逆序对和排序的关系

  • 逆序对的数量衡量了序列的无序程度:逆序对越多,序列越乱;逆序对越少,序列越接近有序。
  • 排序的本质就是消除逆序对

例题

P1908 逆序对
在这里插入图片描述

分析

首先我们可以想到两层for循环暴力统计逆序对个数,但是会超时。

我们可以尝试一下分治的思想,在原数组中找逆序对,相当于先将数组一分为二,在左半边数组找逆序对,在右半边数组找逆序对,再一左一右找逆序对,所有的逆序对数量加起来就是原数组的逆序对数量。

于是我们得到了这样两个数组,分别对左右求逆序对,这咋求???这还不是要两层for循环挨个遍历统计逆序对吗,这算上二分数组的时间复杂度,现在总时间复杂度干到了 O ( n 2 l o g n ) O(n^2logn) O(n2logn),这能对吗?
请添加图片描述
但是当左边这个数组和右边这个数组分别有序的时候,再统计逆序对个数就非常方便了。我们选择一个元素在左边数组,另一个元素在右边数组的逆序对,统计逻辑如下:

请添加图片描述
我们可以发现,这与归并排序中合并有序数组的流程是一致的。

但是这里就有一个问题:对左边数组和右边数组分别排序的话,是否会影响逆序对的统计呢?
不会,因为在统计一左一右的时候,左右数组的逆序对已经统计过了,才形成的左右这样有序的数组,统计逆序对的统计和归并排序是同时进行的。
我们仍可以从极限情况分析,当数组的长度为2时,分成左右数组,它们的长度分别为1,于是左右数组中的逆序对个数肯定为0,我们接下来统计一左一右的逆序对个数,统计完之后,这个长度为2的数组也就通过归并排序变得有序了。另一边肯定也通过这样相同的流程得到了一个长度为2的有序数组,然后对它们进行一左一右的逆序对统计,这时候再思考为什么不分别统计左右数组中的逆序对个数呢?因为已经统计过了,它们就是在一左一右合并的过程中统计的。

代码

#include<iostream>using namespace std; typedef long long LL;const int N = 5e5 + 10;int n, a[N], tmp[N];LL merge(int left, int right)
{if(left >= right) return 0;//1.一分为二int mid = (left + right) >> 1; //2.计算左右数组LL ret = 0; ret += merge(left, mid);ret += merge(mid + 1, right);//3.合并左右数组int cur1 = left, cur2 = mid + 1, i = left;while(cur1 <= mid && cur2 <= right){if(a[cur1] <= a[cur2]) tmp[i++] = a[cur1++];else{tmp[i++] = a[cur2++];ret += mid - cur1 + 1; }	} while(cur1 <= mid) tmp[i++] = a[cur1++];while(cur2 <= right) tmp[i++] = a[cur2++];for(int j=left;j<=right;j++) a[j] = tmp[j];return ret;
}int main()
{cin >> n;for(int i=1;i<=n;i++) cin >> a[i];cout << merge(1,n) << endl;	return 0;
}
http://www.lqws.cn/news/602497.html

相关文章:

  • Nginx重定向协议冲突解决方案:The plain HTTP request was sent to HTTPS port
  • CertiK《Hack3d:2025年第二季度及上半年Web3.0安全报告》(附报告全文链接)
  • OEM怎么掌握软件开发能力
  • 记本好书:矩阵力量:线性代数全彩图解+微课+Python编程
  • Python OrderedDict 用法详解
  • 学习昇腾开发的第11天--主要接口调用流程
  • CMU-15445(6)——PROJECT#2-BPlusTree-Task#1
  • 记一次Ubuntu22安装MongoDB8并同步本地数据过程
  • 应急响应类题练习——玄机第四章 windows实战-emlog
  • 微信小程序使用秋云ucharts echarts
  • 高阶数据结构------并查集
  • 数据结构day7——文件IO
  • STM32——存储器映射(Memory mapping)
  • 反向传播 梯度消失
  • OSE3.【Linux】练习:编写进度条及pv命令项目中的进度条函数
  • 07CSRF 漏洞保护
  • vite项目中引入tailwindcss,难倒AI的操作
  • Modbus协议
  • 数字图像处理学习笔记
  • Spring IOC容器核心阶段解密:★Bean实例化全流程深度剖析★
  • 菜谱大全——字符串处理艺术:从文本解析到高效搜索 [特殊字符][特殊字符]
  • 城市灯光夜景人像街拍摄影后期Lr调色教程,手机滤镜PS+Lightroom预设下载!
  • 自由学习记录(66)
  • RESTful API 设计原则深度解析
  • 转录组分析流程(六):列线图
  • 笨方法学python-习题12
  • JavaScript 安装使用教程
  • 解码知识整理,使您的研究更高效!
  • 分区表设计:历史数据归档与查询加速
  • [论文阅读] 人工智能 + 软件工程 | 从软件工程视角看大语言模型:挑战与未来之路