最长公共子序列——LCS

package jxau.LCS.lyx;

/**
 * 
 * @author: liyixiang
 * @data:2014-9-28
 * @题目大意:
 * 		最长公共子序列
 * @主要思路:
 * 		问题描述:
 * 		字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个
 * 也不去掉)后所形成的字符序列。
 *		令给定的字符序列X= “x0 ,x1 ,…,xm-1”,
 * 		序列Y= “y0 ,y1 ,…,yk-1”是X 的子序列,存在X的一个严格递增下标序列
 * <i0 ,i1 ,…,ik-1> ,使得对所有的j=0 ,1 ,…,k-1 ,有xij=yj 。
 * 例如,X= “ABCBDAB ”,Y= “BCDB ”是X 的一个子序列。

	考虑最长公共子序列问题如何分解成子问题,
设A= “a0 ,a1 ,…,am-1”,B= “b0 ,b1 ,…,bm-1”,并Z= “z0 ,z1 ,…,zk-1”为它们
的最长公共子序列。不难证明有以下性质:

(1 )  如果am-1=bn-1 ,则zk-1=am-1=bn-1 ,且“z0 ,z1 ,…,zk-2”是“a0 ,a1 ,…,
am-2”和“b0 ,b1 ,…,bn-2”的一个最长公共子序列;

(2 )  如果am-1!=bn-1 ,
			则若zk-1!=am-1 ,蕴涵“z0 ,z1 ,…,zk-1”是“a0 ,a1 ,…,
am-2”和“b0 ,b1 ,…,bn-1”的一个最长公共子序列;

(3 )  如果am-1!=bn-1 ,
			则若zk-1!=bn-1 ,蕴涵“z0 ,z1 ,…,zk-1”是“a0 ,a1 ,…,
am-1”和“b0 ,b1 ,…,bn-2”的一个最长公共子序列。

这样,在找A 和B 的公共子序列时,如有am-1=bn-1 ,则进一步解决一个子问题,
找“a0 ,a1 ,…,am-2”和“b0 ,b1 ,…,bm-2”的一个最长公共子序列;
如果am-1!=bn-1 ,则要解决两个子问题,
	找出“a0 ,a1 ,…,am-2”和“b0 ,b1 ,…,bn-1”的一个最长公共子序列
	找出“a0 ,a1 ,…,am-1”和“b0 ,b1 ,…,bn-2”的一个最长公共子序列
	再取两者中较长者作为A 和B 的最长公共子序列。
	
	例子:
		A:
			5 3 6 4 9 2 7
		B:
			3 8 4 9 7
		Z:
			3 4 9 7
			
		从后向前判断,相同则使用(1) 
						不同则判断哪个与A或B数组最后一个元素相同使用(2)或(3)
	
	求解:
		引进一个二维数组c[][],用 c[i][j]记录X[i]与Y[j] 的LCS 的长度,b[i][j]记录c[i][j]是通过
	哪一个子问题的值求得的,以决定搜索的方向 。 
		我们是自底向上进行递推计算,那么在计算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]。

问题的递归式写成:
	            0                                   if x=0 or y=0
	c[i,j] =    c[i-1,j-1] +1                       if i,j>0 and xi = yi
	            max(c[i,j-1],c[i-1,j])              if i,j>0 and xi != yi
	       
回溯输出最长公共子序列过程:	            
	           http://img1.tuicool.com/IJ7fE3.png
	         
 * @时间复杂度:
 * 		算法分析: 
			由于每次调用至少向上或向左(或向上向左同时)移动一步,
		故最多调用(m * n)次就会遇到i = 0或j = 0的情况,此时开始返回。
		返回时与递归调用时方向相反,步数相同,故算法时间复杂度为Θ(m * n)。
 * 
 * @空间复杂度:
 */
public class LCSProblem {

	/**
	 * 
	 * 方法概述:
	 * 		根据公式判断两个序列当前属于三种情况的哪一种情况。
	 * 返回值:
	 * 		b[][]数组
	 * 		1:代表两个序列末尾数组相同 
	 * 		0:zk-1!=am-1
	 * 		-1:zk-1!=bn-1
	 * 参数:
	 * @param x 父序列1
	 * @param y 父序列2
	 */
	public static int[][] getLength(String[] x,String y[]){
		
		int[][] b = new int[x.length][y.length];
		int[][] c = new int[x.length][y.length];
		
		for(int i=1;i<x.length;i++){
			for(int j=1;j<y.length;j++){
				
				//对应第一个性质(1)
				if(x[i] == y[j]){
					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][j] = c[i-1][j];
		          b[i][j] = 0;
		        }
		        //对应第二或者第三个性质
		        else
		        {
		          c[i][j] = c[i][j-1];
		          b[i][j] = -1;
		        }				
			}
		}
		return b;
	}
	
	/**
	 * 
	 * 方法概述:
	 * 		递归调用去除末尾元素方法
	 * 返回值:
	 * 参数:
	 * @param b
	 * 		 判断使用某条性质
	 * @param i
	 * 		 一个序列当前回溯到的长度
	 * @param x
	 * 		 其中一个序列
	 * @param j
	 * 		 另一个序列当前回溯到的长度
	 */
	public static void display(int b[][], String[]x ,int i, int j){
			if(i == 0 || j == 0)
		      return;
		    
		    if(b[i][j] == 1) {
		    	display(b, x, i-1, j-1);
		      System.out.print(x[i] + " ");
		    } else if(b[i][j] == 0) {
		    	display(b, x, i-1, j);
		    }
		    else if(b[i][j] == -1) {
		    	display(b, x, i, j-1);
		    }		
	} 
	
	
	public static void main(String[] args) {
		//保留空字符串是为了getLength()方法的完整性也可以不保留
	    //但是在getLength()方法里面必须额外的初始化c[][]第一个行第一列
	    String[] x = {"", "E", "D", "B", "B", "C", "A", "B","A","D"};
	    String[] y = {"", "C", "D", "E", "A", "B", "A", "C","C","D"};
	    
	    int[][] b = getLength(x, y);
	    
	    display(b, x, x.length-1, y.length-1);
	}
}


你可能感兴趣的:(最长公共子序列——LCS)