20

我在网上搜索了一个 C++ Longest Common Substring 实现,但没有找到一个像样的。我需要一个返回子字符串本身的 LCS 算法,所以它不仅仅是 LCS。

不过,我想知道如何在多个字符串之间执行此操作。

我的想法是检查 2 个字符串之间最长的一个,然后检查所有其他字符串,但这是一个非常缓慢的过程,需要在内存中管理许多长字符串,这使得我的程序非常慢。

知道如何加快多个字符串的速度吗?谢谢你。

重要编辑 我给定的变量之一决定了最长公共子字符串需要包含的字符串数量,因此可以给我 10 个字符串,并找到它们的 LCS(K=10),或 4 个的 LCS他们,但我不知道哪个 4,我必须找到最好的 4。

4

7 回答 7

13

这是一篇关于有效查找所有常见子字符串的优秀文章,其中包含 C 中的示例。如果您只需要最长的部分,这可能有点过分,但它可能比关于后缀树的一般文章更容易理解。

于 2012-04-20T18:24:45.213 回答
10

答案是通用后缀树。http://en.wikipedia.org/wiki/Generalised_suffix_tree

您可以构建具有多个字符串的通用后缀树。

看看这个http://en.wikipedia.org/wiki/Longest_common_substring_problem

每个字符串可以在 O(n) 时间内构建后缀树,总共 k*O(n)。K 是字符串的总数。

所以解决这个问题非常快。

于 2012-04-20T16:58:10.823 回答
4

这是一个动态规划问题,可以在 O(mn) 时间内解决,其中 m 是一个字符串的长度,n 是另一个字符串的长度。

像使用动态规划解决的任何其他问题一样,我们将问题划分为子问题。假设两个字符串是 x1x2x3....xm 和 y1y2y3...yn

S(i,j) 是 x1x2x3...xi 和 y1y2y3....yj 的最长公共字符串,则

S(i,j) = max { 以 xi/yj 结尾的最长公共子串的长度, if ( x[i] == y[j] ), S(i-1, j-1), S(i, j -1), S(i-1, j) }

这是Java中的工作程序。我相信您可以将其转换为 C++。:

public class LongestCommonSubstring {

    public static void main(String[] args) {
        String str1 = "abcdefgijkl";
        String str2 = "mnopabgijkw";
        System.out.println(getLongestCommonSubstring(str1,str2));
    }

    public static String getLongestCommonSubstring(String str1, String str2) {
        //Note this longest[][] is a standard auxialry memory space used in Dynamic
                //programming approach to save results of subproblems. 
                //These results are then used to calculate the results for bigger problems
        int[][] longest = new int[str2.length() + 1][str1.length() + 1];
        int min_index = 0, max_index = 0;

                //When one string is of zero length, then longest common substring length is 0
        for(int idx = 0; idx < str1.length() + 1; idx++) {
            longest[0][idx] = 0;
        }

        for(int idx = 0; idx < str2.length() + 1; idx++) {
            longest[idx][0] = 0;
        }

        for(int i = 0; i <  str2.length(); i++) {
            for(int j = 0; j < str1.length(); j++) {

                int tmp_min = j, tmp_max = j, tmp_offset = 0;

                if(str2.charAt(i) == str1.charAt(j)) {
                    //Find length of longest common substring ending at i/j
                    while(tmp_offset <= i && tmp_offset <= j &&
                            str2.charAt(i - tmp_offset) == str1.charAt(j - tmp_offset)) {

                        tmp_min--;
                        tmp_offset++;

                    }
                }
                //tmp_min will at this moment contain either < i,j value or the index that does not match
                //So increment it to the index that matches.
                tmp_min++;

                //Length of longest common substring ending at i/j
                int length = tmp_max - tmp_min + 1;
                //Find the longest between S(i-1,j), S(i-1,j-1), S(i, j-1)
                int tmp_max_length = Math.max(longest[i][j], Math.max(longest[i+1][j], longest[i][j+1]));

                if(length > tmp_max_length) {
                    min_index = tmp_min;
                    max_index = tmp_max;
                    longest[i+1][j+1] = length;
                } else {
                    longest[i+1][j+1] = tmp_max_length;
                }


            }
        }

        return str1.substring(min_index, max_index >= str1.length() - 1 ? str1.length() - 1 : max_index + 1);
    }
}
于 2013-10-25T04:13:06.557 回答
3

对此有一个非常优雅的动态编程解决方案。

让成为和LCSuff[i][j]之间的最长公共后缀。我们这里有两种情况:X[1..m]Y[1..n]

  • X[i] == Y[j],这意味着我们可以扩展 和 之间的最长公共X[i-1]后缀Y[j-1]。因此LCSuff[i][j] = LCSuff[i-1][j-1] + 1在这种情况下。

  • X[i] != Y[j], 因为最后一个字符本身是不同的,X[1..i]并且Y[1..j]不能有共同的后缀。因此,LCSuff[i][j] = 0在这种情况下。

我们现在需要检查这些最长公共后缀的最大值。

所以, LCSubstr(X,Y) = max(LCSuff(i,j)), 哪里1<=i<=m1<=j<=n

该算法现在几乎可以自己编写。

