Algorytmy i struktury danych/Algorytmy tekstowe I: Różnice pomiędzy wersjami
m |
(poprawki od KDX) |
||
Linia 3: | Linia 3: | ||
__TOC__ | __TOC__ | ||
− | Tekst jest ciągiem symboli | + | Tekst jest ciągiem symboli. Przyjmujemy, że jest on zadany tablicą <math>x[1,\ldots,n]</math>, elementami której są symbole ze skończonego zbioru ''A'' (zwanego alfabetem). Liczba <math>n=|x|</math> jest długością (rozmiarem) tekstu. W większości naszych algorytmów jedyne operacje dopuszczalne na symbolach wejściowych, to porównania dwóch symboli. |
− | Algorytmy na tekstach wyróżniają się tym, że wykorzystują specyficzne kombinatoryczne własności tekstów. '''Okresem''' tekstu <math>x</math> jest każda liczba naturalna niezerowa <math>p</math> taka, że <math>x[i]=x[i+p]</math>, dla każdego <math>i</math>, dla którego obie strony są zdefiniowane. Przez per(x) oznaczmy minimalny okres x. | + | Algorytmy na tekstach wyróżniają się tym, że wykorzystują specyficzne, kombinatoryczne własności tekstów. '''Okresem''' tekstu <math>x</math> jest każda liczba naturalna niezerowa <math>p</math> taka, że <math>x[i]=x[i+p]</math>, dla każdego <math>i</math>, dla którego obie strony są zdefiniowane. Przez ''per(x)'' oznaczmy minimalny okres x. |
− | Pojęciem dualnym do okresu jest '''prefikso-sufiks''' tekstu | + | Pojęciem dualnym do okresu jest '''prefikso-sufiks''' tekstu. Jest to najdłuższy właściwy (nie będący całym tekstem) prefiks tekstu ''x'' będący jednocześnie jego sufiksem. Oczywiste jest, że <math>|x|-per(x)</math> jest długością prefikso-sufiksu ''x''. Jeśli <math>per(x)=|x|</math> to prefikso-sufiksem ''x'' jest słowo puste o długości zerowej. |
Oznaczmy przez <math>P[k]</math> rozmiar prefikso-sufiksu <math>x[1..k]</math>; zatem <math>per(x)=n-P[n]</math>, gdzie <math>n=|x|</math>. | Oznaczmy przez <math>P[k]</math> rozmiar prefikso-sufiksu <math>x[1..k]</math>; zatem <math>per(x)=n-P[n]</math>, gdzie <math>n=|x|</math>. | ||
Linia 22: | Linia 22: | ||
== Obliczanie tablicy Prefikso-Sufiksów== | == Obliczanie tablicy Prefikso-Sufiksów== | ||
− | Przedstawimy jeden z możliwych algorytmów liniowych obliczania tablicy P | + | Przedstawimy jeden z możliwych algorytmów liniowych obliczania tablicy ''P''. Jest to iteracyjna wersja algorytmu rekurencyjnego, który moglibyśmy otrzymać korzystając z faktu: |
− | <center><math>x[j]=x[t+1] | + | <center>Jeśli <math>x[j]=x[t+1]</math> oraz <math>t=P[j-1]<math>, to <math>P[j]= t+1</math></center> |
− | W algorytmie obliczania <math>P[j]</math> korzystamy z wartości <math>P[k]</math> dla <math>k<j</math>. | + | W algorytmie obliczania <math>P[j]</math> korzystamy z wartości <math>P[k]</math>, dla <math>k<j</math>. |
{{algorytm|Prefikso-Sufiksy|algorytm_prefikso_sufiksy| | {{algorytm|Prefikso-Sufiksy|algorytm_prefikso_sufiksy| | ||
− | <math>P[0]:=-1</math>; <math>t:= | + | 1 <math>P[0]:=-1</math>; <math>t:=0</math>; |
− | '''for''' <math>j:=1</math> '''to''' <math>m</math> '''do''' | + | 2 '''for''' <math>j:=1</math> '''to''' <math>m</math> '''do''' |
− | + | 3 '''begin''' | |
− | + | 4 '''while''' <math>t\geq 0</math> '''and''' <math>x[t+1]\neq x[j]</math> '''do''' <math>t:=P[t];</math> | |
+ | 5 <math>t:=t+1</math>; <math>P[j]:=t</math>; | ||
+ | 6 '''end;''' | ||
}} | }} | ||
− | Złożoność liniowa wynika stąd, że w każdej iteracji zwiększamy wartość t co najwyżej o jeden, a wykonanie każdej operacji <math>t:=P[t]</math> zmniejsza wartość t co najmniej o jeden. Proste zastosowanie zasady magazynu (lub potencjału) implikuje, że operacji <math>t:=P[t]</math> wykonujemy co najwyżej n. Dowód poprawności pozostawiamy jako ćwiczenie. | + | Złożoność liniowa wynika stąd, że w każdej iteracji zwiększamy wartość ''t'' co najwyżej o jeden, a wykonanie każdej operacji <math>t:=P[t]</math> zmniejsza wartość ''t'' co najmniej o jeden. Proste zastosowanie zasady magazynu (lub potencjału) implikuje, że operacji <math>t:=P[t]</math> wykonujemy co najwyżej ''n''. Dowód poprawności pozostawiamy jako ćwiczenie. |
== Minimalne słowo pokrywające == | == Minimalne słowo pokrywające == | ||
− | Pokażemy pewne proste zastosowanie | + | Pokażemy pewne, proste zastosowanie tablic prefikso-sufiksów. |
− | Słowem pokrywającym tekst x jest każdy taki tekst y, którego wystąpienia w x | + | Słowem pokrywającym tekst ''x'' jest każdy taki tekst ''y'', którego wystąpienia w ''x'' |
− | pokrywają cały tekst x. Na przykład słowo y=aba pokrywa tekst x=ababaaba, natomiast nie pokrywa tekstu abaaababa. | + | pokrywają cały tekst ''x''. Na przykład słowo ''y=aba'' pokrywa tekst ''x=ababaaba'', natomiast nie pokrywa tekstu ''abaaababa''. |
− | Zajmiemy się problemem: obliczyć w czasie liniowym długość najkrótszego słowa pokrywającego dany tekst x. | + | Zajmiemy się problemem: obliczyć w czasie liniowym długość najkrótszego słowa pokrywającego dany tekst ''x''. |
Niech <math>S[i]</math> | Niech <math>S[i]</math> | ||
będzie rozmiarem minimalnego słowa pokrywającego dla prefiksu <math>x[1..i]</math>. Następujący algorytm oblicza długość minimalnego słowa | będzie rozmiarem minimalnego słowa pokrywającego dla prefiksu <math>x[1..i]</math>. Następujący algorytm oblicza długość minimalnego słowa | ||
− | pokrywającego tekstu x. Obliczamy wartości <math>S[i]</math> | + | pokrywającego tekstu ''x''. Obliczamy wartości <math>S[i]</math> długości najkrótszego słowa pokrywającego <math>x[1 |
\ldots i]</math> dla każdego <math>1 \le i \le n</math>. | \ldots i]</math> dla każdego <math>1 \le i \le n</math>. | ||
W <math>i</math>-tej | W <math>i</math>-tej | ||
− | iteracji | + | iteracji algorytmu pamiętany jest ''znany zakres'' każdego minimalnego słowa pokrywającego. |
+ | |||
+ | <font color="red">TODO - problem z rysunkiem, napis nie pasuje do podpisu, co to jest q?</font> | ||
<center> [[Grafika:Minpokslowo.jpg]]<br> | <center> [[Grafika:Minpokslowo.jpg]]<br> | ||
Linia 57: | Linia 61: | ||
{{algorytm | Rozmiar-Minimalnego-Pokrycia|algorym_rozm_min_pokr| | {{algorytm | Rozmiar-Minimalnego-Pokrycia|algorym_rozm_min_pokr| | ||
− | '''for ''' <math>i:=2</math> '''to''' <math>n</math> '''do''' | + | 1 '''for ''' <math>i:=2</math> '''to''' <math>n</math> '''do''' '''begin''' |
− | + | 2 <math>Zakres[i]=i; S[i]=i;</math> | |
− | + | 3 '''end;''' | |
− | '''for ''' <math>i:=2</math> '''to''' <math>n</math> '''do''' | + | 4 '''for ''' <math>i:=2</math> '''to''' <math>n</math> '''do''' |
− | + | 5 '''if''' <math>P[i]>0</math> '''and''' <math>i-Zakres[S[P[i]] \le S[P[i]]</math> '''then''' '''begin''' | |
− | + | 6 <math>S[i] := S[P[i]]</math>; <math>Zakres[S[P[i]] := i</math>;<br> | |
− | + | 7 '''end;''' | |
− | '''return''' <math>S[n]</math>; | + | 8 '''return''' <math>S[n]</math>; |
}} | }} | ||
Linia 70: | Linia 74: | ||
==Tablica Silnych Prefikso-Sufiksów== | ==Tablica Silnych Prefikso-Sufiksów== | ||
+ | |||
+ | <font color="red">TODO - KDX - ???</font> | ||
+ | |||
Wprowadzimy silną tablicę prefikso-sufiksów dla wzorca <math>x[1..m]</math>: | Wprowadzimy silną tablicę prefikso-sufiksów dla wzorca <math>x[1..m]</math>: | ||
− | + | jeśli <math>j<|x|</math>, to <math>P'[j]=k</math>, gdzie <math>k</math> jest maksymalnym rozmiarem słowa będącego właściwym prefiksem i sufiksem <math>x[1..j]</math> i spełniającego dodatkowy warunek <math>x[k+1]\ne x[j+1]</math> dla <math>j<n</math>. | |
<br> Jeśli takiego k nie ma, to przyjmujemy <math>P'[j]=-1</math>. Przyjmujemy ponadto, że <math>P'[m]=P[m]</math>. | <br> Jeśli takiego k nie ma, to przyjmujemy <math>P'[j]=-1</math>. Przyjmujemy ponadto, że <math>P'[m]=P[m]</math>. | ||
Linia 91: | Linia 98: | ||
{{algorytm|Silne-Prefikso-Sufiksy|algorytm_silne_prefikso_sufiksy| | {{algorytm|Silne-Prefikso-Sufiksy|algorytm_silne_prefikso_sufiksy| | ||
− | <math>P'[0]:=-1</math>; <math>t:=-</math>1; | + | 1 <math>P'[0]:=-1</math>; <math>t:=-</math>1; |
− | '''for''' <math>j:=</math> 1 '''to''' <math>m</math> '''do''' // <math>t=P[j-1]</math | + | 2 '''for''' <math>j:=</math> 1 '''to''' <math>m</math> '''do''' '''begin''' // <math>t=P[j-1]</math> |
− | + | 3 '''while''' <math>t\geq 0</math> '''and''' <math>x[t+1]\neq x[j]</math>'''do''' | |
− | + | 4 <math>t:=P'[t]</math>; | |
− | + | 5 <math>t:=t+1</math>; | |
− | + | 6 '''if''' <math>j=m</math> '''or''' <math>x[t+1]\neq x[j+1]</math> | |
− | + | 7 '''then''' <math>P'[j]:=t</math> '''else''' <math>P'[j]:=P'[t]</math>; | |
+ | 8 '''end;''' | ||
}} | }} | ||
Linia 105: | Linia 113: | ||
Przedstawimy klasyczny algorytm Knutha-Morrisa-Pratta (w skrócie KMP) dla problemu ''string-matching''u: | Przedstawimy klasyczny algorytm Knutha-Morrisa-Pratta (w skrócie KMP) dla problemu ''string-matching''u: | ||
− | obliczyć w w tekście <math>y</math> wszystkie (lub pierwsze) wystąpienia danego tekstu <math>x</math>, zwanego wzorcem | + | obliczyć w w tekście <math>y</math> wszystkie (lub pierwsze) wystąpienia danego tekstu <math>x</math>, zwanego wzorcem. |
Oznaczmy <math>m=|x|, n=|y|</math>, gdzie <math>m\le n</math>. | Oznaczmy <math>m=|x|, n=|y|</math>, gdzie <math>m\le n</math>. | ||
Linia 115: | Linia 123: | ||
{{algorytm|Algorytm KMP|algorytm_kmp| | {{algorytm|Algorytm KMP|algorytm_kmp| | ||
− | <math>i:=0</math>; <math>j:=0</math>; | + | 1 <math>i:=0</math>; <math>j:=0</math>; |
− | '''while''' <math>i\leq n-m</math> '''do''' | + | 2 '''while''' <math>i\leq n-m</math> '''do''' '''begin''' |
− | + | 3 '''while''' <math>j<m</math> '''and''' <math>x[j+1]=y[i+j+1]</math> '''do''' <math>j=j+1</math>; | |
− | + | 4 '''if''' <math>j=m</math> '''then return'''(true); | |
− | + | 5 <math>i:=i+j-P'[j]</math>; <math>j:=\max(0,P'[j])</math> | |
+ | 6 '''end;''' | ||
}} | }} | ||
Linia 149: | Linia 158: | ||
{{algorytm|On-Line-KMP|algorytm_online_KMP| | {{algorytm|On-Line-KMP|algorytm_online_KMP| | ||
− | '''repeat forever''' | + | 1 '''repeat forever''' |
− | + | 2 read(<math>symbol</math>); | |
− | + | 3 '''while''' <math>j>-1</math> and <math>x[j+1]\ne symbol</math> '''do''' <math>j:=P'[j]</math>; | |
− | + | 4 <math>j:=j+1</math>; | |
− | + | 5 '''if''' <math>j=m</math> '''then''' | |
− | + | 6 write(1); <math>j := P'[m]</math>; | |
− | + | 7 '''else''' write(0); | |
}} | }} | ||
Linia 179: | Linia 188: | ||
{{algorytm|Real-Time-KMP|algorytm_rtkmp| | {{algorytm|Real-Time-KMP|algorytm_rtkmp| | ||
− | inicjalizacja: <math>j:=0</math>; Kolejka <math>:= \emptyset</math>; | + | 1 inicjalizacja: <math>j:=0</math>; Kolejka <math>:= \emptyset</math>; |
− | '''repeat forever''' | + | 2 '''repeat forever''' |
− | + | 3 read(symbol); | |
− | + | 4 insert(symbol,Kolejka); | |
− | + | 5 '''write'''(OUTPUT(Kolejka, j)); | |
}} | }} | ||
Linia 193: | Linia 202: | ||
{{algorytm|Real-Time-KMP: funkcja OUTPUT(Kolejka, j)|algorytm_rtkmp_fun| | {{algorytm|Real-Time-KMP: funkcja OUTPUT(Kolejka, j)|algorytm_rtkmp_fun| | ||
− | output <math>:=</math> 0; | + | 1 output <math>:=</math> 0; |
− | '''repeat 2 times''' | + | 2 '''repeat 2 times''' |
− | + | 3 '''if ''' Kolejka niepusta '''then''' | |
− | + | 4 '''if''' <math>j=-1</math> '''then''' | |
− | + | 5 j <math>:=</math> 0; delete(Kolejka); | |
− | + | 6 '''else if''' <math>x[j+1]\ne first(Kolejka)</math> '''then''' <math>j:=P'[j]</math>; | |
− | + | 7 '''else''' | |
− | + | 8 <math>j:=j+1</math>; delete(Kolejka); | |
− | + | 9 '''if''' <math>j=m</math> | |
− | + | 10 output <math>:= </math>1; j <math>:= P'[m]</math>; | |
− | '''return'''(output); | + | 11 '''return'''(output); |
}} | }} | ||
− | ==Wersja algorytmu KMP z 3n/ | + | ==Wersja algorytmu KMP z <math>\frac{3n}{2}</math> porównaniami== |
Algorytm KMP wykonuje co najmniej 2n-m porównań symboli. Załóżmy, że są to operacje dominujące i spróbujmy zmniejszyć stały współczynnik 2 do <math>\frac{3}{2}</math>. Na początku załóżmy, że <math>x=ab</math>. Następujący algorytm znajduje wszystkie wystąpienia wzorca ab w tekście y. | Algorytm KMP wykonuje co najmniej 2n-m porównań symboli. Załóżmy, że są to operacje dominujące i spróbujmy zmniejszyć stały współczynnik 2 do <math>\frac{3}{2}</math>. Na początku załóżmy, że <math>x=ab</math>. Następujący algorytm znajduje wszystkie wystąpienia wzorca ab w tekście y. | ||
{{algorytm|Szukanie-ab|algorytm_szukanie_ab| | {{algorytm|Szukanie-ab|algorytm_szukanie_ab| | ||
− | wzorcem jest <math>ab</math | + | 1 wzorcem jest <math>ab</math> |
− | <math>i:=0</math>; | + | 2 <math>i:=0</math>; |
− | '''while''' <math>i\leq n-m</math> '''do''' | + | 3 '''while''' <math>i\leq n-m</math> '''do''' '''begin''' |
− | + | 4 '''while''' <math>y[i+2]\ne b</math> '''do'''<math>i=i+1</math>; | |
− | + | 5 '''if''' <math>y[i+1]= a</math> '''then''' | |
− | + | 6 wypisz-wystąpienie;i<math>:=</math>i+2 | |
+ | 7 '''end;''' | ||
}} | }} | ||
Linia 283: | Linia 293: | ||
{{algorytm|Równoważność-Cykliczna|algorytm_równoważność_cykliczna| | {{algorytm|Równoważność-Cykliczna|algorytm_równoważność_cykliczna| | ||
− | <math>x:=uu</math>; <math>y:=ww</math>; | + | 1 <math>x:=uu</math>; <math>y:=ww</math>; |
− | <math>i:=0</math>; <math>j:=0</math>; | + | 2 <math>i:=0</math>; <math>j:=0</math>; |
− | '''while''' <math>(i<n)</math> '''and''' <math>(j<n)</math> '''do''' | + | 3 '''while''' <math>(i<n)</math> '''and''' <math>(j<n)</math> '''do''' '''begin''' |
− | + | 4 <math>k:=1</math>; | |
− | + | 5 '''while''' <math>x[i+k]=y[j+k]</math> '''do''' <math>k:=k+1</math>; | |
− | + | 6 '''if''' <math>k>n</math> '''then return''' true; | |
− | + | 7 '''if''' <math>x[i+k]>y[i+k]</math> '''then''' <math>i:=i+k</math> '''else''' <math>j:=j+k</math>; | |
− | '''return''' false; | + | 8 '''end;''' |
+ | 9 '''return''' false; | ||
}} | }} | ||
Wersja z 15:10, 27 wrz 2006
Algorytmy tekstowe I
Tekst jest ciągiem symboli. Przyjmujemy, że jest on zadany tablicą
, elementami której są symbole ze skończonego zbioru A (zwanego alfabetem). Liczba jest długością (rozmiarem) tekstu. W większości naszych algorytmów jedyne operacje dopuszczalne na symbolach wejściowych, to porównania dwóch symboli.Algorytmy na tekstach wyróżniają się tym, że wykorzystują specyficzne, kombinatoryczne własności tekstów. Okresem tekstu
jest każda liczba naturalna niezerowa taka, że , dla każdego , dla którego obie strony są zdefiniowane. Przez per(x) oznaczmy minimalny okres x.
Pojęciem dualnym do okresu jest prefikso-sufiks tekstu. Jest to najdłuższy właściwy (nie będący całym tekstem) prefiks tekstu x będący jednocześnie jego sufiksem. Oczywiste jest, że jest długością prefikso-sufiksu x. Jeśli to prefikso-sufiksem x jest słowo puste o długości zerowej.
Oznaczmy przez
rozmiar prefikso-sufiksu ; zatem , gdzie .
Przykład
Dla
mamy:Wartość
jest wartością sztuczną (przyjmiemy potem ).Obliczanie tablicy Prefikso-Sufiksów
Przedstawimy jeden z możliwych algorytmów liniowych obliczania tablicy P. Jest to iteracyjna wersja algorytmu rekurencyjnego, który moglibyśmy otrzymać korzystając z faktu:
W algorytmie obliczania
korzystamy z wartości , dla .Algorytm Prefikso-Sufiksy
1; ; 2 for to do 3 begin 4 while and do 5 ; ; 6 end;
Złożoność liniowa wynika stąd, że w każdej iteracji zwiększamy wartość t co najwyżej o jeden, a wykonanie każdej operacji
zmniejsza wartość t co najmniej o jeden. Proste zastosowanie zasady magazynu (lub potencjału) implikuje, że operacji wykonujemy co najwyżej n. Dowód poprawności pozostawiamy jako ćwiczenie.Minimalne słowo pokrywające
Pokażemy pewne, proste zastosowanie tablic prefikso-sufiksów. Słowem pokrywającym tekst x jest każdy taki tekst y, którego wystąpienia w x pokrywają cały tekst x. Na przykład słowo y=aba pokrywa tekst x=ababaaba, natomiast nie pokrywa tekstu abaaababa. Zajmiemy się problemem: obliczyć w czasie liniowym długość najkrótszego słowa pokrywającego dany tekst x.
Niech
będzie rozmiarem minimalnego słowa pokrywającego dla prefiksu . Następujący algorytm oblicza długość minimalnego słowa pokrywającego tekstu x. Obliczamy wartości długości najkrótszego słowa pokrywającego dla każdego . W -tej iteracji algorytmu pamiętany jest znany zakres każdego minimalnego słowa pokrywającego.TODO - problem z rysunkiem, napis nie pasuje do podpisu, co to jest q?

Rysunek 3:
mamy -ta iteracja algorytmu dla oraz słowa . Tuż przed rozpoczęciem tej iteracji mamy , , . Po zakończeniu -tej iteracji , ponieważ .Algorytm Rozmiar-Minimalnego-Pokrycia
1 forto do begin 2 3 end; 4 for to do 5 if and then begin 6 ; ;
7 end; 8 return ;
Poprawność jest pozostawiona jako ćwiczenie.
Tablica Silnych Prefikso-Sufiksów
TODO - KDX - ???
Wprowadzimy silną tablicę prefikso-sufiksów dla wzorca
Jeśli takiego k nie ma, to przyjmujemy . Przyjmujemy ponadto, że .
Wartości tablicy P' mogą być znacznie mniejsze niż wartości tablicy P.
Przykład
Dla
mamy:
Algorytm bazuje na następującej relacji między P a P':
Nie musimy obliczać tablicy P; potrzebna jest jedynie ostatnia wartość
, którą obliczamy on-line.
Algorytm Silne-Prefikso-Sufiksy
1; 1; 2 for 1 to do begin // 3 while and do 4 ; 5 ; 6 if or 7 then else ; 8 end;
Gdy weźmiemy
to , , , oraz dla . To jest pesymistyczny przypadek dla algorytmu Silne-Prefikso-Sufiksy, algorytm wykonuje porównań symboli.String-matching: algorytm Knutha-Morrisa-Pratta
Przedstawimy klasyczny algorytm Knutha-Morrisa-Pratta (w skrócie KMP) dla problemu string-matchingu: obliczyć w w tekście
wszystkie (lub pierwsze) wystąpienia danego tekstu , zwanego wzorcem.Oznaczmy
, gdzie .Operacją dominującą w algorytmie jest porównanie dwóch symboli.
Zaczniemy od obliczania jedynie pierwszego wystąpienia. Algorytm KMP przegląda tekst y od lewej do prawej, sprawdzając, czy jest zgodność na pozycji
we wzorcu x oraz na pozycji w tekście y. Jeśli jest niezgodność, to przesuwamy potencjalny początek (pozycja i) wystąpienia x w y. Zakładamy, że algorytm zwraca na końcu wartość false,jeśli nie zwróci wcześniej true. Pozostawiamy dowód poprawności (określenie niezmienników) jako ćwiczenie.
Algorytm Algorytm KMP
1; ; 2 while do begin 3 while and do ; 4 if then return(true); 5 ; 6 end;
Operacją dominującą w algorytmie jest operacja:
.Udowodnimy, że algorytm KMP wykonuje co najwyżej 2n-m porównań symboli. Zauważmy, że dla danej pozycji w tekście y jest ona co najwyżej raz porównana z pewną pozycją we wzorcu w porównaniu pozytywnym (gdy symbole są równe). Jednocześnie każde negatywne porównanie powoduje przesunięcie pozycji
co najmniej o jeden, maksymalna wartość i wynosi n-m, zatem mamy takich porównań co najwyżej n-m, w sumie co najwyżej 2n-m porównań w algorytmie KMP.
Algorytm dla
Obserwacja. Tablicę P' możemy w algorytmie KMP zamienić na P bez zmiany złożoności pesymistycznej.
W wersji on-line algorytmu okaże się, że jest zdecydowana różnica między użyciem P' i P; to właśnie jest motywacją dla wprowadzenia silnych prefikso-sufiksów.

Wersja on-line algorytmu KMP
Przedstawimy teraz wersję on-line algorytmu KMP. Wczytujemy kolejne symbole
i wypisujemy on-line (na bieżąco) odpowiedź:- 0 - gdy dotychczas wczytany tekst nie zawiera x jako sufiks,
- 1 - jeśli zawiera
Algorytm On-Line-KMP
1 repeat forever 2 read(); 3 while and do ; 4 ; 5 if then 6 write(1); ; 7 else write(0);
Oznaczmy przez delay(m) maksymalną liczbę kroków algorytmu On-Line-KMP między wczytaniem symbolu i daniem odpowiedzi. Przez delay'(m) oznaczmy podobną wielkość, w sytuacji gdy zamiast tablicy P' użyjemy P.
Przykład
Jeśli
oraz , to , .Z lematu o okresowości wynika, że zachodzi następujący fakt:
Uzasadnienie pozostawiamy jako ćwiczenie.
Wersja real-time algorytmu KMP
Pokażemy teraz wersję algorytmu on-line, która działa w czasie rzeczywistym, tzn. czas reakcji między wczytaniem symbolu a daniem odpowiedzi jest O(1), niezależnie od rozmiaru alfabetu.
Algorytm zachowuje się podobnie jak algorytm On-Line-KMP; podstawowa różnica polega na tym, że algorytm wkłada do kolejki wczytane symbole, które jeszcze nie są przetworzone w sensie algorytmu KMP. Algorytm zachowuje się podobnie jak algorytm on-line, ale wczytuje kolejne symbole z kolejki, a nie bezpośrednio z wejścia. Rysunek pokazuje relacje tego algorytmu do algorytmu KMP. Symbole z wejścia najpierw wędrują do kolejki.

Rysunek 2: Typowa konfiguracja w algorytmie real-time-KMP.
Algorytm Real-Time-KMP
1 inicjalizacja:; Kolejka ; 2 repeat forever 3 read(symbol); 4 insert(symbol,Kolejka); 5 write(OUTPUT(Kolejka, j));
W celu skrócenia zapisów pojedynczych algorytmów rozbijamy algorytm na dwie części. Zasadnicza część jest zapisana jako osobna funkcja OUTPUT(Kolejka, j). Funkcja ta liczy 0 lub 1, w zależności od tego czy ostatnio wczytany symbol kończy wystąpienie wzorca x. Zmienne Kolejka, j są globalne.
Oczywiste jest, że opóźnienie (czas reakcji) tego algorytmu jest O(1).
Pozostawiamy jako ćwiczenie uzasadnienie tego, że algorytm Real-Time-KMP jest poprawny.
Algorytm Real-Time-KMP: funkcja OUTPUT(Kolejka, j)
1 output0; 2 repeat 2 times 3 if Kolejka niepusta then 4 if then 5 j 0; delete(Kolejka); 6 else if then ; 7 else 8 ; delete(Kolejka); 9 if 10 output 1; j ; 11 return(output);
Wersja algorytmu KMP z porównaniami
Algorytm KMP wykonuje co najmniej 2n-m porównań symboli. Załóżmy, że są to operacje dominujące i spróbujmy zmniejszyć stały współczynnik 2 do
. Na początku załóżmy, że . Następujący algorytm znajduje wszystkie wystąpienia wzorca ab w tekście y.Algorytm Szukanie-ab
1 wzorcem jest2 ; 3 while do begin 4 while do ; 5 if then 6 wypisz-wystąpienie;i i+2 7 end;
Algorytm KMP dla wzorca ab wykonywał 2n-2 porównań symboli, nowy algorytm jest lepszy. Zachodzi fakt: algorytm Szukanie-ab wykonuje co najwyżej n porównań w tym przypadku.
Uzasadnienie pozostawiamy jako ćwiczenie.
Uogólnimy algorytm na dowolne wzorce. Niech x zawiera co najmniej dwa różne symbole,
, gdzie .Oznaczmy skrócony wzorzecPrzykład
, wtedy , .
Podamy nieformalny zarys działania oszczędniejszej wersji algorytmu KMP, w której osobno szukamy x' i osobno części .
Niech będzie taką wersją algorytmu KMP, w której szukamy jedynie wzorca , ale tablica jest obliczona dla wzorca . Jeśli i , to wykonujemy przesunięcie potencjalnego początku i wzorca w y o k+1, gdzie . Inaczej mówiąc, nie szukamy wszystkich wystąpień x', ale jedynie takich, które mają sens z punktu widzenia potencjalnego znalezienia na lewo ciągu .
Tak zmodyfikowany algorytm KMP zastosujemy jako część algorytmu Oszczędny-KMP. Graficzna ilustracja działania algorytmu Oszczędny-KMP jest pokazana na rysunku.
Algorytm Oszczędny-KMP
Znajdujemy wystąpienia x' w tekście
dla każdego wystąpienia x' sprawdzamy, czy na lewo jest wystąpienie ;
nie sprawdzamy tych pozycji w y, których zgodność z pewną pozycją w x jest znana;
Pozostawiamy jako ćwiczenie dokładny zapis algorytmu w pseudokodzie oraz dowód tego, że algorytm Oszczędny-KMP wykonuje co najwyżej
porównan.Ogólna idea jest przedstawiona na rysunku.

Rysunek 4: Ilustracja tego, że liczba operacji dodatkowych jest ograniczona przez .
Niech zasadniczymi operacjami będą operacje sprawdzania pierwszego b na danej pozycji tekstu y, oraz te sprawdzania symboli które sa z wynikiem pozytywnym. Takich operacji jest co najwyżej n. Pozostałe operacje to
(1) sprawdzanie w części
z wynikiem negatywnym; wtedy przesuwamy wzorzec co najmniej o k,(2) sprawdzanie części
na lewo od pozytywnego (w kwadraciku na rysunku), na pozycjach gdzie wcześniej było sprawdzanie negatywnego b. Wtedy odległość między pozytywnymi kolejnymi b jest co najmniej 2w, gdzie liczba sprawdzanych na lewo symboli a. Zatem lokalnie przesunięcie jest co najmniej dwukrotnie większe niż liczba dodatkowych operacji.Suma przesunięć wzorca na tekście
wynosi co najwyżej n, tak więc sumaryczna liczba dodatkowych operacji jest co najwyżej , a liczba wszystkich operacji nie przekracza .Równoważność cykliczna słów
W poprzednich algorytmach porównywaliśmy symbole jedynie w sensie ich równości. Pokażemy teraz problem, który pokazuje użyteczność porządku liniowego na alfabecie.
Rotacją słowa
jest każde słowo postaci . (W szczególności . Niech będą słowami długości ; mówimy, że są one cyklicznie równoważne, gdy dla pewnych .Naturalnym algorytmem sprawdzania cyklicznej równoważności jest szukanie słowa
w słowie , ale podamy algorytm znacznie prostszy bazujący na porządku leksykograficznym, który będzie działał w czasie liniowym i w miejscu (dodatkowa pamięć jest stała).W algorytmie rozszerzamy tablice
na ale robimy to jedynie dla uproszczenia, w rzeczywistości możemy poruszać się cyklicznie po i po ; modyfikację pozostawiamy jako ćwiczenie.Algorytm Równoważność-Cykliczna
1; ; 2 ; ; 3 while and do begin 4 ; 5 while do ; 6 if then return true; 7 if then else ; 8 end; 9 return false;
Problem poprawności pozostawiamy jako ćwiczenie.
Liczba porównań jest oczywiście liniowa. Pozostawiamy jako ćwiczenie wyprowadzenie dokładnego wzoru na maksymalną liczbę porównań symboli dla tekstów długości
.