最长公共子序列

最长公共子序列

题目:

​ 一个给定序列的子序列是在该序列中删去若干元素后得到的序列。确切地说,若给定序列 X = { x 1 , x 2 , . . . , x m } X=\{x_1,x_2,...,x_m\} X={x1,x2,...,xm} ,则另一序列 Z = { z 1 , z 2 , . . . , z k } Z=\{z_1,z_2,...,z_k\} Z={z1,z2,...,zk} ,是X的子序列是指存在一个严格递增下标序列 { i 1 , i 2 , . . . i k } \{i_1,i_2,...i_k\} {i1,i2,...ik} 使得对于所有j=1,2,…,k,有: z j = x i j z_j=x_{i_j} zj=xij 。例如,序列 Z = { B , C , D , B } Z=\{B,C,D,B\} Z={B,C,D,B} 是序列 X = { A , B , C , B , D , A , B } X=\{A,B,C,B,D,A,B\} X={A,B,C,B,D,A,B} 的子序列,相应的递增下标序列为 { 2 , 3 , 5 , 7 } \{2,3,5,7\} {2,3,5,7}

​ 给定两个序列X和Y,当另一序列Z既是X的子序列有是Y的子序列时,称Z是X和Y的公共子序列。现要找出满足条件的最长的公共子序列。

分析:

​ 最简单粗暴的方法是穷举法,对X的所有子序列,检查它是否也是Y的子序列,从而确定它是否是X和Y的公共子序列。并在检查过程中记录最长的公共子序列。X的所有子序列都检查过后即可求出X和Y的最长公共子序列。X的每个子序列相应于下标集 {1,2,…,m} 的一个子集,因此有 2 m 2^m 2m个不同子序列,从而穷举搜索法需要指数时间。

最优子结构性质:

​ 事实上,最长公共子序列问题具有最优子结构性质。

​ 设序列 X = { x 1 , x 2 , . . . , x m } X=\{ x_1,x_2,...,x_m \} X={x1,x2,...,xm} Y = { y 1 , y 2 , . . . , y n } Y=\{ y_1,y_2,...,y_n \} Y={y1,y2,...,yn} 的最长公共子序列为 Z = { z 1 , z 2 , . . . , z m } Z=\{ z_1,z_2,...,z_m \} Z={z1,z2,...,zm} ,则

  1. x m = y n x_m=y_n xm=yn ,则 z k = x m = y n z_k=x_m=y_n zk=xm=yn ,且 Z k − 1 Z_{k-1} Zk1 X m − 1 X_{m-1} Xm1 Y n − 1 Y_{n-1} Yn1 的最长公共子序列。
  2. x m ≠ y n x_m \ne y_n xm=yn ,且 z k ≠ x m z_k \ne x_m zk=xm ,则 Z Z Z X m − 1 X_{m-1} Xm1 Y Y Y 的最长公共子序列。
  3. x m ≠ y n x_m \ne y_n xm=yn ,且 z k ≠ y n z_k \ne y_n zk=yn ,则 Z Z Z X X X Y n − 1 Y_{n-1} Yn1 的最长公共子序列。

证明:

  1. 反证法,若 z k ≠ x m z_k \neq x_m zk=xm ,则 { z 1 , z 2 , . . . , z k , x m } \{ z_1,z_2,...,z_k,x_m\} {z1,z2,...,zk,xm} 才是最长的公共子序列,与原先的结论矛盾,故有 z k = x m = y n z_k=x_m=y_n zk=xm=yn ,由此可得 Z k − 1 Z_{k-1} Zk1 X m − 1 X_{m-1} Xm1 Y n − 1 Y_{n-1} Yn1 的最长公共子序列。
  2. x m ≠ y n x_m \ne y_n xm=yn ,且 z k ≠ x m z_k \ne x_m zk=xm ,那么 z k z_k zk 可能存在在Y中,因为 z k ≠ x m z_k \ne x_m zk=xm z k z_k zk 只能存在于 X m − 1 X_{m-1} Xm1 中,故可证明。
  3. 类似2

由此可见,当满足 x m = y n x_m=y_n xm=yn 时可确定问题的解;当满足条件 x m ≠ y n x_m \neq y_n xm=yn 时,选取 X m − 1 X_{m-1} Xm1 Y Y Y 的最长公共子序列和 X X X Y n − 1 Y_{n-1} Yn1 的最长公共子序列中最长的子问题。因此,最长公共子序列问题具有最优子结构性质。

递归关系:

