《算法导论》第15章动态规划最长公共子序列Java实现

首先定义一个给定序列的子序列,就是将给定序列中零个或多个元素去掉之后得到的结果,其形式化定义如下:给定一个序列X = <x1,x2 ,..., xm>,另一个序列Z =<z1,z2 ,..., zk> 满足如下条件时称为X的子序列,即存在一个严格递增的X的下标序列<i1,i2 ,..., ik>,对于所有j = 1,2,...,k,满足xij = zj,例如,Z=<B,C,D,B>是X=<A,B,C,B,D,A,B>的子序列,对应的下标序列为<2,3,5,7>。给定两个序列X和Y,如果Z是X的子序列,也是Y的子序列,则称它是X和Y的公共子序列。

   最长公共子序列问题(longest-common-subsequence problem)可用动态规划方法高效地求解。

   步骤1:刻画最长公共子序列的特征

   LCS问题具有 最优子结构性质。子问题的自然分类对应两个输入序列的“前缀"对。"前缀"的定义如下:给定一个序列X = <x1,x2 ,..., xm>,对于i = 0,1,...,m,定义X的第i前缀为Xi = <x1,x2 ,..., xi>。例如,若 X = <A,B,C,B,D,A,B>,则 X4 = <A,B,C,B>,X0为空串。

    令X = <x1,x2 ,..., xm>和Y = <y1,y2 ,..., yn> 为两个序列,Z =<z1,z2 ,..., zk>为X和Y的任意LCS。

  1.     如果Xm = Yn,则 Zk =  Xm = Yn且Zk-1 是Xm-1和Yn-1的一个LCS。
  2.     如果 Xm ≠ Yn,那么Zk ≠  Xm意味着Z是Xm-1和Y的一个LCS。
  3.     如果 Xm ≠ Yn,那么Zk ≠  Yn意味着Z是X和Yn-1的一个LCS。

    步骤2:一个递归解

    很容易看出LCS问题的重叠子问题性质。为了求X和Y的一个LCS,我们可能需要求X和Yn-1的一个LCS及Xm-1和Y的一个LCS。但是这几个子问题都包含求解Xm-1和Yn-1的LCS的子子问题。我们定义c[i,j]表示Xi和Yj的LCS的长度。如果i= 0 或j = 0,即一个序列长度为0,那么LCS的长度为0,根据LCS问题的最优子结构性质,可得如下公式:

     步骤3:计算LCS的长度

    过程LCS-LENGTH接受两个序列X = <x1,x2 ,..., xm>和Y = <y1,y2 ,..., yn>为输入。它将c[i,j]的值保存在表c[0...m,0...n]中,过程还维护一个表b[1...m,1...n],帮助构造最优解。b[i,j]指向的表项对应计算c[i,j]时所选择的子问题最优解。过程返回表b和表c,c[m,n]保存了X和Y的长度。

//伪代码
LCS-LENGTH(X,Y)
m = X.length
n = Y.length
let b[1..m,1..n] and c[0..m,0..n]be new tables
for i = 1 to m
    c[i,0] = 0
for j = 0 to n
    c[0,j] = 0
for i = 1 to m
    for j = 1 to n
    if xi == yi
      c[i,j] = c[i-1,j-1] + 1
      b[i,j] = "↖"

    else if c[i-1,j] ≥ c[i,j-1]

      c[i,j] = c[i-1,j]

      b[i,j] = "↑"

    else

      c[i,j] = c[i,j-1]

      b[i,j] = "←"

return c and b

下图显示了LCS-LENGTH对输入序列X= <A,B,C,B,D,A,B>和Y=<B,D,C,A,B,A>生成的结果。过程的运行时间为O(mn)。

    步骤4:构造LCS

    利用LCS-LENGTH返回的表b快速构造X和Y的LCS,只需简单地从b[m,n]开始,并按箭头方向追踪下去即可。挡在表项b[i,j]中遇到一个”↖"时,意味着xi=yi是LCS的一个元素。下面的递归过程会按正确的顺序打印出X和Y的一个LCS。对它的起始调用为PRINT-LCS(b,X,X.length,Y.length)。

代码为:

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

//最长公共子序列问题
public class Lcs {

	public static void main(String[] args) {
		String X= "ABCBDAB";
		String Y = "BDCABA";
		System.out.println("序列X为:"+X);
		System.out.println("序列Y为"+Y);
		HashMap<String,int[][]> map = (HashMap<String, int[][]>) LCS_Length(X, Y);
		int[][] c = map.get("result");
		int[][] b = map.get("path");
		
		System.out.println("动态规划结果表c为:");
		for(int i=0;i<c.length;i++) {
			for(int j=0;j<c[i].length;j++) {
				System.out.print(c[i][j]+" ");
			}
			System.out.println();
		}
		
		System.out.println("动态规划结果表b为:");
		for(int i=0;i<b.length;i++) {
			for(int j=0;j<b[i].length;j++) {
				System.out.print(b[i][j]+" ");
			}
			System.out.println();
		}
		
		System.out.println("X与Y最长公共子序列长度为:"+c[X.length()][Y.length()]);
		System.out.println("X与Y最长公共子序列为:");
		Print_Lcs(X, Y);
	}
	
	
	public static HashMap<String,int[][]> LCS_Length(String X,String Y) {
		//将字符串转换为字符数组
		char[] x = X.toCharArray();
		char[] y = Y.toCharArray();
		//m为序列X的长度,n为序列Y的长度
		int m = x.length;
		int n = y.length;
		//c用于保存最长公共子序列的长度,c[i][j]代表序列X中前i个序列和Y中前j个序列的最长公共子序列长度
		int[][] c = new int[m+1][n+1];
		//b用于保存最长公共子序列的结构,b[i][j]代表计算c[i,j]时所选择的子问题最优解
		int[][] b = new int[m+1][n+1];
		//b中值只有3种:0,1,2
		// b[i][j]=0 : 对应书中↖箭头
		// b[i][j]=1 : 对应书中↑箭头
		// b[i][j]=2 : 对应书中←箭头
		for(int i=0;i<=m;i++) {
			c[i][0]=0;
		}
		for(int j=0;j<=n;j++) {
			c[0][j]=0;
		}
		
		for(int i=1;i<=m;i++) {
			for(int j=1;j<=n;j++) {
				//最优子结构公式
				if(x[i-1] == y[j-1]) {
					c[i][j] = c[i-1][j-1]+1;
					b[i][j] = 0;
				}else if(c[i-1][j]>=c[i][j-1]) {
					c[i][j] = c[i-1][j];
					b[i][j] = 1;
				}else {
					c[i][j] = c[i][j-1];
					b[i][j] = 2;
				}
			}
		}
		HashMap<String,int[][]> map = new HashMap<String, int[][]>();
		map.put("result", c);
		map.put("path", b);
		return map;
	}
	
	public static void Print_Lcs(String s1,String s2) {
		HashMap<String, int[][]> map = LCS_Length(s1, s2);
		int[][] c= map.get("result");
		int[][] b= map.get("path");
		Print_Lcs(b, s1.toCharArray(), s1.length(), s2.length());
	}
	private static void Print_Lcs(int[][] b,char[] x,int i,int j) {
		if(i==0||j==0) {
			return;
		}
		if(b[i][j]==0) {
			Print_Lcs(b, x, i-1, j-1);
			System.out.print(x[i-1]);
		}else if(b[i][j]==1) {
			Print_Lcs(b, x, i-1, j);
		}else {
			Print_Lcs(b, x, i, j-1);
		}
	}

}

运行结果:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值