归并排序是一种稳定、高效的排序方法,使用策略:分治法。时间复杂度O(nlogn)。
分治策略基本思想:将原问题划分成n个规模较小而结构与原问题相似的小问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。
其主要过程分为三步:
1、将原序列分为两半
2、将两半分别排序,递归求解
3、将排完序后的两个序列合并
代码如下:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 22222;
void merge_sort(int* a,int x,int y,int* t)//a 为原数组,范围 x<= i < y,t为辅助数组
{
if(y-x <= 1) return ;
int m = x+(y-x)/2;
merge_sort(a,x,m,t);//对两半分别排序
merge_sort(a,m,y,t);
int i = x,p = x,q = m;
while(p < m || q < y)//合并,取两者较小的
{
if(q >= y || (p < m && a[p] <= a[q])) t[i++] = a[p++];//要注意两边可能为空的情况
else t[i++] = a[q++];
}
for(i = x;i < y;i++) a[i] = t[i];//从辅助数组复制到原数组
}
int num[MAXN],t[MAXN];
int main()
{
int n;
scanf("%d",&n);
for(int i = 0;i < n;i++)
scanf("%d",&num[i]);
merge_sort(num,0,n,t);
for(int i = 0;i < n;i++) printf("%d ",num[i]);
puts("");
return 0;
}
逆序对数
给你一个序列a1~an,求他的逆序数对的个数, 使得 i<j 且 ai > aj。
如果用朴素的方法做,时间复杂度O(n^2),肯定不行。
这里就用分治发做。我们把序列分为两个部分,分别求出了这两个部分各自的逆序对数,即 i、j 都在左边或都在右边,现在问题是怎么在O(n)内搞出i、j分布在两边的。怎么样好求,那就是把两边都先分别排序,然后按照归并排序这样来。如果下一个数用到的是右边的,那么肯定它是两者所有数里最小的,左边剩余的所有数都比他大,就 个数 + (m-p)。递归到最底层,一个数,很显然,个数是 0 。时间复杂度O(nlogn)。
代码如下:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 22222;
int merge_sort(int* a,int x,int y,int* t)//a 为原数组,范围 x<= i < y,t为辅助数组
{
if(y-x <= 1) return 0;
int m = x+(y-x)/2;
int cnt = 0;
cnt += merge_sort(a,x,m,t);//对两半分别排序
cnt += merge_sort(a,m,y,t);
int i = x,p = x,q = m;
while(p < m || q < y)//合并,取两者较小的
{
if(q >= y || (p < m && a[p] <= a[q])) t[i++] = a[p++];//要注意两边可能为空的情况
else
{
t[i++] = a[q++];
cnt += m-p;
}
}
for(i = x;i < y;i++) a[i] = t[i];//从辅助数组复制到原数组
return cnt;
}
int num[MAXN],t[MAXN];
int main()
{
int n;
scanf("%d",&n);
for(int i = 0;i < n;i++)
scanf("%d",&num[i]);
int ans = merge_sort(num,0,n,t);
printf("%d\n",ans);
//for(int i = 0;i < n;i++) printf("%d ",num[i]);
//puts("");
return 0;
}