1. 程式人生 > >最長上升子序列的兩種演算法(LIS)

最長上升子序列的兩種演算法(LIS)

最長上升子序列就是求:給定的一串數字中 找出不斷上升的最長那一串(該串不一定相連,只保證遞增即可)。就比如下面這個例子   其最長上升子序列為2 3 4 7或2 3 4 6  數串長度為4 那麼具體怎麼求的呢

我們拿一個最簡單的例子講:

【題目描述】

給定N個數,求這N個數的最長上升子序列的長度

【樣例輸入】

7

2 5 3 4 1 7 6

【樣例輸出】

4

一、樸素演算法   (複雜度O(n^2))

dp[i]表示從0~i的以a[i]結尾的最大上升子序列長度,而以a[i]結尾的最長上升子序列有兩種:1.只包含a[i]的子序列(即為其自身長度dp[i]=1);  2.在滿足j<i且a[j]<a[i]的以a[j]為結尾的上升子序列末尾,追加上a[i]得到的子序列(即dp[i]=a[j]+1)。
所以有如下遞推關係:

dp[i]的值為max(dp[j]+1,dp[i])也就是:

i 0 1 2 3 4 5 6
a[i] 2 5 3 4 1 7 6
dp[i] 1 2 2 3 1 4 4

沒看懂表格沒關係,下面是具體解釋 對a[i]從頭開始遍歷,第一個數是2,由於2前面沒有數字,則到2的最大長度就是1(其本身)即dp[0]=1。接著a[1]=5。a[1]>a[0].那麼這時候dp[2]=dp[1]+1=2; a[2]=3,往前遍歷3<5不符合條件,3>2滿足dp[3]=dp[1]+1=2;a[3]=4,往前遍歷找滿足條件的最大值 4>3,dp[3]=dp[2]+1=3,4<5不符合條件,4>2 dp[1]+1=2<(dp[3]=3),則dp[3]=3。以此類推 。。。   

下面上程式碼:

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
    int n,dp[100010],a[100010];
    int i,j,maxn;
    while(~scanf("%d",&n)){
        for(i=0;i<n;i++){
            scanf("%d",&a[i]);
        }
        memset(dp,0,sizeof(dp));
        for(i=0;i<n;i++){
            dp[i]=1;
            for(j=i;j>=0;j--){//往前遍歷,找到以a[i]結尾的最大值
                if(a[i]>a[j]){
                    dp[i]=max(dp[i],dp[j]+1);
                }
            }
        }
        maxn=0;
        for(i=0;i<n;i++){//遍歷dp[i]找到最大值
            if(maxn<dp[i]){
                maxn=dp[i];
            }
        }
        printf("%d\n",maxn);
    }
    return 0;
}

二、二分法(複雜度nlogn)

用dp[i]表示數字串長度i的時對應的最小末尾值。然後令 i = 1 to 9 逐個考察這個序列。

此外,我們用一個變數Len記錄目前最長算到多少了

還是以上面的題舉例

首先,把a[1]有序地放到dp裡,令dp[1] = 2,表示當只有1一個數字2的時候,長度為1的LIS的最小末尾是2。這時的Len = 1。

接著,a[2] = 5,a[2] > dp[1],所以令dp[1+1] = dp[2] = a[3] = 5,表示長度為2的LIS的最小末尾是5,這時候dp[1..2] = 2, 5,這時的Len = 2。

接著,a[3] = 3,它正好夾在了1和
5之間,放在1的位置顯然不合適,因為1小於3,長度為1的LIS最小末尾應該是1,這樣很容易推知,長度為2的LIS最小末尾是3,於是把5淘汰掉,這時候dp[1..2] = 2, 3,這時Len = 2。


繼續,a[4] = 4,它在3後面,因為dp[2] = 3, 而4在3後面,於是很容易可以推知dp[3] = 4, 這時dp[1..3] = 2, 3, 4,這時的Len = 3。

 a[5] = 1,這時候往前遍歷你會發現a[5]<dp[1],則這時 將dp[1]替換為a[5]即dp[1]=a[5]=1,此時dp[1..2]=1,3,4

a[6] = 7,它很大,比4大,於是dp[4] = 7,這時的Len = 4。


 a[7] = 6,dp[3]<a[7]<dp[4]得到dp[4] = 6,這時的Len = 4。

注意。這個1,3,4,6不是LIS,它只是儲存的對應長度LIS的最小末尾。有了這個末尾,我們就可以一個一個地插入資料。
下面給出例題模版:

用STL寫:

#include<stdio.h>  
#include<string.h>  
#include<algorithm>  
using namespace std;  
#define INF 0x3f3f3f  
int dp[10010];//dp[i]表示長度為i+1的子序列末尾元素最小值;   
int a[10010];  
int main()  
{  
    int n;  
    while(scanf("%d",&n)!=EOF)  
    {  
        for(int i=0;i<n;i++)  
        {  
            scanf("%d",&a[i]);  
            dp[i]=INF;//不可以用memset對陣列賦值INF,只能賦值0或-1;  
                      //可以用fill(dp,dp+n,INF);   
        }  
        for(int i=0;i<n;i++)  
        {  
            *lower_bound(dp,dp+n,a[i])=a[i];//找到>=a[i]的第一個元素,並用a[i]替換;   
        }  
        printf("%d\n",lower_bound(dp,dp+n,INF)-dp);//找到第一個INF的地址減去首地址就是最大子序列的長度;   
    }  
    return 0;  
}