1、什么是最长公共子序列?
最长公共子序列(LCS)是一个在一个序列集合中(通常为两个序列)用来查找所有序列中最长子序列的问题。这与查找最长公共子串的问题不同的地方是:子序列不需要在原序列中占用连续的位置 。最长公共子序列问题是一个经典的计算机科学问题,也是数据比较程序,比如Diff工具,和生物信息学应用的基础。它也被广泛地应用在版本控制,比如Git用来调和文件之间的改变。
最长公共子串与最长公共子序列的区别:
最长公共子串组成字符需要位置上的连续,而最长公共子序列只需要匹配的字符位置保持相对顺序就行。
eg:有两个字符串序列A、B,求它们的最长公共子序列。
String s1 = "ABCBDAB";
String s2 = "BDCABA";
可以看出来,上题中最长公共子序列的长度是4,
BCBA
BCBA
BDAB
那么如何用编程解决这个问题呢?
首先我们肯定会想到穷举法,就是列出数组A中的所有子序列,然后再数组B中找到与之匹配的最长子序列。若用穷举法解决这个问题,则时间复杂度最大为O(2^m*n),也就是说,这种方法不够节省时间。所以说,对于相应的题目选择一种合适的方法解决问题极为重要。我们一般采用动态规划的方法来解决此类问题。
算法陈述+总结:
两个字符产类型的数组进行比较,索引i、j是从0开始进行比较的,当前面的字符比较过后,后面若有相同的字符则就给函数+1。若 两个字符数组不相等,则就表示没有公共子串,则LCS()函数值为0,函数计算的是最长公共序列的长度。当有两个元素进行比较时,我们只比较末尾的元素,如果不相等,并不能直接说明函数值就是0,因为我们可以交叉比较一下字符是否相等。如果相等,则等于前面的函数+1;
0 2 3 4 5 6 7
String[ ]X A B C B D A B
String[ ]Y B D C A B A
具体逻辑用代码表示如下(递归):
public static int lcs(char[] x,char[] y,int i,int j) {
//i,j为字符串长度
if(i==0||j==0) return 0;
else if (x[i]==y[j]) return lcs(x, y, i-1, j-1)+1;return Max(lcs(x, y, i, j-1),lcs(x, y, i-1, j));
}
动态规划分递归,备忘录法和自底向上(空间换时间)。在递归这种方法中,由于做了很多重复的计算,浪费了较多时间,所以用递归的方法去解这道题并不是一个好的解法。
备忘录法:
private static int lcs(char[] x,char[] y,int i,int j,int[][] bak) {
if(bak[i][j] != -1) return bak[i][j];
if(i == 0 || j == 0) {
bak[i][j] = 0;
}else if(x[i] == y[j]) {
bak[i][j] = lcs(x,y,i - 1,j - 1,bak) + 1;
}else {
bak[i][j] = Max(lcs(x,y,i - 1,j,bak),lcs(x,y,i,j-1,bak));
}
return bak[i][j];
}
但是备忘录法依然不是一个好的方法,因为它的空间复杂度很大,不断地在递归,分配和销毁内存。,对栈的操作浪费了大量的时间。实际上它比自底向上的方法还要慢。
自底向上法:
public static int lcs(char[] x , char[] y,int i ,int j,int[][] bak) {
for(int ii = 0 ; ii <= i ; ii++) {
for(int jj = 0 ; jj <= j; jj++) {
if(ii == 0 || jj == 0) bak[ii][jj] = 0;
else if(x[ii] == y[jj]) bak[ii][jj] = bak[ii - 1][jj - 1] + 1;
else bak[ii][jj] = max(bak[ii - 1][jj],bak[ii][jj - 1]);
}
}
return bak[i][j];
}
code:
package edu.xalead;
public class 最长公共子序列之递归法 {
public static int lcs(char[] x,char[] y,int i,int j) {
//i,j为字符串长度
if(i==0||j==0) return 0;
else if (x[i]==y[j]) return lcs(x, y, i-1, j-1)+1;
return Max(lcs(x, y, i, j-1),lcs(x, y, i-1, j));
}
private static int Max(int a, int b) {
if(a>b) return a;
return b;
}
public static void main(String[] args) {
String s1="ABCBDAB";
char [] c1=new char[s1.length()+1];//带0号字符的数组
char [] t1=s1.toCharArray();
c1[0]=(char)0;
for(int i=0;i<t1.length;i++) {
c1[i+1]=t1[i];
}
String s2="BDCABA";
char [] c2=new char[s2.length()+1];//带0号字符的数组
char [] t2=s2.toCharArray();
c2[0]=(char)0;
for(int i=0;i<t2.length;i++) {
c2[i+1]=t2[i];
}
System.out.println(lcs(c1,c2,c1.length-1,c2.length-1));
}
}
package edu.xalead;
public class 备忘录法 {
/**
* 最大公共子串长度
* @param x
* @param y
* @param i x的字符串长度
* @param j y的字符串长度
* @return
*/
private static int lcs(char[] x,char[] y,int i,int j,int[][] bak) {
if(bak[i][j] != -1) return bak[i][j];
if(i == 0 || j == 0) {
bak[i][j] = 0;
}else if(x[i] == y[j]) {
bak[i][j] = lcs(x,y,i - 1,j - 1,bak) + 1;
}else {
bak[i][j] = Max(lcs(x,y,i - 1,j,bak),lcs(x,y,i,j-1,bak));
}
return bak[i][j];
}
private static int Max(int lcs, int lcs2) {
if(lcs > lcs2)
return lcs;
return lcs2;
}
public static void main(String[] args) {
String s1 = "ABCBDAB";
StringBuffer sb1 = new StringBuffer();
sb1.append((char)0);
sb1.append(s1);
String s2 = "BDCABA";
StringBuffer sb2 = new StringBuffer();
sb2.append((char)0);
sb2.append(s2);
int[][] bak = new int[s1.length() + 1][s2.length() + 1];
for(int i = 0 ; i < sb1.toString().length() ; i++) {
for(int j = 0 ; j < sb2.toString().length() ; j++) {
bak[i][j] = -1;
}
}
System.out.println(
lcs(sb1.toString().toCharArray(),sb2.toString().toCharArray(),sb1.toString().length() - 1,sb2.toString().length() - 1,bak));
//字符串变成数组之后因为是索引所以要减1
}
}
package edu.xalead;
public class 自底向上法 {
public static int lcs(char[] x , char[] y,int i ,int j,int[][] bak) {
for(int ii = 0 ; ii <= i ; ii++) {
for(int jj = 0 ; jj <= j; jj++) {
if(ii == 0 || jj == 0) bak[ii][jj] = 0;
else if(x[ii] == y[jj]) bak[ii][jj] = bak[ii - 1][jj - 1] + 1;
else bak[ii][jj] = max(bak[ii - 1][jj],bak[ii][jj - 1]);
}
}
return bak[i][j];
}
private static int max(int a, int b) {
if(a > b) return a;
return b;
}
public static void main(String[] args) {
String s1 = "ABCBDAB";
char[] c1 = new char[s1.length() + 1];//带0号字符的字符数组
char[] t1 = s1.toCharArray();
c1[0] = (char)0;
for(int i = 0 ; i < t1.length ; i++) {
c1[i + 1] = t1[i];
}
String s2 = "BDCABA";
char[] c2 = new char[s2.length() + 1];//带0号字符的字符数组
char[] t2 = s2.toCharArray();
c2[0] = (char)0;
for(int i = 0 ; i < t2.length ; i++) {
c2[i + 1] = t2[i];
}
int[][] bak = new int[c1.length][c2.length];
System.out.println(lcs(c1,c2,c1.length - 1,c2.length - 1,bak));
}
}