最长公共子序列(LCS)问题

问题描述:见算法导论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
以上性质说明 两个序列的LCS也包含了两个序列前缀的一个LCS,即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元素在当前元素的相邻左上方
构造过程是这样的:首先从b[m, n]处开始,沿着箭头在表格中追踪下去,当在b[i, j]遇到LEFT_UP,即意味着xi = yi是一个LCS元素,打印输出并继续跟踪;若遇到LEFT或UP,则继续按箭头方向跟踪,示意图如下

构造过程应是递归的



代码

#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);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值