动态规划篇——最长公共子序列(c++)

引言:最长公共子序列属于动态规划的基础篇,由字符串的最长公共最序列可以引出很多的问题。

最长子序列详细讲解以及练习题目
需要详细讲解教程的可以观看上面链接的文章,以下是自己做的简单的概括。

一、何为最长公共子序列

A和B的公共子序列中长度最长的(包含元素最多的)叫做A和B的公共子序列。
仍然用序列1,3,5,4,2,6,8,7和序列1,4,8,6,7,5

它们的最长公共子序列是:

1,4,8,7 1,4,6,7

最长公共子序列的长度是4 。 请注意: 最长公共子序列不唯一,但可以长度是惟一并且相同的。

二、如何求最长公共子序列的长度
暴力枚举法:将两个字符串的所有子串相互比较,假设A、B两个子串的长度分别为m和n,那么两个子串分别有2^m和 2 ^n个子串,就需要比较 2 ^(n+m) ,时间复杂度特别复杂。
现在对暴力枚举做进一步改进,上面可以确定的是最长公共子序列长度一定是相同的,如果忽略空子序列的话,对于A长度为1的子序列有C(n,1)个,长度为2的子序列有C(n,2)个,……长度为n的子序列有C(n,n)个。对于B也可以做类似分析,即使只对序列A和序列B长度相同的子序列做比较,那么总的比较次数高达:

C(n,1)*C(m,1)*1 + C(n,2) * C(m,2) * 2+ …+C(n,p) * C(m,p)*p

其中p = min(m, n)。
在确定暴力枚举法不可取后,不妨转换下面这种思路。
我们假设两串子串(就叫A和B吧),现在假设匹配A的x位置,B的y位置(为了方便后面就叫Ax和By),那么A的(m,x)和B的(n,y)(m,n取决于两个子串的长度)已经相互匹配成功了,那现在出现的情况只有两种:匹配成功或不成功。
1、匹配成功,即Ax=By,那么最长的公共子序列长度就是在已经匹配成功的序列长度加1,LCS(x - 1, y - 1) + 1。
2、匹配不成功,即Ax!=By,这种情况下,最长的公共子序列长度不会改变并且已经求出,现在设t为最长子序列的最后一项,则t ≠ Ax和t ≠ By至少有一个成立。
2.1、如果t ≠ Ax,那么t=By,则LCS(x,y)= LCS(x – 1, y);
2.2、如果t ≠ By,那么t=Ax,则LCS(x,y) = LCS(x, y – 1)。
可是,我们事先并不知道t,由定义,我们取最大的一个,因此这种情况下,有LCS(x,y) = max(LCS(x – 1, y) , LCS(x, y – 1))。
那么可以得到
LCS(x,y) =
(1) LCS(x - 1,y - 1) + 1 如果Ax = By
(2) max(LCS(x – 1, y) , LCS(x, y – 1)) 如果Ax ≠ By

这时一个显然的递推式,光有递推可不行,初值是什么呢?

显然,一个空序列和任何序列的最长公共子序列都是空序列!所以我们有:

LCS(x,y) =
(1) LCS(x - 1,y - 1) + 1 如果Ax = By
(2) max(LCS(x – 1, y) , LCS(x, y – 1)) 如果Ax ≠ By
(3) 0 如果x = 0或者y = 0。
以上是求出了最长公共子序列的长度。
三、如何求出最长公共子序列
当LCS(x – 1, y) = LCS(x, y – 1)时,其实走哪个分支都一样,虽然长度时一样的,但是可能对应不同的子序列,所以最长公共子序列并不唯一。
我们在计算长度LCS(x,y)的时候只要多记录一些信息,就可以利用这些信息恢复出一个最长公共子序列来。恢复最长公共序列就好比我们在迷宫里走路,走到每个位置的时候记录下我们时从哪个方向来的,就可以从终点回到起点一样。
在这里插入图片描述
例题:
输入

第1行:字符串A
第2行:字符串B
(A,B的长度 <= 1000)

输出

输出最长的子序列,如果有多个,随意输出1个。

输入示例

abcicba
abdkscab

输出示例

abca

