Categories
不学无术

Longest Common Subsequence 最长公共子序列

这是个很老的问题了,最近总是碰到,大三时候学的算法好多都忘记了,不如重新学一遍。
比起XXblog上的教程,我最喜欢的还是这个版本:http://www.ics.uci.edu/~eppstein/161/960229.html
里面的代码干净利落,解释也非常的清晰,一步一步怎么来的都很好。
这个问题是个动态规划的问题,最基本的样子是这样的:

假设当前搜索到字符串A,B中的字符a[i],b[j].
LCS[i,j]表示字串A,B分别在位置[i][j]及以后的最大子序列数量
IF A[i] == B[j]:
    LCS[i,j] = 1 + LCS[i+1][j+1] # 当前的算入,然后A,B再往后挪一格
ELSE
    LCS[i,j] = max(LCS[i][j+1], LCS[i+1][j]) # 分别挪动A,B后一格看看哪个好
ENDIF

由于这样递归能弄出的子问题里面,好多是重复的,因此我们可以像计算斐波那契数列那样,采用自底向上的方法,用非递归的方式实现。即从字符的末尾往前推导LCS[i][j],这样当计算LCS[i][j]的时候, LCS[i+1][j]或LCS[i][j+1]已经计算好了。
具体还是看英文教程吧,这里给出C++实现的代码。因为下午例会还得讲PPT,下面的代码用了全局变量传递计算好的序列,有点丑。

#include <iostream>
using namespace std;
/**
 * Longest Common Subsequence
 */
int** storeMat;
int max(int x, int y){
    return x>y? x:y;
}
int getLCSLength(char* A, char* B){
    int sz_A = strlen(A);
    int sz_B = strlen(B);
    storeMat = new int*[sz_A+1];
    for(int i=0; i<=sz_A; i++){
        storeMat[i] = new int[sz_B+1];  // Allocate memory
    }
    for(int i=sz_A; i>=0; i--){
        for(int j=sz_B; j>=0; j--){
            if(A[i] == '\0' || B[j] == '\0'){
                storeMat[i][j] = 0;
            } else if (A[i] == B[j]) {
                storeMat[i][j] = 1 + storeMat[1+i][1+j];
            } else {
                storeMat[i][j] = max(storeMat[1+i][j], storeMat[i][1+j]);
            }
        }
    }
    return storeMat[0][0];
}
string getLCS(char* A, char* B){
    if(NULL == storeMat){
        getLCSLength(A, B);
    }
    int i=0, j=0;
    string result;
    int sz_A = strlen(A); int sz_B = strlen(B);
    while(i < sz_A && j<sz_B){
        if(A[i] == B[j]){
            result += A[i];
            i++;
            j++;
        } else if (storeMat[i+1][j] > storeMat[i][j+1]){
            i++;
        } else {
            j++;
        }
    }
    return result;
}
int main() {
    char a[50], b[50];
    scanf("%s %s", a, b);
    cout << getLCS(a, b);
    return 0;
}