web-dev-qa-db-fra.com

Générer toutes les sous-chaînes uniques pour une chaîne donnée

Quelle que soit la chaîne s, quelle est la méthode la plus rapide pour générer un ensemble de toutes ses sous-chaînes uniques?

Exemple: pour str = "aba" nous aurions substrs={"a", "b", "ab", "ba", "aba"}.

L'algorithme naïf consisterait à parcourir la chaîne entière en générant des sous-chaînes de longueur 1..n à chaque itération, donnant une limite supérieure O(n^2).

Une meilleure liaison est-elle possible?

(C'est techniquement un devoir, les pointeurs uniquement sont les bienvenus)

56
Yuval Adam

Comme d'autres affiches l'ont indiqué, il existe potentiellement des sous-chaînes O (n ^ 2) pour une chaîne donnée, de sorte que leur impression ne peut être effectuée plus rapidement. Cependant, il existe une représentation efficace de l'ensemble qui peut être construite en temps linéaire: l'arbre de suffixe .

40
Rafał Dowgird

Il n’ya aucun moyen de faire cela plus vite que O (n2) car il y a un total de O (n2) dans une chaîne, donc si vous devez les générer all , leur nombre sera n(n + 1) / 2 dans le pire des cas, d’où le plus haut borne inférieure de Sur2) Ω (n2).

12
IVlad

Le premier est la force brute dont la complexité est O (N ^ 3) et qui pourrait être réduite à O (N ^ 2 log (N)) Deuxième utilisant HashSet qui a la complexité O (N ^ 2) Troisièmement, on utilise LCP en trouvant initialement tout le suffixe d’une chaîne donnée qui a le cas le plus défavorable O (N ^ 2) et le meilleur cas O (N Log (N)).

Première solution: -

import Java.util.Scanner;

public class DistinctSubString {
  public static void main(String[] args) {
    Scanner in = new Scanner(System.in);
    System.out.print("Enter The string");
    String s = in.nextLine();
    long startTime = System.currentTimeMillis();
    int L = s.length();
    int N = L * (L + 1) / 2;
    String[] Comb = new String[N];
    for (int i = 0, p = 0; i < L; ++i) {
      for (int j = 0; j < (L - i); ++j) {
        Comb[p++] = s.substring(j, i + j + 1);
      }
    }
    /*
     * for(int j=0;j<N;++j) { System.out.println(Comb[j]); }
     */
    boolean[] val = new boolean[N];
    for (int i = 0; i < N; ++i)
      val[i] = true;
    int counter = N;
    int p = 0, start = 0;
    for (int i = 0, j; i < L; ++i) {
      p = L - i;
      for (j = start; j < (start + p); ++j) {
        if (val[j]) {
          //System.out.println(Comb[j]);
          for (int k = j + 1; k < start + p; ++k) {
            if (Comb[j].equals(Comb[k])) {
              counter--;
              val[k] = false;
            }
          }
        }

      }

      start = j;
    }
    System.out.println("Substrings are " + N
        + " of which unique substrings are " + counter);
    long endTime = System.currentTimeMillis();
    System.out.println("It took " + (endTime - startTime) + " milliseconds");
  }
}

Deuxième solution: -

import Java.util.*;

public class DistictSubstrings_usingHashTable {

  public static void main(String args[]) {
    // create a hash set

    Scanner in = new Scanner(System.in);
    System.out.print("Enter The string");
    String s = in.nextLine();
    int L = s.length();
    long startTime = System.currentTimeMillis();
    Set<String> hs = new HashSet<String>();
    // add elements to the hash set
    for (int i = 0; i < L; ++i) {
      for (int j = 0; j < (L - i); ++j) {
        hs.add(s.substring(j, i + j + 1));
      }
    }
    System.out.println(hs.size());
    long endTime = System.currentTimeMillis();
    System.out.println("It took " + (endTime - startTime) + " milliseconds");
  }
}

Troisième solution: -

import Java.io.BufferedReader;
import Java.io.IOException;
import Java.io.InputStreamReader;
import Java.util.Arrays;

public class LCPsolnFroDistinctSubString {

