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;}}