最長公共子序列--動態規划求解


本文鏈接:https://www.cnblogs.com/chenleideblog/p/10455723.html

問題:例如:X={A,B,C,B,A,D,B},Y={B,C,B,A,A,C},那么,二者的最長公共子序列是{B,C,B,A},長度為4。

我們首先需要搞清楚以下兩個概念:

最長公共子序列 VS 最長公共子串:

找兩個字符串的最長公共子串,這個子串要求在原字符串中是連續的。而最長公共子序列則並不要求連續。

上述問題中的最長公共子序列與最長公共子串是一樣的。

但是再舉例X={A,B,C,B,A,D,B},Y={B,C,B,A,A,B},二者的最長公共子序列是{B,C,B,A,B},而二者的最長公共子串是{B,C,B,A}。

 

求解思路:

1.分析最優解的結構特征:

設Zk={z1,z2,z3,......zk}是Xm={x1,x2,x3,......xm}和Yn={y1,y2,y3,......yn}的最長公共子序列。

則可以得到:

若xm=yn=zk,那么Zk-1={z1,z2,z3,......zk-1}是Xm-1={x1,x2,x3,......xm-1}和Yn-1={y1,y2,y3,......yn-1}的最長公共子序列;

若xm≠yn,xm≠zk,則去除xm后,Zk={z1,z2,z3,......zk}仍然是Xm-1={x1,x2,x3,......xm-1}和Yn={y1,y2,y3,......yn}的最長公共子序列;

若xm≠yn,yn≠zk,則去除yn后,Zk={z1,z2,z3,......zk}仍然是Xm={x1,x2,x3,......xm}和Yn-1={y1,y2,y3,......yn-1}的最長公共子序列;

2.建立最優值的遞歸式

數據結構選擇:

用c[i][j]表示Xi和Yj的最長公共子序列長度(這一步很關鍵,越到右下角,值會越來越大,我們最后只需要選取右下角的值就可以確定最長公共子序列的長度)

討論:

若xi=yj=zk,那么c[i][j] = c[i-1][j-1] + 1;

若xi≠yj,xi≠zk,那么Xi需要進一步縮小一個長度進行匹配,即去除xi不影響整體的最長子序列變化,c[i][j] = c[i-1][j] ;

若xi≠yj,yj≠zk,那么Yj需要進一步縮小一個長度進行匹配,即去除yj不影響整體的最長子序列變化,c[i][j] = c[i][j-1] ;

結束條件,若i=0或者j=0,則c[i][j]=0。

所以,在xi≠yj的情況下,有兩種情況,c[i][j]必等於兩種情況下的最大值,即c[i][j] = max(c[i-1][j], c[i][j-1])

 

3.自底向上計算最優值,並記錄最優值與最優策略

我們由上面可以知道,c[0][j]=0或者c[i][0]=0.

先使i = 1,則求x1與{y1,y2,y3,......yn}逐一比較。如圖,x1≠y1,執行c[1][1] = max(c[0][1], c[1][0])  =0,接着,x1=y2,則執行,c[1][2] = c[0][1] + 1=1 ,接着,x1≠y3,則執行c[1][3] = max(c[0][3], c[1][2])  =1,......這樣就求出了X1與Yn的最長公共子序列長度;

 

然后,i = 2,則建立在x1比較的基礎上就可以求出{x1,x2}與{y1,y2,y3,......yn}的最長公共子序列長度;

 

然后,i = 3,建立在{x1,x2}的基礎上,則可以求出{x1,x2,x3}與{y1,y2,y3,......yn}的最長公共子序列長度;

......

然后,i = m,建立在{x1,x2,......xm-1}的基礎上,則可以求出{x1,x2,......xm-1}與{y1,y2,y3,......yn}的最長公共子序列長度;

4.構造最優解

在知道了最長公共子序列的長度之后,我們還需要知道最長公共子序列中都是哪些元素。我們在c[i][j]數組的右下角能夠得到最長公共子序列的長度,那么,我們可以反向推出這個元素分別是什么。

