Longest Increasing Subsequences(最長遞增子序列)的兩種DP實現

1、本文內容
最長遞增子序列的兩種動態規劃算法實現,O(n^2)及O(nlogn).
 
 
2、問題描述
最長遞增子序列:給定一個序列,從該序列找出最長的 升序/遞增 子序列。
特色:一、子序列不要求連續; 二、子序列在原序列中按嚴格(strictly)升序排序; 三、最長遞增子序列不惟一。
 
注:下文最長遞增子序列用縮寫LIS表示。
 
example:
0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15
 
對應的LIS:
0, 2, 6, 9, 13, 15
0, 4, 6, 9, 11, 15
 
 
3、算法描述
一、考察第i+1個元素時,不考慮前面i個元素的狀態
給定長度爲n的序列A[0..n-1],對於給定某個的階段狀態,它之前各階段的狀態沒法直接影響它將來的決策,而只能間接經過當前狀態來影響;換句話說,每一個狀態都是過去歷史的完整總結。即LIS[i]當前的狀態與LIS[0..i-1]已無關。這幾句話能夠用Fibonacci的遞歸樹來驗證,對LIS的遞歸樹一樣適用。 以下面的遞歸樹:
 
                                    LIS(3)          
                                /       |      \
                      LIS(2)     LIS(1)    LIS(0) 
                      /     \       /        
             LIS(1)  LIS(0)  LIS(0)
            /   
         LIS(0) 
 
很顯然,遞歸樹有不少重複/重疊的子問題,而大問題的最優解又能夠由這些子問題的最優解獲得,符合DP的兩個條件,故能夠用DP來解決LIS問題。
那麼給定序列A[0..n-1],求它的LIS,能夠top-down分解任務,獲得遞歸樹;遞歸樹 bottom-up就能夠創建子問題的查詢表以供求解當前問題時查詢。
這就是DP的兩種處理方式: Memoization(top-down) 和  Tabulation  (bottom-up).
 
假設LIS[i]表示A[0..i]的最長遞增子序列的長度,那麼
LIS[i+1] = max{1, LIS[j]+1}, A[i+1]>A[j], for any j<=i;    注意:到A[i]的子序列長度不必定大於A[j]的子序列長度(如上例當A[j]=12, LIS[j]={0, 4, 12}和當A[i]=2, LIS[i]={0, 2})
若A[i+1]>A[j],則A[i+1]能夠接在LIS[j]長的子序列後面構成一個更長的子序列。
同時, 從A[i+1]開始又構成一個長度爲1的子序列。故二者取大值。
 
 
二、考察第i+1個元素時,考慮前面i個元素的狀態
那麼,何時在一個已存在的序列中添加或者替換一個元素是安全的呢?(DP算法都是offline algorithm,須要全局考慮)
顯然,咱們須要維護全部遞增的子序列(稱其爲 active list,便可能成爲max{LIS}的子序列)。而這些子序列的長度都不一樣,
能夠按照插入排序的思想,一個一個元素地從前面找到其應該歸屬的子序列(active list)。
 
有A[i]>A[i-1]和A[i]<A[i-1]兩種狀況,A[i]<A[i-1]又有一種特殊狀況A[i]是當前序列中的最小元素,即A[i]<A[j], for any j<i;
 
case 1. If A[i] is smallest among all end candidates of active lists, we will start new active list of length 1. 
(when we encounter new smallest element in the array, it can be a potential candidate to start new sequence,{2, 5, 3,  1, 2, 3, 4, 5, 6})
 
case 2. If A[i] is largest among all end candidates of active lists, we will clone the largest active list, and extend it by A[i].
 
case 3. If A[i] is in between, we will find a list with largest end element that is smaller than A[i]. Clone and extend this list by A[i]. We will discard all other lists of same length as that of this modified list. 
(找到end element小於A[i]的active list以後,其餘相同長度的active list將被刪除,由於A[i]小於這些等長active list的end element,用新的active list代替將被刪除的active list:複製小於A[i]的active list,並添加A[i])
 
