問題描述:
最長公共子序列也稱作最長公共子串(不要求連續),英文縮寫爲LCS(Longest Common Subsequence)。其定義是,一個序列 S ,如果分別是兩個或多個已知序列的子序列,且是所有符合此條件序列中最長的,則 S 稱爲已知序列的最長公共子序列。
例如:X(A,B,C,B,D,A,B)
Y(B,D,C,A,B,A)
那麼最長公共子序列就是:B,C,B,A
算法設計:用動態規劃方法解決
最長公共子序列的結構:
設X = { x1 , ... , xm },Y = { y1 , ... , yn }及它們的最長子序列Z = { z1 , ... , zk }則:
1、若 xm = yn , 則 zk = xm = yn,且Z[k-1] 是 X[m-1] 和 Y[n-1] 的最長公共子序列
2、若 xm != yn ,且 zk != xm , 則 Z 是 X[m-1] 和 Y 的最長公共子序列
3、若 xm != yn , 且 zk != yn , 則 Z 是 Y[n-1] 和 X 的最長公共子序列
子問題的遞歸結構:
當 i = 0 , j = 0 時 , c[i][j] = 0
當 i , j > 0 ; xi = yi 時 , c[i][j] = c[i-1][j-1] + 1
當 i , j > 0 ; xi != yi 時 , c[i][j] = max { c[i][j-1] , c[i-1][j] }
還是以:X(A,B,C,B,D,A,B)
Y(B,D,C,A,B,A) 爲例
看下面的圖:
由上面的分析:我們得到的源代碼:
//最長公共子序列代碼模板
#include <iostream>
using namespace std;
#define N 105
int dp[N+1][N+1];
char str1[N],str2[N];
//比較兩個數的大小
int maxx(int a,int b){
if(a>b){
return a;
}
return b;
}
//函數功能:獲取兩個字符串的最長公共子序列的數目
//len1:字符串1的長度
//len2:字符串2的長度
int LCSL(int len1,int len2){
int i,j;
int len=maxx(len1,len2);
for(i=0;i<=len;i++){
dp[i][0]=0;//y爲空字符串
dp[0][i]=0;//x爲空字符串
}
for(i=1;i<=len1;i++){
for(j=1;j<=len2;j++){
if(str1[i-1]==str2[j-1]){
dp[i][j]=dp[i-1][j-1]+1;
}
else{
dp[i][j] = maxx(dp[i - 1][ j ] , dp[i][j - 1]) ;
}
}
}
return dp[len1][len2];
}
int main(){
while(cin>>str1>>str2){
int len1=strlen(str1);//獲取字符串1的長度
int len2=strlen(str2);//獲取字符串2的長度
cout<<LCSL(len1,len2)<<endl;
}
return 0;
}
動態規劃有一個經典問題是最長公共子序列,但是這裏的子序列不要求連續,如果要求序列是連續的,我們叫公共子串,那應該如何得到這個串呢?
最簡單的方法就是依次比較,以某個串爲母串,然後生成另一個串的所有長度的子串,依次去母串中比較查找,這裏可以採用先從最長的子串開始,減少比較次數,但是複雜度依然很高!
然後重新看一下這個問題,我們建立一個比較矩陣來比較兩個字符串str1和str2。
定義 lcs(i,j) ,當str1[i] = str2[j]時lcs(i,j)=1,否則等於0。
example:
str1 = "bab"
str2 = "caba"
建立矩陣
--b a b
c 0 0 0
a 0 1 0
b 1 0 1
a 0 1 0
連續i子串的特點就是如果str1[i]和str2[j]是屬於某公共子串的最後一個字符,那麼一定有str1[i]=str2[j] && str1[i-1] = str2[j-1],從矩陣中直觀的看,就是由“1”構成的“斜線”代表的序列都是公共子串,那麼最長公共子串肯定就是斜線“1”最長的那個串。
那麼現在問題就可以轉化了,只要構造出如上的一個矩陣,用n^2的時間就可以得到矩陣,然後再到矩陣中去尋找最長的那個“1”構成的斜線就可以了!那麼,現在又有了新的問題?如何快速的找到那個“1”構成的最長斜線呢?
採用DP的思想,如果str1[i] = str2[j],那麼此處的包含str1[i] 和 str2[j]公共子串的長度必然是包含str1[i-1]和str2[j-1]的公共子串的長度加1,那麼現在我們可以重新定義lcs(i,j),即是 lcs(i,j) = lcs(i-1,j-1) + 1,反之,lcs(i,j) = 0。那麼上面的矩陣就變成了如下的樣子:
--b a b
c 0 0 0
a 0 1 0
b 1 0 2
a 0 2 0
現在問題又變簡單了,只需要花n^2的時間構造這樣一個矩陣,再花n^2的時間去找到矩陣中最大的那個值,對應的就是最長公共子串的長度,而最大值對應的位置對應的字符,就是最長公共子串的最末字符。
算法還可以改進,我們可以將查找最大長度和對應字符的工作放在構造矩陣的過程中完成,一邊構造一邊記錄當前的最大長度和對應位置,這樣就節省了n^2的查找時間。
空間上也可以做改進,如果按照如上的方式構造,我們發現,當矩陣的第i+1行的值計算完成後,第i行的值就沒有用了,即便是最長的長度出現在第i 行,我們也已經用變量記錄下來了。因此,可以將矩陣縮減成一個向量來處理,向量的當前值對應第i行,向量的下一個循環後的值對應第i+1行。
// 最長公共子串(連續) LCS
// Deng Chao
// 2012.12.4
#include <iostream>
#include <cstring>
using namespace std;
// 查找公共子串
// lcs記錄公共子串
// return 公共子串長度
int LCS(const char *str1 , int len1 , const char *str2 , int len2 , char *&lcs)
{
if(NULL == str1 || NULL == str2)
{
return -1; //空參數
}
// 壓縮後的最長子串記錄向量
int *c = new int[len2+1];
for(int i = 0 ; i < len2 ; ++i)
{
c[i] = 0;
}
int max_len = 0; //匹配的長度
int pos = 0; //在str2上的匹配最末位置
for(int i = 0 ; i < len1 ; ++i)
{
for(int j = len2 ; j > 0 ; --j) //更新時從後往前遍歷 !!忽略了j=0的情況
{
if(str1[i] == str2[j-1])
{
c[j] = c[j-1] + 1;
if(c[j] > max_len)
{
max_len = c[j];
pos = j-1;
}
}
else
{
c[j] = 0;
}
}
}
if(0 == max_len)
{
delete [] c;
return 0;
}
// 得到公共子串
lcs = new char[max_len];
for(int i = 0 ; i < max_len ; ++i)
{
lcs[i] = str2[pos-max_len+1+i];
}
cout<<"pos = "<<pos<<endl;
delete [] c;
delete [] lcs;
return max_len;
}
// test
int main()
{
const char *str1 = "abacaba";
const char *str2 = "caba";
int len1 = strlen(str1);
int len2 = strlen(str2);
char *lcs;
int len = LCS(str1 , len1 , str2 , len2 , lcs);
cout<<"max length = "<<len<<endl;
for(int i = 0 ; i < len ; ++i)
{
cout<<lcs[i]<<" ";
}
}
該程序爲原版轉載程序,仍舊有錯誤,比如忽略了長度相同的情況和當j=0的情況:
C++轉載版本
本文轉載:http://www.cnblogs.com/LolaLiu/p/3981232.html
http://www.cnblogs.com/-dante-/archive/2013/08/15/3260995.html