程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 關於輸出多個LCS(最長公共子序列)的簡單技巧,多個lcs

關於輸出多個LCS(最長公共子序列)的簡單技巧,多個lcs

編輯:C++入門知識

關於輸出多個LCS(最長公共子序列)的簡單技巧,多個lcs


LCS(最長公共子序列)

百度百科

  一個序列 S ,如果分別是兩個或多個已知序列的子序列,

  且是所有符合此條件序列中最長的,則 S 稱為已知序列的最長公共子序列。

 

在這裡我們要注意的是,S在已知序列中可以不連續。

比如ABCBDAB和BDCABA的LCS為BCBA,BCBA在原序列中並不連續出現。

 

LCS通常利用動態規劃算法求解,最簡單的求解方式如《算法導論》所述:

  1.設輸入為X1X2…Xm,Y1Y2…Yn,構造二維數組B、C;

     其中B[i][j]存儲C[i][j]的最優解指向,C[i][j]存儲X[1…i]和Y[1…j]的LCS長度;

  2.B、C數組同時更新:

    2.1.令B[0][k]、C[0][k](k=0…n)和B[k][0]、C[k][0](k=0…m)為0;

    2.2.按下標從小到大的順序依次計算每行的B[i][j]和C[i][j],規則如下:

      ①.若X[i]=Y[j],則C[i][j]=C[i-1][j-1]+1,B[i][j]=0;

      ②.若X[i]!=Y[j],則C[i][j]=Max(C[i][j-1],C[i-1][j]),B[i][j]=1或2;

  3.更新完B、C後,從C[m][n]開始遞歸輸出,根據B[i][j]的指向打印LCS;

 

相信大家都明白這個原理,簡單的用一張圖來說明一下:

  1.箭頭代表B[i][j]的值,其中用指向左上方的箭頭代表0,←和↑代表1和2(或2和1);

  2.箭頭下方的數字為C[i][j]的值,即子問題X[1…i]和Y[1…j]的LCS長度;

 

可是這樣的構造只能輸出一個LCS,但是我們發現BCBA、BCAB、BDAB都是該序列集的LCS。

為什麼呢?因為該算法在C[i-1][j]=C[i][j-1]時,只記錄了一種情況!即沒有保存分支!

如何簡單的修改算法,以求得多個LCS呢?下面介紹一種改進的方法。

 

還是利用數組B、C,並且C的計算規則不變,下面修改B的計算規則:

  1.若X[i]=Y[j],B[i][j]=0;

  2.若X[i]!=Y[j],有以下判斷:

    ①.若C[i-1][j]>C[i][j-1],則C[i][j]=C[i-1][j],B[i][j]=1;

    ②.若C[i-1][j]<C[i][j-1],則C[i][j]=C[i][j-1],B[i][j]=2;

    ③.若C[i-1][j]=C[i][j-1],則C[i][j]的長度任取,B[i][j]=3;

 

此時用另一張圖表示該算法:

  1.用四種箭頭表示B[i][j]的值,其中←和↑都存在時,表示C[i-1][j]=C[i][j-1];

  2.輸出的時候采用正則表達式的思想:遇到B[i][j]=3時,同時對兩種情況進行遞歸;

     形式為“(←+↑)”,其中←和↑分別表示兩個子問題的LCS;

 

補充:(A+B),在正則表達式中意義為A或B,即“+”表示二選一的關系。

 

下面給出C++源代碼幫助大家更好地理解:(編譯器為Codeblocks)

 1 #include<iostream>
 2 #include<string.h>
 3 #define M 100
 4 #define N 100
 5 using namespace std;
 6 
 7 void LCS_length(string x,string y,int B[M][N],int C[M][N])/*動態規劃*/
 8 {   for(int i=1;i<=x.length()-1;i++)    C[i][0]=0;
 9     for(int j=0;j<=y.length()-1;j++)    C[0][j]=0;
10     for(int i=1;i<=x.length()-1;i++)
11         for(int j=1;j<=y.length()-1;j++)
12         {   if(x[i]==y[j])
13             {   C[i][j]=C[i-1][j-1]+1;B[i][j]=0/*Up&Left*/;}
14             else if(C[i-1][j]>C[i][j-1])
15             {   C[i][j]=C[i-1][j];B[i][j]=1/*Up*/;}
16             else if(C[i-1][j]<C[i][j-1])
17             {   C[i][j]=C[i][j-1];B[i][j]=2/*Left*/;}
18             else
19             {   C[i][j]=C[i][j-1];B[i][j]=3/*Up+Left*/;}}}
20 
21 void Print_LCS(string x,int B[M][N],int i,int j)/*輸出LCS*/
22 {   if(!i||!j)    return;
23     if(!B[i][j])
24     {   Print_LCS(x,B,i-1,j-1);cout<<x[i];}
25     else if(B[i][j]==1)
26         Print_LCS(x,B,i-1,j);
27     else if(B[i][j]==2)
28         Print_LCS(x,B,i,j-1);
29     else
30     {   cout<<'(';
31         Print_LCS(x,B,i-1,j);
32         cout<<'+';
33         Print_LCS(x,B,i,j-1);
34         cout<<')';}}
35 
36 int main()
37 {   string x,y;
38     cin>>x;cin>>y;
39     x=' '+x;y=' '+y;
40     int B[M][N],C[M][N];
41     LCS_length(x,y,B,C);cout<<endl;
42     Print_LCS(x,B,x.length(),y.length());}

 

看完Print_LCS()函數,大家應該大致有所了解,下面給出運行結果:

  輸入為ABCBDAB、BDCABA;

  輸出為(BCBA+(BC+BD)AB),即表示BCBA、BCAB、BDAB都為輸入的LCS;

 

當然,如果不想采用這種形式,也可以:

  1.用字符型數組(如Vector)或String型變量保存輸出;

  2.根據括號分解,輸出多個LCS;

 

還有一種方式:

  1.遍歷數組B,用棧保存B[i][j]=3的點的坐標,比如[7,6],[5,3]等;

  2.讓這些點的賦值從11……11逐漸變為22……22;

因為B[i][j]=3可以理解為B[i][j]=1或2,那麼用二進制序列的思想遍歷每種賦值情況;

當然此時時間開銷更大,還可能重復輸出,所以建議第一種方式。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved