1. 程式人生 > >最長上升子序列(Longest increasing subsequence)

最長上升子序列(Longest increasing subsequence)

問題描述         對於一串數A={a1a2a3…an},它的子序列為S={s1s2s3…sn},滿足{s1<s2<s3<…<sm}。求A的最長子序列的長度。

動態規劃法

演算法描述:         設數串的長度為n,L[i]為以第i個數為末尾的最長上升子序列的長度,a[i]為數串的第i個數。         L[i]的計算方法為:從前i-1個數中找出滿足a[j]<a[i](1<=j<i)條件的最大的L[j],L[i]等於L[j]+1。 動態規劃表示式:

程式碼實現:

int LIS(int a[], int n)
{
    int len[MAXSIZE];
    int i, j;
    int maxlen = 0;
    //計算以第i個數為結尾的最長上升子序列的長度
    for (i = 1; i <= n; i++)
    {
        len[i] = 0;
        //從前i-1個數中找出滿足a[j]<a[i](1<=j<i)條件的最大的L[j]
        for (j = i-1; j >= 1; j--)
        {
            if (a[j] < a[i] && len[j] > len[i])
            {
                len[i] = len[j];
            }
        }
        len[i]++;
 
        if (len[i] > maxlen)
        {
            maxlen = len[i];
        }
    }
    return maxlen;
}

上述演算法的時間複雜度為O(n2)。

改進演算法:         在從前i-1個數中找出滿足a[j]<a[i](1<=j<i)條件的最大的L[j]的時間複雜度為O(n),這裡採用二分查詢的方法對它進行優化,使其複雜度降為O(nlogn)。         增設一個m[]陣列,m[x]存放長度為x的最長上升子序列的最小末尾數。例:m[3] = 17表示長度為3的最長上升子序列的最小末尾數為17。         由於子序列是上升的,所以m陣列中的元素有一個性質,當x<y時,m[x]<m[y],利用這個性質來使用二分查詢。 設m陣列所儲存的最長上升子序列的長度為k,當前計算的數為第i個 如果a[i]>m[k],則m[++k]=a[i]; 否則在m[1~k]內二分查詢小於(等於)a[i]的最大值的位置p,m[p]=a[i]。

程式碼實現:

int BSearch(int a[], int n, int t)
{
    int low = 1;
    int high = n;
    
    while (low <= high)
    {
        int mid = (low + high) / 2;
        if (t == a[mid])
        {
            return mid;
        }
        else if (t > a[mid])
        {
            low = mid + 1;
        }
        else
        {
            high = mid - 1;
        }
    }
    return low;
}
 
int LIS_BSearch(int a[], int m[], int n)
{
    int maxlen = 1;        //最長上升子序列的長度
    m[maxlen] = a[1];
 
    int i;
    for (i = 2; i <= n; i++)
    {
        if (a[i] > m[maxlen])
        {
            m[++maxlen] = a[i];
        }
        else
        {
            //返回小於a[i]的最大值的位置p
            int p = BSearch(m, maxlen, a[i]);
            m[p] = a[i];
        }
    }
    return maxlen;
}

改進後的演算法時間複雜度為O(nlogn)。