您的位置:首页 > 其它

最长公共子序列(LCS)和最长公共子串(LCS)

2015-12-23 19:13 330 查看
最长公共子序列(Longest Common Subsequence)和最长公共子串( Longest
Common Substring),虽然它们都简称为LCS,但含义不同,前者是可以不连续的,而后者要求是一个连续的字符串。比如:

X="bdcaba";

Y="abcbdab"

X和Y的Longest Common Sequence为<b, c, b, a>,长度为4
X和Y的Longest Common Substring为 <b, d>长度为2
有时候,两个字符串的LCS(不管是哪种LCS)可能不止一个。
1. 动态规划法(DP)求最长公共子序列(Longest Common Sequence)。

.设X,Y的长度分别为m,n。

.定义二维数组dp,用来保存公共子序列的长度,初始化DP数组dp[m]
为0。

.因为当任何一个数组长度为0时,是不存在LCS的,所以dp[0][0...n-1]和dp[0...m-1][0]的值都是0,也就是二维数组的第一列和第一行都为0.

.当X[i]=Y[j]时,则dp[i][j] = dp[i-1][j-1] + 1。1<i<m, 1<j<n。

.当X[i] != Y[j]时,则dp[i][j] = max{dp[i-1][j], dp[i][j-1]}

.等到将dp数组填满时,dp[m]
是值就是LCS的长度--没错,不管两串最后一个字符相不相等。贴一张网上的图:



这里给出自己实现的代码,还是比较简洁、全面的!结果能输出所有的LCS。

#include<iostream>

using namespace std;

#define MAX_LEN 20
int dp[MAX_LEN + 1][MAX_LEN + 1];
char chX[MAX_LEN], chY[MAX_LEN];
char chLCS[MAX_LEN]; //保存输出结果

void display_LCS(int i, int j, int idx)
{
if (i == 0 || j == 0)
{
cout << chLCS;
cout << endl;
return;
}
if (chX[i - 1] == chY[j - 1] && dp[i][j] == dp[i - 1][j - 1] + 1)
{
chLCS[idx--] = chX[i - 1];
display_LCS(i-1, j-1, idx);
}
else if (chX[i - 1] != chY[j - 1])
{
if(dp[i][j] == dp[i][j - 1])
display_LCS(i, j-1, idx);
if(dp[i][j] == dp[i - 1][j])	//注意这里不是else if,所以才能区分出所有情况
display_LCS(i-1, j, idx);
}
}

int main()
{
cout << "Input two string:" << endl;
cin.getline(chX, MAX_LEN);
cin.getline(chY, MAX_LEN);

size_t i, j;
size_t xlen = strlen(chX);
size_t ylen = strlen(chY);

for (i = 1; i <= xlen; i++)
{
for (j = 1; j <= ylen; j++)
{
if (chX[i - 1] == chY[j - 1])			//比较的两字符相等时
dp[i][j] = dp[i - 1][j - 1] + 1;
else if (dp[i - 1][j] > dp[i][j - 1])	//比较的两字符不等时,取LCS值较大的。
dp[i][j] = dp[i - 1][j];
else
dp[i][j] = dp[i][j - 1];
}
}
cout << "Length of LCS is: " << dp[xlen][ylen] << endl;

cout << "LCS(s): ";

//下面是网上普遍的写法,只能输出一种结果
i = xlen;
j = ylen;
int idx = dp[xlen][ylen];
while (i>0 && j>0)
{
if (chX[i-1] == chY[j-1] && dp[i][j] == dp[i - 1][j - 1] + 1)
{
chLCS[--idx] = (chX[i-1]);
--i; --j;
}
else if (/*chX[i-1] != chY[j-1] && */dp[i][j] == dp[i][j - 1])
--j;
else
--i;
}
cout << chLCS << endl;

//采用递归方法,输出所有的LCS
display_LCS(xlen, ylen, dp[xlen][ylen]-1);
}

2. 动态规划法(DP)求最长公共子串(Longest Common Substring)。

求最长公共子串可以用暴力法,求解思路编码也很清晰。

int maxlen;    /* 记录最大公共子串长度 */
int maxidx;  /* 记录最大公共子串在串1的起始位置 */

int compare(char * p, char * q)
{
int len = 0;
while (*p && *q && *p++ == *q++)
{
++len;
}
return len;
}

void LCS_base(char * X, int xlen, char * Y, int ylen)
{
for (int i = 0; i < xlen; ++i)
{
for (int j = 0; j < ylen; ++j)
{
int len = compare(&X[i], &Y[j]);
if (len > maxlen)
{
maxlen = len;
maxidx = i;
}
}
}
string out(&X[maxidx], &X[maxidx + maxlen]);
cout << "LCS: " << out << endl;
}
求最长公共子串同样可以用DP的方法,方法与上面相似,但求最长公共子串最大的特点是要求连续,所以重要的一点就是只要当X[i] != Y[j]时,dp[i][j] 就要设为0,和dp[i-1][j]与dp[i][j-1]的值无关。状态转移方程:

1 X[i] == Y[j],dp[i][j] = dp[i-1][j-1] + 1

2 X[i] != Y[j],dp[i][j] = 0

这里填满dp二维数组后的情形应该是一组“左上-右下”的一串递增的数字,其它值都为0.

代码如下:

void LCS2(char *X, size_t xlen, char *Y, size_t ylen)
{
int maxlen = 0;
int maxidx = 0;

for (size_t i = 0; i < xlen; i++)
{
for (size_t j = 0; j < ylen; j++)
{
if (X[i] == Y[j])
{
if (i == 0 || j == 0)
dp[i][j] = 1;
else
dp[i][j] = dp[i - 1][j - 1] + 1;

if (maxlen < dp[i][j])
{
maxlen = dp[i][j];
maxidx = i - maxlen + 1;
}
}
}
}
string out(&X[maxidx], &X[maxidx + maxlen]);
cout << "LCS: " << out << endl;
}
同样,LCS结果可能有多个,这里只输出了一个。不过,根据上面描述的dp二维数组的特点,很容易将所有结果输出出来。略

这里有n个字符串最长公共子序列的解法,有兴趣可以研究下,用递归方法调用动态规划法求解,本质还是两个字符串的LCS动态规划解法,在实现时使用了一些小技巧,有点抽象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: