动态规划——最长公共子序列
问题:
问题:给定2个序列X={x1 ,x2 ,…,xm}和Y={y1 ,y2 ,…,yn},找出X和Y的最长公共子序列。子序列和子串的区别:是否连续。
分析:
首先考虑蛮力法,求X和Y的所有公共子序列,找出最长的。判断X一个子序列是否是Y子序列时间O(n),X有2m个子序列,最坏情况下时间复杂度O(n2m)。
考虑使用动态规划算法求解:
——分析最优子结构:子结构由原序列尝试减少最后一个元素得到。设序列X={x1 ,x2 ,…,xm}和Y={y1 ,y2 ,…,yn}的最长公共子序列为Z={z1 ,z2 ,…,zk } ,则(a)若xm= yn,则zk=xm=yn,且zk-1是xm-1和yn-1的最长公共子序列。(b)若xm≠ yn且zk≠xm,则Z是xm-1和Y的最长公共子序列。©若xm≠ yn且zk≠yn,则Z是X和yn-1的最长公共子序列。
——建立递推关系:用c[i][j]记录序列Xi和Yj的最长公共子序列的长度。其中,Xi={x1 ,x2 ,…,xi};Yj={y1 ,y2 ,…,yj}。建立递推关系如下:c[i][j] = 0,i=0 or j=0;c[i][j] = c[i-1][j-1] + 1,i>0,j>0,xi=xj;c[i][j] = max{c[i][j-1],c[i-1][j]},i>0,j>0,xi!=xj;
——计算最优值:设x序列y序列的长度分别是m和n,则子问题共有mn个,搜索所有的子问题即可,需要记录最优解的话就需要额外的一个数组b[i][j],记录Xi={x1 ,x2 ,…,xi};Yj={y1 ,y2 ,…,yj}是由哪个子问题的解得到的,后续traceback即可。
——构造最优解:根据b[i][j]traceback即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define MAXN 105
// 求最优值,即各个子问题最长公共子序列的长度
// 输入:序列x,y,子问题最优值记录矩阵c,最优解记录矩阵b,x长度m,y长度n
void LCS(char x[],char y[],int c[][MAXN],int b[][MAXN],int m,int n){
// 初始化最小子问题
for(int i = 0;i <= m;i++)
c[i][0] = 0;
for(int i = 0;i <= n;i++)
c[0][i] = 0;
// 根据递推公式计算解空间
for(int i = 1;i <= m;i++){
for(int j = 1;j <= n;j++){
if(x[i-1]==y[j-1]){
c[i][j] = c[i-1][j-1] + 1;
b[i][j] = 1;
}else{
if(c[i][j-1] > c[i-1][j]){
c[i][j] = c[i][j-1];
b[i][j] = 2;
}else{
c[i][j] = c[i-1][j];
b[i][j] = 3;
}
}
}
}
}
// 根据记录矩阵b还原最优解
void traceback(int b[][MAXN],char x[],char y[],int m,int n){
if(m==0||n==0){
return;
}
if(b[m][n]==1){
traceback(b, x, y, m-1, n-1);
cout<<x[m-1]<<" ";
}else if(b[m][n]==2){
traceback(b, x, y, m, n-1);
}else if(b[m][n]==3){
traceback(b, x, y, m-1, n);
}
}
int main(){
int m = 7,n = 6;
char x[7] = {'A','B','C','B','D','A','B'};
char y[6] = {'B','D','C','A','B','A'};
int b[MAXN][MAXN],c[MAXN][MAXN];
memset(b, 0, sizeof(b));
memset(c, 0, sizeof(c));
LCS(x, y, c, b, m, n);
traceback(b, x, y, m, n);
}
时间复杂度为O(mn,仅与问题输入规模有关)