3+字符串的最长公共子序列

2024-05-16 07:47:36 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图找到3个或更多字符串的最长公共子序列。Wikipedia文章对how to do this for 2 strings有很好的描述,但我有点不确定如何将其扩展到3个或更多字符串。

有很多库可以找到两个字符串的LCS,所以如果可能的话,我想使用其中的一个。如果我有3个字符串A,B和C,那么找到A和B的LCS作为X,然后找到X和C的LCS是有效的,还是这样做是错误的?

我用Python实现了它,如下所示:

import difflib

def lcs(str1, str2):
    sm = difflib.SequenceMatcher()
    sm.set_seqs(str1, str2)
    matching_blocks = [str1[m.a:m.a+m.size] for m in sm.get_matching_blocks()]
    return "".join(matching_blocks)

print reduce(lcs, ['abacbdab', 'bdcaba', 'cbacaa'])

这输出“ba”,但它应该是“baa”。


Tags: to字符串for文章序列wikipediahowsm
3条回答

我只是为了做一个家庭作业,所以这里是我的python动态编程解决方案,非常有效。它是O(n m l),其中n,m和l是三个序列的长度。

该解决方案通过创建一个3D数组,然后枚举所有三个序列来计算最长子序列的路径。然后,您可以在数组中进行回溯,以从其路径重建实际的子序列。

因此,将数组初始化为所有零,然后枚举这三个序列。在枚举的每个步骤中,您可以在最长子序列的长度(如果存在匹配项)中添加一个子序列,或者从枚举的上一个步骤中仅结转最长子序列。

枚举完成后,现在可以跟踪数组,以便根据所采取的步骤重建子序列。i、 e.当你从数组中的最后一个条目向后移动时,每次遇到匹配项时,你都会在任何序列中查找它(使用数组中的坐标)并将其添加到子序列中。

def lcs3(a, b, c):
    m = len(a)
    l = len(b)
    n = len(c)
    subs = [[[0 for k in range(n+1)] for j in range(l+1)] for i in range(m+1)]

    for i, x in enumerate(a):
        for j, y in enumerate(b):
            for k, z in enumerate(c):
                if x == y and y == z:
                    subs[i+1][j+1][k+1] = subs[i][j][k] + 1
                else:
                    subs[i+1][j+1][k+1] = max(subs[i+1][j+1][k], 
                                              subs[i][j+1][k+1], 
                                              subs[i+1][j][k+1])
    # return subs[-1][-1][-1] #if you only need the length of the lcs
    lcs = ""
    while m > 0 and l > 0 and n > 0:
        step = subs[m][l][n]
        if step == subs[m-1][l][n]:
            m -= 1
        elif step == subs[m][l-1][n]:
            l -= 1
        elif step == subs[m][l][n-1]:
            n -= 1
        else:
            lcs += str(a[m-1])
            m -= 1
            l -= 1
            n -= 1

    return lcs[::-1]

只是推广了递推关系。

对于三个字符串:

dp[i, j, k] = 1 + dp[i - 1, j - 1, k - 1] if A[i] = B[j] = C[k]
              max(dp[i - 1, j, k], dp[i, j - 1, k], dp[i, j, k - 1]) otherwise

应该很容易从这里推广到更多的字符串。

要查找2个字符串A和B的最长公共子序列(LCS),可以按对角方式遍历二维数组,如所发布的链接中所示。数组中的每个元素都对应于查找子字符串A'和B(A按行号剪切,B按列号剪切)的LCS的问题。这个问题可以通过计算数组中所有元素的值来解决。必须确保在计算数组元素的值时,计算该给定值所需的所有子问题都已解决。这就是为什么要对角遍历二维数组。

此解决方案可以缩放到查找N个字符串之间最长的公共子序列,但这需要一种迭代N维数组的通用方法,以便只有在元素需要解决的所有子问题都已解决时才能到达任何元素。

不必按特殊顺序迭代N维数组,还可以递归地解决该问题。对于递归,保存中间解很重要,因为许多分支将需要相同的中间解。我用C语言写了一个小例子:

string lcs(string[] strings)
{
    if (strings.Length == 0)
        return "";
    if (strings.Length == 1)
        return strings[0];
    int max = -1;
    int cacheSize = 1;
    for (int i = 0; i < strings.Length; i++)
    {
        cacheSize *= strings[i].Length;
        if (strings[i].Length > max)
            max = strings[i].Length;
    }
    string[] cache = new string[cacheSize];
    int[] indexes = new int[strings.Length];
    for (int i = 0; i < indexes.Length; i++)
        indexes[i] = strings[i].Length - 1;
    return lcsBack(strings, indexes, cache);
}
string lcsBack(string[] strings, int[] indexes, string[] cache)
{
    for (int i = 0; i < indexes.Length; i++ )
        if (indexes[i] == -1)
            return "";
    bool match = true;
    for (int i = 1; i < indexes.Length; i++)
    {
        if (strings[0][indexes[0]] != strings[i][indexes[i]])
        {
            match = false;
            break;
        }
    }
    if (match)
    {
        int[] newIndexes = new int[indexes.Length];
        for (int i = 0; i < indexes.Length; i++)
            newIndexes[i] = indexes[i] - 1;
        string result = lcsBack(strings, newIndexes, cache) + strings[0][indexes[0]];
        cache[calcCachePos(indexes, strings)] = result;
        return result;
    }
    else
    {
        string[] subStrings = new string[strings.Length];
        for (int i = 0; i < strings.Length; i++)
        {
            if (indexes[i] <= 0)
                subStrings[i] = "";
            else
            {
                int[] newIndexes = new int[indexes.Length];
                for (int j = 0; j < indexes.Length; j++)
                    newIndexes[j] = indexes[j];
                newIndexes[i]--;
                int cachePos = calcCachePos(newIndexes, strings);
                if (cache[cachePos] == null)
                    subStrings[i] = lcsBack(strings, newIndexes, cache);
                else
                    subStrings[i] = cache[cachePos];
            }
        }
        string longestString = "";
        int longestLength = 0;
        for (int i = 0; i < subStrings.Length; i++)
        {
            if (subStrings[i].Length > longestLength)
            {
                longestString = subStrings[i];
                longestLength = longestString.Length;
            }
        }
        cache[calcCachePos(indexes, strings)] = longestString;
        return longestString;
    }
}
int calcCachePos(int[] indexes, string[] strings)
{
    int factor = 1;
    int pos = 0;
    for (int i = 0; i < indexes.Length; i++)
    {
        pos += indexes[i] * factor;
        factor *= strings[i].Length;
    }
    return pos;
}

我的代码示例可以进一步优化。正在缓存的许多字符串是重复的,有些是只添加了一个额外字符的重复字符串。当输入字符串变大时,这会占用比所需更多的空间。

输入:“666222054263314443712”,“5432127413542377777”,“666466456544057425”

返回的LCS是“54442”

相关问题 更多 >