#include<iostream>
#include<algorithm>
#include<string.h>
char a[1001],b[1001];
int lcs[1001][1001];
char dp[1001];
using namespace std;
main()
{
	gets(a+1);
	gets(b+1);
	int l1=strlen(a+1);
	int l2=strlen(b+1);
	for(int i=0;i<=l1;i++)
	   for(int j=0;j<=l2;j++)
	   { if(i==0||j==0) lcs[i][j]=0;
	     else if(a[i]==b[j]) lcs[i][j]=lcs[i-1][j-1]+1;
	   	 else lcs[i][j]=max(lcs[i-1][j],lcs[i][j-1]);	   
		}
		int ans=l1,bns=l2,num=0;
		while(ans!=0&&bns!=0)//恢复最长公共序列,相当于从一串字符串中找到A、B两串中共有的字符,这里从A串中还原最长公共字符串
		{
			if(lcs[ans][bns]!=lcs[ans-1][bns])//lcs[i][j]来自于lcs[i][j-1]或者lcs[i-1][j-1],不管来自哪个,Ax肯定包含在最长公共字符串中
			 {
			 	 dp[++num]=a[ans];//记录最长公共串
			 	 ans--;//继续判断A串中前一个字符
			 	 bns--;//不管By来自与哪个表达式,By已经被判断过了,现在应该继续B的前一个字符
			 }
			 else if(lcs[ans][bns]==lcs[ans][bns-1])//如果来自lcs[i][j-1],那么说明By不包含在最长公共字符串中,继续遍历B的前一个字符
			       bns--;
			else ans--;//说明Ax不包含在最长公共字符串中,继续遍历A的前一个字符
			  
			  
		}
		for(int i=lcs[l1][l2];i>0;i--)
		 cout<<dp[i];//输出最长子序列
}

  • 37
    点赞
  • 138
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
为了找到两个字符串的最长公共子序列,可以使用分治算法来解决。分治算法将问题划分为更小的子问题,并通过解决子问题来解决原始问题。 以下是使用分治算法解决最长公共子序列问题的步骤: 1. 定义递归函数 `LCS(s1, s2, m, n)`,其中 `s1` 和 `s2` 是输入的两个字符串,`m` 和 `n` 是两个字符串的长度。 2. 如果 `m` 或 `n` 为0,则返回空字符串。 3. 如果 `s1[m-1]` 等于 `s2[n-1]`,则将 `s1[m-1]` 添加到最长公共子序列中,并递归调用 `LCS(s1, s2, m-1, n-1)`。 4. 如果 `s1[m-1]` 不等于 `s2[n-1]`,则有两种情况: - 将 `s1` 的最后一个字符从最长公共子序列中删除,并递归调用 `LCS(s1, s2, m-1, n)`。 - 将 `s2` 的最后一个字符从最长公共子序列中删除,并递归调用 `LCS(s1, s2, m, n-1)`。 5. 返回两种情况中的较长子序列作为结果。 下面是使用C++实现的代码示例: ```cpp #include <iostream> #include <string> using namespace std; string LCS(string s1, string s2, int m, int n) { if (m == 0 || n == 0) { return ""; } if (s1[m-1] == s2[n-1]) { return LCS(s1, s2, m-1, n-1) + s1[m-1]; } else { string lcs1 = LCS(s1, s2, m-1, n); string lcs2 = LCS(s1, s2, m, n-1); return (lcs1.length() > lcs2.length()) ? lcs1 : lcs2; } } int main() { string s1 = "ABCD"; string s2 = "ACDF"; int m = s1.length(); int n = s2.length(); string result = LCS(s1, s2, m, n); cout << "最长公共子序列为:" << result << endl; return 0; } ``` 这段代码中,我们定义了一个递归函数 `LCS` 来计算最长公共子序列。在 `main` 函数中,我们传入两个字符串 `s1` 和 `s2`,并调用 `LCS` 函数来获取最长公共子序列。最后,我们将结果打印出来。 注意:这段代码只是一个示例,可能不是最优的解决方案。在实际应用中,可能需要考虑使用动态规划等更高效的算法来解决最长公共子序列问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值