逆序对的定义:在一个数组a中如果有两个元素满足i < j && a[i] > a[j],那么(i, j)就称为数组中的一个逆序对
现在,我们要求的是,给定一个长度为N的数组a,求出该数组的逆序对的个数
比如 int a[4] = {7, 5, 6, 4} 逆序对为(7, 6), (7, 5), (7, 4), (6, 4), (5, 4)
1.常规的做法是依次枚举所有的元素对(a[i], a[j]),显然,这样的时间复杂为平方级别的O(n^2)
2.还有一种做法是基于冒泡排序,从最后一个元素开始,逐次往上面冒,每当交换发生的时候,逆序对的个数就加1,简易证明如下
a[1]a[2]a[3]...a[i]a[i+1]..a[n-1],假定之前逆序对的个数为l,现在在冒泡的过程中发现a[i+1] > a[i], 显然要交换这两个元素的位置,并且一个逆序对已经产生,即(a[i], a[i+1]),交换之后的数组为a[1]a[2]a[3]...a[i+1]a[i]..a[n-1],这个时候,任意两个元素l,m != i, j(a[l],a[m])如果在交换之前满足逆序,那么在交换之后同样满足逆序,因为他们的位置并没有发生变化,因此,此次交换对逆序对的总数没有影响,再由于冒泡是从后往前冒,所以能够保证每一次交换产生的逆序对在原来的数组中都存在,证毕!
由于冒泡排序可以通过各种优化来降低时间复杂度,但是最坏情况下冒泡的时间复杂度依然是O(n^2)
3.基于分治的思想,我们可以将数组分为两半,假定左边的逆序对已经求毕,右边的逆序对也已经求毕,比如还是数组int a[4] ={7, 5, 6, 4},左半边{7, 5}逆序对为1, 右半边{6, 4}逆序对为1,剩下的逆序对肯定是一个元素在左半边,另一个元素在右半边,那么,在O((n^2)/4)的时间内可以枚举所有的这种类型的逆序对,但是根据主定理
T(n) = 2*T(n/2) + O(n^2)最后计算的时间复杂度T(n)依然是平方级别的,有没有什么更好的办法呢?
我们继续思考,如果我们假定左半边和右半边的数组均已经排好序了,比如左半边的数组为{5, 7}右半边的数组为{4, 6},我们首先比较7和6,发现7>6由于7在数组左半边,6在数组右半边,显然7在原数组中的序号小于6在原数组中的序号,(7, 6)满足逆序对,由于右半边的数组是排好序的,也就是说6之前的所有元素都比6小,当然也比7小,所以6之前的所有元素和7也满足逆序对的条件,这样就免去了6之前所有元素的扫描。
这样,每当前半部分的当前元素p大于后半部分的当前元素q,逆序对就增加右半部分区间中q之前的元素个数,并且将p向左边移动
如果p <= q显然p,q不满足逆序对,那么直接将q往前移一步继续比较,直到两边的元素至少有一边比较完毕
以上的步骤都是基于左半边和右半边都已经排好序的情况下,这个假定貌似有点呵呵了,但是,如果聪明的你熟悉归并排序的话,这个假定对你来说就是小菜一碟了,归并排序的思想就是每次将左半边和右半边的元素分别排序,然后将排好序的元素归并, 这个归并过程我们刚好可以“顺便”计算一下逆序对,只要将归并的过程从两半的数组尾部开始进行即可,C++实现代码如下,你也可点击这里(IE浏览器除外)直接运行下面的代码
#include <iostream>
#include <string>
#define MAX 110
using namespace std;
int merge_sort_main(int a[], int copy[], int begin, int end)
{
if (begin == end)
return 0;
int mid = begin + (end - begin) / 2;
int left = merge_sort_main(copy, a, begin, mid);
int right = merge_sort_main(copy, a, mid + 1, end);
int i = mid, j = end, k = end + 1;
int ans = left + right;
while (i >= begin && j >= mid+1)
{
if (copy[i] > copy[j])
{
a[--k] = copy[i--];
ans += j - mid;
}
else
a[--k] = copy[j--];
}
while (i >= begin)
a[--k] = copy[i--];
while (j >= mid + 1)
a[--k] = copy[j--];
return ans;
}
int merge_sort(int a[], int n)
{
int copy[MAX];
for (int i = 0; i < n; ++i)
copy[i] = a[i];
return merge_sort_main(a, copy, 0, n-1);
}
int main() {
int a[10] = {7, 5, 6, 4};
cout << merge_sort(a, 4) << endl;
return 0;
}