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

最长公共子序列(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][n]为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][n]是值就是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动态规划解法,在实现时使用了一些小技巧,有点抽象。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值