動態規劃---LCS問題 最長公共子序列

今天看到個帖子,原文如下
想要求出兩個串的最長公共子序列(不是子串,子序列可以是不連續的),以下的這個算法(用的是lcs,動態規劃)能求出一個最長公共子序列,是badb,長度是4.可是這兩個串bacdbd 和dbcbadb的最長公共子序列有三個,分別是badb,bcbd,bcdb,長度都是4,如何把它們都求出來?
Java code
import java.io.*;
public class ys {static int length= 0;//保存最長公共子序列的長度
    static String  str_same= ""//保存最長公共子序列
    static int k= 0;
   
public static void main(String args[])
    {
       
char x[],y[];
        String str_x
="bacdbd",str_y="dbcbadb";//原始比較的兩個串
        try{
            str_x
=" "+str_x;       
            str_y
=" "+str_y;
        }
catch(Exception e){
            e.printStackTrace();
        }
        x
=str_x.toCharArray();
        y
=str_y.toCharArray();
       
int b[][]=new int[x.length][y.length]; lcsLength(x,y,b); lcs(x.length-1,y.length-1,x,b);
        System.out.println(
"length:"+length);
        System.out.println(
"str_same:"+str_same);
        System.out.print(
"/n");
    }
   
public static void lcsLength(char []x,char []y,int [][]b)
    {
       
int xMaxIndex=x.length-1;
       
int yMaxIndex=y.length-1;
       
int count[][]=new int[xMaxIndex+1][yMaxIndex+1];
       
for(int i=0;i<=xMaxIndex;i++)
        {
            count[i][
1]=0;
        }
       
for(int i=0;i<=yMaxIndex;i++)
        {
            count[
0][i]=0;
        }
       
for(int i=1;i<=xMaxIndex;i++)
       
for(int j=1;j<=yMaxIndex;j++)
        {
           
if(x[i]==y[j])//如果相等 則對角線加一
            {
                count[i][j]
=count[i-1][j-1]+1;
                b[i][j]
=1;
            }
           
//如果不等,則比較上方和左方,取最大值
            else if(count[i-1][j]>=count[i][j-1])
            {
                count[i][j]
=count[i-1][j];
                b[i][j]
=2;
            }
           
else
            {
                count[i][j]
=count[i][j-1];
                b[i][j]
=3;
            }
           
        }
    }
   
public static void lcs(int i,int j,char []x,int [][]b)
    {
       
if(i==0||j==0)
        {
           
return;
        }
       
if(b[i][j]==1)
        {
            length
++;          
            lcs(i
-1,j-1,x,b);
            str_same
+= x[i];
        }
       
else if(b[i][j]==2)
        {
           
            lcs(i
-1,j,x,b);
           
        }
       
else if(b[i][j]==3)
        {
            lcs(i,j
-1,x,b);
        }
    }
}
分析後解答如下
import java.io.*;
public class Ys
{
    static int length = 0; //保存最長公共子序列的長度
    static int maxLength = 0;//記錄最長子序列長度
    static int count = 0;
    
