最长公共子序列&&最长公共子串---[动态规划]


最长公共子序列


第一节、问题描述

    什么是最长公共子序列呢?好比一个数列 S,如果分别是两个或多个已知数列的子序列,且是所有符合此条件序列中最长的,则S 称为已知序列的最长公共子序列。

    举个例子,如:有两条随机序列,如 1 3 4 5 5 ,and 2 4 5 5 7 6,则它们的最长公共子序列便是:4 5 5。

    注意最长公共子串(Longest CommonSubstring)和最长公共子序列(LongestCommon Subsequence, LCS)的区别:

子串(Substring)是串的一个连续的部分,子序列(Subsequence)则是从不改变序列的顺序,而从序列中去掉任意的元素而获得的新序列;

更简略地说,前者(子串)的字符的位置必须连续,后者(子序列LCS)则不必。

比如字符串acdfg同akdfc的最长公共子串为df,而他们的最长公共子序列是adf。

LCS可以使用动态规划法解决。下文具体描述。


第二节、LCS问题的解决思路

  • 穷举法

    解最长公共子序列问题时最容易想到的算法是穷举搜索法,即对X的每一个子序列,检查它是否也是Y的子序列,从而确定它是否为X和Y的公共子序列,并且在检查过程中选出最长的公共子序列。X和Y的所有子序列都检查过后即可求出X和Y的最长公共子序列。X的一个子序列相应于下标序列{1, 2, …, m}的一个子序列,因此,X共有2m个不同子序列(Y亦如此,如为2^n),从而穷举搜索法需要指数时间(2^m * 2^n)。

  • 动态规划算法

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

记:

Xi=﹤x1,⋯,xi﹥即X序列的前i个字符 (1≤i≤m)(前缀)

Yj=﹤y1,⋯,yj﹥即Y序列的前j个字符 (1≤j≤n)(前缀)

假定Z=﹤z1,⋯,zk﹥∈LCS(X , Y)。



子问题的递归结构

    由最长公共子序列问题的最优子结构性质可知,要找出X=<x1, x2, …, xm>和Y=<y1, y2, …, yn>的最长公共子序列,可按以下方式递归地进行:

当xm=yn时,找出Xm-1和Yn-1的最长公共子序列,然后在其尾部加上xm(=yn)即可得X和Y的一个最长公共子序列。

当xm≠yn时,必须解两个子问题,即找出Xm-1和Y的一个最长公共子序列及X和Yn-1的一个最长公共子序列。

这两个公共子序列中较长者即为X和Y的一个最长公共子序列。

    由此递归结构容易看到最长公共子序列问题具有子问题重叠性质。例如,在计算X和Y的最长公共子序列时,可能要计算出X和Yn-1及Xm-1和Y的最长公共子序列。而这两个子问题都包含一个公共子问题,即计算Xm-1和Yn-1的最长公共子序列。

    与矩阵连乘积最优计算次序问题类似,我们来建立子问题的最优值的递归关系。

    用c[i,j]记录序列Xi和Yj的最长公共子序列的长度。其中Xi=<x1, x2, …, xi>,Yj=<y1, y2, …, yj>。

    当i=0或j=0时,空序列是Xi和Yj的最长公共子序列,故c[i,j]=0。

    其他情况下,由定理可建立递归关系如下:

    我们用c[i][j]记录X[0~i]与Y[0~j] 的LCS 的长度,那么在计算c[i,j]之前,c[i-1][j-1],c[i-1][j]与c[i][j-1]均已计算出来。

    此时我们根据X[i] = Y[j]还是X[i] != Y[j],就可以计算出c[i][j]。问题的递归式写成:




例如,设所给的两个序列为X=<A,B,C,B,D,A,B>和Y=<B,D,C,A,B,A>。由算法LCS_LENGTH和LCS计算出的结果如下图所示:



1、字符相同,则指向左上,并加1

2、字符不同,则指向左边或者上边较大的那个