  public static void main(String[] args) throws IOException {

    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    System.out.println("Enter Desired String ");
    String string = br.readLine();
    int length = string.length();
    String[] arrayString = new String[length];
    for (int i = 0; i < length; ++i) {
      arrayString[i] = string.substring(length - 1 - i, length);
    }

    Arrays.sort(arrayString);
    for (int i = 0; i < length; ++i)
      System.out.println(arrayString[i]);

    long num_substring = arrayString[0].length();

    for (int i = 0; i < length - 1; ++i) {
      int j = 0;
      for (; j < arrayString[i].length(); ++j) {
        if (!((arrayString[i].substring(0, j + 1)).equals((arrayString)[i + 1]
            .substring(0, j + 1)))) {
          break;
        }
      }
      num_substring += arrayString[i + 1].length() - j;
    }
    System.out.println("unique substrings = " + num_substring);
  }

}

Quatrième solution: -

  public static void printAllCombinations(String soFar, String rest) {
    if(rest.isEmpty()) {
        System.out.println(soFar);
    } else {
        printAllCombinations(soFar + rest.substring(0,1), rest.substring(1));
        printAllCombinations(soFar , rest.substring(1));
    }
}

Test case:-  printAllCombinations("", "abcd");
6
Sumit Kumar Saha

Pour les gros oh ... Le mieux que vous puissiez faire serait O (n ^ 2)

Nul besoin de réinventer la roue, ce n’est pas basé sur des ficelles, mais sur des jeux, vous devrez donc prendre les concepts et les appliquer à votre propre situation.

Algorithmes

Livre blanc vraiment bon de MS

En profondeur PowerPoint

Blog sur les permanentes

3
Nix

eh bien, puisqu'il y a potentiellement n*(n+1)/2 différentes chaînes (+1 pour la sous-chaîne vide), je doute que vous puissiez être mieux que O(n*2) (pire des cas). le plus simple est de les générer et d’utiliser une table de consultation Nice O(1) (telle qu’une table de hachage) pour exclure les doublons dès que vous les trouvez.

2
back2dos
class SubstringsOfAString {
    public static void main(String args[]) {

        String string = "Hello", sub = null;

        System.out.println("Substrings of \"" + string + "\" are :-");

        for (int i = 0; i < string.length(); i++) {
            for (int j = 1; j <= string.length() - i; j++) {
                sub = string.substring(i, j + i);
                System.out.println(sub);
            }
        }
    }
}
1
Nishant Srivastava
class program
{

        List<String> lst = new List<String>();
        String str = "abc";
        public void func()
        {

            subset(0, "");
            lst.Sort();
            lst = lst.Distinct().ToList();

            foreach (String item in lst)
            {
                Console.WriteLine(item);
            }
        }
        void subset(int n, String s)
        {
            for (int i = n; i < str.Length; i++)
            {
                lst.Add(s + str[i].ToString());
                subset(i + 1, s + str[i].ToString());
            }
        }
}
1
Atal Kishore

Voici mon code en Python. Il génère toutes les sous-chaînes possibles d'une chaîne donnée. 

def find_substring(str_in):
    substrs = []
    if len(str_in) <= 1:
        return [str_in]

    s1 = find_substring(str_in[:1])
    s2 = find_substring(str_in[1:])

    substrs.append(s1)
    substrs.append(s2)
    for s11 in s1:
        substrs.append(s11)            
        for s21 in s2:            
            substrs.append("%s%s" %(s11, s21))

    for s21 in s2:
        substrs.append(s21)

    return set(substrs)

Si vous transmettez str_ = "abcdef" à la fonction, les résultats suivants sont générés:

ab, abc, abcd, abcde, abcdef, abcdf, abce, abcef, abcf, abd, abde, abdef, abef, abef, abf, ab, acd, acde, acde, acdef, acdf, ace, ace, ace, acf, ad, adef, adef, adf, ade, aef, af, b, bc, bcd, bcde, bcdef, bcdf, bce, bcef, bcf, bcf, bd, bde, bdef, bdf, être, bef, bf, bf, c, cde, cdef, cdf, ce, cef, cf, d, de, def, df, e, ef, f

0
Ali

Cela ne peut être fait qu'en un instant (n ^ 2), car le nombre total de sous-chaînes uniques d'une chaîne serait n (n + 1)/2.

Exemple:

chaîne s = "abcd"

passe 0: (toutes les chaînes sont de longueur 1)

a, b, c, d = 4 chaînes

passe 1: (toutes les cordes sont de longueur 2)

ab, bc, cd = 3 chaînes

