动态规划做最长公共子序列(LCS)及路径还原
子问题的递归结构:
由最长公共子序列问题的最优子结构性质可知,要找出X={x1,…,xm}和Y={y1,…,yn}的一个最长公共子序列,可按以下方式递归地进行:
当 xm = yn时,找出X(m-1)和Y(n-1)的最长公共子序列,然后在其尾部加上xm(=yn)即可得X和Y的一个最长公共子序列。
当xm != yn时,必须解2个子问题,即找出X(m-1)和Y的一个最长公共子序列及X和Y(n-1)的一个最长公共子序列。这2个公共子序列中较长者即为X和Y的一个最长公共子序列。
由最长公共子序列问题的最优子结构性质建立子问题最优值的递归关系。用c[i][j]记录序列和的最长公共子序列的长度。其中, Xi={x1,x2,…,xi};Yj={y1,y2,…,yj}。当i=0或j=0时,空序列是Xi和Yj的最长公共子序列。故此时c[i][j]=0。其它情况下,由最优子结构性质可建立递归关系如下:
递推公式如下:
在程序中构建一个如下的长度矩阵
代码如下:
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
string a,b; //a串和b串
int lena,lenb; //lena为a串的长度,lenb为b串的长度
int dp[1005][1005]; //长度空间
/*
构造一个a/b串下标对应的长度矩阵
赋初值如下
a/b 0 1 2 3 ..
0 0 0 0 0
1 0
2 0
3 0
..
*/
int LCS() //最长公共子序列
{
int inA,inB; //a、b串对应于长度矩阵的下标,即ina=a+1,inb=b+1
lena=a.length(); lenb=b.length(); //lena为a串的长度,lenb为b串的长度
memset(dp,0,sizeof(dp)); //初始化长度矩阵
for(int i=0;i<lena;i++) //遍历串
{
for(int j=0;j<lenb;j++) //遍历串
{
inA=i+1; inB=j+1; //将a串下标转换为矩阵的行下标,将b串下标转换为矩阵的列下标
if(a[i]==b[j]) //a串和b串当前位置相同,长度加一
dp[inA][inB]=dp[inA-1][inB-1]+1;
else dp[inA][inB]=max(dp[inA-1][inB],dp[inA][inB-1]);
//不同,取上一层迭代的最大值作为当前位置的长度
}
}
return dp[lena][lenb]; //返回结果
}
void rollback() //路径还原函数
{
string ans=""; //初始化字符串
int i=a.length(); int j=b.length(); //lena为a串的长度,lenb为b串的长度
while(i>0&&j>0) //其中有一个串已结束,则循环结束
{
if(dp[i][j]==dp[i-1][j-1]+1&&a[i-1]==b[j-1])
{ //若当前位置是前一位置的加一得到,且两串的前位置相同,则都向前移
ans+=a[i-1]; //加入结果字符串
i--; //下标移动
j--; //下标移动
}
else if(dp[i-1][j]>dp[i][j-1])
i--; //下标移动
else j--; //下标移动
}
reverse(ans.begin(),ans.end()); //倒置字符串
cout<<ans<<endl; //输出路径
}
int main()
{
int MaxLen;
while(cin>>a>>b) //输入a串和b串
{
MaxLen=LCS();
cout<<MaxLen<<endl; //输出结果
rollback();
}
return 0;
}
/*
abcicba
abdkscab
MaxLen=4
abca
*/