在经典的算法导论那本书里,文章使用了b[][]来记录c[i,j] 选自于哪一个(从递归式里,我们知道有三个不同的选择)
但是,我们可以不借助于数组b而借助于数组c本身临时判断c[i,j]的值是由c[i-1,j-1],c[i-1,j]和c[i,j-1]中哪一个数值元素所确定,原理是通过我们如何选取c[i,j]来决定的。 

算法分析:
由于每次调用至少向上或向左(或向上向左同时)移动一步,故最多调用(m * n)次就会遇到i = 0或j = 0的情况,此时开始返回。返回时与递归调用时方向相反,步数相同,故算法时间复杂度为Θ(m * n)。[这个不太确定]
/**
 * 最长公共子序列(LongestCommon Subsequence, LCS)
 * Eg.X=ABCBDAB和Y=BDCABA
 * 输出最长公共子序列长度为4,BCBA
 * */
public class LCS {
	public static int[][] longestCS(String x,String y){
		int m=x.length();
		int n=y.length();
		int opt[][]=new int[m+2][n+2];//用c[i][j]记录X[0~i]与Y[0~j] 的LCS 的长度
		for(int i=0;i<=m+1;i++){
			opt[i][0]=0;
			opt[i][n+1]=0;//防止之后输出的时候数组越界,把四周都包上0
		}
		for(int j=0;j<=n;j++){
			opt[0][j]=0;
			opt[m+1][j]=0;
		}
		for(int j=1;j<=n;j++){
			for(int i=1;i<=m;i++){
				if(x.charAt(i-1)==y.charAt(j-1)){
					opt[i][j]=opt[i-1][j-1]+1;
				}else{
					opt[i][j]=(opt[i-1][j]>opt[i][j-1]?opt[i-1][j]:opt[i][j-1]);
				}
			}
		}
		return opt;
	}
	public static void printLCS(int opt[][],String x,String y,int i,int j){
		if(i==(x.length()+1)||j==(y.length()+1)){return;}
		if(x.charAt(i-1)==y.charAt(j-1)){
			System.out.print(x.charAt(i-1));
			printLCS(opt,x,y,i+1,j+1);
		}else if(opt[i][j+1]>=opt[i+1][j]){
			printLCS(opt,x,y,i,j+1);
		}else{printLCS(opt,x,y,i+1,j);}
	}
	public static void main(String[] args){
		String x="ABCBDAB";
		String y="BDCABA";
		int m=x.length();
		int n=y.length();
		int opt[][]=new int[m+1][n+1];
		opt=longestCS(x,y);
		printLCS(opt,x,y,1,1);
	}

}



最长公共子串

测试样例:

"1AB2345CD",9,"12345EF",7

返回:4


解题思路:
理解最长公共子序列以后很快就能看懂这个
这次建一个opt[n][m](n,m分别为两个字符的长度)二维数组,记录A[0~i]、B[0~i]可以找到的最长公共子串
只判断字符相等的情况,如果没有字符相等这次循环直接pass掉
最开始i==0||j==0时直接帮忙赋值opt[i][j]=1
之后就要看斜上方对角线的数值再加1,即dp[i][j]=dp[i-1][j-1]+1

//最长公共子串
import java.util.*;
public class LCSub {
	 public static int findLongest(String A, int n, String B, int m) {
	        // write code here
	        int opt[][]=new int[n][m];
	        int length=0;
	        int end=0;
	        for(int j=0;j<m;j++){
	            for(int i=0;i<n;i++){
	                if(A.charAt(i)==B.charAt(j)){
	                    if(i==0||j==0){
	                    	opt[i][j]=1;
	                    	}else{
	                    		opt[i][j]=opt[i-1][j-1]+1;
	                    		}
	                    if(opt[i][j]>length){
	                    	length=opt[i][j];end=i;
	                    	}
	                    }
	                }
	            }
	        return (A.substring(end-length+1,end+1)).length();
	    }
	 public static void main(String[] args){
		 String A="1AB2345CD";
		 String B="12345EF";
		 int n=9;
		 int m=7;
		 System.out.println(findLongest(A,n,B,m));
	 }

}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值