在上一篇文章中,我们介绍了基准排列,并介绍了从基准排列到最终排列的调整过程中会产生逆序对. 然后我们基于最终排列的编号序列计算逆序对.总的来说这个过程分成三步:
- 生成基准排列(A)的映射关系,我们用数组索引表示编号
- 根据映射关系生成最终排列(B)的编号序列
- 求B的编码序列的逆序对
我们把上述过程转变为java代码
- 生成基准排列(A)的映射关系
private Map<Comparable,Integer> setupMapping(Comparable[] a){
int len = a.length;
Map<Comparable,Integer> map = new HashMap<>(len);
for(int i=0;i<len;i++)
map.put(a[i],i);
return map;
}
- 根据映射关系生成最终排列(B)的编号序列
private int[] getCodeSequence(Comparable[] a,Comparable[] b){
Map<Comparable,Integer> map = setupMapping(a);
int[] codes = new int[a.length];
for(int i=0;i<b.length;i++){
codes[i] = map.get(b[i]);
}
return codes;
}
- 求B的编码序列的逆序对
如何求逆序对呢?最直接的方式就是遍历编码序列的每个元素,然后跟它后面的所有元素进行比较,如果后者小于前者,那么计数器加一. 完全没问题,但是时间复杂度是O(n^2). 看到n平方的复杂度,我们应该下意识的做出生理反应,所以merge sort来了.
//基于编码序列查找逆序对
private int getReversedPairs(int[] codeSequence){
int[] aux = new int[codeSequence.length];
return divide(codeSequence,aux,0,codeSequence.length-1);
}
//利用归并排序查找
private int divide(int[] codeSequence,int[] aux, int lo,int hi){
if(hi <= lo) return 0;
int inversions = 0;
int mid = lo + (hi-lo)/2;
inversions += divide(codeSequence,aux,lo,mid);
inversions += divide(codeSequence,aux,mid+1,hi);
inversions += merge(codeSequence,aux,lo,mid,hi);
return inversions;
}
private int merge(int[] a,int[] aux, int lo,int mid,int hi){
int pair = 0;
int i=lo;
int j=mid+1;
for(int k=lo;k<=hi;k++)
aux[k] = a[k];
for(int k=lo;k<=hi;k++){
if(i>mid) a[k] = aux[j++];
else if(j>hi)a[k] = aux[i++];
else if(aux[j]<(aux[i])){
StdOut.printf("%d - %d \n",aux[j],aux[i]);
pair += mid-i+1;
a[k] = aux[j++];
}
else a[k] = aux[i++];
}
return pair;
}
//入口函数,接受两个排列
public int getInversions(Comparable[] a,Comparable[] b){
int[] codes = getCodeSequence(a,b);
return getReversedPairs(codes);
}
写个ut测试一把
private static void test1(){
Integer[] a = {0,3,1,6,2,5,4};
Integer[] b = {1,0,3,6,4,2,5};
P19<Integer> merge = new P19<>();
int count = merge.getInversions(a,b);
StdOut.println("total numbers " + count);
}
public static void main(String[] args) {
test1();
}
逆序编号输出:
0 - 2
1 - 2
4 - 6
5 - 6
total numbers 4
注意 上面输出的逆序元素的编号,如果要得到最终的逆序元素,可以通过最终排列B的映射关系找出.还有一种简单的办法,在上面的分析中我们知道,编号其实就是基准数组的索引.所以可以直接把这些逆序编号作为索引来访问A元素,那么这些元素就是逆序元素. 大家可以当作练习把上面的代码修改一下.
逆序元素输出
0-1
3-1
2-4
5-4
总结
我们从求Kentall tau距离作为切入点,引出了如何求逆序对这个问题,然后展示了如何用归并排序来解决逆序对.通过这个例子,我们会发现,这些基本算法的用途非常广,对于我们来说如何熟练掌握这些基本算法,并且合理的应用到对应的场景是关键.这条路没有捷径,只能多刷题,如果你想成为算法大牛,无他,唯手熟尔.
参考
en.wikipedia.org/wiki/Kendal… www.iteye.com/blog/shmily… algs4.cs.princeton.edu/25applicati…