KMP

Z RNO-Wiki

Spis treści

Problem wyszukiwania wzorca (WW)

Mamy dane słowo T i wzorzec P. Nasze zadanie polega na tym, żeby znaleźć wszystkie pozycje występowania wzorca P w tekście P. Przy czym mówimy, że wzorzec P występuje na i-tej pozycji, wtedy i tylko wtedy, gdy począwszy od i-tej pozycji, wzorzec "pasuje" do tekstu.

Automaty skończone

Wyszukiwanie wzorca w tekście można realizować za pomocą pewnych automatów skończonych. Oto przykład automatu rozpoznającego w tekscie wzorzec "abaaba" :

Abaaba.gif

Funkcja prefiksowa Knutha

Dla słowa P[0..(m-1)] definiujemy funkcję prefiksową Knutha następująco:

 pi[0] = 0
 pi[k] = długość najdłuższego prefiksu słowa P[0..(k-1)] będącego sufiksem słowa P[1..(k)],  k=1,...,n-1
int   m, k, q, pi[1000001];
char  P[1000001]; // P[] - wzorzec o dlugosci m
 
void CPF() // obliczanie funkcji prefiksowej Knutha
{
        // m = strlen(P);
        pi[0] = 0; q = 0;
        for(k=1; k<m; k++)
        {
                //niezmiennik: q=pi[k-1]
                while(q>0 && P[k] != P[q])
                        q=pi[q-1];
                if(P[k] == P[q])
                        pi[k] = ++q;
                else
                        pi[k] = 0;
        }
}

Algorytm Knutha-Morrisa-Pratta

Generalnie idea algorytmu jest bardzo prosta. Niech n oznacza długość tekstu T, a m - długość wzorca P. Problem WW można bardzo prosto rozwiązać sprawdzając dla każdego i, czy kolejne m pozycji się zgadza. Takie sprawdzanie jest jednak bardzo nieefektywne, gdyż dla pesymistycznego przypadku wychodzi nam złożoność rzędu <math>n^2</math>. Taki algorytm nazywany jest algorytmem N ("naiwny"). Można, jednak, taki sam pomysł napisać tak, aby działał liniowo. Zauważmy, że nie trzeba za każdym razem "przesuwać się" w tekście T o jedną pozycję. Czasami możemy zrobić duży skok. To zdecydowanie polepsza złożoność algorytmu. Załóżmy, że mamy policzoną tablicę pi. Dzięki temu wiemy, że gdy na pozycji j występuje niezgodność wzorca z tekstem, możemy j ustawić na pi[j] (dlatego, że na pewno pi[j] pierwszych liter wzorca zgadza się z pi[j] ostatnich liter wzorca) i sprawdzać dalej, pomijając trochę liter.

Poniższy program rozwiązuje zadanie [1]

#include <cstdio>
#include <string>
 
using namespace std;
 
int   n, m, k, q, pi[1000001];
char  P[1000001]; // P[] - wzorzec o dlugosci m
char  T[1000001]; // T[] - tekst o dlugosci n
 
void CPF() // obliczanie funkcji prefiksowej Knutha
{
//  tak samo, jak powyżej
}
 
void kmp()
{
    int q=0;
    //  n = strlen(s);
    for(int i=0; i<n; i++)
    {
        while(q>0 && T[i] != P[q]) q = pi[q-1];
        if (T[i] == P[q]) q++;
        if(q == m)
        {
            // znaleziono wzorzec !!!
            printf("%d\n", i-m + 1); // plus jeden
            q = pi[q-1];
        }
    }
}
 
int main()
{
    int Test;
    scanf("%d\n", &Test); //Test - liczba przypadków testowych.
    while(Test--)
    {
        scanf("%d\n", &m);
        scanf("%s", P); // m = strlen(P);
//      scanf("%d\n", &n);
        scanf("%s", T); n = strlen(T);
        CPF();
        if(m <= n) kmp();
    }
    return 0;
}

Linki

http://pl.wikipedia.org/wiki/Algorytm_KMP

Osobiste