這個世界上根本就不存在“不會做”這回事,當你失去了所有的依靠的時候,自然就什麼都會了。
0. 前言
最長公共子序列的問題常用於解決字符串的相似度,是一個非常實用的算法,作爲碼農,此算法是我們的必備基本功。最長公共子串(Longest Common Substirng)和最長公共子序列(Longest Common Subsequence,LCS)的區別爲:子串是串的一個連續的部分,子序列則是從不改變序列的順序,而從序列中去掉任意的元素而獲得新的序列;也就是說,子串中字符的位置必須是連續的,子序列則可以不必連續。
1.最長公共子序列概述
問題描述: 字符序列的子序列是指從給定字符序列中隨意地(不一定連續)去掉若干個字符(可能一個也不去掉)後所形成的字符序列。令給定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一個嚴格遞增下標序列
2. 動態規劃求解最長公共子序列
2.1 刻畫最長公共子序列的最優子結構的特徵
如果使用暴力搜索求解LCS問題,需要窮舉X的所有子序列,對每一個子序列檢查它是否是Y的子序列,記錄找到的最長的子序列。X的每個子序列對應的X的下標集合是
定理: 考慮最長公共子序列問題如何分解成子問題,設A=“a1,…,am”,B=“b1,…,bn”,並Z=“z1,…,zk”爲它們的最長公共子序列。不難證明有以下性質:
(1) 如果am=bn,則zk=am=bn,且“z1,…,zk-1”是“a1,…,am-1”和“b1,…,bn-1”的一個最長公共子序列;
(2) 如果am!=bn,則若zk!=am,蘊涵“z1,…,zk”是“a1,…,am-1”和“b1,…,bn”的一個最長公共子序列;
(3) 如果am!=bn,則若zk!=bn,蘊涵“z1,…,zk”是“a1,…,am”和“b1,…,bn-1”的一個最長公共子序列。
上面的定理告訴我們:兩個序列的LCS包含兩個序列前綴的LCS,因此LCS問題具有最優子結構的性質。
2.2 構建遞歸解
通過上面的定理我們可以發現在求
【1】如果am=bn,我們應該求
【2】如果am!=bn,若zk!=am,我們應該求
我們可以很容易的發現子問題重疊的性質。在求A和B的LCS的過程中,我們的2種情況,都存在
在上面的問題中我們通過限制條件限定了需要求解哪些子問題。在前面的算法中我們沒有限定排除任何子問題,在這裏我們需要根據限制條件排除相應的子問題。
2.3 計算LCS的長度(求最優解)
根據2.2的遞歸公式,我們可以很痛以的寫出一個指數時間的遞歸算法。但是,由於LCS問題只有
算法僞代碼:
LCS-Length(X[],Y[]){
int m=x.length;
int n=Y.length;
int[][] c = new int[m + 1][n + 1];
char[][] b = new char[m + 1][n + 1];
for(i=1 to m){
c[i,0]=0;//
}
for(j=0 to n){
c[0,j]=0;
}
/**
i是行 j是列
*/
for(i=1 to m){
for(j=1 to n){
if(xi==yj){
c[i,j]=c[i-1,j-1]+1;
b[i,j]='\';
}
elseif(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;
}
2.4 構造LCS(構造最優解)
我們可以使用輔助表b來快速構造x和y的LCS,只需要從b[m,n]開始按照箭頭的方向追蹤即可.遞歸算法如下:
PRINT_LCS(X,b[][],i,j){
if(i==0 || j==0){
return;
}
if(b[i,j]=="\"){
PRINT_LCS(X,b[][],i-1,j-1);
print xi;
}else if(b[i,j]=="|"){
PRINT_LCS(X,b[][],i-1,j)
}else{
PRINT_LCS(X,b[][],i,j-1)
}
}
3. 動態規劃Java實現
算法實現類
package lbz.ch15.dp.ins3;
/**
* @author LbZhang
* @version 創建時間:2016年3月9日 下午9:51:22
* @description 最大公共子序列
*/
public class LCS {
/***
* * 這一部分我們使用輔助表,從左上角開始計算每一個位置上LCS的長度 判斷算法:
*/
public static int[][] lcsLength(Object[] x, Object[] y) {
int m = x.length;
int n = y.length;
int[][] c = new int[m + 1][n + 1];
char[][] b = new char[m + 1][n + 1];
int i, j;
for (i = 1; i <= m; i++) {
c[i][0] = 0;
}
for (j = 0; j <= n; j++) {
c[0][j] = 0;
}
for (i = 1; i <= m; i++) {
for (j = 1; j <= n; j++) {
if (x[i - 1].equals(y[j - 1])) {
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;
}
/**
* 爲了輸出最長公共子序列,改進的輸出
* @param x
* @param y
* @return
*/
public static char[][] lcsPrint(Object[] x, Object[] y) {
int m = x.length;
int n = y.length;
int[][] c = new int[m + 1][n + 1];
char[][] b = new char[m + 1][n + 1];
int i, j;
for (i = 1; i <= m; i++) {
c[i][0] = 0;
}
for (j = 0; j <= n; j++) {
c[0][j] = 0;
}
for (i = 1; i <= m; i++) {
for (j = 1; j <= n; j++) {
if (x[i - 1].equals(y[j - 1])) {
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 b;
}
// ///print the lcs
// //採用遞歸的方式將結果打印出來
public static void printLcs(int[][] c, Object[] x, Object[] y, int i, int j) {
if (i == 0 || j == 0) {
return;
}
if (x[i - 1].equals(y[j - 1])) {
printLcs(c, x, y, i - 1, j - 1);
System.out.print(x[i - 1] + " ");
} else if (c[i - 1][j] >= c[i][j - 1]) {
printLcs(c, x, y, i - 1, j);
} else {
printLcs(c, x, y, i, j - 1);
}
}
public static void printBySignalLcs(char[][] b, Object[] x, int i, int j) {
if (i == 0 || j == 0) {
return;
}
if (b[i][j]=='\\') {
printBySignalLcs(b,x,i-1,j-1);
System.out.print(x[i - 1] + " ");
} else if (b[i][j]=='|') {
printBySignalLcs(b,x,i-1,j);
} else {
printBySignalLcs(b,x,i,j-1);
}
}
}
測試類
package lbz.ch15.dp.ins3;
/**
* @author LbZhang
* @version 創建時間:2016年3月10日 下午3:19:57
* @description 最長公共子序列測試類
*/
public class Test {
public static void main(String[] args) {
Character[] x = { 'A', 'C', 'C', 'G', 'G', 'T', 'C', 'G', 'A', 'G',
'T', 'G', 'C', 'G', 'C', 'G', 'G', 'A', 'A', 'G', 'C', 'C',
'G', 'G', 'C', 'C', 'G', 'A', 'A' },
y = { 'G', 'T', 'C', 'G',
'T', 'T', 'C', 'G', 'G', 'A', 'A', 'T', 'G', 'C', 'C', 'G',
'T', 'T', 'G', 'C', 'T', 'C', 'T', 'G', 'T', 'A', 'A', 'A' };
Integer[] a = { 389, 207, 155, 300, 299, 170, 158, 65 }, b = { 389,
300, 299, 207, 170, 158, 155, 65 };
int[][] c;
char[][] p;
c = LCS.lcsLength(x, y);
//p=LCS.lcsPrint(x, y);
LCS.printLcs(c, x, y, 29, 28);
System.out.println();
c = LCS.lcsLength(a, b);//二維表
LCS.printLcs(c, a, b, 8, 8);
System.out.println();
System.out.println("--------******改進******---------");
p=LCS.lcsPrint(x, y);
LCS.printBySignalLcs(p, x, 29, 28);
System.out.println();
p=LCS.lcsPrint(a, b);
LCS.printBySignalLcs(p, a, 8, 8);
}
}
實驗結果