2015-06-17 8 views
7

Jakie są dobre sposoby na znalezienie wszystkie wspólne subsekwencje długości k dwóch ciągów?Wspólne podsekwencje o zadanej długości

przykład:

s1 = AAGACC

s2 = AGATAACCAGGAGCTGC

wszystkie typowe subsekwencje długości 5: AAGACAAACCAGACCAAGCC

+0

OP, jesteś zaznajomiony z * programowania dynamicznego *? Powinieneś go znaleźć w każdej dobrej książce algorytmów. –

+0

Czy wspólne podciągi są równe łańcuchom, ale różne, ponieważ sekwencje pozycji źródłowych są uważane za równe? Np. W twoim przykładzie istnieją 3 * 15 = 45 sposobów tworzenia wspólnego podciągu "AA", więc czy 'AA' powinno być wyprowadzane 45 razy, czy tylko raz? –

+0

@j_random_hacker tylko raz. –

Odpowiedz

1

Utwórz trie wszystkich podciągi o zadanej długości k od s1, a następnie przejść przez s2 i sprawdzić dla każdej sekwencji o długości k, jeśli jest w trie.

+0

Jako @NikklasB.zauważyłem, problem polega na tym, że istnieją pod-sekwencje O (Wybierz (n, k)) (nie będące podciągami), które są w 'O (min {n^k, n^(nk)})' (Tak, dużo) – amit

+0

Prawda. Czy istnieje sposób na osiągnięcie tego przy użyciu macierzy pamięciowej LCS (najdłuższy wspólny podciąg)? –

+0

@amit - Zgadzam się, ale w każdym przypadku musisz sprawdzić wszystkie opcje, chyba że czegoś brakuje. Zamiast tego możesz zrobić skomplikowaną zagnieżdżoną pętlę - pomyślę o tym. –

4

Jednym względnie prostym sposobem byłoby zrekonstruowanie sekwencji z macierzy LCS. Oto algorytm O (n^2 * k + x * n), w którym to przypadku, gdzie x jest wielkością wyjściową (to jest liczbą wspólnych podsekwencji o długości k). To jest w C++, ale powinno być raczej łatwo przetłumaczyć na C:

const int N = 100; 
int lcs[N][N]; 
set<tuple<string,int,int,int>> vis; 

string s1 = "AAGACC"; 
string s2 = "AGATAACCAGGAGCTGC"; 

void reconstruct(const string& res, int i, int j, int k) { 
    tuple<string,int,int,int> st(res, i, j, k); 
    if (vis.count(st)) 
     return; 
    vis.insert(st); 
    if (lcs[i][j] < k) return; 
    if (i == 0 && j == 0 && k == 0) { 
     cout << res << endl; 
     return; 
    } 
    if (i > 0) 
     reconstruct(res, i-1, j, k); 
    if (j > 0) 
     reconstruct(res, i, j-1, k); 
    if (i>0 && j>0 && s1[i-1] == s2[j-1]) 
     reconstruct(string(1,s1[i-1]) + res, i-1, j-1, k-1); 
} 

int main() { 
    lcs[0][0] = 0; 
    for (int i = 0; i <= s1.size(); ++i) 
     lcs[i][0] = 0; 
    for (int j = 0; j <= s1.size(); ++j) 
     lcs[0][j] = 0; 
    for (int i = 0; i <= s1.size(); ++i) { 
     for (int j = 0; j <= s2.size(); ++j) { 
      if (i > 0) 
       lcs[i][j] = max(lcs[i][j], lcs[i-1][j]); 
      if (j > 0) 
       lcs[i][j] = max(lcs[i][j], lcs[i][j-1]); 
      if (i > 0 && j > 0 && s1[i-1] == s2[j-1]) 
       lcs[i][j] = max(lcs[i][j], lcs[i-1][j-1] + 1); 
     } 
    } 
    reconstruct("", s1.size(), s2.size(), 5); 
} 

Nie powinno być także O (n * (k + x)) sposobem rozwiązania tego w oparciu o nieco innym podejściu DP: Niech f (i, k) będzie minimalnym indeksem j takim, że lcs (i, j)> = k. Mamy nawrotowi

f(i, 0) = 0 for all i 
f(i, k) = min{f(i-1, k), 
       minimum j > f(i-1, k-1) such that s2[j] = s1[i]} 

może również zrekonstruować sekwencje długości K z matrycy F.

+0

Próbowałem go z s1 = 'ACACTTAGTGGGAGTCTCA' i s2 =' TACGC', i 'k' =' 3', jednym z wyjść jest 'GAC'; co jest oczywiście ** nie ** wspólnym podobieństwem pomiędzy s1 i s2. –

+0

@CamiloCelisGuzman Interesujące. Cóż, w takim przypadku tymczasowo wycofam się do mojej początkowej odpowiedzi, która powinna być poprawna –

+0

Czy generuje ona każdy ciąg równy zwykłemu podciągowi tylko raz? PO (z opóźnieniem) wskazał to jako wymóg. Jeśli nie, to myślę, że zaadaptowanie tego będzie wymagało przestrzeni O (| A |^k) w najgorszym przypadku, aby zapisać status tego, czy dany ciąg został już wyświetlony (z | A | rozmiarem alfabetu). –

0

Oto wersja algorytmu, który używa rekurencji, stos o rozmiarze k i zawiera dwie optymalizacje, aby pominąć znaki, które już zostały wyświetlone i pominąć podkwerendy, które nie istnieją. Łańcuchy nie są unikalne (mogą występować duplikaty), więc uruchom wyjście przez uniq.

#include <stdio.h> 
#include <string.h> 

/* s1 is the full first string, s2 is the suffix of the second string 
* (starting after the subsequence at depth r), 
* pos is the positions of chars in s1, r is the recursion depth, 
* and k is the length of subsequences that we are trying to match 
*/ 
void recur(char *s1, char *s2, size_t pos[], size_t r, size_t k) 
{ 
    char seen[256] = {0};  /* have we seen a character in s1 before? */ 
    size_t p0 = (r == 0) ? 0 : pos[r-1] + 1; /* start at 0 or pos[r-1]+1 */ 
    size_t p1 = strlen(s1) - k + r;    /* stop at end of string - k + r */ 
    size_t p; 

    if (r == k)   /* we made it, print the matching string */ 
    { 
     for (p=0; p<k; p++) 
      putchar(s1[pos[p]]); 
     putchar('\n'); 
     return; 
    } 

    for (p=p0; p<=p1; p++)  /* at this pos[], loop through chars to end of string */ 
    { 
     char c = s1[p];   /* get the next char in s1 */ 
     if (seen[c]) 
      continue;   /* don't go any further if we've seen this char already */ 
     seen[c] = 1; 

     pos[r] = p; 
     char *s = strchr(s2, c);  /* is this char in s2 */ 
     if (s != NULL) 
      recur(s1, s+1, pos, r+1, k);  /* recursively proceed with next char */ 
    } 
} 


int main() 
{ 
    char *s1 = "AAGACC"; 
    char *s2 = "AGATAACCAGGAGCTGC"; 
    size_t k = 5; 
    size_t pos[k]; 

    if (strlen(s1) < k || strlen(s2) < k)  /* make sure we have at least k chars in each string */ 
     return 1;  /* exit with error */ 

    recur(s1, s2, pos, 0, k); 
    return 0; 
} 

Wyjście jest:

AAGAC 
AAGCC 
AAACC 
AGACC