Temat: Algorytmy wyszukiwania wzorca w tekście 1. Sformułowanie

Transkrypt

Temat: Algorytmy wyszukiwania wzorca w tekście 1. Sformułowanie
Temat: Algorytmy wyszukiwania wzorca w tekście
1. Sformułowanie problemu
Dany jest tekst T oraz wzorzec P, będące ciągami znaków o
długości równej odpowiednio n i m (n≥m≥1), nad pewnym
ustalonym i skończonym alfabetem ∑. Należy znaleźć
wszystkie wystąpienia wzorca P w tekście T.
Będziemy zakładać, że zarówno tekst jak i wzorzec są
ciągami znaków zapamiętanymi w strukturze danych o
dostępie indeksowym (np. w tablicach: char T[n], P[m])
Przykład
T = aabbcadbbbacadbdcbbacadba
P = cad
Wystąpienia wzorca P w tekście T podamy wskazując pozycje
s w teście, dla których:
T[s] = P[0] i T[s+1] = P[1] i ... i T[s+m-1]=P[m-1].
W przykładzie s = 4, s = 11 , s = 20.
Gdy
T = alalalala
P = ala
to wystąpienia wzorca P w tekście T są następujące:
s = 0, s = 2 , s = 4, s = 6.
1
2. Notacja i terminologia
∑* - zbiór wszystkich tekstów (słów) utworzonych z symboli
alfabetu ∑
ε - słowo o długości zero nazywane słowem pustym
x- długość słowa x
xy - konkatenacja (złożenie) słów x i y
w⊂x
w jest prefiksem (przedrostkiem ) słowa x,
gdy x = wy dla pewnego słowa y∈∑*
w⊃x
w jest sufiksem (przyrostkiem ) słowa x,
gdy x = yw dla pewnego słowa y∈∑*
X[s ... k]
fragment tekstu X od znaku o indeksie s do
znaku o indeksie k
Xk
k - znakowy prefiks tekstu X.
Jeżeli X = X[0... t], to Xk=X[0.. k-1]
Gdy T[s ... s+m-1] = P[0 ... m-1] dla 0 ≤ s ≤ n − m , to
mówimy, że wzorzec P występuje z przesunięciem s w tekście
T albo równoważnie, że wzorzec P występuje w tekście T od
pozycji s.
Jeżeli P występuje w tekście T z przesunięciem s, to s
nazywamy poprawnym przesunięciem, w przeciwnym razie s
nazywamy niepoprawnym przesunięciem.
2
Przykład
ab ⊂ abcca
słowo ab jest prefiksem słowa abcca
cca ⊃ abcca
słowa cca jest sufiksem słowa abcca
Jeżeli X = abcabcd, to X[2 ... 5] = cabc oraz X3= abc
T = ababcdabc
P = bcd
Przesunięcie s=0 jest niepoprawnym przesunięciem wzorca P
względem tekstu T, natomiast przesunięcie s = 3 jest
poprawnym przesunięciem wzorca P względem tekstu T.
3. Algorytm naiwny
Idea tego algorytmu polega na przeglądaniu tekstu T
sekwencyjnie, kolejno przesuwając się po jednym znaku
tekstu od lewej do prawej i sprawdzaniu za każdym razem,
czy kolejne znaki wzorca P pokrywają się z kolejnymi
znakami tekstu.
Algorytm naiwny
s = 0;
while (s <= n-m)
{
j =0;
while (j < m)
if (P[j]= =T[s+j]) j = j+1; else break;
if (j == m) “P występuje w T od pozycji s” ;
s++;
}
3
Przykład
n=18, m=4
T: aaabaababbababaaba
P: baba
P: baba
P:
baba
P:
baba
.........................................
P:
baba
P występuje w T od pozycji 9
.........................................
P:
baba
P występuje w T od pozycji 11
.........................................
P:
baba
Koszt czasowy algorytmu naiwnego
Operacją elementarną są porównania między znakami wzorca
i tekstu. Jeśli wzorzec nie występuje w tekście i przekonujemy
się o tym po pierwszym porównaniu (przy każdym położeniu
wzorca względem tekstu), to wykonujemy minimalną liczbę
porównań równą n-m+1. Jeżeli wzorzec występuje na każdej
pozycji tekstu, na przykład, gdy:
T: an, P: am,
to wykonujemy maksymalną liczbę porównań równą :
(n - m+1)⋅ m.
Zatem:
Tmax (n, m ) = Θ((n − m + 1)m )
4
4. Algorytm Knutha - Morrisa - Pratta (KMP)
Algorytm KMP opiera się na wykorzystaniu pomocniczej
funkcji Π (zwanej funkcją prefiksową), którą wyznacza się dla
wzorca, niezależnie od tekstu. Algorytm wyznaczający
funkcję prefiksową ma złożoność liniową względem długości
wzorca. Funkcja ta pozwala na zwiększenie (ale tylko w
określonych sytuacjach) przesunięcia wzorca względem
tekstu. W algorytmie naiwnym przesunięcie zwiększa się
zawsze tylko o jeden.
Π : {1, ... , m} → {0,1, ... , m}
Π[q] = "maksymalna długość prefiksu wzorca P, który jest
równocześnie sufiksem Pq"
Π[q]= max {k : k < q, Pk ⊃ Pq }
Wiedząc, że q znaków wzorca pasuje przy przesunięciu o s,
następne potencjalnie poprawne przesunięcie s' można
wyliczyć jako:
s' = s+ q - Π[q]
W najlepszym przypadku s' = s+q (gdy Π[q]=0) i eliminujemy
wówczas przesunięcia: s+1, s+2, ..., s+q-1.
5
Przykład
P: ababaca
Π[1] = 0
P: ababaca
ababaca
Π[2] = 0
P: ababaca
ababaca
ababaca
Π[3] = 1
P: ababaca
ababaca
ababaca
ababaca
Π[4] = 2
P: ababaca
ababaca
ababaca
ababaca
ababaca
Π[5] = 3
6
P: ababaca
ababaca
ababaca
ababaca
ababaca
ababaca
Π[6] = 0
P: ababaca
ababaca
ababaca
ababaca
ababaca
ababaca
ababaca
Π[7] = 1
T : abacbababaabcbab ...
P : ababaca
q
1
2
3
0
0
1
Π[q]
...
przesunięcie
4
2
5
3
6
0
7
1
a b a c b a b a b a a b c b a b ...
a b a b a c a
a b a b a c a
s
s'
s' = s+ q - Π[q]
s' = 5+ 5 - 3 = 7
7
Po przesunięciu wzorca o 5 pozycji w prawo, 5 kolejnych
znaków wzorca pokrywa się ze znakami tekstu. Znając te 5
znaków wzorca wiemy, że przesunięcie o tylko jedno miejsce
w prawo nie jest poprawne, gdyż a wypadnie pod literą b.
Natomiast przesunięcie o dwa miejsca w prawo ma szansę
powodzenia. Informacje tego typu mogą być wydedukowane
na podstawie samego wzorca.
Algorytm obliczania funkcji prefiksowej Π
Π[1] =0;
k=0;
for (q = 2; q<=m; q++)
{
while (k>0 && P[k]!=P[q-1]) k = Π [k];
if (P[k] = =P[q-1]) k = k+1;
Π [q] = k;
}
Algorytm KMP
q= 0;
for ( i =0; i < n; i++)
{
while (q>0 && P[q]!=T[i]) q = Π [q];
if (P[q] = = T[i]) q= q+1;
if (q = = m)
{
"wzorzec znaleziono na pozycji „;
q = Π [q];
}
}
8
Koszt czasowy algorytmu KMP
Koszt obliczenia wszystkich wartości funkcji Π wynosi O(m),
gdyż :
(1) warunek P[k] =P[q-1] (po pętli) może być sprawdzany co
najwyżej m razy, a z drugiej strony,
(2) warunek P[k]!=P[q] (w pętli) też może być
sprawdzany co najwyżej m razy.
Stąd, razem mamy co najwyżej 2m porównań. Analogicznie
można uzasadnić, że koszt zasadniczego algorytmu KMP jest
równy O(n). Można pokazać, że całkowity koszt algorytmu
KMP wynosi O(n+m). Jest to właściwie koszt średni, ale
przypadek pesymistyczny dla algorytmu KMP zdarza się
bardzo rzadko. Tym przypadkiem jest sytuacja, w której
wzorzec P=am oraz tekst T=an. Wówczas przesunięcie w
każdym kroku pętli (*) zwiększa się tylko o jeden.
9
5. Algorytm Rabina-Karpa (RK)
Metodę zastosowaną w tym algorytmie można nazwać metodą
''odcisku''. Zamiast porównywać znak po znaku wzorzec z
tekstem, używa się specjalnej funkcji (właśnie "odcisku”),
która wiąże z każdym ciągiem znaków o długości m jedną
liczbę. Liczba ta ma identyfikować blok o m znakach. Zamiast
porównywać ciągi znaków, porównuje się reprezentujące je
liczby (odciski).
Ogólnie można przyjąć, że każdy znak jest cyfrą w systemie
pozycyjnym o podstawie d, gdzie d=card(Σ). Zatem każdy
ciąg m kolejnych znaków można rozumieć jako liczbę m
cyfrową w systemie pozycyjnym o podstawie d.
Niech p oznacza liczbę odpowiadającą wzorcowi P, a ts
oznacza liczbę odpowiadającą ciągowi znaków T[s ... s+m-1].
Prawdziwa jest następująca własność:
p = ts wtedy i tylko wtedy, gdy T[s ... s+m-1] =P[0 ... m-1],
czyli wzorzec P występuje w tekście T od pozycji s.
Liczba p jest wartością wielomianu:
W p (x ) = P[m − 1] + P[m − 2]x + P[m − 3]x 2 + ... + P[0]x m −1
dla x=d.
Liczba ts jest wartością wielomianu:
Wt s ( x ) = T [s + m − 1] + T [s + m − 2]x + T [s + m − 3]x 2 + ... + T [s ]x m −1
dla x=d.
10
Wartości p i t0 możemy policzyć kosztem liniowym stosując
schemat Hornera :
W ( x ) = a 0 + x(a1 + x(a 2 + ... + x(a n −1 + xa n ))...)
Łatwo zauważyć, że wartości ts dla s=1, 2, ..., n-m można
obliczyć kosztem stałym, ze wzoru:
ts =d ⋅ (ts-1 -dm-1⋅T[s])+T[s+m]
Algorytm Rabina-Karpa
s = 0;
p = Wp(d);
// d = card(Σ)
t0 = Wt0(d);
if ( p = = t0) „wzorzec P występuje w tekście T z
przesunięciem s=0”;
while (s<=n-m)
{
ts =d ⋅ (ts-1 -dm-1⋅T[s])+T[s+m];
s++;
if ( p = = ts) „wzorzec P występuje w tekście T z
przesunięciem s”;
}
Przykład
Σ={0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, d=10
T: 34587656743256989
P:56983
Wówczas:
t0 = 34587,
t1 =10⋅ (34 587-104⋅3)+6=45876
t2 =58765,
t3 = 87656 itd.
11
Koszt czasowy algorytmu RK
Operacją elementarną nie są tym razem porównania między
znakami wzorca i tekstu, a operacje arytmetyczne realizowane
przy obliczaniu odcisku wzorca i tekstu.
Odcisk wzorca można policzyć kosztem O(m). Odcisk t0
również można obliczyć takim samym kosztem. Odciski
t1,...,tn-m można policzyć stałym kosztem, gdy mamy wcześniej
policzoną jednorazowo wartość dm-1 (można tę wartość
policzyć kosztem O(logm), stosując algorytm oparty na
metodzie "dziel i zwyciężaj". Zatem koszt całego algorytmu
wyniesie O(n+m).
W związku z tym, że alfabet wzorca i tekstu może być duży
(np. d = card(Σ)=256) pojawiają się dwa problemy związane z
implementacją algorytmu RK.
Problem 1
Wartości p i t0, t1,..., tn-m mogą być bardzo duże, a wtedy nie
można zakładać, że każda operacja arytmetyczna ma ten sam
koszt co operacja arytmetyczna dla liczb mieszczących się w
słowie maszynowym.
Rozwiązanie Problemu 1
Problem ten można rozwiązać stosując zamiast zwykłej
arytmetyki, arytmetykę modulo, tzn. obliczone wartość
odcisku dzieli się modulo pewna wybrana liczba pierwsza q.
Zwykle wybiera się q takie, że d⋅q powinno być nie większe
niż jedno słowo maszynowe. Przy tym założeniu w trakcie
obliczania wartości odcisków będą używane standardowe
operatory arytmetyczne (tj. dla „małych liczb”).
12
Algorytm Rabina-Karpa w arytmetyce modularnej
s = 0;
p = Wp(d) % q;
t0 = Wt0(d) % q
if ( p = = t0) „wzorzec P występuje w tekście T z
przesunięciem s=0”;
while (s<=n-m)
{
ts =(d⋅ (ts-1 -d m-1⋅T[s])+T[s+m]) % q;
s++;
if ( p = = ts) „wzorzec P występuje w tekście T z
przesunięciem s”;
}
Problem 2
Pojawia się jednak problem niejednoznaczności, ponieważ
prawdziwość warunku p = = ts nie oznacza, że na pewno
P[0...m-1]=T[s ... s+m-1].
Równość reszt z dzielenia dwóch liczb nie oznacza bowiem,
że same liczby są na pewno sobie równe.
Aby algorytm nie zwracał niepoprawnych wyników należy
zatem, w każdym przypadku, gdy p = = ts dodatkowo
sprawdzić równość odpowiednich ciągów badając je znak po
znaku. Powoduje to jednak, że koszt algorytmu RK, w
najgorszym przypadku wynosi Θ((n-m+1) ⋅m). Przykładem
przypadku pesymistycznego dla algorytmu RK jest przypadek:
T = an i P = am. Wówczas każdy blok tekstu o długości m
daje ten sam odcisk równy odciskowi wzorca i konieczne jest
sprawdzenie znak po znaku.
13
W bardzo wielu zastosowaniach, zajście zdarzenia
p== ts
przy jednoczesnej niezgodności wzorca z tekstem, jest bardzo
rzadkie.
Liczbę
q
można
tak
wybrać,
aby
prawdopodobieństwo takiego zdarzenia było równe 1/n. Przy
dużym n prawdopodobieństwo pomyłki jest zatem bardzo
małe i można nie sprawdzać zgodności znak po znaku, co
poowduje, że czas oczekiwany (złożoność średnia) wykonania
algorytmu RK wynosi O(n+m).
• Wersja bez sprawdzania symbol po symbolu i z
arytmetyką modulo q gwarantuje liniowy czas
wykonania, ale z małym prawdopodobieństwem wynik
może się okazać niepoprawny. Takie algorytmy, które z
dopuszczalnym prawdopodobieństwem zwracają wynik
niepoprawny nazywamy algorytmami Monte Carlo
(zawsze szybko i prawdopodobnie poprawnie).
• Wersja algorytmu ze sprawdzaniem w przypadku, gdy
odcisk wzorca jest zgodny modulo q z odciskiem bloku
tekstu gwarantuje poprawny wynik, ale z małym
prawdopodobieństwem algorytm ten będzie działał dłużej
niż liniowo. Algorytmy tego typu nazywane są
algorytmami Las Vegas (zawsze poprawnie i
prawdopodobnie szybko).
Problem 3
Wartości Wp(d) oraz Wt0(d) to nadal duże liczby, dla
dużego d, pomimo tego, że wartości t0 oraz p są mniejsze
równe q. Jak zatem obliczyć t0 oraz p, aby nie używać
arytmetyki dużych liczb?
14
Rozwiązanie problemu 3
Wartości t0 oraz p obliczamy stosując algorytm potęgowania
modularnego:
h=1;
for (i=0; i<m; i++) // obliczamy h=dm-1 % q
h=(d*h) % q;
p = 0; ts = 0;
// obliczamy wartości t0 oraz p
for (i = 0; i<m ; i++)
{
p = (d*p+P[i]) % q;
ts = (d*ts + T[i]) % q;
}
Algorytm Rabina-Karpa w wersji Las Vegas
h=1;
for (i=0; i<m; i++)
h=(d*h) % q;
p = 0; ts = 0;
for (i = 0; i<m ; i++)
{
p = (d*p+P[i]) % q;
ts = (d*ts + T[i]) % q;
}
for (s = 0; s<=n-m; s++)
{
bool=0;
if (p = = ts)
{
i=0; bool=1;
15
while (i < m && bool)
{
if (P[i] !=T[s+i]) bool = 0;
i++;
}
}
if (bool)
“wzorzec P występuje w tekście T z przesunięciem s”;
if (s < n-m)
{
ts=(ts+d*q-T[s]*h) % q;
ts=(ts*d+T[s+m])% q;
}
}
16

Podobne dokumenty