【算法28】冒泡排序中的交換次數問題


問題描述

題目來源:Topcoder SRM 627 Div2 BubbleSortWithReversals

給定待排序數組A,在最多反轉K個A的不相交子數組后,對A采用冒泡排序,問最小的swap次數是多少?冒泡排序的偽代碼如下:

BubbleSort(A):

   循環len(A) - 1次:

     for i from 0 to len(A) - 2:

         if (A[i] > A[i+1])

              swap(A[i], A[i+1])

問題分析

    首先,容易分析得到:對於任意待排序數組A,其采用冒泡排序所需要的swap次數=A中逆序對的個數。這是因為冒泡排序的過程就是對於任意兩個元素,判斷兩個元素是否逆序(即小的元素排在大元素之后),如果逆序,則swap。上面的結論是顯而易見的。

    【思路1】接下來,問題就變為,求給定數組在reverse最多K個子數組之后,A中的逆序對數的最小值。給定一個數組A,求解逆序對數是比較簡單的,直接兩個for循環判斷並計數就可以了。問題難在允許reverse最多K個子數組,這樣我們需要依次考慮reverse 0, 1, 2, ..., K個子數組,假設我們此時考慮reverse k (0 =< k <= K) 個子數組, 我們還需要去找到是哪k個子數組,reverse后計算逆序對數,這情況就多了去了,很難理清頭緒,這條路似乎難以走通。

    【思路2】我們意識到這是一個典型的優化問題,對於復雜優化問題動態規划可是神器,讓我們來試試看。運用動態規划需要滿足兩個條件:(1)重疊子問題,即在求解最優解的過程中會反復求解一些規模更小的子問題;(2)最優子結構,即當前問題的最優解可以通過其子問題的最優解得到。

    考慮由下標j貢獻的逆序對數 = 下標i的個數滿足i < j && A[i] > A[j], 即在j之前且與A[j]成逆序關系的元素個數。容易觀察到下標j前面的元素排序並不影響下標j貢獻的逆序對數,因為j前面的元素無論如何排序,大於A[j]的元素的個數是不會變的。定義子問題 f(x, k) 表示在最多reverse k個不重疊子數組后,由所有大於x的下標j貢獻的逆序對數,即\[f(x, k) =\sum_{j >= x}^{n} contribution(j), with\ revserse\ at\ most\ k\ disjoint\ subarrays\ \] 那么f(0, K)表示在最多reverse K個不重疊子數組后,由所有元素j >= 0構成的逆序對數,即為原問題的解。

    [1] Base Case: f(n, k) = 0, k = 0, 1, ..., K, 因為沒有大於等於n的下標。接下來分為兩種情況,

    [2] reverse的子數組中不包含x :此時f(x, k)的值等於有x貢獻的逆序對數+y(y >= x + 1)貢獻的逆序對數,由於reverse的數組不包含x,而前面已經說明在x前的子數組無論如何reverse是不會影響x貢獻的逆序對數的,因而reverse的子數組在x之后最多有k個,從而\[f(x, k) = contribution(x) + f(x + 1, k)\]

    [3] reverse 的子數組中包含x: 此時我們只需要考慮從x開始reverse的子數組即可(為什么?因為其他情況可以轉化為這種情況),假設我們reverse了子數組(A[x], A[x+1],...A[y-1], A[y])從而得到子數組(A[y], A[y-1], ..., A[x + 1], A[x]), 那么我們需要計算所有的contribution(j), x <= j <= y. 為了計算contribution(j) 我們可以取子數組B = (A[0], A[1], ..., A[x], ....A[y]), 在B中計算contribution(j)。 之后問題轉化為在y+1之后的數組最多reverse (k - 1)個子數組 (因為已經用掉一次reverse)后的逆序對數, 即\[f(x, k) = \sum_{j >= x}^{y}contribution(j) + f(y + 1, k - 1)\] 遍歷y = x + 1, ..., n的每個取值,取所有情況中最小的f(x,k)。

[4] 在[2]和[3]情況中選擇最小的f(x, k).

