求解最长公共子序列问题
一、【问题描述】字符序列的子序列是指从给定字符序列中随意地(不一定要联系)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。给定两个序列A和B,称序列Z是A和B的公共子序列,是指Z同是A和B的子序列,该问题是求两序列A和B的最长公共子序列(LCS)
二、【问题求解】
若设A= (a0, a1, . am-1),B= (b0, b1, … bn-1) ,设Z= (z0, z1, .,zk-1) 为它们的最长公共子序列。不难证明有以下性质:
上图有一些难以理解,那么我们用更简单的方法来理解,假设有两个字符串A、B,分别有两个指针指向这两个字符串,设这两个指针分别为i和j,如今我们设定一个状态方程f(i,j)表示i指向字符串A的某个字符而j指向字符串B的某个字符的情况。那么此时就会有两种情况:
①两个指针所指向的字符相同:那么两个指针继续向右移动,此时的状态为f(i+1,j+1)+1;
②两个指针所指向的字符不相同:那么此时的状态又分为两种分别为f(i+1,j)和f(i,j+1)
因此我们定义一个二维动态规划数组dp,其中dp[i][j]为子序列A和B的最长公共子序列的长度。
那么状态转移方程如下:
dp[i][j]=0 (当i=0或者j=0即边界条件的情况下)
dp[i][j]=dp[i-1][j-1]+1 (当a[i-1]=b[i-1])
dp[i][j]=max(dp[i][j-1],dp[i-1][j]) (当a[i-1]!=b[j-1])
那么dp[m][n]就是我们要求的最终结果
总结:
用vector字符向量subs存放一个LCS,k=dp[m][n](LCS的字符个数),从k到1循环求出subs中的k个字符:
①如果dp[i][j]=dp[i-1][j](上方),说明a[i-1]或者b[j-1]不是LCS中的字符。
→→ i-1;
②如果dp[i][j]=dp[i][j-1](左方),说明a[i-1]或者b[j-1]不是LCS中的字符。→→j-1
③其他情况,说明a[i-1]或者b[j-1]是LCS中的字符。
→→i-1,j-1,k-1表示求的字符减少一个
废话少说,放码过来:
//求解最长公共子序列问题的算法
#include <iostream>
#include <string.h>
#include <vector>
#include <string>
using namespace std;
#define max(x,y) ((x)>(y)?(x):(y))
#define MAX 51 //序列中最多的字符个数
//问题表示
int m,n;
string a,b;
//求解结果表示
int dp[MAX][MAX]; //动态规划数组
vector<char> subs; //存放LCS
void LCSlength() //求dp
{
int i,j;
for (i=0;i<=m;i++) //将dp[i][0]置为0,边界条件
dp[i][0]=0;
for (j=0;j<=n;j++) //将dp[0][j]置为0,边界条件
dp[0][j]=0;
for (i=1;i<=m;i++)
for (j=1;j<=n;j++) //两重for循环处理a、b的所有字符
{ if (a[i-1]==b[j-1]) //情况(1)
dp[i][j]=dp[i-1][j-1]+1;
else //情况(2)
dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
}
}
void Buildsubs() //由dp构造从subs
{
int k=dp[m][n]; //k为a和b的最长公共子序列长度
int i=m;
int j=n;
while (k>0) //在subs中放入最长公共子序列(反向)
if (dp[i][j]==dp[i-1][j])
i--;
else if (dp[i][j]==dp[i][j-1])
j--;
else
{
subs.push_back(a[i-1]); //subs中添加a[i-1]
i--; j--; k--;
}
}
int main()
{
a="abcbdb";
b="acbbabdbb";
m=a.length(); //m为a的长度
n=b.length(); //n为b的长度
LCSlength(); //求出dp
Buildsubs(); //求出LCS
cout << "求解结果" << endl;
cout << " a: " << a << endl;
cout << " b: " << b << endl;
cout << " 最长公共子序列: ";
vector<char>::reverse_iterator rit;
for (rit=subs.rbegin();rit!=subs.rend();++rit)
cout << *rit;
cout << endl;
cout << " 长度: " << dp[m][n] << endl;
return 0;
}
运行结果:
debug代码:
//求解最长公共子序列问题的算法
#include <iostream>
#include <string.h>
#include <vector>
#include <string>
using namespace std;
#define max(x,y) ((x)>(y)?(x):(y))
#define MAX 51 //序列中最多的字符个数
//问题表示
int m,n;
string a,b;
//求解结果表示
int dp[MAX][MAX]; //动态规划数组
vector<char> subs; //存放LCS
void LCSlength() //求dp
{
int i,j;
for (i=0;i<=m;i++) //将dp[i][0]置为0,边界条件
dp[i][0]=0;
for (j=0;j<=n;j++) //将dp[0][j]置为0,边界条件
dp[0][j]=0;
for (i=1;i<=m;i++)
for (j=1;j<=n;j++) //两重for循环处理a、b的所有字符
{ if (a[i-1]==b[j-1]) //情况(1)
dp[i][j]=dp[i-1][j-1]+1;
else //情况(2)
dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
}
}
void Buildsubs() //由dp构造从subs
{
printf("求LCS\n");
int k=dp[m][n]; //k为a和b的最长公共子序列长度
int i=m;
int j=n;
while (k>0) //在subs中放入最长公共子序列(反向)
if (dp[i][j]==dp[i-1][j])
i--;
else if (dp[i][j]==dp[i][j-1])
j--;
else
{
printf("i=%d,j=%d,添加元素a[i-1]=%c\n",i,j,a[i-1]);
subs.push_back(a[i-1]); //subs中添加a[i-1]
i--; j--; k--;
}
}
int main()
{
a="abcbdb";
b="acbbabdbb";
m=a.length(); //m为a的长度
n=b.length(); //n为b的长度
LCSlength(); //求出dp
printf("输出dp\n");
for (int i=0;i<=n;i++)
{
for (int j=0;j<=n;j++)
printf("%3d",dp[i][j]);
printf("\n");
}
Buildsubs(); //求出LCS
cout << "求解结果" << endl;
cout << " a: " << a << endl;
cout << " b: " << b << endl;
cout << " 最长公共子序列: ";
vector<char>::reverse_iterator rit;
for (rit=subs.rbegin();rit!=subs.rend();++rit)
cout << *rit;
cout << endl;
cout << " 长度: " << dp[m][n] << endl;
return 0;
}
运行结果:
参考自教材《算法设计与分析》第二版 李春葆