动态规划之----最长公共子序列(LCS)

这个问题是动态规划算法中的一个经典问题,先看问题描述:

LCS:两个字符串 X和 Y,找到他们最长的公共子序列,该序列不要求是连续的。公共子序列的意思就是序列的相对顺序是不变的,但是序列不一定是连续的。


例如:

X = "BDCABA"

Y = "ABCBDAB"

他们的最长公共子序列是BCBA和BDAB,长度都为4,找到任意一个即可。


LCS问题也满足最优子结构:

定义  Xi 表示X的前I个字符组成的序列,同理得到Yj是Y的前j个字符组成的序列。

定义  z(i,j)表示Xi和Yj的最长公共子序列的长度LCS(Xi,Yj)


如此,可知:

我们要计算LCS(Xm,Yn),则有如下情况:

1.X[m] = Y[n],那么z(m,n) = LCS(Xm,Yn) = LCS(Xm-1,Yn-1) + 1

2.当X[m] != Y[n] ,那么z(m,n) 便是  LCS(Xm,Yn-1)和LCS(Xm-1,Yn)中较大者。


LCS也满足重叠子问题结构,计算LCS(Xm,Yn-1)和LCS(Xm-1,Yn)的时候也计算了LCS(Xm-1,Yn-1)。


因此,采用动态规划算法...


代码如下:

//	最长公共子序列 LCS 
//	Deng Chao
//	2012.12.4

#include <iostream>
#include <cstring>
using namespace std;


//	计算LCS长度并记录LCS各个字符的前驱
//	z:保存前驱记录,用于得到公共子序列
//	return:	LCS长度
int LCS(const char *x , int xl, const char *y , int yl , char **&z)
{	
	//	构造动态规划的长度记录表
	//	z 是一个 (xl+1)*(yl+1)的矩阵
	int **c = new int*[xl+1];
	z = new char*[xl+1];
	for(int i = 0 ; i <= xl ; ++i)
	{
		c[i] = new int[yl+1];
		z[i] = new char[yl+1];
	}
	
	//	初始化,很重要!
	for(int i = 0 ; i <=xl; ++i)
	{
		c[i][0] = 0;
	}
	for(int i = 0 ; i <= yl ; ++i)
	{
		c[0][i] = 0;
	}
	
	//	从1开始填充表
	for(int i = 1 ; i <= xl ; ++i)
	{
		for(int j = 1 ; j <= yl ; ++j)
		{	
			if(x[i-1] == y[j-1])
			{
				//	当前值匹配
				c[i][j] = c[i-1][j-1] + 1;
				z[i][j] = 'm';	//表示此处匹配
			}
			else
			{
				if(c[i-1][j] > c[i][j-1])
				{
					c[i][j] = c[i-1][j];
					z[i][j] = 'u';	//向上
				}
				else
				{
					c[i][j] = c[i][j-1];
					z[i][j] = 'l';	//向左
				}
			}
			
			cout<<"c["<<i<<"]["<<j<<"] = "<<c[i][j]<<endl;
		}
	}
	
	int r = c[xl][yl];
	
	//	清空临时空间
	for(int i = 0 ; i <= xl ; ++i)
	{
		delete [] c[i];
	}
	delete [] c;
	
	return r;
}

//	打印序列
void Print_LCS(const char *x , const char *y , char **z , int xl , int yl)
{
	if(0 == xl || 0 == yl)
	{
		return;
	}
	
	if('m' == z[xl][yl])
	{
		Print_LCS(x , y , z , xl-1 , yl - 1);
		cout<<x[xl-1]<<" ";
	}
	else if('u' == z[xl][yl])
	{
		Print_LCS(x , y , z , xl-1 , yl);
	}
	else
	{
		Print_LCS(x, y , z , xl , yl-1);
	}
	
	return;
}


//	test
int main()
{
	const char *x = "BDCABA";
	const char *y = "ABCBDAB";
	int xl = strlen(x);
	int yl = strlen(y);
	
	char **z = NULL ;
	int len = LCS(x , xl , y , yl , z);
	cout<<"LCS_Length = "<<len<<endl;
	Print_LCS(x , y , z , xl , yl);
}

后记:

这里描述的是找到一个最长公共子序列,而在例子中也看到可能不止一个序列,因此,如果要找到所有的最长公共子序列,应该怎么处理???

思路:不采用记录前缀的矩阵z,而直接采用记录长度的矩阵c

如果c[i][j] = c[i-1][j-1] + 1,则对应的值是最长公共序列的,且可以通过c[i-1][j-1]递归回溯;

如果c[i][j] = c[i-1][j] , 则该值不属于最长公共序列,但也可以通过c[i-1][j]进行回溯,c[i][j] = c[i-[j-1]的情况相同

如此可以回溯所有的可能路径,得到所有的最长公共序列。 


最长公共子序列问题(Longest Common Subsequence,简称LCS)是指在两个序列中找到一个最长的公共子序列,其中一个序列的所有元素按原序列中出现的顺序排列,而另一个序列中的元素则不要求按原序列中出现的顺序排列。 动态规划方法可以很好地解决LCS问题。设A和B是两个序列,LCS(A,B)表示A和B的最长公共子序列。则可以设计如下的状态转移方程: 当A和B的末尾元素相同时,LCS(A,B) = LCS(A-1,B-1) + 1。 当A和B的末尾元素不同时,LCS(A,B) = max(LCS(A-1,B), LCS(A,B-1))。 其中,LCS(A-1,B-1)表示A和B的末尾元素相同时的情况,LCS(A-1,B)表示A的最后一个元素不在最长公共子序列中,而B中的最后一个元素在最长公共子序列中的情况,LCS(A,B-1)表示B的最后一个元素不在最长公共子序列中,而A中的最后一个元素在最长公共子序列中的情况。 根据这个状态转移方程,可以使用动态规划算法来求解LCS问题。具体方法是,构建一个二维数组dp,其中dp[i][j]表示A前i个元素和B前j个元素的LCS。初始化dp[0][j]和dp[i][0]为0,然后按照上述状态转移方程进行递推,最终得到dp[lenA][lenB],其中lenA和lenB分别表示A和B的长度。dp[lenA][lenB]即为A和B的最长公共子序列的长度。要找到具体的最长公共子序列,可以从dp[lenA][lenB]开始,按照状态转移方程反向推导出每个元素,即可得到最长公共子序列LCS问题是动态规划算法的经典应用之一,时间复杂度为O(n*m),其中n和m分别为A和B的长度。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值