Longest Increasing Subsequence

來源:互聯網
上載者:User
   傳統上解決longest incresing subsequence 的演算法的時間複雜度是O(n^2) ,演算法如下:

int lcs( int* a, int N ) {
   int *best, *prev, i, j, max = 0;
   best = (int*) malloc ( sizeof( int ) * N );
   prev = (int*) malloc ( sizeof( int ) * N );
 
   for ( i = 0; i < N; i++ ) best[i] = 1, prev[i] = i;
 
   for ( i = 1; i < N; i++ )
      for ( j = 0; j < i; j++ )
         if ( a[i] > a[j] && best[i] < best[j] + 1 )
            best[i] = best[j] + 1, prev[i] = j;  // prev[] is for backtracking the subsequence
 
   for ( i = 0; i < N; i++ )
      if ( max < best[i] )
         max = best[i];
 
   free( best );
   free( prev );
 
   return max;
}
 
// Sample usage.
int main(){
  int b[] = { 1, 3, 2, 4, 3, 5, 4, 6 };
  // the longest increasing subsequence = 13456?
  // the length would be 5, as well lcs(b,8) will return.
  printf("%d ", lcs( b, 8 ) );
}

    由於只是找上升的子序列,因此可以進一步最佳化,最佳化演算法的思想這樣 (http://www.mydrs.org/program/list-id=583.htm):先回顧經典的O(n^2)的動態規划算法,設A[i]表示序列中的第i個數,F[i]表示從1到i這一段中以i結尾的最長上升子序列的長度,初始時設F[i] = 0(i = 1, 2, ..., len(A))。則有動態規劃方程:F[i] = max{1, F[j] + 1} (j = 1, 2, ..., i - 1, 且A[j] < A[i])。

  現在,我們仔細考慮計算F[i]時的情況。假設有兩個元素A[x]和A[y],滿足

   (1)x < y < i (2)A[x] < A[y] < A[i] (3)F[x] = F[y]

  此時,選擇F[x]和選擇F[y]都可以得到同樣的F[i]值,那麼,在最長上升子序列的這個位置中,應該選擇A[x]還是應該選擇A[y]呢?

  很明顯,選擇A[x]比選擇A[y]要好。因為由於條件(2),在A[x+1] ... A[i-1]這一段中,如果存在A[z],A[x] < A[z] < a[y],則與選擇A[y]相比,將會得到更長的上升子序列。

  再根據條件(3),我們會得到一個啟示:根據F[]的值進行分類。對於F[]的每一個取值k,我們只需要保留滿足F[i] = k的所有A[i]中的最小值。設D[k]記錄這個值,即D[k] = min{A[i]} (F[i] = k)。

  注意到D[]的兩個特點:

  (1) D[k]的值是在整個計算過程中是單調不上升的。
  (2) D[]的值是有序的,即D[1] < D[2] < D[3] < ... < D[n]。

  利用D[],我們可以得到另外一種計算最長上升子序列長度的方法。設當前已經求出的最長上升子序列長度為len。先判斷A[i]與D[len]。若A[i] > D[len],則將A[i]接在D[len]後將得到一個更長的上升子序列,len = len + 1, D[len] = A[i];否則,在D[1]..D[len]中,找到最大的j,滿足D[j] < A[i]。令k = j + 1,則有D[j] < A[i] <= D[k],將A[i]接在D[j]後將得到一個更長的上升子序列,同時更新D[k] = A[i]。最後,len即為所要求的最長上升子序列的長度。

  在上述演算法中,若使用樸素的順序尋找在D[1]..D[len]尋找,由於共有O(n)個元素需要計算,每次計算時的複雜度是O(n),則整個演算法的時間複雜度為O(n^2),與原來的演算法相比沒有任何進步。但是由於D[]的特點(2),我們在D[]中尋找時,可以使用二分尋找高效地完成,則整個演算法的時間複雜度下降為O(nlogn),有了非常顯著的提高。需要注意的是,D[]在演算法結束後記錄的並不是一個符合題意的最長上升子序列!

  這個演算法還可以擴充到整個最長子序列系列問題,整個演算法的痛點在於二分尋找的設計,需要非常小心注意。   這種最佳化的演算法的時間複雜度為O(nlog(n)) ,演算法例子為: (http://www.algorithmist.com/index.php/Longest_Increasing_Subsequence.cpp)

#include <vector>
using namespace std;
 
/* Finds longest strictly increasing subsequence. O(n log k) algorithm. */
template<typename T> vector<int> find_lis(vector<T> &a)
{
    vector<int> b, p(a.size());
    int u, v;
 
    if (a.size() < 1) return b;
 
    b.push_back(0);
 
    for (int i = 1; i < (int)a.size(); i++) {
        if (a[b.back()] < a[i]) {
            p[i] = b.back();
            b.push_back(i);
            continue;
        }
 
        for (u = 0, v = b.size()-1; u < v;) {
            int c = (u + v) / 2;
            if (a[b[c]] < a[i]) u=c+1; else v=c;
        }
 
        if (a[i] < a[b[u]]) {
            if (u > 0) p[i] = b[u-1];
            b[u] = i;
        }    
    }
 
    for (u = b.size(), v = b.back(); u--; v = p[v]) b[u] = v;
    return b;
}
 
/* Example of usage: */
#include <cstdio>
int main()
{
    int a[] = { 1, 9, 3, 8, 11, 4, 5, 6, 4, 19, 7, 1, 7 };
    vector<int> seq(a, a+sizeof(a)/sizeof(a[0]));
    vector<int> lis = find_lis(seq);
 
    for (unsigned i = 0; i < lis.size(); i++)
        printf(i+1 < lis.size() ? "%d " : "%d ", seq[lis[i]]);
 
    return 0;
}

 

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.