前不久了解了归并排序,那么归并排序有没有什么特殊的应用呢?当然,我们可以运用归并排序来计算一个数组中有多少个逆序对数,然而,求逆序对数好像有更简单的办法,没错,如果一个大小为n的数组,我们可以用两个循环去比较数组中任意的两个值,然后累加出逆序对的数量,这样的算法虽然思路简单,但是解决数组数量较小的问题还是可以的,如果数组数量非常的大,那么作为一个O(n^2)复杂度的算法,计算机算出逆序对将要花费大量的时间,这个时候,我们就可以借助归并算法来解决这个问题。
首先,让我们来复习一下归并算法的图解:
归并算法不停的把一个较大的数组去分解为小的数组,分到不能够在分(达到出口条件)则停止分组,然后第二步在进行排序的操作,我们只需要在后面加一个第三步,顺便计算逆序对的数量,随着递归的不停调用,逆序对数量慢慢累加,最后返回出来的结果就是所求。
对于上图,归并算法在第二步的时候,因为1小于2,所以arr[0]被赋值为1,之后我们可以清楚的发现,因为递归返回的数组是已经完全排好序的,所以1不仅比2小,而且比2后面的所有数字(3,6,8)都要小,因此我们的逆序对数只要在原来的基础上在加上一个4即可,其他的步骤以此类推,以下是具体的源码实现:
// 计算逆序数对的结果以long long返回
// 对于一个大小为N的数组, 其最大的逆序数对个数为 N*(N-1)/2, 非常容易产生整型溢出
// merge函数求出在arr[l...mid]和arr[mid+1...r]有序的基础上, arr[l...r]的逆序数对个数
long long __merge( int arr[], int l, int mid, int r){
int *aux = new int[r-l+1];
for( int i = l ; i <= r ; i ++ )
aux[i-l] = arr[i];
// 初始化逆序数对个数 res = 0
long long res = 0;
// 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
int j = l, k = mid + 1;
for( int i = l ; i <= r ; i ++ ){
if( j > mid ){ // 如果左半部分元素已经全部处理完毕
arr[i] = aux[k-l];
k ++;
}
else if( k > r ){ // 如果右半部分元素已经全部处理完毕
arr[i] = aux[j-l];
j ++;
}
else if( aux[j-l] <= aux[k-l] ){ // 左半部分所指元素 <= 右半部分所指元素
arr[i] = aux[j-l];
j ++;
}
else{ // 右半部分所指元素 < 左半部分所指元素
arr[i] = aux[k-l];
k ++;
// 此时, 因为右半部分k所指的元素小
// 这个元素和左半部分的所有未处理的元素都构成了逆序数对
// 左半部分此时未处理的元素个数为 mid - j + 1
res += (long long)(mid - j + 1);
}
}
delete[] aux;
return res;
}
// 求arr[l..r]范围的逆序数对个数
// 思考: 归并排序的优化可否用于求逆序数对的算法? :)
long long __inversionCount(int arr[], int l, int r){
if( l >= r )
return 0;
int mid = l + (r-l)/2;
// 求出 arr[l...mid] 范围的逆序数
long long res1 = __inversionCount( arr, l, mid);
// 求出 arr[mid+1...r] 范围的逆序数
long long res2 = __inversionCount( arr, mid+1, r);
return res1 + res2 + __merge( arr, l, mid, r);
}
// 递归求arr的逆序数对个数
long long inversionCount(int arr[], int n){
return __inversionCount(arr, 0, n-1);
}
我定义了一个只有5个数的数组进行测试,以下为测试结果:
int main()
{
int arr[5]={10,9,8,7,6};
int result=inverstion(arr,0,4);
cout<<result;
cout<<endl;
return 0;
}