最大公共子序列 vs 最大公共子串

        最大公共子序列及最大公共子串问题是动态规划算法中比较经典,而且经常在一起提到。其主要的不同点就是公共子序列不要求元素的下标值连续,而公共字串则要求下标连续。

     动态规划算法重点是要发现题目的最优解的结构特征,尽可能用一个递归的方程式进行描述从而实现自底向上计算最优值。一般这里的最优值还不是动态规划题目的最终解,最优值所构建的是动态规划问题的解空间,我们需要根据这个解空间来最终构造出最优解。

     最大公共子序列问题的最优解结构用递归方程描述如下, 表示的是字符序列 和字符序列的最大公共子序列元素个数。注意,公式条件中  中的下标代指实际问题中字符序列X和字符序列Y的实际元素个数。在程序实现时,由于字符数组下标是从0开始,所以实际指代的是字符数组中的元素  



这是问题的关键,有了它,我们可以构建出问题的最优值解空间。下图是以给定的字符数组 a, b,构建的最优值解空间


char[] a = {'A','C','D','E','H','L'};
char[] b = {'A','B','C','I','F','H','L'};




有了最优值解空间后,我们可以通过回溯把问题的最优解求出。



上图中着色的单元格是求解过程中所经过的路径,黄色的为最优解的元素,即最大公共子序列,绿色的部分为非公共子序列元素。


代码实现

public class CLS {

	
	private void commonCLS(char[] a,char[] b){
		
		int i,j;
		int[][] c = new int[a.length + 1][b.length + 1]; //构建最优值解空间。
		for ( i = 0 ;i <= a.length ;i++) c[i][0] = 0;
		for ( j = 0 ;j <= b.length; j++) c[0][j] = 0;
		for (i = 1 ;i <= a.length ;i++)
			for (j = 1;j <=b.length; j++){
				if (a[i - 1] == b[j - 1]) //由于数组元素下标从零开始,故实际需要查看数组中的a[i-1] 和 b[j-1]。
				   {c[i][j] = c[i - 1][j - 1] + 1;}
				else
				{
					if (c[i - 1][j] >= c[i][j - 1]) 
						c[i][j] = c[i-1][j];
					else
						c[i][j] = c[i][j - 1];
				}
			}
		constructSolution(a.length,b.length,a,b,c);
	}
	
	private void constructSolution(int i,int j,char[] a,char[] b,int[][] c){
		if ( i == 0 || j == 0) return;
		if (c[i][j] == c[i-1][j-1] + 1 && a[i-1] == b[j-1]){ //由于数组元素下标从零开始,故实际需要查看数组中的a[i-1] 和 b[j-1]。
		    constructSolution(i-1,j-1,a,b,c);
			System.out.print(a[i-1] + " ");
		}else
		{
		if (c[i - 1][j] >= c[i][j-1]) 
			constructSolution(i-1,j,a,b,c);
		else
			constructSolution(i,j-1,a,b,c);
		}
			
	}
	

	public static void main(String[] args) {
		
		char[] a = {'A','C','D','E','H','L'};
		char[] b = {'A','B','C','I','F','H','L'};
		
		CLS cls = new CLS();
		cls.commonCLS(a, b);


	}

}

输出结果

A C H L 

        对于最大公共字串,其最优解空间要相对简单,由于其要求子串的小标在两个字符数组中都是连续的,那么问题的最优解就会在二维矩阵解空间的斜对角线上。其最优解结构的递归方程式可描述如下


        最大公共子序列问题的最优解结构用递归方程描述如下,  表示的是字符序列  和字符序列 的最大公共子串元素个数。注意,公式条件中   中的下标代指实际问题中字符序列X和字符序列Y的实际元素个数。在程序实现时,由于字符数组下标是从0开始,所以 实际指代的是字符数组中的元素  

        同样以最大公共子序列用到的字符数组 a, b为例,构建的最优值解空间如下


char[] a = {'A','C','D','E','H','L'};
char[] b = {'A','B','C','I','F','H','L'};

不难看出上述最优值解空间中构造出的最优解如下图


        另外  ,我们通过上述最优值解空间去求最优解的话,我们还需要一个坐标点位置,就是从那里开始回溯来最优解。因为对于给定的不同字符串,生成的最优值解空间也会不一样,我们要找到所有斜对角线上最长且连续的一串自然数,就需要记录下其一端的坐标 点p (x,y)。对于当前给定的a,b字符数组,坐标点p的位置为x=6,y=7。

代码如下

public class SequentialLCS {
	
	
	private void sLCS(char[] a,char[] b){
		int sum = 0;
		int m=0,n=0;
		int[][] c = new int[a.length + 1][b.length + 1];
		for (int i=0;i<=a.length;i++) c[i][0] = 0;
		for (int j=0;j<=a.length;j++) c[0][j] = 0;
		for (int i=1;i<=a.length;i++)
			for (int j=1;j<=b.length;j++){
				if (a[i-1] == b[j-1]) //由于数组元素下标从零开始,故实际需要查看数组中的a[i-1] 和 b[j-1]。
					c[i][j] = c[i-1][j-1] + 1;
				else
					c[i][j] = 0;	
				if (sum < c[i][j]){
					sum  = c[i][j];
				    m = i;
				    n = j;
				}
			}

	   constructSolution(c,a,m,n);	
	}
	
	private void constructSolution(int[][] c, char[] a,int m, int n) {
		if ((m ==0 || n==0) ||(c[m][n] == 0)) return;
		constructSolution(c,a,m-1,n-1);
		System.out.print( a[m-1] + " ");
		
	}

	public static void main(String[] args){
		
		char[] a = {'A','C','D','E','H','L'};
		char[] b = {'A','B','C','I','F','H','L'};

		SequentialLCS sLCS = new SequentialLCS();
		sLCS.sLCS(a, b);
		

		
		
	}

}

运行结果如下

H L 




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值