passe 2: (toutes les cordes sont de longueur 3)

abc, bcd = 2 chaînes

passe 3: (toutes les cordes sont de longueur 4)

abcd = 1 chaînes

En utilisant cette analogie, nous pouvons écrire une solution avec une complexité temporelle de o (n ^ 2) et une complexité d'espace constante.

Le code source est comme ci-dessous:

#include<stdio.h>

void print(char arr[], int start, int end)
{
    int i;
    for(i=start;i<=end;i++)
    {
        printf("%c",arr[i]);
    }
    printf("\n");
}


void substrings(char arr[], int n)
{
    int pass,j,start,end;
    int no_of_strings = n-1;

    for(pass=0;pass<n;pass++)
    {
        start = 0;
        end = start+pass;
        for(j=no_of_strings;j>=0;j--)
        {
            print(arr,start, end);
            start++;
            end = start+pass;
        }
        no_of_strings--;
    }

}

int main()
{   
    char str[] = "abcd";
    substrings(str,4);
    return 0;
}
0
AlienOnEarth

Ceci imprime des sous-chaînes uniques . https://ideone.com/QVWOh0

def uniq_substring(test):
    lista=[]
    [lista.append(test[i:i+k+1]) for i in range(len(test)) for k in
    range(len(test)-i) if test[i:i+k+1] not in lista and 
    test[i:i+k+1][::-1] not in lista]
    print lista

uniq_substring('rohit')
uniq_substring('abab')

['r', 'ro', 'roh', 'rohi', 'rohit', 'o', 'oh', 'ohi', 'ohit', 'h',   
'hi', 'hit', 'i', 'it', 't']
['a', 'ab', 'aba', 'abab', 'b', 'bab']
0
Rohit Malgaonkar

Essayez ce code en utilisant un tableau de suffixes et le préfixe commun le plus long. Il peut également vous donner le nombre total de sous-chaînes uniques. Le code peut provoquer un débordement de pile dans Visual Studio mais fonctionner correctement sous Eclipse C++. C'est parce qu'il renvoie des vecteurs pour des fonctions. Je ne l'ai pas testé contre des chaînes extrêmement longues. Je vais le faire et faire rapport.

  // C++ program for building LCP array for given text
#include <bits/stdc++.h>
#include <vector>
#include <string>
using namespace std;

#define MAX 100000
int cum[MAX];


// Structure to store information of a suffix
struct suffix
{
    int index; // To store original index
    int rank[2]; // To store ranks and next rank pair
};

// A comparison function used by sort() to compare two suffixes
// Compares two pairs, returns 1 if first pair is smaller
int cmp(struct suffix a, struct suffix b)
{
    return (a.rank[0] == b.rank[0])? (a.rank[1] < b.rank[1] ?1: 0):
        (a.rank[0] < b.rank[0] ?1: 0);
}

// This is the main function that takes a string 'txt' of size n as an
// argument, builds and return the suffix array for the given string
vector<int> buildSuffixArray(string txt, int n)
{
    // A structure to store suffixes and their indexes
    struct suffix suffixes[n];

    // Store suffixes and their indexes in an array of structures.
    // The structure is needed to sort the suffixes alphabatically
    // and maintain their old indexes while sorting
    for (int i = 0; i < n; i++)
    {
        suffixes[i].index = i;
        suffixes[i].rank[0] = txt[i] - 'a';
        suffixes[i].rank[1] = ((i+1) < n)? (txt[i + 1] - 'a'): -1;
    }

    // Sort the suffixes using the comparison function
    // defined above.
    sort(suffixes, suffixes+n, cmp);

    // At his point, all suffixes are sorted according to first
    // 2 characters. Let us sort suffixes according to first 4
    // characters, then first 8 and so on
    int ind[n]; // This array is needed to get the index in suffixes[]
    // from original index. This mapping is needed to get
    // next suffix.
    for (int k = 4; k < 2*n; k = k*2)
    {
        // Assigning rank and index values to first suffix
        int rank = 0;
        int prev_rank = suffixes[0].rank[0];
        suffixes[0].rank[0] = rank;
        ind[suffixes[0].index] = 0;

        // Assigning rank to suffixes
        for (int i = 1; i < n; i++)
        {
            // If first rank and next ranks are same as that of previous
            // suffix in array, assign the same new rank to this suffix
            if (suffixes[i].rank[0] == prev_rank &&
                    suffixes[i].rank[1] == suffixes[i-1].rank[1])
            {
                prev_rank = suffixes[i].rank[0];
                suffixes[i].rank[0] = rank;
            }
            else // Otherwise increment rank and assign
            {
                prev_rank = suffixes[i].rank[0];
                suffixes[i].rank[0] = ++rank;
            }
            ind[suffixes[i].index] = i;
        }

        // Assign next rank to every suffix
        for (int i = 0; i < n; i++)
        {
            int nextindex = suffixes[i].index + k/2;
            suffixes[i].rank[1] = (nextindex < n)?
                                suffixes[ind[nextindex]].rank[0]: -1;
        }

        // Sort the suffixes according to first k characters
        sort(suffixes, suffixes+n, cmp);
    }

    // Store indexes of all sorted suffixes in the suffix array
    vector<int>suffixArr;
    for (int i = 0; i < n; i++)
        suffixArr.Push_back(suffixes[i].index);

    // Return the suffix array
    return suffixArr;
}