程序源碼

    經過上面的分析,我們可以采用bottom to up的方法給出如下源碼

 1 #include <iostream>
 2 #include <string>
 3 #include <vector>
 4 #include <map>
 5 #include <set>
 6 #include <algorithm>
 7 #include <functional>
 8 #include <cstdio>
 9 #include <cstdlib>
10 using namespace std;
11 
12 // nSwap == nNiXuNumber
13 class BubbleSortWithReversals
14 {
15 public:
16     // return the number of nixu with index >= x
17     int getCurNiXuNumber(vector<int> A, int x)
18     {
19         int n = A.size();
20         int cnt = 0;
21         for (int i = x; i < n; ++i)
22         {
23             for (int j = 0; j < i; ++j)
24             {
25                 if (A[j] > A[i])
26                 {
27                     cnt++;
28                 }
29             }
30         }
31 
32         return cnt;
33     }
34 
35     // DP solution:
36     // Define: f(x, k) = the number of nixu with indices i >= x that can reverse
37     //         at most k subarray without overlap, thea number of xinu at index i,
38     //         is the number of index j with j < i && A[j] > A{i]
39     //
40     //         Then the f(0, K) is the answer of original problem
41     //
42     // Base Case: f(n, k) = 0 with k = 0, 1, 2, ... MAX_K;
43     // Recursive relationship:
44     // Case 1: A[x] is not in the reversed subarray, which means A[x] stays in index
45     //         x after the most k reverses of subarray, Note that the order of elems
46     //         A[j] with j < x do not affect the the number of Nixu at index x
47     //         So in this case
48     //         f(x, k) = The number of nixu at index x + f(x + 1, k)
49     // Case 2: A[x] is in the reversed subarray, we only need to consider the reversed      
50     //            subarray (A[x], A[x+1], ..., A[y-1], A[y]), cause if the reversing 
51     //         start before index x such as (A[a], A[b], A[c], A[x], ...), then when
52     //         x = a, it equals exactly the situation of current time
53     //         So in this Case:
54     //         We first revere (A[x], A[x+1], ..., A[y-1], A[y]) to obtain
55     //         (A[0],...A[x-1], A[y], A[y-1], ..., A[x+1], A[x])
56     //         Then we caculate The number of nixu at index x + f(y+1, k-1)
57     //         f(x, k) = the number of nixu at x + f(y + 1, k - 1);
58     // Compare case 1 and case 2 to get the minimum
59 
60 
61     int getMinSwaps(vector<int> A, int K)
62     {
63         int n = A.size();
64         int f[MAX_K][MAX_K] = {0};    
65         // init
66         for (int k = 0; k < MAX_K; ++k) f[n][k] = 0;
67 
68         //
69         for (int x = n - 1; x >= 0; --x)
70         {
71             for (int k = 0; k <= K; ++k)
72             {
73                 // Case 1: x not in the reversed subarray
74                 vector<int> B1(A.begin(), A.begin() + x + 1);
75                 f[x][k] = getCurNiXuNumber(B1, x) + f[x+1][k];
76 
77                 // Case 2: x in the reversed subarray
78                 if (k >= 1)
79                 {
80                     for (int y = x + 1; y < n; ++y)
81                     {
82                         vector<int> B2(A.begin(), A.begin() + y + 1);
83                         reverse(B2.begin() + x, B2.begin() + y + 1);
84                         f[x][k] = min(f[x][k],
85                                       getCurNiXuNumber(B2, x) + f[y+1][k-1]);
86                     }
87                 }
88             }
89         }
90 
91         return f[0][K];
92     }
93 public:
94     static const int MAX_K = 51;
95 };

復雜度分析

     子函數獲取當前逆序對數的時間復雜度為 O(n^2), 主函數外層循環nk次, 對於case 1, 只需要計算x處的貢獻,因而子函數在此處復雜度為O(n);對於case 2, 子函數需要計算在x,..., y的contribution, 而在i(x <= i <=y)處需循環i次,從而復雜度為 $\sum_{y = x + 1}^{y < n} \sum_{i = x}^{y} i = O(n^2)$,從而總的時間復雜度為在case2時出現,為O(NK^3)。

參考文獻

[1] Topcoder Editorial SRM 627

[2] Dynamic Programming


免責聲明!

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



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