最长公共子序列

问题:给定两个字符串,求出它们的最长公共子序列的长度。如:a = "xzyzzyx", b = "zxyyzxz",则其最长公共子序列的长度为4,"zyyx".

注意:最长公共子序列不一定要连续(连续的叫最长公共子串),最长公共子序列也可能不唯一。如上面也可以是:"xyyx".

 

穷举一般都能解决问题,但效率过低(指数时间),这个在密码学上叫计算上不可行。我们可以运用动态规划的思想,可以在多项式时间内解决。

 

算法基本思路:

1.求最长公共子序列的长度。

给定字符串 a 和 b,可知它们的长度 alen 和 blen。设有二维数组 L[0...alen][0...blen],L[i][j] 其含义为:i 表示 a 目前的长度即串a0a1..ai-1(若i为0,则表示a为空串),j表示 b 目前长度即串b0b1...bj-1(若j为0,则表示b为空串)。L[i][j] 表示串a0a1..ai-1与串b0b1...bj-1的最长公共子序列的长度。

注意,a 和 b 的下标由0开始,即 i-1(j-1)表示当前字符。

如果 a[i-1] == b[j-1] 即当前字符相等,则 L[i][j] = L[i-1][j-1] + 1 即最长公共子序列长度加1;

如果 a[i-1] != b[j-1] 即当前字符不等,则 L[i][j] = max(L[i-1][j], L[i][j-1]) 即忽略ai-1取最长公共子序列长度与忽略bj-1最长公共子序列长度作比较取较大值;

算法自底向上逐步求解,可以每一步都是当前最优解,结果也是最优解。

2.求最长公共子序列的一个解。

求得最长公共子序列长度后,可以很轻松得到其一个解,只需要运用已有的 L.

从后向前回溯,设 i = alen, j = blen,一直到 i 或 j 为 0(即其中一个串遍历完毕)

如果 a[i-1] == b[j-1],则保存a[i-1],然后后退(即 i --, j --);

否则,查看 L[i][j] 是否和 L[i-1][j] 相等,若相等,则意味着可以抛弃掉 a[i-1]即 i --;若不等,那么 L[i][j] 就会和 L[i][j-1] 相等,则抛弃掉 b[j-1]即 j --。

 

算法实现:

思路如上。在开始前应该将 L[0][*] 和 L[*][0] 初始化为0,因为空串子序列就是空串。这些作为后续构造最优解的基础。

动态规划在于找到一个最优的递推式,而递推式的存在必须有基础。

注意,我用java来实现这个算法,算法同时返回长度和一个子序列,不熟悉java的人可以忽略那部分代码,不会影响算法的理解。

/*
input: String a, String b

table: int l[0..an][0..bn]        //l[i][j] = lcs(a[0..i-1], b[0..j-1])

if a[i-1] == b[j-1]:    l[i][j] = l[i-1][j-1] + 1
if a[i-1] != b[j-1]:    l[i][j] = max(l[i-1][j], l[i][j-1])
because if a[i-1] == b[j-1], then add a[i-1], len+1;
        if a[i-1] != b[j-1], then there are two cases:    (1)add a[i-1], then l[i][j-1]
                                                        (2)add b[j-1], then l[i-1][j]

get the string:
reverse search l,     if a[i-1] == b[j-1], then l[i-1][j-1];
                    else  check l[i-1][j] or l[i][j-1] equals l[i][j]
*/

import java.util.ArrayList;

public class LCS{

    public static ArrayList lcs(String a, String b){
        int aLen = a.length(), bLen = b.length();
        int[][] l = new int[aLen+1][bLen+1];
        
        //if j = 0, then l[i][0] = 0
        for(int i = 0; i < aLen; i ++){
            l[i][0] = 0;
        }
        //if i = 0, then l[0][j] = 0
        for(int j = 0; j < bLen; j ++){
            l[0][j] = 0;
        }
        //i is the current aLen
        //j is the current bLen
        for(int i = 1; i <= aLen; i ++){
            for(int j = 1; j <= bLen; j ++){
                if(a.charAt(i-1) == b.charAt(j-1)){
                    l[i][j] = l[i-1][j-1] + 1;
                }
                else{
                    l[i][j] = Math.max(l[i][j-1], l[i-1][j]);
                }
            }
        }
        String result = getLcsString(a, b, l);
        ArrayList al = new ArrayList();
        al.add(l[aLen][bLen]);
        al.add(result);
//        for(int i = 0; i < l.length; i ++){
//            for(int j = 0; j < l[i].length; j ++){
//                System.out.print(l[i][j] + "	");
//            }
//            System.out.println();
//        }
        return al;
    }

    private static String getLcsString(String a, String b, int[][] l){
        //build the result string
        String result = "";
        int i = a.length(), j = b.length();
        while(i > 0 && j > 0){
            if(a.charAt(i-1) == b.charAt(j-1)){
                result = a.charAt(i-1) + result;
                i --;
                j --;
            }
            else{
                if(l[i][j] == l[i][j-1]){
                    j --;
                }
                else{
                    i --;
                }
            }
        }
        return result;
    }

    public static void main(String[] args) {
        //Unit Testing
        String a = "xzyzzyx";
        String b = "zxyyzxz";
        ArrayList al = lcs(a, b);
        int len = (int) al.get(0);
        String s = (String) al.get(1);
        System.out.println(len + " : " + s);
    }

}
Java

 

原文地址:https://www.cnblogs.com/7hat/p/3422697.html