處理過程以下
example:A[ ] = {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}
A[0] = 0. Case 1. There are no active lists, create one.
0.
-----------------------------------------------------------------------------
A[1] = 8. Case 2. Clone and extend.
0.
0,  8.
-----------------------------------------------------------------------------
A[2] = 4. Case 3. Clone, extend and discard.
0.
0,  4.
0, 8. Discarded
-----------------------------------------------------------------------------
A[3] = 12. Case 2. Clone and extend.
0.
0,  4.
0, 4,  12.
-----------------------------------------------------------------------------
A[4] = 2. Case 3. Clone, extend and discard.
0.
0,  2.
0, 4. Discarded.
0, 4,  12.
-----------------------------------------------------------------------------
A[5] = 10. Case 3. Clone, extend and discard.
0.
0,  2.
0, 2,  10.
0, 4, 12. Discarded.
-----------------------------------------------------------------------------
A[6] = 6. Case 3. Clone, extend and discard.
0.
0,  2.
0, 2,  6.
0, 2, 10. Discarded.
-----------------------------------------------------------------------------
A[7] = 14. Case 2. Clone and extend.
0.
0,  2.
0, 2,  6.
0, 2, 6,  14.
-----------------------------------------------------------------------------
A[8] = 1. Case 3. Clone, extend and discard.
0.
0,  1.
0, 2. Discarded.
0, 2,  6.
0, 2, 6,  14.
-----------------------------------------------------------------------------
A[9] = 9. Case 3. Clone, extend and discard.
0.
0,  1.
0, 2,  6.
0, 2, 6,  9.
0, 2, 6, 14. Discarded.
-----------------------------------------------------------------------------
A[10] = 5. Case 3. Clone, extend and discard.
0.
0,  1.
0, 1,  5.
0, 2, 6. Discarded.
0, 2, 6,  9.
-----------------------------------------------------------------------------
A[11] = 13. Case 2. Clone and extend.
0.
0,  1.
0, 1,  5.
0, 2, 6,  9.
0, 2, 6, 9,  13.
-----------------------------------------------------------------------------
A[12] = 3. Case 3. Clone, extend and discard.
0.
0,  1.
0, 1,  3.
0, 1, 5. Discarded.
0, 2, 6,  9.
0, 2, 6, 9,  13.
-----------------------------------------------------------------------------
A[13] = 11. Case 3. Clone, extend and discard.
0.
0,  1.
0, 1,  3.
0, 2, 6,  9.
0, 2, 6, 9,  11.
0, 2, 6, 9, 13. Discarded.
-----------------------------------------------------------------------------
A[14] = 7. Case 3. Clone, extend and discard.
0.
0,  1.
0, 1,  3.
0, 1, 3,  7.
0, 2, 6, 9. Discarded.
0, 2, 6, 9,  11.
----------------------------------------------------------------------------
A[15] = 15. Case 2. Clone and extend.
0.
0,  1.
0, 1,  3.
0, 1, 3,  7.
0, 2, 6, 9,  11.
0, 2, 6, 9, 11,  15. <-- LIS List
----------------------------------------------------------------------------
 
注:觀察上面的處理過程,咱們都只在處理全部active list的最後一個元素end element(粗體),那麼僅僅須要維護全部active list構成的end element集合(粗體斜邊),
      能夠用一維數組來存儲。discard操做能夠用replace操做來模擬。
 
 
4、算法實現
 
第三節的算法描述序號分別對應下面的算法實現
一、時間複雜度爲O(n^2)的DP實現
 1 /**
 2  @description: Longest Increasing Subsequence
 3  @author:   seiyagoo
 4  @create:   2013.10.25
 5  @modified: 2013.10.26
 6 **/
 7 int LIS_1(int A[], int size){
 8 
 9     int *LIS   = new int[size];
10     vector<int> *vec = new vector<int>[size];
11 
12     /* Compute optimized LIS values in bottom up manner */
13     for(int i=0; i < size; i++){
14         LIS[i]=1;                        //初始化默認長度
15         int max_j=0, flag=0;
16         for(int j=0; j < i; j++){        //查表,找出前面最長的序列, 若將A[i]加入LIS[j](LIS[j]+1的含義)的遞增子序列比當前的LIS[i]更長, 則更新LIS[i]
17             if(A[i] > A[j] && LIS[i] < LIS[j]+1){
18                 LIS[i] = LIS[j]+1;
19                 max_j=j;
20                 flag=1;
21             }
22         }
23         if(flag)                        //copy前面最長子序列到vec[i]
24             vec[i].insert(vec[i].end(), vec[max_j].begin(), vec[max_j].end());
25         vec[i].push_back(A[i]);       //最後放入A[i]
26     }
27     
28     /*Show LIS of the current state*/
29     vector<int>::iterator it;
30     cout<<left;
31     for(int i=0; i<size; i++){
32         cout<<setw(2)<<A[i]<< "  -->  ";
33         for(it = vec[i].begin(); it!=vec[i].end(); it++)
34             cout<<*it<<" ";
35         cout<<endl;
36     }
37     
38     /* Pick maximum of all LIS values, namely max{LIS[i]} */
39     int max_len=0;
40     for(int i = 0; i < size; i++ )
41       if( max_len < LIS[i] )
42          max_len = LIS[i];
43          
44     delete[] LIS;
45     delete[] vec;
46 
47     return max_len;
48 }

 

