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

最长公共子序列 LCS

问题

  • 给定2个序列X,Y。Z是2个序列的最长子序列,可以不连续,可能不唯一。
  • 比如说 X : A C D G E,Y : A D G E 则最长LCS(longest common sequence)为:A D E or A G E

解题步骤

  • 输入2个字符串 s1 s2 ,设MaxLen(i, j)表示s1左边共i个字符形成的字串与s2左边共j个字符形成的字串的最长公共子序列长度。i和j从0开始算。
  • 假设len1 = str(s1),len2 = str(s2),则就是求MaxLen(len1,len2)。
    在这里插入图片描述
思路:2个序列有下面3种情况
    1. 2个序列之中有一个0序列,则LCS = 0
    2. 2个m,n长的序列,他们的最后一个数据相同,则求前m-1, n-1的LCS + 最后一项(如果求长度就+13. 如果最后一项不同,则m ≠ n,有可能m>n or m<n,所以求MAX(LCS(n,m-1), LCS(m,n-1))

显然
MaxLen(n, 0) = 0 (n=0....len1)
MaxLen(0, n) = 0 (n=0....len2)
// 因为任何串和0串的LCS都是0

递推公式:
if s1[i-1] == s2[j-1]      // s1最左边字符是s1[0]
	MaxLen(i, j) = MaxLen(i-1, j-1) + 1	
	// (数组从零开始)i-1位置的数据相等,则它必是LCS中的一员,只需考虑i-1之前的最大LCS,最后再+1
else
	MaxLen(i, j) = max{ MaxLen(i, j-1)MaxLen(i-1, j)}
	// i-1位置数据不等,但是s1的i-1可能与s2的j相等,所以只要求出他们之前的最大LCS

求解LCS长度递推的伪码表示

  • 从已知条件推未知条件,只要先求出初试状态,此题为先求出MaxLen(0, n) and MaxLen(n, 0)。
  • 用二维数组保存s1,s2。
  • JAVA代码查看<<算法设计与分析第四版>>Page 6 0。
int MaxLen[MAX][MAX]	//MAX为s1,s2长度的最大值,预先设定
int main(){
	//给s1,s2赋值 and 初始化
	while(cin >> s1 >> s2){	
		int len1 = str(s1)
		int len2 = str(s2)
            
        // 初始化,只要存在0序列则 LCS = 0
		for(int i=0; i<= len1; i++)
			MaxLen[i][0] = 0
		for(int j=0; j<= len2; j++)
			MaxLen[0][j]= 0
	}
	
	// 执行递推公式
	int i,j
	for(i = 1; i<= len1; i++)
		for(j = 1; j<= len2; j++)
			if( s1[i-1] == s2[j-1] )
				MaxLen[i][j] = MaxLen[i-1][j-1] + 1
			else
				MaxLen[i][j] = max{ MaxLen[i][j-1],MaxLen[i-1][j] }
			/* else位置还可以改成
			if(Max[i][j-1] > MaxLen[i-1][j])
				MaxLen[i][j] = Max[i][j-1]
			if(Max[i][j-1] < MaxLen[i-1][j])
				MaxLen[i][j] = Max[i-1][j]
				*/
	// 输出s1 and s2 LCS的值,求的值就是下面的
	cout << MaxLen[len1][len2]
}	

递推伪代码的图像解释(重点)

  • i-1处即是判断 s1[i-1] 是否等于 s2[j-1],如果相等(最长公共子序列的长度)就是绿色箭头所指位置的值+1,如果不等就是红色箭头所指位置的值中最大的。
  • 重点:其实是把所有可能都求解出来的。
    在这里插入图片描述

求解LCS串

  • 之前求出的是LCS的长度,下面求LCS的具体数据,即求出子串。
  • 用标记函数LCS确定路径,二维数组B的值确定状态。
// 递推公式修改成
	int B[][]	//标记函数用来记录MaxLen[i][j]的值由哪一个字符得到的
	for(i = 1; i<= len1; i++)
		for(j = 1; j<= len2; j++)
			if( s1[i-1] == s2[j-1] )
				MaxLen[i][j] = MaxLen[i-1][j-1] + 1
				B[i][j] = 1	// 代表2者相等,之后LCS就向左斜向上方寻找
			
			if(Max[i][j-1] > MaxLen[i-1][j])
				MaxLen[i][j] = Max[i][j-1]
				B[i][j] = 2 // 向左边寻找
			
			if(Max[i][j-1] < MaxLen[i-1][j])
				MaxLen[i][j] = Max[i-1][j]
				B[i][j] = 3	// 向上边寻找
	
	int LCS(int i, int j, char s1[], int B[][]){
		// i:i行 s1的长度   j:j列 s2的长度    
        //s1[]:因为是公共子序列所以s1,s2都行,看你输出是 s1[i] 还是 s2[j] 
		      
        //此时递归结束,如果有值就输出了!!!!!!!!
        if (i==0 || j==0)	
			return
		
		if (B[i][j] == 1)	
			LCS(i-1, j-1, s1, B)	
			//s1 B都是对象,数组是对象,所以直接传入,递归地求解就能得到LCS串
			System.out.print(s1[i])
            
            ******//递归是从内往外执行的,所以从开始前面开始打印值
		
		if (B[i][j] == 2)
			LCS(i, j-1, s1, B)
		
		if (B[i][j] == 3)
			LCS(i-1, j, s1, B)
	}

JAVA代码表示

	

时间复杂度

  • 求解LCS的长度时间复杂度是判断2个子串是否相等,需要遍历2层,所以是O(mn)。
  • 求解LCS串是O(m+n),因为每次递归i-1,直到0为止。

应用

  • 自然界中检测2个DNA链中有没有公共的,判断它们DNA的相似度,越长越相似。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值