C++ 求解最长公共子序列(不仅仅求出其大小)(简单易懂!!!)(动态规划)

求解两个数组的最长公共子序列我们需要用到的知识点有:动态规划dp,递归算法

先来说说动态规划dp 很多初学者在学习动态规划的时候总是百思不得其解,什么是动态规划呢,其实总结来说就是程序将计算过的结果记录下来,在下次使用到这条记录的时候不需要再次计算,而可以直接使用,这样看来是不是节省了很大的时间开销呢!

动态规划解决问题一般套路可以分为两步,1:自顶向下的分析问题 2:自底向上的解决问题

为什么要自顶向下的分析问题呢? 我们要知道问题在多数值的情况下往往会具有一般性。

而自底向上的解决问题则是因为数据是从最底的数据开始记录,由少的数值计算得出多的数值。

我们可以用盖房子为例子,工程师是负责设计架构的,一般是从房子的头部开始向下设计,而建造房子则是需要从最底部开始搭建,所以自顶向下的设计,自底向上的解决。

现在回到我们的问题上,求两个字符串的最长序列,假设第一个字符串为S1,字符长度为i,第二个字符串为S2,字符长度为j。建立二维数组dp[i+1][j+1],dp[i][j]的意思为第一个字符串的前i个与第二个字符串的前j个的最长子序列。我们自顶向下的分析问题即从S1的最后一个即第i个与S2的最后一个即第j个进行判断。显而易见可以看出假如S1[i]==S2[j]相等的话那么i与j判断完毕,接下来就从i-1与j-1开始判断(因为i与j判断完毕就往前判断)故dp[i][j]=dp[i-1][j-1]+1。

假如S1[i]==S2[j]不相等的话,那么会出现两种情况,要知道最大子序列是两者共有的,那么i和j不相等,他们之中一定有一个不在子序列当中,所以要么i不在,要么j不在,i不在那么我们就从i-1开始找,j不在我们就从j-1开始找。故dp[i][j]=max(dp[i-1][j],dp[i][j-1])。

代码如下:

	for(int p=1;p<=i;p++)// i为S1的长度       p,q皆从1开始往后面循环                          
	{
		for(int q=1;q<=j;q++) //j为S2的长度      自底向上解决问题
		{
			
			if(s1[p-1]==s2[q-1])  //字符串从0开始 这里容易出错
			{
				dp[p][q]=dp[p-1][q-1]+1;
			}
			else
			{
				dp[p][q]=max(dp[p-1][q],dp[p][q-1]);
			}
			
		}
	}

这样我们就可以得到最长子序列的长度了,可以这样我们无法得知最长子序列到底是什么样的?

想要得出这个结果,我们就需要使用递归来解决。

在先前我们已经得出了dp数组的完整数值,我们可以使用dp数组的值递归来解决,我们先准备一个空数组,假如S1[i]==S[j],那么我们就可以直接把这个值记录到数组里,假如他们不相等,那么就是上面的情况了分别是i不在和j不在。具体我们已经通过dp计算出来了即dp[i][j]==dp[i-1][j]那么就是i不在序列中,我们从S1的第1个到i-1个与S2的第1个到第j个开始寻找。dp[i][j]==dp[i][j-1]那么就是j不在序列中,我们从S1的第1个到i个与S2的第1个到第j-1个开始寻找。这样重复递归,当i等于0或者j为0时停止递归。

代码如下:

void found(string a,string b,int i,int j,char rem[],int dp[][100]){
 	if(i==0 ||j==0){
 		
	 return;
 	
	 }
 	if(a[i-1]==b[j-1])
 	{
 		rem[m]=a[i-1];
 		m++;
 		found(a,b,i-1,j-1,rem,dp);
	 }
	 else{
	 	if(dp[i][j]==dp[i-1][j])
	 	found(a,b,i-1,j,rem,dp);
	 	else if (dp[i][j]==dp[i][j-1])
	 	found(a,b,i,j-1,rem,dp);
	 	
	 }
		
	}

所以总体的代码为:

#include<iostream>
using namespace std;
#include<algorithm>
#include <cstring>
int m;   //m用来记录两个字符串相等的个数
void found(string a,string b,int i,int j,char rem[],int dp[100][100]);

int main()
{
	string s1;
	string s2;
	int i,j;
	cout<<"请输入第一个字符串:"; 
	cin>>s1;
	cout<<"请输入第二个字符串:"; 
	cin>>s2;
	i=s1.length();
	j=s2.length();
	int dp[i+1][100];
	char rem[max(i,j)];
	memset(dp,0,sizeof(dp));
	for(int p=1;p<=i;p++)
	{
		for(int q=1;q<=j;q++)
		{
			
			if(s1[p-1]==s2[q-1])
			{
				dp[p][q]=dp[p-1][q-1]+1;
			}
			else
			{
				dp[p][q]=max(dp[p-1][q],dp[p][q-1]);
			}
			
		}
	}
    
	cout<<"最大子序列的个数为:"<<dp[i][j]<<endl;
	
	found(s1,s2,i,j,rem,dp);
	for(int g=m-1;g>=0;g--)     //因为是逆序保存的所以需要逆序输出
	{
		cout<<rem[g];
	}

	
}
void found(string a,string b,int i,int j,char rem[],int dp[][100]){
 	if(i==0 ||j==0){
 		
	 return;
 	
	 }
 	if(a[i-1]==b[j-1])
 	{
 		rem[m]=a[i-1];
 		m++;
 		found(a,b,i-1,j-1,rem,dp);
	 }
	 else{
	 	if(dp[i][j]==dp[i-1][j])
	 	found(a,b,i-1,j,rem,dp);
	 	else if (dp[i][j]==dp[i][j-1])
	 	found(a,b,i,j-1,rem,dp);
	 	
	 }
		
	}

运行结果案例如下:

 

 

 

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值