归并
如果a,b数组是已经排序后的两个数组,最终我们需要的是合并到c数组。将a数组的所有数字单独看成n个子数组,将b数组所有数字都单独看成m个子数组。每次将a,b的两个子数组比较,最后可能某个数组没有到最后,再都添加到c数组。这样按大小添加到c数组中,保证了每个数字前后左右要么是同一数组,要么也经过了大小比较排序,肯定是排好序的数组
const int maxn=?;
int a[n],b[m],c[maxn]; //c数组必须开的足够大
void merge(){
int i,j,k;
i=j=k=0;
while(i<n && j<m){ //n、m分别是a和b数组的长度
if (a[i]<b[j]) //实现从大到小排序就把<改为>
c[k++]=a[i++];
else c[k++]=b[j++];
}
while(i<n) c[k++]=a[i++];
while(j<m) c[k++]=b[j++];
}
若想实现去重复,先将k的初始值改为1,再将while里的语句改为下面:
故输出时c数组从下标1开始
if(a[i]<b[j])
c[k]=a[i++];
else
c[k]=b[j++];
if(c[k]!=c[k-1])
k++;
归并排序
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。一般是将两个有序表合并成一个有序表。归并排序是一种稳定的排序方法,时间复杂度O(nlogn)
按照归并的思想,显然需要一个辅助数组t,在归并的时候,将上面归并的几个判断条件简化,即:
1.只要有一个序列非空就要继续合并
2.如果第二个为空,则复制左半部分a[p]。如果两个都非空,因为我们要将较小的排在前面,那么就要当a[p]<=a[q]时复制a[p]
3.否则复制a[q]
const int maxn=?;
int a[maxn],t[maxn];
void merge_sort(int x,int y){
if(y-x>1){
int mid=x+(y-x)/2;
int p=x,q=mid,i=x;
merge_sort(x,mid);
merge_sort(mid,y);
while(p<mid || q<y){
if(q>=y || (p<mid && a[p]<=a[q])) t[i++]=a[p++];
else t[i++]=a[q++];
}
for(int i=x;i<y;i++) a[i]=t[i]; //从辅助数组复制到原数组
}
}
应用
逆序对
对于一个数组a若 i > j && a[i] < a[j] 那么称a[i]与a[j]是一个逆序对,其实逆序对就是求每个数前面有多少个比它大的数
归并排序可以顺带完成逆序对数的计算:由于合并操作是从小到大进行的,当右边的a[q]复制到t[i]中时,左边还没来得及复制到t的数就是比a[q]大的数,因此在累加器中加上左边元素个数m-p即可
尽管我们在归并时归并多次,但是每次归并后都能保证同一区间前面的数比它小,后面就不需考虑
#include <iostream>
using namespace std;
const int maxn=5e5+10;
int a[maxn],t[maxn];
long long cnt=0;
void merge_sort(int x,int y){
if(y-x>1){
int mid=x+(y-x)/2;
int p=x,q=mid,i=x;
merge_sort(x,mid);
merge_sort(mid,y);
while(p<mid || q<y){
if(q>=y || (p<mid && a[p]<=a[q])) t[i++]=a[p++];
else{
t[i++]=a[q++];
cnt+=mid-p;
}
}
for(int i=x;i<y;i++) a[i]=t[i];
}
}
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
merge_sort(0,n);
printf("%lld\n",cnt);
return 0;
}