最长子序列问题是应用动态规划解决的典型问题。
该问题符合动态规划算法求解问题的两个要素:最优子结构和子问题重叠。
最优子结构是指两个序列的子序列的最长子序列必然是原序列的某一个最长子序列的一部分。所以就可以根据子序列的解来组成原序列的解。
子问题重叠是指在最长子序列求解的过程中,会重复调用相同的子问题。
LCS问题的基本解题思想的描述:
给定的两个序列,有三种情况:
1、 两个序列的最后一个元素相同,则这个元素是最长子序列中的一个元素,然后将两个序列的该元素删除,将得到的新的序列进行重新判断。
2、 两个序列的最后一个元素不同,那么可以构造出两种情况的新的序列对,就是一个序列不变,将另一个序列的最后元素删除,判断这两种情况所得到的最长子序列哪一个更长,更长的最长子序列是原序列的部分解。
按公式可描述如下:
公式的第一行是基本情况,因为若有一个序列的长度为0的话,LCS肯定为0.
公式的第二行就是上面描述的第一种情况,第三行是第二种情况。
根据公式可以看出,LCS问题可以应用递归的方法解决。但是因为递归对于每一个子问题都会重新求解,所以导致运行的时间大幅度增加,为指数时间。而动态规划通过对中间结果进行存储,使时间缩短至多项式时间。
以下是对算法导论中该LCS-LENGTH(X,Y)伪代码的粗略实现。为了方便,全部采用了静态变量和静态方法。
该算法使用的是动态规划中自底向上的方法,就是先对所有可能得子序列求解LCS,然后再由子序列的解得到原序列的解。
数组d用于构造LCS,对于伪代码中的数组b,伪代码中的三种方向↑,↖,←分别用数字1,2,3代替。
e数组用于存储当前子序列的LCS长度,比如e[i][j]存储的Xi,Yj子序列的LCS长度。
通过对数组d和e构成矩阵的回溯,就可以得到原问题的LCS。
代码中的a[0]和b[0]仅仅只是占位作用。真正求解的序列为{A,B,C,B,D,A,B}和{B,D,C,A,B,A}。
package myjava.algorithms;
import java.util.Arrays;
/**
*
* 最长公共子序列
* 采用动态规划的方法解决
* 采用从头到尾的方法
*/
public class LongestCommonSubsequence2 {
static char[] a={'a','A','B','C','B','D','A','B'};
static char[] b={'b','B','D','C','A','B','A'};
static int[][] d = new int[a.length][b.length];
static int[][] e = new int[a.length][b.length];
public static void lcs2(){
int m = a.length;
int n = b.length;
for(int i=0;i<m;i++)
e[i][0] = 0;
for(int j=0;j<n;j++)
e[0][j] = 0;
for(int i=1;i<m;i++)
for(int j=1;j<n;j++){
if(a[i] == b[j]){
e[i][j] = e[i-1][j-1]+1;
d[i][j] = 2;
}else if(e[i-1][j]>=e[i][j-1]){
e[i][j] = e[i-1][j];
d[i][j] = 1;
}else{
e[i][j] = e[i][j-1];
d[i][j] = 3;
}
}
}
public static void printLcs(int i,int j){
if(i==0 || j==0)
return;
if(d[i][j] == 2){
printLcs(i-1,j-1);
System.out.print(a[i]+" ");
}else if(d[i][j] == 1){
printLcs(i-1,j);
}else
printLcs(i,j-1);
}
public static void main(String[] args){
lcs2();
for(int i=0;i<e.length;i++)
System.out.println(Arrays.toString(e[i]));
System.out.println("---------------------");
for(int i=0;i<d.length;i++)
System.out.println(Arrays.toString(d[i]));
printLcs(a.length-1,b.length-1);
}
}
[0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 1, 1]
[0, 1, 1, 1, 1, 2, 2]
[0, 1, 1, 2, 2, 2, 2]
[0, 1, 1, 2, 2, 3, 3]
[0, 1, 2, 2, 2, 3, 3]
[0, 1, 2, 2, 3, 3, 4]
[0, 1, 2, 2, 3, 4, 4]
这是数组e的运行结果。比如e[6][7]为4,表示原序列的LCS长度为4。
---------------------
[0, 0, 0, 0, 0, 0, 0]
[0, 1, 1, 1, 2, 3, 2]
[0, 2, 3, 3, 1, 2, 3]
[0, 1, 1, 2, 3, 1, 1]
[0, 2, 1, 1, 1, 2, 3]
[0, 1, 2, 1, 1, 1, 1]
[0, 1, 1, 1, 2, 1, 2]
[0, 2, 1, 1, 1, 2, 1]
这是d的运行结果,主要用来构造LCS。
e和d可以组合成下面的矩阵(d中的1,2,3替换为↑,↖,←。)
安装图中的回溯结果可得到解LCS为{B,C,B,A}。