算法录 之 逆序对。
问题如下:
给一个长度为N的数组,a1,a2。。。aN,问其中逆序对的个数,其中逆序对的定义是 i<j 且 ai>aj 。
1,比较朴素的想法是直接for循环,内层再一个for循环找到这个数前面比他大的个数。
1 int getans() { 2 int ret=0; 3 for(int i=1;i<=N;++i) 4 for(int j=1;j<i;++j) 5 if(a[j]>a[i]) 6 ++ret; 7 return ret; 8 }
这样的复杂度是N^2的,比较慢。
2,前面的算法比较慢,能不能优化?
对于外层的for循环的每个ai,如果不用内层for循环而是用其他的方法更快的算出前面比ai大的个数,那么就快多了。
用数据结构BST来维护。
对于每个数,求出BST中比他大的个数,然后ret直接加上,然后再在BST中插入这个数。
1 int getans() { 2 BIT tree; 3 int ret=0; 4 5 for(int i=1;i<=N;++i) { 6 ret+=tree.findMax(a[i]); 7 tree.insert(a[i]); 8 } 9 10 return ret; 11 }
这样的话复杂度就是NlogN了,已经足够快了。
3,还有一种也是NlogN的算法,而且速度比较稳定。需要用到归并排序的知识。
先来看看归并排序,把a1,a2,a3。。。aN拆成两半,分别递归排序,然后再把这两个数组用O(N)来进行合并。
那么在其中再加上一些代码。
假设要求N个数的逆序对个数,如果先递归求好a1,a2。。。aN/2 和 aN/2+1。。。aN 这两个子数组的逆序对个数。
然后在求i在左边且j在右边的逆序对个数,加起来就是答案了。
因为i在左边,j在右边,如果用for循环来算的话,又变成N^2来算了。
现在注意一个性质:因为左边那个和右边那个已经分别递归求好了,那么现在求i在左边,j在右边的逆序对的时候,两个子数组中数的的顺序已经没所谓了,所以先分别给左边的数和右边的数排好序。
那么for循环右边那个,对于每个数二分搜索找到左边的有多少个比他大的。
1 int getans(int L,int R) { 2 if(R==L) return 0; 3 4 int M=(L+R)/2; 5 int ret=getans(L,M)+getans(M+1,R); // 递归求解两个子序列并且排序(归并排序的内容) 6 7 for(int i=M+1;i<=R;++i) 8 ret+=M-find(L,M,a[i])+1; // 二分查找L到M中比ai大的第一个数的位置。 9 10 merge(L,R); // 归并排序的合并步骤。 11 }
例子如下:
求 2 4 3 5 1 7 9 8 的逆序对个数:
先分别递归求出 2 4 3 5 和 1 7 9 8 的逆序对个数是1和1,然后求i在左边,j在右边的。先分别排序 2 3 4 5 和 1 7 8 9
然后求左边中比1大的个数(因为是有序的,所以直接二分就行了。)是 4 ,所以ret加上4。
再求左边中比7大的,再求比8大的,再求比9大的。都加起来的话就是答案了。
这样在合并的时候是NlogN(二分搜索)的复杂度,那么根据主定理来求 T(N)=2*T(N/2)+NlogN 得 T(N)=NlogNlogN的复杂度。
4,上面的算法还是效率不高,还能再优化。
find这个二分搜索需要log的复杂度,可以优化到O(1)来。
因为左右两个都是有序的,如果对于右数组的 ai 来说,设左数组中比他大的第一个数的位置是 bi,那么可以证明 bi 是递增的。
比如例子:
2 4 5 7 和 1 3 6 9
比1大的左数组的第一个数的位置是1,比3大的第一个位置是2,比6大的第一个位置是4,比9大的第一个位置是5(超出表示没有),也就是这四个b是递增的。
那么每次找左数组中比x大的数的位置时,可以不用find函数了,而是直接设一个指针递增。
代码如下:
1 int getans(int L,int R) { 2 if(R==L) return 0; 3 4 int M=(L+R)/2; 5 int ret=getans(L,M)+getans(M+1,R); // 递归求解两个子序列并且排序(归并排序的内容) 6 7 int p=L; 8 for(int i=M+1;i<=R;++i) { 9 while(p<=M && a[p]<=a[i]) ++p; // 因为位置是递增的。 10 ret+=M-p+1; 11 } 12 13 merge(L,R); // 归并排序的合并步骤。 14 }
复杂度的话,因为p只会递增不会递减,所以while最多N次,均摊下来的话复杂度也是O(1)的对于每个for,那么总复杂度就是O(N)的来进行合并计算。
T(N)=2*T(N/2)+N 求出 T(N)=NlogN 的复杂度。