2-4 逆序对
设A[1...n]是一个包含n个不同数的数组。如果在i<j的情况下,有A[i]>A[j],则(i,j)就称为A中的一个逆序对。
(1)列出数组{2,3,8,6,1}的五个逆序。
(2)如果数组的元素取自{1,2...,n},那么,怎样的数组含有最多的逆序对?
(3)插入排序的时间与输入数组中逆序对的数量之间有怎样的关系?
(4)给出一个算法,能用Θ(n㏒n)的最坏运行时间,确定n个元素的任何排列中你逆序对的数目。(提示:修改合并排序)
解答:
(1) 略
(2)逆序数组
(3)插入排序的时间与输入数组中逆序对的数量呈线性正相关关系。通过观察插入排序的算法伪码可知,算法的运行步骤主要取决于内层循环中元素移动的次数,而每次移动就意味着数组的逆序数减一,当排序结束时逆序数为零。
(4)依据分治法,如果我们将数组分解成两个子序列,分别求出两个子序列的逆序数,再求出两个子序列之间元素的逆序数,就可以得出整个数组的逆序数了。可以做以下考虑:
分解:将问题分成前后两个规模为n/2的数组
解决:分别求解各自的逆序对数。如果子问题规模为2或1,可直接求解。
合并:此时虽然知道两个子序列各自的逆序对数,但两个子序列之间的逆序对数无法轻易获知,如果进行两两比较的话,合并操作的时间复杂度就是n2 ,分治法没有意义。
再考虑上述“合并”的问题,如果此时两个子序列都是有序的话,则通过修改合并排序的MERG过程就可以得出子序列之间的逆序数:在MERG对两个子序列的第一个元素之间进行选则时,如果前一个序列的首元素被选中,则逆序数不变——该元素不会和后一个序列中的剩下元素构成逆序对,如果第二个序列的首元素被选中,则逆序数增加“第一个序列剩下的元素数”——该元素和前一序列中剩下的每个元素构成逆序对,MERG后这些逆序对消除。按着这个思路分治算法重新设计如下:
分解:将问题分成前后两个规模为n/2的数组
解决:分别进行递归合并排序,并记录累加排序所消除的的逆序对数。如果子问题规模为2或1,可直接求解。
合并:通过合并排序的MERG进行合并,在MERG过程中按上述方法累加逆序数。
PS:在最初用分治法考虑问题(4)时,排序的作用在一开并不那么明显,但通过对“合并”的分析,要求对子问题的求解需要产生“排序”的副作用。这种”副作用“在分治法中是值得注意的。
代码:
import java.util.Arrays; public class Test { public static int count = 0; public static void 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 static int[] 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 static int[] merge_and_count(int[] a, int[] b) { int i = 0; int j = 0; int result[] = new int[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; } }