背景:给一列数a1,a2,a3,,,,,an,求它的逆序对数,即有多少个有序对(i,j),使得i < j但ai > aj,n高达10^6.
O(n*n)的枚举超时,所以用时间复杂度为O(nlogn)为归并排序进行统计逆序对数。
归并排序按照分治三步法:
划分问题:把序列元素个数分成尽量相等的两半
递归求解:把两半元素分别排序
合并问题:把两个有序表合并成一个
关于第3步,合并,根据算法入门经典上的讲解,加上自己的理解,用归并排序实现求逆序对的思想为:
递归求解已经将数组分为左半数组和右半数组,对于每一个右半数组的j,只求左半数组中大于j位置处的数的个数m-p,而m-p的总和就是逆序对的总数。详细理解如下:
在用临时空间存值过程中,只要左半数组中存在数<=右半数组j位置的数,那么必定会存入临时空间,所以当将右半数组j位置的数存入临时空间时,说明左半数组中的从p位置开始的数全大于右半数组j位置的数,这种情况下左半数组的个数和=m-p。
void merge_sort(int *a,int x,int y,int *b)
{
if( y - x <= 1)//边界条件:只剩下两个元素
return;
int m = x + (y-x)/2;
merge_sort(a,x,m,b);
merge_sort(a,m,y,b);
int p,q,i;
p = i = x;
q = m;
while(p < m||q < y)
{
if( q >= y||(p < m&&a[p] <= a[q]))//从左半数组复制到临时空间
b[i++] = a[p++];
else
{
b[i++] = a[q++];//从右半数组复制到临时空间
ans += m-p;//对于右边的q,统计左边比它大的元素个数m-p,则所有m-p之和就是答案
}
}
for(int j = x; j < y; j ++)
a[j] = b[j];
return ;
}
int main()
{
ans = 0;
merge_sort(num,0,n,vis);//归并排序
printf("%d\n",ans);
return 0;
}