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
算法實現一
算法實現二
參考:
《編程之美》