string LCSubstr(string x, string y){
    int m = x.length(), n=y.length();

    int LCSuff[m][n];

    for(int j=0; j<=n; j++)
        LCSuff[0][j] = 0;
    for(int i=0; i<=m; i++)
        LCSuff[i][0] = 0;

    for(int i=1; i<=m; i++){
        for(int j=1; j<=n; j++){
            if(x[i-1] == y[j-1])
                LCSuff[i][j] = LCSuff[i-1][j-1] + 1;
            else
                LCSuff[i][j] = 0;
        }
    }

    string longest = "";
    for(int i=1; i<=m; i++){
        for(int j=1; j<=n; j++){
            if(LCSuff[i][j] > longest.length())
                longest = x.substr((i-LCSuff[i][j]+1) -1, LCSuff[i][j]);
        }
    }
    return longest;
}
于 2015-05-31T16:57:51.143 回答
0

这是使用两个数组的动态编程找到最长公共子字符串的 C# 版本(您可以参考:http ://codingworkout.blogspot.com/2014/07/longest-common-substring.html了解更多详细信息)

class LCSubstring
        {
            public int Length = 0;
            public List<Tuple<int, int>> indices = new List<Tuple<int, int>>();
        }
        public string[] LongestCommonSubStrings(string A, string B)
        {
            int[][] DP_LCSuffix_Cache = new int[A.Length+1][];
            for (int i = 0; i <= A.Length; i++)
            {
                DP_LCSuffix_Cache[i] = new int[B.Length + 1];
            }
            LCSubstring lcsSubstring = new LCSubstring();
            for (int i = 1; i <= A.Length; i++)
            {
                for (int j = 1; j <= B.Length; j++)
                {
                    //LCSuffix(Xi, Yj) = 0 if X[i] != X[j]
                    //                 = LCSuffix(Xi-1, Yj-1) + 1 if Xi = Yj
                    if (A[i - 1] == B[j - 1])
                    {
                        int lcSuffix = 1 + DP_LCSuffix_Cache[i - 1][j - 1];
                        DP_LCSuffix_Cache[i][j] = lcSuffix;
                        if (lcSuffix > lcsSubstring.Length)
                        {
                            lcsSubstring.Length = lcSuffix;
                            lcsSubstring.indices.Clear();
                            var t = new Tuple<int, int>(i, j);
                            lcsSubstring.indices.Add(t);
                        }
                        else if(lcSuffix == lcsSubstring.Length)
                        {
                            //may be more than one longest common substring
                            lcsSubstring.indices.Add(new Tuple<int, int>(i, j));
                        }
                    }
                    else
                    {
                        DP_LCSuffix_Cache[i][j] = 0;
                    }
                }
            }
            if(lcsSubstring.Length > 0)
            {
                List<string> substrings = new List<string>();
                foreach(Tuple<int, int> indices in lcsSubstring.indices)
                {
                    string s = string.Empty;
                    int i = indices.Item1 - lcsSubstring.Length;
                    int j = indices.Item2 - lcsSubstring.Length;
                    Assert.IsTrue(DP_LCSuffix_Cache[i][j] == 0);
                    for(int l =0; l<lcsSubstring.Length;l++)
                    {
                        s += A[i];
                        Assert.IsTrue(A[i] == B[j]);
                        i++;
                        j++;
                    }
                    Assert.IsTrue(i == indices.Item1);
                    Assert.IsTrue(j == indices.Item2);
                    Assert.IsTrue(DP_LCSuffix_Cache[i][j] == lcsSubstring.Length);
                    substrings.Add(s);
                }
                return substrings.ToArray();
            }
            return new string[0];
        }

单元测试在哪里:

[TestMethod]
        public void LCSubstringTests()
        {
            string A = "ABABC", B = "BABCA";
            string[] substrings = this.LongestCommonSubStrings(A, B);
            Assert.IsTrue(substrings.Length == 1);
            Assert.IsTrue(substrings[0] == "BABC");
            A = "ABCXYZ"; B = "XYZABC";
            substrings = this.LongestCommonSubStrings(A, B);
            Assert.IsTrue(substrings.Length == 2);
            Assert.IsTrue(substrings.Any(s => s == "ABC"));
            Assert.IsTrue(substrings.Any(s => s == "XYZ"));
            A = "ABC"; B = "UVWXYZ";
            string substring = "";
            for(int i =1;i<=10;i++)
            {
                A += i;
                B += i;
                substring += i;
                substrings = this.LongestCommonSubStrings(A, B);
                Assert.IsTrue(substrings.Length == 1);
                Assert.IsTrue(substrings[0] == substring);
            }
        }
于 2014-07-31T10:59:59.297 回答
0

我为此尝试了几种不同的解决方案,但它们似乎都很慢,所以我想出了下面的方法,并没有真正测试太多,但对我来说似乎工作得更快一些。

#include <iostream>

std::string lcs( std::string a, std::string b )
{
    if( a.empty() || b.empty() ) return {} ;

    std::string current_lcs = "";

    for(int i=0; i< a.length(); i++) {
        size_t fpos = b.find(a[i], 0);
        while(fpos != std::string::npos) {
            std::string tmp_lcs = "";
            tmp_lcs += a[i];
            for (int x = fpos+1; x < b.length(); x++) {
                tmp_lcs+=b[x];
                size_t spos = a.find(tmp_lcs, 0);
                if (spos == std::string::npos) {
                    break;
                } else {
                    if (tmp_lcs.length() > current_lcs.length()) {
                        current_lcs = tmp_lcs;
                    }
                }
            }
            fpos = b.find(a[i], fpos+1);
        }
    }
    return current_lcs;
}

int main(int argc, char** argv)
{
    std::cout << lcs(std::string(argv[1]), std::string(argv[2])) << std::endl;
}
于 2018-04-07T18:36:00.380 回答
-6

从所有考虑的字符串中找到最大的子字符串。从 N 个字符串中,您将有 N 个子字符串。选择这 N 个中最大的一个。

于 2012-04-20T15:12:20.087 回答