根据上述的最优子结构性质得出的递归关系如下:
c [ i ] [ j ] = { 0 i > 0 ; j = 0 c [ i − 1 ] [ j − 1 ] + 1 i , j > 0 ; x i = y i max ⁡ { c [ i ] [ j − 1 ] , c [ i − 1 ] [ j ] } i , j > 0 ; x i ≠ y i c[i][j]= \begin{cases} 0 && i>0;j=0 \\ c[i-1][j-1]+1 && i,j>0;x_i=y_i \\ \max\{ c[i][j-1],c[i-1][j] \} && i,j>0;x_i \neq y_i \end{cases} c[i][j]=0c[i1][j1]+1max{c[i][j1],c[i1][j]}i>0;j=0i,j>0;xi=yii,j>0;xi=yi

实现:

计算子序列长度:

  1. 记录长度和子问题的数组都从1开始,首先初始化数组:

    // 初始化第一列,长度为0,自然公共子序列也为0
    for(int i=0; i<= m; i++){
        c[i][0] = 0;
    }
    // 初始化第一行,理由同上
    for(int j=0; j<= n; j++){
        c[0][j] = 0;
    }
    
  2. 根据最优子结构性质,可以一行一行填写 (m+1)*(n+1) 的表格,当对应位置的 x[i] == y[j] 时,在原有基础上直接加1,将此子问题标记为1:

    if(x[i-1] == y[j-1]){
        // 相等,子序列加1
        c[i][j] = c[i-1][j-1]+1;
        b[i][j] = 1;
    }
    
  3. 当对应位置的 x[i] != y[j] 时,取 max{c[i-1][j], c[i][j-1]} ,当 c[i-1][j] 更大时将子问题标记为2,并令 c[i][j] = c[i-1][j]

    else if(c[i-1][j] >= c[i][j-1]){
        // 不相等,取 c[i-1][j]
        c[i][j] = c[i-1][j];
        b[i][j] = 2;
    }
    

    c[i][j-1] 更大时将子问题标记为3,并令 c[i][j] = c[i][j-1]

    else{
        // 不相等,取 c[i][j-1]
        c[i][j] = c[i][j-1];
        b[i][j] = 3;
    }
    
  4. 最后取 c[m][n] 即为最长子序列长度

求最长公共子序列:

  1. 通过递归求得,边界条件为 m==0 || n==0 :

    if(m==0 || n==0){
        return;
    }
    
  2. b[m][n] == 1 时,说明该字符在公共子序列中,故可输出。为了正序输出,因此要先进入递归,此时的长度应该都减1:

    if(b[m][n] == 1){
        lcs(m-1, n-1, x, b);
        printf("%c ", x[m-1]);  // x是从0开始的,b从1开始
    }
    
  3. b[m][n] == 2 时,说明最优子序列在子问题(m-1, n)中,因此m-1后进入递归:

    else if(b[m][n] == 2){
        lcs(m-1, n, x, b);
    }
    
  4. b[m][n] == 3 时,说明最优子序列在子问题(m, n-1)中,因此n-1后进入递归:

    else{
        lcs(m, n-1, x, b);
    }
    

完整代码:

#include<stdio.h>
/**
最长公共子序列长度
m: 序列X长度
n: 序列Y长度
x: 序列x
y: 序列y
c: 子序列长度最优解
b: 记录对应c的值是由哪个子问题的解得到
	1:相等,取 c[i-1][j-1]
	2:不相等,取 c[i-1][j]
	3:不相等,取 c[i][j-1]
*/
void lcsLength(int m, int n, char *x, char *y, int **c, int**b){
	// 初始化第一列,长度为0,自然公共子序列也为0
	for(int i=0; i<= m; i++){
		c[i][0] = 0;
	}
	// 初始化第一行,理由同上
	for(int j=0; j<= n; j++){
		c[0][j] = 0;
	}
	// 序列X
	for(int i=1; i<=m; i++){
		// 序列Y
		for(int j=1; j<=n; j++){
			if(x[i-1] == y[j-1]){
				// 相等,子序列加1
				c[i][j] = c[i-1][j-1]+1;
				b[i][j] = 1;
			} else if(c[i-1][j] >= c[i][j-1]){
				// 不相等,取 c[i-1][j]
				c[i][j] = c[i-1][j];
				b[i][j] = 2;
			} else{
				// 不相等,取 c[i][j-1]
				c[i][j] = c[i][j-1];
				b[i][j] = 3;
			}
		}
	}
}
/**
最长公共子序列
*/
void lcs(int m, int n, char *x, int **b){
	if(m==0 || n==0){
		return;
	}
	if(b[m][n] == 1){
		lcs(m-1, n-1, x, b);
		printf("%c ", x[m-1]);  // x是从0开始的,b从1开始
	} else if(b[m][n] == 2){
		lcs(m-1, n, x, b);
	} else{
		lcs(m, n-1, x, b);
	}
}
void main(){
	char x[] = {'A', 'B', 'C', 'B', 'D', 'A', 'B'};
	char y[] = {'B', 'D', 'C', 'A', 'B', 'A'};
	int m = sizeof(x) / sizeof(char);
	int n = sizeof(y) / sizeof(char);
	int* c[m+1];
	int* b[m+1];
	for(int i=0; i<=m; i++){
		c[i] = (int*) malloc(sizeof(int)*(n+1));
		b[i] = (int*) malloc(sizeof(int)*(n+1));
	}
	lcsLength(m, n, x, y, c, b);
	printf("c:\t\tb:\n");
	for(int i=1; i<=m; i++){
		for(int j=1; j<=n; j++){
			printf("%d ", c[i][j]);
		}
		printf("\t");
		for(int j=1; j<=n; j++){
			printf("%d ", b[i][j]);
		}
		printf("\n");
	}
	printf("\n");
	printf("最长子序列长度:%d\n", c[m][n]);
	lcs(m, n, x, b);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值