/* To construct and return LCP */
vector<int> kasai(string txt, vector<int> suffixArr)
{
    int n = suffixArr.size();

    // To store LCP array
    vector<int> lcp(n, 0);

    // An auxiliary array to store inverse of suffix array
    // elements. For example if suffixArr[0] is 5, the
    // invSuff[5] would store 0. This is used to get next
    // suffix string from suffix array.
    vector<int> invSuff(n, 0);

    // Fill values in invSuff[]
    for (int i=0; i < n; i++)
        invSuff[suffixArr[i]] = i;

    // Initialize length of previous LCP
    int k = 0;

    // Process all suffixes one by one starting from
    // first suffix in txt[]
    for (int i=0; i<n; i++)
    {
        /* If the current suffix is at n-1, then we don’t
        have next substring to consider. So lcp is not
        defined for this substring, we put zero. */
        if (invSuff[i] == n-1)
        {
            k = 0;
            continue;
        }

        /* j contains index of the next substring to
        be considered to compare with the present
        substring, i.e., next string in suffix array */
        int j = suffixArr[invSuff[i]+1];

        // Directly start matching from k'th index as
        // at-least k-1 characters will match
        while (i+k<n && j+k<n && txt[i+k]==txt[j+k])
            k++;

        lcp[invSuff[i]] = k; // lcp for the present suffix.

        // Deleting the starting character from the string.
        if (k>0)
            k--;
    }

    // return the constructed lcp array
    return lcp;
}

// Utility function to print an array
void printArr(vector<int>arr, int n)
{
    for (int i = 0; i < n; i++)
        cout << arr[i] << " ";
    cout << endl;
}

// Driver program
int main()
{
    int t;
    cin >> t;
    //t = 1;

    while (t > 0)  {

        //string str = "banana";
        string str;

        cin >> str; // >> k;

        vector<int>suffixArr = buildSuffixArray(str, str.length());
        int n = suffixArr.size();

        cout << "Suffix Array : \n";
        printArr(suffixArr, n);

        vector<int>lcp = kasai(str, suffixArr);

        cout << "\nLCP Array : \n";
        printArr(lcp, n);

        // cum will hold number of substrings if that'a what you want (total = cum[n-1]
        cum[0] = n - suffixArr[0];

    //  vector <pair<int,int>> substrs[n];

        int count = 1;


        for (int i = 1; i <= n-suffixArr[0]; i++)  {
            //substrs[0].Push_back({suffixArr[0],i});
            string sub_str = str.substr(suffixArr[0],i);
            cout << count << "  "  <<  sub_str << endl;
            count++;
        }

        for(int i = 1;i < n;i++)  {

            cum[i] = cum[i-1] + (n - suffixArr[i] - lcp[i - 1]);

            int end = n - suffixArr[i];
            int begin = lcp[i-1] + 1;
            int begin_suffix = suffixArr[i];

            for (int j = begin, k = 1; j <= end; j++, k++)  {

                //substrs[i].Push_back({begin_suffix, lcp[i-1] + k});
        //      cout << "i Push "  << i  << "   " << begin_suffix << " " << k << endl;
                string sub_str = str.substr(begin_suffix, lcp[i-1] +k);
                cout << count << "  "  <<  sub_str << endl;
                count++;

            }

        }

        /*int count = 1;

        cout << endl;

        for(int i = 0; i < n; i++){

            for (auto it = substrs[i].begin(); it != substrs[i].end(); ++it )    {

                string sub_str = str.substr(it->first, it->second);
                cout << count << "  "  <<  sub_str << endl;
                count++;
            }

        }*/


        t--;

    }

    return 0;
}

