算法導論--動態規劃(最長公共子序列)

最長公共子序列問題(LCS)

給定兩個序列X=x1,x2,x3...xmY=y1,y2,y3...xn ,求X和Y的最長公共子序列。
例如:X=A,B,C,B,D,A,B ,和Y=B,D,C,A,B,A ,的最長公共子序列爲B,C,B,A ,長度爲4;
對於此問題,可以採用暴力求解的方式來比對,即窮舉出X的所有子序列,用每個子序列與y做一 一比較。假如X序列共有m個元素,對每個元素可以決定選或不選,則X的子序列個數共有2m 個,可見與長度m呈指數階,這種方法效率會很低。

動態規劃

前幾篇博文已介紹了採用動態規劃的方法解決裝配線問題及鋼鐵切割問題,它們都滿足了兩個條件:1.具有最優子結構的特徵 2.子問題重疊。

最長公共子序列的特徵

首先定義前綴Xi 的含義爲序列X=x1,x2,x3...xn 的前i項組成的子序列,即Xi=x1,x2,x3...xi(0in) ,X0 爲空串。
則有以下定理:


假如序列X=x1,x2,x3...xmY=y1,y2,y3...xn 的一個LCS爲Z=z1,z2,z3...zk
1.如果xm=yn,zk=xm=ynZk1Xm1Yn1LCS
2.如果xmyn,zkxmZXm1YLCS
2.如果xmyn,zkynZXYn1LCS


上述定理即爲要求得X和Y的LCS,可以根據xmyn 的關係,如果xm=yn ,則問題成了求Xm1Yn1 的LCS;如果xmyn ,則問題變成求兩對子序列的LCS, Xm1,Yn 的LCS及Xm,Yn1 的LCS,而且具有重疊子問題的性質都需要求Xm1,Yn1 的LCS。

由以上分析:令c[i][j]爲表示序列XiYj 的LCS的長度,則有:

c[i][j]=0i=0j=0
c[i][j]=c[i1][j1]+1i,j>0,xi=yj
c[i][j]=max(c[i][j1],c[i1][j])i,j>0,xiyj

計算LCS的長度

用數組c[m][n]保存子序列Xm,Yn 的LCS的長度,由上分析,c[i][j]的值取決於c[i-1][j-1]、c[i][j-1]、c[i-1][j],可以採用自底向上的方法向上逐次計算,這樣每個子問題只需要計算一次;用b[i][j]保存計算c[i][j]時,所選的子問題;
X=A,B,C,B,D,A,B ,和Y=B,D,C,A,B,A
從上到下,從做到右:複雜度爲Θ(mn)
這裏寫圖片描述

構造LCS

1.可以採用b[i][j]中記錄的值,依次倒序輸出,從b[7][6]開始倒序輸出。也可以用遞歸的方法,從底層開始,正序輸出;
2.不用b[i][j],即改進版,由於c[i][j]是由c[i-1][j-1]、c[i-1][j]、c[i][j-1],可以根據它們的關係,判斷出方向;運行時間都爲O(m+n) ;

例程

X=A,B,C,B,D,A,B ,和Y=B,D,C,A,B,A 的一個LCS

/************************************************************************
CSDN 勿在浮沙築高臺 
http://blog.csdn.net/luoshixian099
算法導論--動態規劃(最長公共子序列)
2015年6月5日
main.cpp                   
************************************************************************/
#include<iostream>
using namespace std;
int b[8][7]={0},c[8][7]={0};       
void Lcs_Length(char *X,char *Y) // 遍歷序列X和Y,自底向上計算出c[i][j]
{
    for(int i=1;i<=7;i++)
        for(int j=1;j<=6;j++)
        {
            if(X[i] == Y[j])            
            {
                c[i][j] = c[i-1][j-1]+1;      
                b[i][j] = 2;              //左上方
            }
            else if(c[i-1][j] >= c[i][j-1])
            {
                c[i][j] = c[i-1][j];
                b[i][j]=1;             //上方
            }
            else
            {
                c[i][j] = c[i][j-1];
                b[i][j] = 3;       //左方
            }
        }
}
void Print_Lcs(int b[][7],char *X,int i,int j)  //採用b[i][j]遞歸輸出LCS
{
    if(i == 0 || j == 0)
        return ;
    if(b[i][j] == 2)
    {
        Print_Lcs(b,X,i-1,j-1);
        cout<<"  "<<X[i];
    }
    else if(b[i][j] == 1)
        Print_Lcs(b,X,i-1,j);
    else
        Print_Lcs(b,X,i,j-1);
}
void Print_Lcs(char *X,char *Y,int i,int j)//不用b[i][j],遞歸輸出LCS
{

    if(i == 0 || j == 0)
        return ;
    if(X[i] == Y[j])
    {
        Print_Lcs(X,Y,i-1,j-1);
        cout<<"  "<<X[i];
    }
    else if(c[i-1][j] >= c[i][j-1])
        Print_Lcs(X,Y,i-1,j);
    else
        Print_Lcs(X,Y,i,j-1);
}
int main()
{
    char X[]={' ','A','B','C','B','D','A','B'};
    char Y[]={' ','B','D','C','A','B','A'};
    Lcs_Length(X,Y);
    for(int i=0;i<8;i++)   //輸出數組內的值
    {
        for(int j=0;j<7;j++)
            cout<<"  "<<c[i][j];
        cout<<endl;
    }
    cout<<endl;
    Print_Lcs(b,X,7,6);  //輸出LCS
    cout<<endl;
    Print_Lcs(X,Y,7,6);  //改進的版本
    cout<<endl;
    return 0;
}
發佈了57 篇原創文章 · 獲贊 268 · 訪問量 60萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章