    static int k = 0;
    public static void main(String args[])
    {
     String  str_same = "";  //保存最長公共子序列
        char x[],y[];
        String str_x="bacdbd",str_y="dbcbadb"; //原始比較的兩個串
        try{
            str_x=" "+str_x;        
            str_y=" "+str_y;
        } catch(Exception e){
            e.printStackTrace();
        }
        x=str_x.toCharArray();
        y=str_y.toCharArray();
        int b[][]=new int[x.length][y.length];
        lcsLength(x,y,b);
        lcs(x.length-1,y.length-1,x,b,"");
    }
    public  static void lcsLength(char []x,char []y,int [][]b)
    {
        int xMaxIndex=x.length-1;
        int yMaxIndex=y.length-1;
        int count[][]=new int[xMaxIndex+1][yMaxIndex+1];
        for(int i=0;i <=xMaxIndex;i++)
        {
            count[i][1]=0;
        }
        for(int i=0;i <=yMaxIndex;i++)
        {
            count[0][i]=0;
        }
        for(int i=1;i <=xMaxIndex;i++)
        for(int j=1;j <=yMaxIndex;j++)
        {
            if(x[i]==y[j]) //如果相等 則對角線加一
            {
                count[i][j]=count[i-1][j-1]+1;
                maxLength = Math.max(maxLength,count[i][j]);
                b[i][j]=1;
            }
            //如果不等,則比較上方和左方,取最大值
            else if(count[i-1][j]>count[i][j-1]) 
            {
                count[i][j]=count[i-1][j];
                maxLength = Math.max(maxLength,count[i][j]);
                b[i][j]=2;
            }
else if (count[i-1][j]<count[i][j-1])
             {
                count[i][j]=count[i][j-1];
                maxLength = Math.max(maxLength,count[i][j]);
                b[i][j]=3;
             
             }
            else 
            {
                count[i][j]=count[i-1][j];
                maxLength = Math.max(maxLength,count[i][j]);
                b[i][j]=4; 

            }

        }
    }
    public static void lcs(int i,int j,char []x,int [][]b,String strResult)
    {
        if(i==0 ¦ ¦j==0)
        {
            return;
        }
        if(b[i][j]==1)
        {
         strResult = x[i] + strResult;
         if (strResult.length() == maxLength) {
         System.out.println(strResult.length());
         System.out.println(strResult);
         }

            length ++;           
            lcs(i-1,j-1,x,b,strResult);

        }
        else if(b[i][j]==2)
        {
            
            lcs(i-1,j,x,b,strResult);
            
        }
        else if(b[i][j]==3)
        {
            lcs(i,j-1,x,b,strResult);
        }
        else if(b[i][j]==4)
        {
            lcs(i-1,j,x,b,strResult);
            lcs(i,j-1,x,b,strResult);
        }     
}
}
// 次程序仍有問題,時間複雜度太大,有待改進
查閱相關知識如下:
問題描述
一個給定序列的子序列是在該序列中刪去若干元素後得到的序列。確切地說,若給定序列X=<x1, x2,…, xm>,則另一序列Z=<z1, z2,…, zk>是X的子序列是指存在一個嚴格遞增的下標序列 <i1, i2,…, ik>,使得對於所有j=1,2,…,k 有Xij=Zj;
例如,序列Z=<B,C,D,B>是序列X=<A,B,C,B,D,A,B>的子序列,相應的遞增下標序列爲<2,3,5,7>。

給定兩個序列X和Y,當另一序列Z既是X的子序列又是Y的子序列時,稱Z是序列X和Y的公共子序列。例如,若X=<A, B, C, B, D, A, B>和Y=<B, D, C, A, B, A>,則序列<B, C, A>是X和Y的一個公共子序列,序列<B, C, B, A>也是X和Y的一個公共子序列。而且,後者是X和Y的一個最長公共子序列,因爲X和Y沒有長度大於4的公共子序列。
最長公共子序列(LCS)問題:給定兩個序列X=<x1, x2, …, xm>和Y=<y1, y2, … , yn>,要求找出X和Y的一個最長公共子序列。

最長公共子序列問題LCS
問題描述
參考解答
動態規劃算法可有效地解此問題。下面我們按照動態規劃算法設計的各個步驟來設計一個解此問題的有效算法。

1.最長公共子序列的結構
解最長公共子序列問題時最容易想到的算法是窮舉搜索法,即對X的每一個子序列,檢查它是否也是Y的子序列,從而確定它是否爲X和Y的公共子序列,並且在檢查過程中選出最長的公共子序列。X的所有子序列都檢查過後即可求出X和Y的最長公共子序列。X的一個子序列相應於下標序列{1, 2, …, m}的一個子序列,因此,X共有2m個不同子序列,從而窮舉搜索法需要指數時間。

事實上,最長公共子序列問題也有最優子結構性質,因爲我們有如下定理:

定理: LCS的最優子結構性質

設序列X=<x1, x2, …, xm>和Y=<y1, y2, …, yn>的一個最長公共子序列Z=<z1, z2, …, zk>,則:

若xm=yn,則zk=xm=yn且Zk-1是Xm-1和Yn-1的最長公共子序列;
若xm≠yn且zk≠xm ,則Z是Xm-1和Y的最長公共子序列;
若xm≠yn且zk≠yn ,則Z是X和Yn-1的最長公共子序列。
其中Xm-1=<x1, x2, …, xm-1>,Yn-1=<y1, y2, …, yn-1>,Zk-1=<z1, z2, …, zk-1>。