二、 時間複雜度爲O(nlogn)的DP實現
 1 /**
 2  @description: Longest Increasing Subsequence
 3  @author:   seiyagoo
 4  @create:   2013.10.25
 5  @modified: 2013.10.26
 6 **/
 7 
 8 // Binary search (note boundaries in the caller)
 9 // A[] is ceilIndex in the caller
10 int CeilIndex(int A[], int l, int r, int key) {
11     int m;
12  
13     while( r - l > 1 ) {
14         m = l + (r - l)/2;
15         (A[m] >= key ? r : l) = m; // ternary expression returns an l-value
16     }
17  
18     return r;
19 }
20 
21 int LIS_2(int A[], int size) {
22     // boundary case: when array size is one
23     if( 1 == size ) return 1;
24  
25     int *tailTable   = new int[size];
26     vector<int> *vec = new vector<int>[size];
27     int len; // always points empty slot
28  
29     //memset(tailTable, INT_MAX, sizeof(tailTable[0])*size);  @bug
30 
31     for(int i = 0; i < size; i++)
32         tailTable[i] = INT_MAX;
33     
34     tailTable[0] = A[0];          //tailTable[0] store the smallest value
35     vec[0].push_back(A[0]);
36     
37     len = 1;
38     for( int i = 1; i < size; i++ ) {
39         if( A[i] < tailTable[0] ) {  //case 1: new smallest value
40             tailTable[0] = A[i];
41 
42             /*discard and create*/
43             vec[0].clear();
44             vec[0].push_back(A[i]);
45         }
46         else if( A[i] > tailTable[len-1] ) { //case 2: A[i] wants to extend largest subsequence
47             tailTable[len++] = A[i];
48             
49             /*clone and extend*/
50             vec[len-1] = vec[len-2];
51             vec[len-1].push_back(A[i]);
52         }
53         else { //case 3: A[i] wants to be current end candidate of an existing subsequence, It will replace ceil value in tailTable
54             int ceilIndex = CeilIndex(tailTable, -1, len-1, A[i]);
55             tailTable[ceilIndex] = A[i];
56 
57             /*discard, clone and extend*/
58             vec[ceilIndex].clear();
59             vec[ceilIndex] = vec[ceilIndex-1];
60             vec[ceilIndex].push_back(A[i]);
61         }
62         
63         /*Printf all the active lists*/
64         vector<int>::iterator it;
65         cout<<left;
66         cout<<"A["<<i<<"] = "<<A[i]<<endl<<endl;
67         cout<<"active lists:"<<endl;
68         for(int i=0; i<len; i++){
69             for(it = vec[i].begin(); it!=vec[i].end(); it++)
70                 cout<<*it<<" ";
71             cout<<endl;
72         }
73 
74         /*Printf end elements of all the active lists*/
75         cout<<endl<<"end elements array:"<<endl;
76         for(int i = 0; i < size; i++)
77             if(tailTable[i] != INT_MAX)
78                 cout<<tailTable[i]<<" ";
79         cout<<endl;
80         cout<<"-------------------------"<<endl;
81     }
82     
83     
84     delete[] tailTable;
85     delete[] vec;
86  
87     return len;
88 }

 

5、運行結果算法

example:express

0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15
 
算法實現一
 
算法實現二
 
 
參考:
《編程之美》
相關文章
相關標籤/搜索