由上述,我們可以得到:

若xi=yj=zk,c[i][j] = c[i-1][j-1] + 1;

若xi≠yj,xi≠zk,c[i][j] = c[i-1][j] ;

若xi≠yj,yj≠zk,c[i][j] = c[i][j-1] ;

所以,c[i][j]由上述三個等式中的一個得到,那么我們只需要記錄下c[i][j]是從三個等式中哪一個得到的,那么對應的元素我們就知道了。

這樣,我們就必須在借助一個數組就行保存了,我們建立新的數組b[i][j]來記錄是從哪個等式中得到,即構造出下列結構。

若xi=yj=zk,c[i][j] = c[i-1][j-1] + 1,則b[i][j] = 1,那么我們就可以取出xi或者yj作為最長公共子序列中的元素;

若xi≠yj,yj≠zk,c[i][j] = c[i][j-1],則b[i][j] = 2,那么我們就可以去追蹤c[i][j-1];

若xi≠yj,xi≠zk,c[i][j] = c[i-1][j] ,則b[i][j] = 3,那么我們就可以去追蹤c[i-1][j];

追蹤到i=0或者j=0,停止,如下圖則是根據c[i][j]取值的來源將b[i][j]數組補充完整

下圖為兩個序列的最終比較情況:我們由上述構造b[i][j]的方法得知,若 b[i][j]= 2,那么我們去追蹤c[i][j-1],若b[i][j]= 3,那么我們去追蹤c[i-1][j],換言之,就是b[i][j]= 2,就往左找,b[i][j]= 3,就往右找,若b[i][j]= 1,就可以輸出此時的xi或者yj

 

上面圖片全部來自於 陳小玉老師的《趣學算法》,很好的一本書,值得大家看。

 

上述的思路理解清楚了,我們就可以上代碼了,代碼如下:

復制代碼
 1 #include <iostream>
 2 #include <cstring>
 3 using namespace std;
 4 const int N=1024;
 5 int c[N][N],b[N][N];
 6 char s1[N],s2[N];
 7 int len1,len2;
 8 void LCS()
 9 {
10     for(int i = 1; i <= len1; i++){
11         for(int j = 1; j <= len2; j++){
12             if(s1[i-1] == s2[j-1]){ //注:此處的s1與s2序列是從s1[0]與s2[0]開始的
13                 c[i][j] = c[i-1][j-1] + 1;
14                 b[i][j] = 1;
15             }
16             else{
17                 if(c[i][j-1] >= c[i-1][j]){
18                     c[i][j] = c[i][j-1];
19                     b[i][j] = 2;
20                 }
21                 else{
22                     c[i][j] = c[i-1][j];
23                     b[i][j] =3;
24                 }
25             }
26         }
27     }
28 }
29 
30 void LCS_PRINT(int i, int j)
31 {
32     if(i==0 || j==0){
33         return;
34     }
35     if(b[i][j] == 1){
36         LCS_PRINT(i-1,j-1);
37         cout << s1[i-1];
38     }
39     else if(b[i][j] == 2){
40         LCS_PRINT(i,j-1);
41     }
42     else{
43         LCS_PRINT(i-1,j);
44     }
45 }
46 
47 int main()
48 {
49     cout << "請輸入X字符串" << endl;
50     cin >> s1;
51     cout << "請輸入Y字符串" << endl;
52     cin >> s2;
53     len1 = strlen(s1);
54     len2 = strlen(s2);
55     for(int i = 0; i <= len1; i++){
56         c[i][0] = 0;
57     }
58     for(int j = 0; j <= len2; j++){
59         c[0][j] = 0;
60     }
61     LCS();
62     cout << "s1與s2的最長公共子序列的長度是:" << c[len1][len2] <<endl;
63     cout << "s1與s2的最長公共子序列是:";
64     LCS_PRINT(len1,len2);
65     return 0;
66 }
復制代碼

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM