依据分治法,如果我们将数组分解成两个子序列,分别求出两个子序列的逆序数,再求出两个子序列之间元素的逆序数,就可以得出整个数组的逆序数了。可以做以下考虑:
分解:将问题分成前后两个规模为n/2的数组
解决:分别求解各自的逆序对数。如果子问题规模为2或1,可直接求解。
合并:此时虽然知道两个子序列各自的逆序对数,但两个子序列之间的逆序对数无法轻易获知,如果进行两两比较的话,合并操作的时间复杂度就是n2 ,分治法没有意义。
再考虑上述“合并”的问题,如果此时两个子序列都是有序的话,则通过修改合并排序的MERG过程就可以得出子序列之间的逆序数:在MERG对两个子序列的第一个元素之间进行选则时,如果前一个序列的首元素被选中,则逆序数不变——该元素不会和后一个序列中的剩下元素构成逆序对,如果第二个序列的首元素被选中,则逆序数增加“第一个序列剩下的元素数”——该元素和前一序列中剩下的每个元素构成逆序对,MERG后这些逆序对消除。按着这个思路分治算法重新设计如下:
分解:将问题分成前后两个规模为n/2的数组
解决:分别进行递归合并排序,并记录累加排序所消除的的逆序对数。如果子问题规模为2或1,可直接求解。
合并:通过合并排序的MERG进行合并,在MERG过程中按上述方法累加逆序数。
PS:在最初用分治法考虑问题(4)时,排序的作用在一开并不那么明显,但通过对“合并”的分析,要求对子问题的求解需要产生“排序”的副作用。这种”副作用“在分治法中是值得注意的。
import java.util.Arrays;
public class Test {
public staticint count = 0;
public staticvoid main(String[] args) {
int arr[] = { 6, 5, 4, 3, 2, 1 };
int[] result = sort_and_count(arr);
System.out.println("逆序数:"+count);
for(int i=0;i<result.length;i++){
System.out.print(result[i]+" ");
}
}
private staticint[] sort_and_count(int[] arr) {
if (arr.length == 1) {
return arr;
}
int length = arr.length;
int alength = length / 2;
int A[] = Arrays.copyOfRange(arr,0, alength);
int B[] = Arrays.copyOfRange(arr, alength, length);
A = sort_and_count(A);
B = sort_and_count(B);
arr = merge_and_count(A, B);
return arr;
}
//此函数有两个功能:
//(1)归并排序中的归并
//(2)计算逆序数
private staticint[] merge_and_count(int[] a,int[] b) {
int i = 0;
int j = 0;
int result[] = newint[a.length+b.length];
int current = 0;
while (i < a.length && j < b.length) {
if(a[i]<b[j]){
result[current++] = a[i];
i++;
}
if(a[i]>b[j]){
result[current++] = b[j];
count += (a.length - i);
j++;
}
}
if(i==a.length){
for(;j<b.length;j++){
result[current++] = b[j];
}
}
if(j==b.length){
for(;i<a.length;i++){
result[current++] = a[i];
}
}
return result;
}
}