web-dev-qa-db-fra.com

Comment trouver la plus longue sous-chaîne commune à l'aide de C++

J'ai cherché en ligne une implémentation de la plus longue sous-chaîne commune C++, mais je n'ai pas réussi à en trouver une bonne. J'ai besoin d'un algorithme LCS qui renvoie la sous-chaîne elle-même, donc ce n'est pas seulement LCS.

Je me demandais cependant comment je pouvais faire cela entre plusieurs chaînes.

Mon idée était de vérifier la plus longue entre 2 chaînes, puis d'aller vérifier toutes les autres, mais il s'agit d'un processus très lent qui nécessite la gestion de nombreuses chaînes longues dans la mémoire, ce qui ralentit considérablement mon programme.

Une idée de la façon dont cela peut être accéléré pour plusieurs chaînes? Je vous remercie.

Important Edit Une des variables que je reçois détermine le nombre de chaînes dans lesquelles la sous-chaîne commune la plus longue doit être insérée. Ainsi, on peut me donner 10 chaînes et trouver le LCS de toutes (K = 10), ou LCS de 4 d’entre eux, mais on ne me dit pas lequel, je dois trouver le meilleur 4.

16
David Gomes

Voici un excellent article sur trouver toutes les sous-chaînes courantes efficacement, avec des exemples en C. Cela peut être exagéré si vous avez besoin du plus long, mais il peut être plus facile à comprendre que les articles généraux sur les arbres à suffixes.

9
Adrian McCarthy

La réponse est GENERALIZED SUFFIX TREE. http://en.wikipedia.org/wiki/Generalised_suffix_tree

Vous pouvez créer une arborescence de suffixes généralisée avec plusieurs chaînes.

Regardez ceci http://en.wikipedia.org/wiki/Longest_common_substring_problem

L'arborescence du suffixe peut être construite en O(n) fois pour chaque chaîne, k * O (n) au total. K est le nombre total de chaînes.

C'est donc très rapide de résoudre ce problème.

10
Lxcypp

Ceci est un problème de programmation dynamique et peut être résolu en O(mn) time, où m est la longueur d'une chaîne et n est de l'autre.

Comme tout autre problème résolu en utilisant la programmation dynamique, nous allons diviser le problème en sous-problème. Disons que si deux chaînes sont x1x2x3 .... xm et y1y2y3 ... yn

S (i, j) est la plus longue chaîne commune pour x1x2x3 ... xi et y1y2y3 .... yj, alors

S (i, j) = max { Longueur de la plus longue sous-chaîne commune se terminant par xi/yj, si (x [i] == y [j]), S (i-1, j-1), S (i, j-1), S (i-1, j) }

Voici le programme de travail en Java. Je suis sûr que vous pouvez le convertir en 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);
    }
}
4
snegi

Il existe une solution de programmation dynamique très élégante à cela. 

Soit LCSuff[i][j] le plus long suffixe commun entre X[1..m] et Y[1..n]. Nous avons deux cas ici:

  • X[i] == Y[j], cela signifie que nous pouvons étendre le suffixe commun le plus long entre X[i-1] et Y[j-1]. Donc LCSuff[i][j] = LCSuff[i-1][j-1] + 1 dans ce cas.

  • X[i] != Y[j], étant donné que les derniers caractères eux-mêmes sont différents, X[1..i] et Y[1..j] ne peuvent pas avoir un suffixe commun. Donc, LCSuff[i][j] = 0 dans ce cas.

Nous devons maintenant vérifier la valeur maximale de ces suffixes les plus longs. 

Donc, LCSubstr(X,Y) = max(LCSuff(i,j)), où 1<=i<=m et 1<=j<=n

L'algorithme s'écrit presque tout seul maintenant.

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;
}
2
Ankesh Anand

J'ai essayé plusieurs solutions différentes, mais elles semblaient toutes très lentes. J'ai donc proposé la solution ci-dessous, je n'ai pas vraiment testé beaucoup, mais cela semble fonctionner un peu plus vite pour moi.

#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;
}
0
Cormac Guerin

Voici une version C # pour trouver la plus longue sous-chaîne commune en utilisant la programmation dynamique de deux tableaux (vous pouvez vous reporter à: http://codingworkout.blogspot.com/2014/07/longest-common-substring.html pour plus détails)

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];
        }

Où les tests unitaires sont:

[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);
            }
        }
0
Dreamer