證明

用反證法。若zk≠xm,則<z1, z2, …, zk ,xm >是X和Y的長度爲k十1的公共子序列。這與Z是X和Y的一個最長公共子序列矛盾。因此,必有zk=xm=yn。由此可知Zk-1是Xm-1和Yn-1的一個長度爲k-1的公共子序列。若Xm-1和Yn-1有一個長度大於k-1的公共子序列W,則將xm加在其尾部將產生X和Y的一個長度大於k的公共子序列。此爲矛盾。故Zk-1是Xm-1和Yn-1的一個最長公共子序列。
由於zk≠xm,Z是Xm-1和Y的一個公共子序列。若Xm-1和Y有一個長度大於k的公共子序列W,則W也是X和Y的一個長度大於k的公共子序列。這與Z是X和Y的一個最長公共子序列矛盾。由此即知Z是Xm-1和Y的一個最長公共子序列。
與 2.類似。
這個定理告訴我們,兩個序列的最長公共子序列包含了這兩個序列的前綴的最長公共子序列。因此,最長公共子序列問題具有最優子結構性質。

2.子問題的遞歸結構
由最長公共子序列問題的最優子結構性質可知,要找出X=<x1, x2, …, xm>和Y=<y1, y2, …, yn>的最長公共子序列,可按以下方式遞歸地進行:當xm=yn時,找出Xm-1和Yn-1的最長公共子序列,然後在其尾部加上xm(=yn)即可得X和Y的一個最長公共子序列。當xm≠yn時,必須解兩個子問題,即找出Xm-1和Y的一個最長公共子序列及X和Yn-1的一個最長公共子序列。這兩個公共子序列中較長者即爲X和Y的一個最長公共子序列。

由此遞歸結構容易看到最長公共子序列問題具有子問題重疊性質。例如,在計算X和Y的最長公共子序列時,可能要計算出X和Yn-1及Xm-1和Y的最長公共子序列。而這兩個子問題都包含一個公共子問題,即計算Xm-1和Yn-1的最長公共子序列。

與矩陣連乘積最優計算次序問題類似,我們來建立子問題的最優值的遞歸關係。用c[i,j]記錄序列Xi和Yj的最長公共子序列的長度。其中Xi=<x1, x2, …, xi>,Yj=<y1, y2, …, yj>。當i=0或j=0時,空序列是Xi和Yj的最長公共子序列,故c[i,j]=0。其他情況下,由定理可建立遞歸關係如下:

3.計算最優值
直接利用(2.2)式容易寫出一個計算c[i,j]的遞歸算法,但其計算時間是隨輸入長度指數增長的。由於在所考慮的子問題空間中,總共只有θ(m*n)個不同的子問題,因此,用動態規劃算法自底向上地計算最優值能提高算法的效率。

計算最長公共子序列長度的動態規劃算法LCS_LENGTH(X,Y)以序列X=<x1, x2, …, xm>和Y=<y1, y2, …, yn>作爲輸入。輸出兩個數組c[0..m ,0..n]和b[1..m ,1..n]。其中c[i,j]存儲Xi與Yj的最長公共子序列的長度,b[i,j]記錄指示c[i,j]的值是由哪一個子問題的解達到的,這在構造最長公共子序列時要用到。最後,X和Y的最長公共子序列的長度記錄於c[m,n]中。

Procedure LCS_LENGTH(X,Y);begin m:=length[X]; n:=length[Y]; for i:=1 to m do c[i,j]:=0; for j:=1 to n do c[0,j]:=0; for i:=1 to m do for j:=1 to n do if x[i]=y[j] then begin c[i,j]:=c[i-1,j-1]+1; b[i,j]:="↖"; end else if c[i-1,j]≥c[i,j-1] then begin c[i,j]:=c[i-1,j]; b[i,j]:="↑"; end else begin c[i,j]:=c[i,j-1]; b[i,j]:="←" end; return(c,b);end;
由於每個數組單元的計算耗費Ο(1)時間,算法LCS_LENGTH耗時Ο(mn)。

