问题描述:见算法导论P208-P209
前提概念
给定一个序列X = (x1, x2, ..., xm),对i = 0, 1, ..., m,记X的第i个前缀为Xi = (x1, x2, ..., xi),故Xm = X,而X0是个空序列
一个给定序列的子序列就是该序列去掉0个或多个元素(不一定连续),如BCDB是ABCBDAB的一个子序列
基于以上定义,最长公共子序列(LCS)有如下性质:
设X = (x1, x2, ..., xm)和Y = (y1, y2, ..., yn)为两个序列,并设Z = (z1, z2, ..., zk)为X和Y的一个LCS
- 如果xm = yn,那么zk = xm = yn而且Zk-1是Xm-1和Yn-1的一个LCS
- 如果xm ≠ yn,那么zk ≠ xm → Z是Xm-1和Y的一个LCS
- 如果xm ≠ yn,那么zk ≠ yn → Z是X和Yn-1的一个LCS
递归表达式
记c[i, j]为Xi和Yj的LCS的长度,则由上面给出的LCS的性质得如下递归表达式
自底向上的求解
由分析知,矩阵C中的任意元素c[i, j]的值只取决于其左方、上方以及左上方元素的值。因此,应对矩阵C按从上至下,从左至右的方向进行逐行遍历求值。原问题最优解的值为c[m, n]
构造最优解
使用矩阵B记录最优解的构造,其任意元素b[i, j]的值为枚举型,只取以下三个值
- LEFT:表示前一LCS元素在当前元素的相邻左侧
- UP:表示前一LCS元素在当前元素的相邻上方
- LEFT_UP:表示前一LCS元素在当前元素的相邻左上方
构造过程应是递归的
代码
#define LEFT 1
#define UP 2
#define LEFT_UP 3
void dp(char * x, char * y, int len1, int len2){ //计算LCS长度,len1和len2表示序列X和Y的长度
int ** c = (int **)malloc((len1+1)*sizeof(int *)); //矩阵C和B长度为(len+1)×(len2+1),要考虑序列长度为0的情况
int ** b = (int **)malloc((len1+1)*sizeof(int *)); //矩阵C记录LCS长度,矩阵B记录LCS构造
for(int i = 0; i < len1+1; i++){
c[i] = (int *)malloc((len2+1)*sizeof(int));
memset(c[i], 0, len2+1);
b[i] = (int *)malloc((len2+1)*sizeof(int));
memset(b[i], 0, len2+1);
}
for(int i = 0; i < len1+1; i++){ //注意i的取范,c[i][j]表示长度为i的X前缀和长度为j的Y前缀的LCS长度
for(int j = 0; j < len2+1; j++){
if(i == 0 || j == 0){
c[i][j] = 0;
}
else if(x[i-1] == y[j-1]){ //注意x和y的下标,c[i][j]对应的是{x0, ..., xi-1}和{y0, ..., yj-1}的LCS长度
c[i][j] = c[i-1][j-1]+1;
b[i][j] = LEFT_UP;
}
else{
if(c[i-1][j] > c[i][j-1]){
c[i][j] = c[i-1][j];
b[i][j] = UP;
}
else{
c[i][j] = c[i][j-1];
b[i][j] = LEFT;
}
}
}
}
printf("LCS(%d): ", c[len1][len2]);
print_LCS(x, y, len1, len2, b);
printf("\n");
}
void print_LCS(char * x, char * y, int i, int j, int ** b){ //递归地构造LCS
if(i != 0 && j != 0){
if(b[i][j] == LEFT_UP){
print_LCS(x, y, i-1, j-1, b);
printf("%c", x[i-1]);
}
else if(b[i][j] == UP)
print_LCS(x, y, i-1, j, b);
else
print_LCS(x, y, i, j-1, b);
}
}