Et voici un algorithme plus simple:

#include <iostream>
#include <string.h>
#include <vector>
#include <string>
#include <algorithm>
#include <time.h>

using namespace std;


char txt[100000], *p[100000];
int m, n;

int cmp(const void *p, const void *q) {
    int rc = memcmp(*(char **)p, *(char **)q, m);
    return rc;
}
int main() {
    std::cin >> txt;
    int start_s = clock();


    n = strlen(txt);
    int k; int i;
    int count = 1;
    for (m = 1; m <= n; m++) {
        for (k = 0; k+m <= n; k++)
            p[k] = txt+k;
        qsort(p, k, sizeof(p[0]), &cmp);
        for (i = 0; i < k; i++) {
            if (i != 0 && cmp(&p[i-1], &p[i]) == 0){
                continue;
            }
            char cur_txt[100000];
            memcpy(cur_txt, p[i],m);
            cur_txt[m] = '\0';
            std::cout << count << "  "  << cur_txt << std::endl;
            count++;
        }
    }

    cout << --count  << endl;

    int stop_s = clock();
    float run_time = (stop_s - start_s) / double(CLOCKS_PER_SEC);
    cout << endl << "distinct substrings  \t\tExecution time = " << run_time << " seconds" << endl;
    return 0;
}

Cependant, les deux algorithmes indiquaient un processus trop lent pour des chaînes extrêmement longues. J'ai testé les algorithmes sur une chaîne de plus de 47 000 longueurs, qui ont duré 20 minutes, la première prenant 1 200 secondes et la seconde 1360 secondes. Donc, pour probablement des chaînes de longueur allant jusqu'à 1000, vous pourriez obtenir une solution fonctionnelle. Les deux solutions ont toutefois calculé le même nombre total de sous-chaînes uniques. J'ai testé les deux algorithmes contre des longueurs de chaîne de 2000 et 10 000. Les temps étaient pour le premier algorithme: 0,33 s et 12 s; pour le deuxième algorithme, il était de 0,535 s et 20 s. Il semble donc qu'en général le premier algorithme est plus rapide.

0
te7

De nombreuses réponses, dont 2 pour les boucles et un appel .sstrstring (), réclament une complexité temporelle O (N ^ 2). Cependant, il est important de noter que le cas le plus défavorable pour un appel .substring () dans Java (post-mise à jour 6 dans Java 7) est O (N). Donc, en ajoutant un appel .substring () dans votre code, l'ordre de N a augmenté de un.

Par conséquent, 2 pour les boucles et un appel .substring () au sein de ces boucles est égal à une complexité temporelle O (N ^ 3).

0
Ayomide Oyekanmi

Algorithme naïf prend O (n ^ 3) temps au lieu de O (n ^ 2) temps . Il y a O (n ^ 2) nombre de sous-chaînes . Et si vous mettez O (n ^ 2) nombre de sous-chaînes, par exemple, set, then set compare O(lgn) des comparaisons pour chaque chaîne afin de vérifier si elle existe ou non dans l'ensemble . De plus, il faut O(n) temps pour la comparaison de chaînes . Par conséquent, cela prend O (n ^ 3 lgn) temps si vous utilisez set. et vous pouvez le réduire en temps O (n ^ 3) si vous utilisez hashtable au lieu de set.

Le fait est que ce ne sont pas des comparaisons de nombres, mais des comparaisons de chaînes.

Donc, l’un des meilleurs algorithmes, disons que si vous utilisez un tableau de suffixes et le plus long algorithme de préfixe commun (LCP), il réduit le temps O (n ^ 2) pour ce problème . Construction d’un tableau de suffixes avec O(n) algorithme temporel . Temps pour LCP = O(n) temps . Étant donné que pour chaque paire de chaînes du tableau de suffixes, effectuez LCP de sorte que le temps total soit O (n ^ 2). longueur de soustractions distinctes.

En outre, si vous voulez imprimer toutes les sous-chaînes distinctes, cela prend O (n ^ 2) fois.

0
user2761895