4.構造最長公共子序列
由算法LCS_LENGTH計算得到的數組b可用於快速構造序列X=<x1, x2, …, xm>和Y=<y1, y2, …, yn>的最長公共子序列。首先從b[m,n]開始,沿着其中的箭頭所指的方向在數組b中搜索。當b[i,j]中遇到"↖"時,表示Xi與Yj的最長公共子序列是由Xi-1與Yj-1的最長公共子序列在尾部加上xi得到的子序列;當b[i,j]中遇到"↑"時,表示Xi與Yj的最長公共子序列和Xi-1與Yj的最長公共子序列相同;當b[i,j]中遇到"←"時,表示Xi與Yj的最長公共子序列和Xi與Yj-1的最長公共子序列相同。

下面的算法LCS(b,X,i,j)實現根據b的內容打印出Xi與Yj的最長公共子序列。通過算法的調用LCS(b,X,length[X],length[Y]),便可打印出序列X和Y的最長公共子序列。

Procedure LCS(b,X,i,j);begin if i=0 or j=0 then return; if b[i,j]="↖" then begin LCS(b,X,i-1,j-1); print(x[i]); {打印x[i]} end else if b[i,j]="↑" then LCS(b,X,i-1,j)
else LCS(b,X,i,j-1);end;
在算法LCS中,每一次的遞歸調用使i或j減1,因此算法的計算時間爲O(m+n)。

例如,設所給的兩個序列爲X=<A,B,C,B,D,A,B>和Y=<B,D,C,A,B,A>。由算法LCS_LENGTH和LCS計算出的結果如圖2所示。

 

j 0 1 2 3 4 5 6
i yj B D C A B A
┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
0 xi │ 0 0 0 0 0 0 │
│ ↑ ↑ ↑ ↖ ↖ │
1 A │ 0 0 0 0 1 ← 1 1 │
│ ↖ ↑ ↖ │
2 B │ 0 1 ← 1 ← 1 1 2 ← 2 │
│ ↑ ↑ ↖ ↑ ↑ │
3 C │ 0 1 1 2 ← 2 2 2 │
│ ↖ ↑ ↑ ↑ ↖ │
4 B │ 0 1 1 2 2 3 ← 3 │
│ ↑ ↖ ↑ ↑ ↑ ↑ │
5 D │ 0 1 2 2 2 3 3 │
│ ↑ ↑ ↑ ↖ ↑ ↖ │
6 A │ 0 1 2 2 3 3 4 │
│ ↖ ↑ ↑ ↑ ↖ ↑ │
7 B │ 0 1 2 2 3 4 5 │
└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
圖2 算法LCS的計算結果

5.算法的改進
對於一個具體問題,按照一般的算法設計策略設計出的算法,往往在算法的時間和空間需求上還可以改進。這種改進,通常是利用具體問題的一些特殊性。

例如,在算法LCS_LENGTH和LCS中,可進一步將數組b省去。事實上,數組元素c[i,j]的值僅由c[i-1,j-1],c[i-1,j]和c[i,j-1]三個值之一確定,而數組元素b[i,j]也只是用來指示c[i,j]究竟由哪個值確定。因此,在算法LCS中,我們可以不藉助於數組b而藉助於數組c本身臨時判斷c[i,j]的值是由c[i-1,j-1],c[i-1,j]和c[i,j-1]中哪一個數值元素所確定,代價是Ο(1)時間。既然b對於算法LCS不是必要的,那麼算法LCS_LENGTH便不必保存它。這一來,可節省θ(mn)的空間,而LCS_LENGTH和LCS所需要的時間分別仍然是Ο(mn)和Ο(m+n)。不過,由於數組c仍需要Ο(mn)的空間,因此這裏所作的改進,只是在空間複雜性的常數因子上的改進。

另外,如果只需要計算最長公共子序列的長度,則算法的空間需求還可大大減少。事實上,在計算c[i,j]時,只用到數組c的第i行和第i-1行。因此,只要用2行的數組空間就可以計算出最長公共子序列的長度。更進一步的分析還可將空間需求減至min(m, n)。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章