【算法32】計算數組中的逆序對


問題描述

    設 A[1...n] 是一個數組,如果對於 i < j 有 A[i] > A[j], 則 A[i] 和 A[j] 構成一對逆序。給定一個數組,計算數組中逆序對的個數。例如數組 a[] = {1, 4, 3, 2}, 則 {4, 3} {4, 2} {3, 2}是逆序對,返回 3。

解法一:暴力求解

    兩個 for 循環枚舉所有的數對,如果是逆序對,則 count++,最終返回 count 即可。時間復雜度 O(n^2),代碼如下:

 1 #include <iostream>
 2 #include <string>
 3 #include <vector>
 4 using namespace std;
 5 
 6 int CountInversions(const vector<int>& a)
 7 {
 8     int cnt = 0;
 9     for (size_t i = 0; i < a.size(); i++)
10     {
11         for (size_t j = i + 1; j < a.size(); j++)
12         {
13             if (a[i] > a[j]) cnt++;
14         }
15     }
16     return cnt;
17 }
18 
19 int main()
20 {
21     int a[] = {1, 4, 3, 2};
22     vector<int> v(a, a + 4);
23     cout << CountInversions(v) << endl;
24     return 0;
25 }

解法二:Divide & Conquer (修改歸並排序)

    這個解法在《算法導論》中歸並排序那一節的思考題 2-4 中有提到。原題如下:

    設 A[1...n] 是一包含 n 個不同數的數組,如果在 i < j 的情況下,有 A[i] > A[j], 則 (i, j) 稱為 A 中的一個逆序 (inversion)。

    (a) 列舉出 <2, 3, 8, 6, 1> 的 5 個逆序對。      

    答: <2, 1> <3, 1> <8, 6> <8, 1> <6, 1>

    (b) 如果數組的元素取自集合 {1, 2, 3, ..., n}, 那么怎么樣的數組含有最多的逆序? 它包含多少個逆序?

    答:n 個元素最多有 n(n-1) / 2 個數組,在最壞的情況下,所有的數對都是逆序,因而最多有 n(n-1)/2 個逆序。這樣的數組為倒序 {n,n-1, ..., 2, 1}

    (c) 插入排序的運行時間與輸入數組中逆序對的數量之間有怎么樣的關系?說明你的理由。

    答:插入排序的核心過程如下,可以看到內循環的執行實際上是因為 {i, j} 構成了逆序,換句話說,內循環執行完畢后后 {i, j}的逆序被消去,所以插入排序總的循環次數 == 數組中逆序對個數。

1 for (int i = 1; i < n; ++i)
2 {
3     int x = a[i];
4     for (int j = i - 1; j >= 0 && a[j] > x; --j)
5     {
6         a[j + 1] = a[j];
7     }
8     a[j + 1] = x;
9 }

    (d) 給出一個算法,它能用 O(nlogn) 的最壞情況運行時間,確定 n 個元素的任何排列中的逆序對數。(提示:修改歸並排序)

    歸並排序的基本思想就是 Divide & Conquer: 將數組划分為左右兩部分,歸並排序左部分,歸並排序右部分,然后 Merge 左右兩個數組為一個新的數組,從而完成排序。按照這個基本思想,我們也可以運用到計算逆序對中來,假設我們將數組划分左右兩部分,並且我告訴你左邊部分的逆序對有 inv1 個, 右邊部分的逆序對有 inv2 個,如下圖所示

      那么剩余的逆序對必然是一個數出現在左邊部分,一個數出現在右邊部分,並且滿足出現左邊部分的數 a[i] > a[j], 如下圖所示, 值得說明是:左右兩部排序與否,不會影響這種情況下的逆序對數,因為左右兩部分的排序只是消除了兩部分內部的逆序對,而對於 a[i] 來自左邊, a[j] 來自右邊構成的逆序,各自排序后還是逆序

      那么接下來就需要我們就需要在 Merge 的過程中計算 a[i], a[j] 分別來自左右兩部分的逆序對數,如下圖所示,如果所有 a[i] 都小於 b[j] 的第一個數,顯然是沒有逆序的。只有當 a[i] > b[j] 是才會發生逆序,由於我們事先對 a[] 和 b[] 已經排好序,而所以如果發生 a[i] > b[j], 那么所有 a[ii] (ii > i) 也都滿足 a[ii] > b[j], 也就是說和 b[j] 構成逆序的數有 {a[i], a[i+1]...a[end]},所以逆序數增加 end- i + 1個, 所以我們每次碰到 a[i] > b[j] 的情況, 逆序對數增加 (end - i + 1) 個即可。

至此整個算法的框架圖可以如下表示:

整個算法的代碼如下,時間復雜度當然是 O(nlogn)

 1 #include <iostream>
 2 #include <string>
 3 #include <vector>
 4 using namespace std;
 5 
 6 
 7 typedef long long llong;
 8 
 9 llong MergeAndCount(vector<unsigned int> &a, int left, int mid, int right, vector<unsigned int> &temp);
10 llong MergeSortAndCount(vector<unsigned int>& a, int left, int right, vector<unsigned int>& temp);
11 llong CountInversions(vector<unsigned int>& a);
12 
13 int main()
14 {
15     //freopen("data.in", "r", stdin);
16 
17     int N;
18     cin >> N;
19 
20     vector<unsigned int> a(N, 0);
21     for (int i = 0; i < N; ++i)
22     {
23         cin >> a[i];
24     }
25 
26     llong result = CountInversions(a);
27     cout << result << endl;
28     return 0;
29 }
30 
31 llong MergeAndCount(vector<unsigned int> &a, int left, int mid, int right, vector<unsigned int> &temp)
32 {
33     int i = left;
34     int j = mid + 1;
35     int k = left;
36     llong cnt = 0;
37 
38     while (i <= mid && j <= right)
39     {
40         if (a[i] <= a[j])
41         {
42             temp[k++] = a[i++];
43         }
44         else 
45         {
46             temp[k++] = a[j++];
47             cnt += mid - i + 1;    // key step
48         }
49     }
50 
51     while (i <= mid) temp[k++] = a[i++];
52     while (j <= right) temp[k++] = a[j++];
53 
54     for (i = left; i <= right; ++i)
55     {
56         a[i] = temp[i];
57     }
58     return cnt;
59 }
60 
61 llong MergeSortAndCount(vector<unsigned int>& a, int left, int right, vector<unsigned int>& temp)
62 {
63     // base case
64     if (left >= right) return 0;
65 
66     int mid = (left + right) / 2;
67     
68     llong InversionCnt1 = MergeSortAndCount(a, left, mid, temp);
69     llong InversionCnt2 = MergeSortAndCount(a, mid+1, right, temp);
70     llong MergeInversionCnt = MergeAndCount(a, left, mid, right, temp);
71 
72     return InversionCnt1 + InversionCnt2 + MergeInversionCnt;
73 }
74 
75 llong CountInversions(vector<unsigned int>& a)
76 {
77     int n = a.size();
78     vector<unsigned int> temp(a.begin(), a.end());
79     llong ans = MergeSortAndCount(a, 0, n-1, temp);
80     return ans;
81 }

 參考文獻

[1] 《算法導論》中文第二版, P24,2-4 逆序對。

[2]  http://blog.csdn.net/qcgrxx/article/details/8005221

[3]  www.cs.umd.edu/class/fall2009/cmsc451/lectures/Lec08-inversions.pdf


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM