Algorytmy i struktury danych/Algorytmy tekstowe I: Różnice pomiędzy wersjami

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
Tprybick (dyskusja | edycje)
Nie podano opisu zmian
 
m Zastępowanie tekstu – „<math> ” na „<math>”
 
(Nie pokazano 126 wersji utworzonych przez 7 użytkowników)
Linia 1: Linia 1:
<!--%--------+---------+---------+---------+---------+---------+---------+---------+-->\noindent {\Large \bf ASD-Moduł.\ Algorytmy tekstowe I} \vskip 0.5cm
<font size="6"> Algorytmy tekstowe I </font>
<!--%--><!--%--------+---------+---------+---------+---------+---------+---------+---------+-->Tekst jest ciągiem symboli, przyjmujemy że jest on zadany tablicą x[1..n] elementami którejsą symbole ze 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 kombinatorycznewł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 i dla którego obie strony są zdefiniowane. Przez per(x) oznaczmyminimalny okres x. Okresowość spełnia następującą  ciekawą własność kombinatoryczną. Niech <math>nwd(p,q)</math> oznaczanajmnieszy wspólny dzielnik p,q.<!--%-->\paragraph'''Lemat o okresowości.\\'''Jeśli x ma okresy p, q oraz <math>p+q \le |x|</math> to <math>nwd(p,q)</math> jest również okresem x. \myskipLematten wynika z poprawności algorytm Euklidesa z odejmowaniem, który liczy nwd(p,q). Zauważmy, żejeśli <math>p>q</math> są okresami to p-q też jest okresem. Dokładny dowód zostawiamy jako ćwiczenie.<!--%-------------------------------------------->\myskip Lemat ten można wzmocnić osłabiając założenia. Dowód pozostawiamy jako ćwiczenie.<!--%-->\paragraph'''Silny lemat o okresowości.\\'''Jeśli x ma okresy p, q oraz <math>p+q \le |x|+nwd(p,q)</math> to <math>nwd(p,q)</math> jest również okresem x. \myskip<!--%----------------------------------------------------------------------------------------------------->Pojęciem dualnym do okresu jestprefikso-sufiks tekstu, jest to najdłuższy własciwy (nie będący całym x) prefiks tekstu x  będącyjednocześnie sufiksem x.  Oczywistym 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.\vskip 0.1cm
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>.\paragraph{Przykład.\\} Dla <math>x\ =\ abababababb</math> mamy:<center><math>P[1..11]\ =\ [0,\ 0,\ 1,\ 2,\ 3,\ 4,\ 5,\ 6,\ 7,\ 8,\ 0].</math></center>Wartość <math>P[0]</math> jest warością sztuczną  (przyjmiemy potem <math>P[0]=-1</math>).\subsection*{Liczenie tablicy  Prefisko-Sufiksów}<!--%\myskip-->Przedstawimy jeden z możliwych algorytmów liniowych oblicznaia tablicy P, jest to iteracyjna wersja algorytmu rekurencyjnego, który moglibyśmy otrzymac korzystając z faktu:<!--%--><center><math>x[j]=x[t+1]\ \textrm{oraz}\ t=P[j-1] \ \Rightarrow\ P[j]= t+1</math></center>W algorytmie do liczenia <math>P[j]</math> korzystamy z wartości <math>P[k]</math> dla <math>k<j</math>. <!--%--><!--%------------------------------------------------------------------->\vskip 0.3cm\hspace*{0.6cm}\textbf{Algorytm} \textit{Pefikso-Sufiksy};\\\hspace*{1.2cm}<math>P[0]:=-1</math>; <math>t:=-1</math>;\\\hspace*{1.2cm}\textbf{for} <math>j:=1</math> \textbf{to} <math>m</math> \textbf{do}\\\hspace*{1.8cm}\textbf{while} <math>t\geq 0</math> \textbf{and} <math>x[t+1]\neq x[j]</math> \textbf{do}<math>t:=P[t]</math>;\\\hspace*{1.8cm}<math>t:=t+1</math>; <math>P[j]:=t</math>;\\<!--%\hspace*{1.2cm}\textbf{end}\\-->\myskip Złożoność liniowa wynika stąd, że w każdej iteracji zwiększamy wartość t co najwyżejo jeden, a wykonanie każdej operacji <math>t:=P[t]</math> zmniejsza wartość t co najmniej o jeden. Prostezastosowanie zasady magazynu (lub potencjału) implikuje, że operacji <math>t:=P[t]</math> wykonujemy conajwyżej n. Dowód poprawności pozostawiamy jako ćwiczenie.<!--%-->
<!--%---------------------------------------------------------------------->\subsection*{Tablica Silnych Prefisko-Sufiksów}
Wprowadzimy silną tablicę prefikso-sufisó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 rozmiarm słowa będącego prefiksem i sufiksem <math>x[1..j]</math>najdłuższego własciwegoi spełniającego dodatkowy warunek <math>x[k+1]\ne x[j+1]</math> dla <math>j<n</math>. \\Jeśli takiego k nie ma toprzyjmujemy <math>P'[j]=-1</math>. Przyjmujemy ponadto, że <math>P'[m]=P[m]</math>.\myskip Wartości tablicy P' mogą być znacznie mniejsze niż wartości tablicy P. %\paragraph{Przykład} Dla <math>x\ =\ abaab</math> mamy:<center><math>P[0..5]\ =\ [-1,\ 0,\ 0,\ 1,\ 1,\ 2\ ];\ \ \P'[0..5]\ =\ [-1,\ 0,\ -1,\ 1,\ 0,\ 2\ ].</math></center><!--%-->Algorytm bazuje na następującej relacji między P i P':<!--%--><center><math>(t=P[j]\ \textrm{oraz}\ x[t+1]\neq x[j+1])\ \Rightarrow\ P'[j]=t</math></center><center><math>(t=P[j],\ t\ge 0,\ \textrm{oraz}\ x[t+1]= x[j+1])\ \Rightarrow\ P'[j]=P'[t]</math></center><!--%-->Nie musimy liczyćtablicy P, potrzebna jest jedynie ostatnia wartość <math>t=P[j]</math>, którą liczymy on-line.<!--%------------------------------------------------------------------->\myskip\begin{center}\fbox{\begin{minipage}{9cm}\vskip0.3cm\hspace*{0.6cm}\textbf{Algorytm} Silne-Prefikso-Sufiksy;\\<!--%\hspace*{0.6cm}\{ computes table <math>P'</math> for pattern <math>x</math> \}\\-->\hspace*{1.2cm}<math>P'[0]:=-1</math>; <math>t:=-</math>1;\\\hspace*{1.2cm}\textbf{for} <math>j:=</math> 1 \textbf{to} <math>m</math> \textbf{do }// <math>t=P[j-1]</math> \\\hspace*{1.8cm}\textbf{while} <math>t\geq 0</math> \textbf{and} <math>x[t+1]\neq x[j]</math>\textbf{do}\\ \hspace*{2.5cm}  <math>t:=P'[t]</math>;\\\hspace*{1.8cm}<math>t:=t+1</math>;\\\hspace*{1.8cm}\textbf{if} <math>j=m</math> \textbf{or}  <math>x[t+1]\neq x[j+1]</math>\\\hspace*{2cm} \textbf{then} <math>P'[j]:=t</math>\\textbf{else} <math>P'[j]:=P'[t]</math>;\\<!--%\hspace*{1.2cm}\textbf{end}\\-->\vskip0.4cm\end{minipage}}\end{center}\myskipGdyweżmiemy <math>x\ =\ aba^{m-2}</math> to <math>P'[0]=-1</math>, <math>P'[1]=0</math>, <math>P'[2]=-1</math>,oraz <math>P'[j]=1</math>, dla <math>3\leq j\leq m</math>.\ \noindent To jest pesymistyczny przypadek dla algorytmu Silne-Prefikso-Sufiksy, algorytm wykonuje<math>3m-5</math> porównań symboli.<!--%--------------------------------------------------------------------------------------------><!--%------------------------------------------------------------------->\subsection*{String-matching: algorytm Knutha-Morrisa-Pratta}Przedstawimy klasyczny algorytm Knutha-Morrisa-Pratta (w skrócie KMP) dla problemu {\em string-matching}u:
obliczyćw w tekście <math>y</math> wszystkie (lub pierwsze) wystąpienia danego tekstu <math>x</math>, zwanego wzorcem (ang. pattern).\vskip 0.1cm\noindent Oznaczmy <math>m=|x|,\ n=|y|</math>, gdzie <math>m\le n</math>.\myskip Operacją {\em dominującą} w algorytmie jest porównanie dwóch symboli.\myskip \noindent Zaczniemy od obliczania jedynie pierwszego wystąpienia. Algorytm KMP przegląda tekst y od lewej doprawej, sprawdzając, czy jest zgodność na pozycji <math>j+1</math> we wzorcu x, oraz na pozycji <math>i+j+1</math> wtekście y. Jeśli jest niezgodność to przesuwamy potencjalny początek (pozycja i) wystąpienia x w y.Zakładamy, że algorytm {\em zwraca} wartość {\em false} gdy nie zwróci wcześniej {\em true}.Pozostawiamy dowód poprawności(określenie niezmienników) jako ćwiczenie.\myskip<!--%--><!--%------------------------------------------------------------------->\begin{center}\fbox{\begin{minipage}{12cm}\vskip0.3cm\hspace*{0.6cm}\textbf{Algorithm} KMP;  %\{ algorithm of Morris and Pratt \}\\\hspace*{1.2cm}<math>i:=0</math>; <math>j:=0</math>;\\\hspace*{1.2cm}\textbf{while} <math>i\leq n-m</math> \textbf{do }\\\hspace*{1.8cm}\textbf{while} <math>j<m</math> \textbf{and} <math>x[j+1]=y[i+j+1]</math> \textbf{do} \<math>j=j+1</math>;\\\hspace*{1.8cm}\textbf{if} <math>j=m</math> \textbf{then return}(true);\\<!--%\hspace*{1.8cm} Shift := <math>j-P'[j]</math>.\\-->\hspace*{1.8cm}<math>i:=i+j-P'[j]</math>;\ \ <math>j:=\max(0,P'[j])</math>;\\<!--%\hspace*{1.2cm}\textbf{return}(false)\\-->\vskip0.4cm\end{minipage}}\end{center}<!--%------------------------------------------------------------------->\myskip Operacją {\em dominującą} w algorytmie jest  operacja:\ <math>x[j+1]=y[i+j+1]</math>.
Udowodnimy, że algorytm KMP wykonuje co najwyżej 2n-m porównań symboli. Zauważmy, że dla danejpozycji w tekście y jest ona co najwyżej raz porównana z pewną pozycją we wzorcu w porównaniupozytywnym (gdy symbole są równe). Jednocześnie każde negatywne porównanie powoduje przesunięciepozycji <math>i</math> co najmniej o jdeden, maksymalna wartość i wynosi n-m, zatem mamy takich porównań conajwyżej n-m, w sumie co najwyżej 2n-m porównań w algorytmi KMP.


__TOC__


Algorytm dla <math>x=ab</math>, <math>y=aa..a</math> wykonuje 2n-2porównania, zatem 2n-m jest dolną i jednocześnie górną granicą na liczbę porównań w algorytmie.%--------------\myskip'''Obserwacja'''.\ Tablicę P' możemy w algorytmie KMP zamienić na P bez zmiany złożoności pesymistycznej.\myskipW wersji on-line algorytmu okaże się, że jest zdecydowana różnica między użyciem P' i P,to właśnie jest motywem wprowadzenia silnych prefikso-sufiksów.\myskip <!--%--><!--%************************************************************-->\begin{figure}[hbt]\begin{center}\includegraphics[width=6in]{teksty_fig3.eps}\caption{Jedna iteracja algorytmu KMP. Przesunięcie <math>shift=j-P'[j]</math> potencjalnego początku wystąpienia wzorca gdy <math>x[j+1]\ne y[i+j+1]</math>.}  <span id="figure2-1" \> \end{center}\end{figure}<!--%**********************************************************-->\subsection*{Wersja on-line algorytmu KMP}Przedstawimy teraz wersję on-line algorytmu KMP. Wczytujemy kolejne symbole <math>y</math> i wypisujemy on-line (nabieżąco) odpowiedż:
Algorytmy tekstowe mają decydujące znaczenie przy wyszukiwaniu informacji typu tekstowego, ten typ informacji jest szczególnie popularny w informatyce, np. w edytorach tekstowych i wyszukiwarkach internetowych. 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.  
\myskip<!--%------------------------------------------------------------------->\begin{center}\fbox{\begin{minipage}{11cm}\vskip0.3cm\hspace*{0.6cm}\textbf{Algorithm} \textit{On-Line-KMP};\\<!--%\hspace*{1.2cm}read(<math>symbol</math>); <math>j:=0</math>;\\-->\hspace*{1.2cm}\textbf{repeat forever}\\ % \vskip 0.2cm \noindent\hspace*{1.8cm} read(<math>symbol</math>);\\ \hspace*{1.8cm} \textbf{while} <math>j>-1</math> and <math>x[j+1]\ne symbol</math> \textbf{do} <math>j:=P'[j]</math>;\\\hspace*{1.8cm}<math>j:=j+1</math>; \\\hspace*{1.8cm} \textbf{if} <math>j=m</math> \textbf{then}\\\hspace*{2.8cm} write(<math>1</math>);\ j := <math>P'[m]</math>;\\\hspace*{1.8cm}  \textbf{else} write(<math>0</math>);\\\vskip0.4cm\end{minipage}}\end{center}<!--%------------------------------------------------------------------->\myskipOznaczmy przez delay(m) maksymalną liczbę kroków algorytmu On-Line-KMP między  wczytaniem symbolui daniem odpowiedzi. Przez delay'(m) oznaczmy podobną wielkość, w sytuacji gdy zamiast tablicy P' użyjemy P.\myskip'''Przykład}.  Jeśli <math>x=aaaa\dots a</math> oraz <math>y=a^{m-1'''b</math>, to <math>delay(m)=O(1)</math>, <math>delay'(m)=\Theta(m) </math>.\myskipZ lematu o okresowości wynika, że zachodzi następujący fakt:<center><math> delay(m)\ =\ O(\log m)</math></center>Uzasadnienie pozostawiamy jako ćwiczenie.\myskipSłowa Fibonacciego definiujemy następująco:<center><math>F_0=a,\ F_1=ab,\ F_{n+1}\ =\ F_n\cdot F_{n-1}</math></center>\noindent  Na przykład: <math>F_3=abaab,\ F_4=abaababa,\ F_5=abaababaabaab.</math>\myskipNiech <math>F'_n</math> oznacza słowo Fibonacciego z obciętymi ostatnimi dwoma symbolami. Jeśli jako wzorzec weżmiemy słowo Fionacciego <math>F_n</math>, a jako tekst słowo <math>F'_ncc</math> to przy wczytywaniu <math>|F_n-1|</math>-ego symbolu algorytm ma opóżnienie logarytmiczne, iterujemy <math>\Omega(\log n)</math> razy operację: <math>j:=P'[j]</math>. Uzasadnienie pozostawiamy jako ćwiczenie.
Przy okazji wprowadzenia słów Fibonacciego zostawiamy jako ćwiczenie podaniewzoru na tablice P i P' dla słów Fibonacciego, we wzorze możemy używać liczb Fibonacciego.W związku z tym proponujemy jako ćwiczenie napisanie wersji algorytm KMP dla wzorca będącego słowem Fibonacciego w czasie liniowym i bez dodatkowej tablicy (typu P lub P').
<!--%-------------------------------------------------------------------------------------->\subsection*{Wersja real-time algorytmu KMP}Pokażemy teraz wersje algorytmu on-line która działa real-time, tzn. czas reakcji między wczytaniem symbolui daniem odpowiedzi jest O(1).
Algorytm zachowuje się podobnie jak algorytm On-Line-KMP, podstawowa różnica polega na tym, że algorytmwkłada do kolejki wczytane symbole, które jeszcze nie 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. Rysunekpokazuje relacje tego algorytmu do algorytmu KMP. Symbole z wejścia najpierw wędrują do kolejki.\myskip<!--%************************************************************-->\begin{figure}[hbt]\begin{center}\includegraphics[width=6.2in]{teksty_fig4.eps}\caption{Typowa konfiguracja w algorytmie real-time-KMP.}  <span id="real-time-KMP" \> \end{center}\end{figure}<!--%**********************************************************-->
<!--%----------------------------------><!--%------------------------------------------------------------------->\begin{center}\fbox{\begin{minipage}{7cm}\vskip0.3cm\hspace*{0.3cm}\textbf{Algorytm} \textit{Real-Time-KMP};\\<!--%\hspace*{0.6cm}\{ on-line linear version of {KMP} search \}  \noindent-->\hspace*{.8cm} inicjalizacja:\  <math>j:=0</math>;\ Kolejka := <math>\emptyset</math>;\ \vskip 0.1cm \noindent\hspace*{0.5cm}\textbf{repeat forever}  \vskip 0.2cm \noindent<!--%-->\hspace*{0.8cm} read(symbol); \\<!--%--><!--%\hspace*{0.8cm} Compute-Output:\vskip 0.2cm \noindent-->\hspace*{1.cm}insert(symbol,Kolejka); \\\hspace*{1cm} '''write'''(OUTPUT(Kolejka,\ j));\\<!--%%\vskip 0.2cm \noindent--><!--% \hspace*{1.cm} '''repeat 2 times'''\\--><!--%%\vskip 0.2cm \noindent--><!--% \hspace*{1.8cm} '''if ''' Kolejka niepusta '''then'''\\--><!--%\hspace*{2.1cm} '''if''' <math>j=-1</math> '''then''' \\--><!--%\hspace*{2.7cm} <math>j</math> := 0; delete(Kolejka);\\--><!--%\hspace*{2.1cm} \textbf{else if}  <math>x[j+1]\ne first(Kolejka)</math> '''then'''  \ <math>j:=P'[j]</math>;\\--><!--%\hspace*{2.1cm}\textbf{ else}\\--><!--% \hspace*{2.7cm} <math>j:=j+1</math>; delete(Kolejka); ;\\--><!--%\hspace*{2.7cm} \textbf{if} <math>j=m</math> \\--><!--%\hspace*{3.1cm}--><!--%output := 1;\ j := <math>P'[m]</math>; '''goto''' OUTPUT  \vskip 0.2cm \noindent--><!--% \hspace*{1.cm}OUTPUT:\  '''write'''(output);\\-->\vskip 0.4cm\end{minipage}}\end{center}<!--%------------------------------------------------------------------->\myskipW celu skrócenia zapisów pojedyńczych algorytmów rozbijamy algorytm na dwie części. Zasadniczaczęść jest zapisana jako osobna funkcja OUTPUT(Kolejka,\ j). Funkcja taliczy 0 lub 1, w zależności od tego czy ostatnio wczytany symbol kończy wystąpieniewzorca x. Zmienne Kolejka, j  są globalne.
\noindent Oczywistym jest że opóżnienie (czas reakcji) tego algorytmu jest O(1).\myskipPozostawiamy jako ćwiczenie uzasadnienie tego, że algorytm Real-Time-KMP jest poprawny.
<!--%------------------------------------------------------------------->\begin{center}\fbox{\begin{minipage}{12cm}\vskip0.3cm\hspace*{0.3cm}\textbf{Funkcja} \textit{OUTPUT(Kolejka,\ j)};\\\hspace*{1.cm}output := 0;\\<!--%\vskip 0.2cm \noindent-->\hspace*{1.cm} '''repeat 2 times'''\\<!--%\vskip 0.2cm \noindent-->\hspace*{1.8cm} '''if ''' Kolejka niepusta '''then'''\\\hspace*{2.1cm} '''if''' <math>j=-1</math> '''then''' \\\hspace*{2.7cm} <math>j</math> := 0; delete(Kolejka);\\\hspace*{2.1cm} \textbf{else if}  <math>x[j+1]\ne first(Kolejka)</math> '''then'''  \ <math>j:=P'[j]</math>;\\\hspace*{2.1cm}\textbf{ else}\\\hspace*{2.7cm} <math>j:=j+1</math>; delete(Kolejka); ;\\\hspace*{2.7cm} \textbf{if} <math>j=m</math> \\\hspace*{3.1cm}output := 1;\ j := <math>P'[m]</math>; \vskip 0.2cm \noindent\hspace*{1.cm} '''return'''(output);\\\vskip 0.4cm\end{minipage}}\end{center}
<!--%------------------------------------------------------------------->\subsection*{Wersja algorytmu KMP z <math>\frac{3}{2}n</math> porównaniami}Algorytm KMP wykonuje co najmniej 2n-m porównań symboli. Załóżmy, że są to operacje dominujące ispróbujmy zmniejszyć stały wspó:lczynnik 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.\myskip<!--%--><!--%------------------------------------------------------------------->\begin{center}\fbox{\begin{minipage}{12cm}\vskip0.3cm\hspace*{0.6cm}\textbf{Algorithm} Szukanie-ab; \\wzorcem jest <math>ab</math> %\{ algorithm of Morris and Pratt \}\\\hspace*{1.2cm}<math>i:=0</math>; ;\\\hspace*{1.2cm}\textbf{while} <math>i\leq n-m</math> \textbf{do }\\\hspace*{1.8cm}\textbf{while} <math>y[i+2]\ne b</math> {do} \<math>i=i+1</math>;\\\hspace*{1.8cm}\textbf{if} <math>y[i+1]= a</math> \textbf{then }\\\hspace*{2.4cm} wypisz-wystąpienie; i:=i+2;\\\vskip0.4cm\end{minipage}}\end{center}<!--%------------------------------------------------------------------->\myskipAlgorytm KMP dla wzorca ab wykonywał 2n-2 porównań symboli, nowy algorytm jest lepszy. Zachodzi fakt:
algorytm Szukanie-abwykonuje co najwyżej n porównań w tym przypadku. \myskip\noindent Uzasadnienie pozostawimay jako ćwiczenie.\myskipUogólnimy algorytm na dowolne wzorce. Niech x zawiera co najmniej dwa różne symbole, \ <math>x=a^kb\alpha</math>, gdzie <math>a\ne b</math>.Oznaczmy <math>x'=b\alpha</math> ({\em skrócony wzorzec}).\myskip'''Przykład.'''\ <math>x\ =\ aaaabaaaababa</math>, wtedy <math>x'\ =\ baaaababa</math>, <math>\alpha\ =\ aaaababa</math>.\myskipPodamy nieformalny zarys działania oszczędniejszej wersji algorytmu KMP, w której osobno szukamy x' i osobno części <math>a^k</math>. \myskip
Niech <math>KMP'</math> będzie taką wersją algorytmu KMP w której jedynie szukamy wzorca <math>x'</math>, ale tablica <math>P'</math> jest policzona względem wzorca <math>x</math>.Jeśli <math>j>0</math> i <math>shift\le k</math> to wykonujemy przesunięcie potencjalnego początku i wzorca w y o k+1, gdzie <math>shift=j-P'[j]</math>. Inaczej mówiąc, nie szukamy wszystkich wystąpień x', ale jedynie takich, które mają sens pod względem potencjalnego znalezienia na lewo ciągu <math>a^k</math>.\myskipTak zmodyfikowany algorytm KMP zastosujemy jako część algorytmu Oszczędny-KMP.
\noindent Graficzna ilustracja działania algorytmu Oszczędny-KMP jest pokazana na rysunku.\myskip '''Algorytm''' Oszczędny-KMP;\begin{description}\item\hspace*{0.7cm}Znajdujemy wystąpienia x' w tekście <math>y[k+1..m]</math> algorytmem KMP';\\dla każdego wystąpienia x' sprawdzamy czy na lewo jest wystąpienie <math>a^k</math>;\\nie sprawdzamy tych pozycji w  y, których zgodność z pewną pozycją w x jest znana; \end{description}


\begin{figure}[hbt]\begin{center}\includegraphics[width=5.9in]{teksty_fig5.eps}\caption{Typowa konfiguracja w algorytmie Oszczędny-KMP.} <span id="real-time-KMP" \> \end{center}\end{figure}
Algorytmy na tekstach wyróżniają się tym, że wykorzystują specyficzne, kombinatoryczne własności tekstów. '''Okresem''' tekstu <math>x</math> jest każda niezerowa liczba naturalna <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.  
<!--%********************-->\noindent Pozostawiamy jako ćwiczenie dokładny zapis algorytmu w pseudokodzie oraz dowód tego, że algorytm Oszczędny-KMP wykonuje co najwyżej <math>\frac{3}{2}n</math> porównan. \myskipOgólna idea jest przedsatwiona na rysunku.  


\begin{figure}[hbt]                                                                                                                    \begin{center}                                                                                                                          \includegraphics[width=5.9in]{teksty_fig6.eps}                                                                                          \caption{Ilustracja tego, że liczba operacji dodatkowych jest ograniczona przez <math>\frac{1}{2}n</math>.}  <span id="real-time-KMP" \>
\end{center}                                                                                                                                                                 
\end{figure}                                                                                                                                                                                                                                                                                                                                                %********************


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.


Niech zasadniczymi operacjami będą operacje sprawdzania pierwszego b na danej pozycji tekstu y,oraz te sprawdzania symboli ktore sa z wynikiem pozytywnym. Takich operacji jest co najwyżej n. Pozostałe operacje to  
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>.
(1) sprawdzanie w części <math>\alpha</math> z wynikiem negatywnym, wtedy przesuwamy wzorzec co najmniej o k,
 
(2) sprawdzanie części <math>a^k</math> na lewo od {\em pozytywnego} <math>b</math> (w kwadraciku na rysunku), na pozycjach gdzie wcześniej było sprawdzanie{\em negatywnego} b. Wtedy odległość między pozytywnymi kolejnymi b jest co najmniej 2w, gdzie <math>w\le k</math> liczba sprawdzanych na lewo symboli a.Zatem lokalnie przesunięcie jest co najmniej dwukrotnie większe niż liczba dodatkowych operacji.
 
\noindent Suma przesunięć wzorca na tekście <math>y</math> wynosi co najwyżej n, tak więc sumaryczna liczba  dodatkowych  operacjijest co najwyżej <math>\frac{1}{2}n</math>, a liczb wszstkich nie przekracza <math>\frac{3}{2}n</math>.
 
{{przyklad|||
Dla <math>x= abababababb</math> mamy:
<center><math>P[1..11]= [0,\ 0,\ 1,\ 2,\ 3,\ 4,\ 5,\ 6,\ 7,\ 8,\ 0]</math>.</center>
 
Wartość <math>P[0]</math> jest wartością sztuczną  (przyjmiemy, że <math>P[0]=-1</math>).
}}
<br>
Wprowadzimy również  tablicę ‘’'silnych 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>.
<br> Przyjmujemy ponadto, że <math>P'[m]=P[m]</math>.
<br>
Wartości tablicy P' mogą być znacznie mniejsze niż wartości tablicy P.
 
{{przyklad|||
Dla <math>x= abaab</math> mamy:
<center><math>P[0..5]= [-1,\ 0,\ 0,\ 1,\ 1,\ 2\ ];\ \ P'[0..5]= [-1,\ 0,\ -1,\ 1,\ 0,\ 2\ ]</math>.</center>
}}
 
== 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:
 
<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>.
 
{{algorytm|Prefikso-Sufiksy|algorytm_prefikso_sufiksy|
1  <math>P[0]:=-1</math>; <math>t:=-1</math>;
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.
 
== 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 <math>S[i]</math>
będzie rozmiarem minimalnego słowa pokrywającego tekst <math>x[1..i]</math>.
 
Następujący algorytm oblicza długość minimalnego słowa
pokrywającego tekstu ''x''.  Algorytm jest efektywny ponieważ liczy dodatkową tablicę ''Zakres''.
W <math>i</math>-tej
iteracji algorytmu pamiętany jest ''znany dotychczas zakres'' każdego minimalnego słowa pokrywającego.
 
<center> [[Grafika:Minpokslowo.jpg]]<br></center>
 
Rysunek 1: <math>i</math>-ta iteracja algorytmu dla <math>i=15</math> oraz słowa  <math>x= abaabababaababa\ldots</math>. Tuż przed
rozpoczęciem tej iteracji mamy <math>P[i]=8</math>, <math>S[8]=2,\ Zakres[3]=13</math>.
Zatem spełniony jest warunek  <math>i-Zakres[S[P[i]] \le S[P[i]]</math>. Po zakończeniu <math>i</math>-tej iteracji
mamy  <math>S[15]=3,Zakres[3]=15</math>.
 
{{algorytm | Rozmiar-Minimalnego-Pokrycia|algorym_rozm_min_pokr|
<Source>
[[pascal,1]]
for i:=2 to n do begin
  Zakres[i]=i; S[i]=i;
end;
for i:=2 to n do
  if P[i]>0 and i-Zakres[S[P[i]]] <= S[P[i]] then begin
    S[i] := S[P[i]];  Zakres[S[P[i]] := i
  end;
return S[n];
</Source>
}}
 
<!--
{{algorytm | Rozmiar-Minimalnego-Pokrycia|algorym_rozm_min_pokr|
1  '''for ''' <math>i:=2</math> '''to''' <math>n</math> '''do''' '''begin'''
2    <math>Zakres[i]=i; S[i]=i;</math>
3  '''end;'''
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;'''
8  '''return''' <math>S[n]</math>;
}}-->
 
==Algorytmy Knutha-Morrisa-Pratta i Morrisa-Pratta==
 
Przedstawimy klasyczne algorytmy Knutha-Morrisa-Pratta (w skrócie KMP) oraz Morrisa-Pratta (w skrócie MP)
dla problemu ''string-matching''u:
&nbsp; obliczyć w w tekście <math>y</math> wszystkie (lub pierwsze) wystąpienia danego tekstu <math>x</math>, zwanego wzorcem.
 
Algorytmy MP i KMP różnią się jedynie tym że jeden używa tablicy P a drugi P'. Tablica P' jest bardziej skomplikowana, będziemy się zatem głównie koncentrować na algorytmie MP, poza wersją on-line (gdzie waśnie P' ma przewagę).
 
Oznaczmy <math>m=|x|, n=|y|</math>, gdzie <math>m\le n</math>.
 
Zaczniemy od obliczania jedynie pierwszego wystąpienia. Algorytm MP przegląda tekst y od lewej do prawej, sprawdzając, czy jest zgodność na pozycji <math>j+1</math> we wzorcu x oraz na pozycji <math>i+j+1</math> 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''.  
 
 
{{algorytm|Algorytm MP|algorytm_kmp|
1  <math>i:=0</math>; <math>j:=0</math>;
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;'''
}}
 
<br>
'''Uwaga:''' Algorytm działa podobnie gdy zamiast  prefikso-sufiksów użyjemy tablicy P' silnych prefisko-sufksów. Algorytm w całości jest wtedy bardziej skomplikowany  ze względu na trudniejszy ''preprocessing''
(liczenie P' jest trudniejsze od P).
 
Algorytm MP z tablicą P' zamiast P nazywamy '''algorytmem Knutha-Morrisa-Pratta''' i oznaczamy przez KMP.
<br>
 
Operacją '''dominującą''' w algorytmach KMP i MP jest  operacja porównania symboli: <math>x[j+1]=y[i+j+1]</math>.
Algorytmy KMP i MP wykonują 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 <math>i</math> 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ń.
<br> Poniższa animacja pokazuje przykładowe działanie algorytmu KMP.
 
 
<center>
<flash>file=KMP.swf|width=450|height=220</flash></center>
 
Algorytm dla <math>x=ab</math>, <math>y=aa..a</math> wykonuje 2n-2porównania, zatem 2n-m jest dolną i jednocześnie górną granicą na liczbę porównań w algorytmie.<br>
 
'''Obserwacja'''.
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.
 
<center>[[Grafika:Kmp.gif]]
 
 
<br>
Rysunek 1: Jedna iteracja algorytmu KMP. Przesunięcie <math>shift=j-P'[j]</math> potencjalnego początku wystąpienia wzorca gdy <math>x[j+1]\ne y[i+j+1]</math>.</center>
 
==Wersja on-line algorytmu KMP==
Przedstawimy teraz wersję on-line algorytmu KMP. Wczytujemy kolejne symbole <math>y</math> 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|algorytm_online_KMP|
0  <math>j:=0;</math>
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);
}}
 
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.
 
{{przyklad|||
Jeśli <math>x=aaaa\dots a</math> oraz <math>y=a^{m-1}b</math>, to <math>delay(m)=O(1)</math>, <math>delay'(m)=\Theta(m)</math>.
 
Z lematu o okresowości wynika, że zachodzi następujący fakt:
<center><math>delay(m)= \Theta(\log m)</math></center>
 
Uzasadnienie pozostawiamy jako ćwiczenie.
}}
 
 
 
 
 
==Obliczanie Tablicy Silnych Prefikso-Sufiksów==
 
 
Algorytm liczenia silnych prefikso-sufiksów bazuje na następującej relacji między P a P':
<center><math>(t=P[j]\ \text{oraz}\ x[t+1]\neq x[j+1])\ \Rightarrow\ P'[j]=t</math></center><br>
 
<center><math>(t=P[j],\ t\ge 0,\ \text{oraz}\ x[t+1]= x[j+1])\ \Rightarrow\ P'[j]=P'[t]</math></center><br>
 
Nie musimy obliczać tablicy P; potrzebna jest jedynie ostatnia wartość <math>t=P[j]</math>, którą obliczamy on-line.
 
 
{{algorytm|Silne-Prefikso-Sufiksy|algorytm_silne_prefikso_sufiksy|
1  <math>P'[0]:=-1</math>; <math>t:=-1</math>;<br>
2  '''for''' <math>j:=</math> 1 '''to''' <math>m</math> '''do''' <br>
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>;
}}
<br>
Gdy weźmiemy <math>x= aba^{m-2}</math> to
<br>
<math>P'[0]=-1</math>, <math>P'[1]=0</math>, <math>P'[2]=-1</math>, oraz dla <math>3\leq j\leq m</math> <math>P'[j]=1</math>. <br>
Jest to jest pesymistyczny przypadek dla algorytmu Silne-Prefikso-Sufiksy, algorytm wykonuje wtedy <math>3m-5</math> porównań symboli.

Aktualna wersja na dzień 10:28, 5 wrz 2023

Algorytmy tekstowe I

Algorytmy tekstowe mają decydujące znaczenie przy wyszukiwaniu informacji typu tekstowego, ten typ informacji jest szczególnie popularny w informatyce, np. w edytorach tekstowych i wyszukiwarkach internetowych. Tekst jest ciągiem symboli. Przyjmujemy, że jest on zadany tablicą x[1,,n], elementami której są symbole ze skończonego zbioru A (zwanego alfabetem). Liczba n=|x| 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 x jest każda niezerowa liczba naturalna p taka, że x[i]=x[i+p], dla każdego i, 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 |x|per(x) jest długością prefikso-sufiksu x. Jeśli per(x)=|x| to prefikso-sufiksem x jest słowo puste o długości zerowej.

Oznaczmy przez P[k] rozmiar prefikso-sufiksu x[1..k]. Zatem per(x)=nP[n], gdzie n=|x|.


Przykład

Dla x=abababababb mamy:

P[1..11]=[0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0].

Wartość P[0] jest wartością sztuczną (przyjmiemy, że P[0]=1).


Wprowadzimy również tablicę ‘’'silnych prefikso-sufiksów dla wzorca x[1..m]: jeśli j<|x|, to P[j]=k, gdzie k jest maksymalnym rozmiarem słowa będącego właściwym prefiksem i sufiksem x[1..j] i spełniającego dodatkowy warunek x[k+1]x[j+1] dla j<n.
Jeśli takiego k nie ma, to przyjmujemy P[j]=1.
Przyjmujemy ponadto, że P[m]=P[m].
Wartości tablicy P' mogą być znacznie mniejsze niż wartości tablicy P.

Przykład

Dla x=abaab mamy:

P[0..5]=[1, 0, 0, 1, 1, 2 ];  P[0..5]=[1, 0, 1, 1, 0, 2 ].

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:

Jeśli x[j]=x[t+1] oraz t=P[j1], to P[j]=t+1

W algorytmie obliczania P[j] korzystamy z wartości P[k], dla k<j.

Algorytm Prefikso-Sufiksy


1  P[0]:=1; t:=1;
2  for j:=1 to m do
3   begin
4    while t0 and x[t+1]x[j] do t:=P[t];
5    t:=t+1; P[j]:=t;
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 t:=P[t] zmniejsza wartość t co najmniej o jeden. Proste zastosowanie zasady magazynu (lub potencjału) implikuje, że operacji t:=P[t] 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 S[i] będzie rozmiarem minimalnego słowa pokrywającego tekst x[1..i].

Następujący algorytm oblicza długość minimalnego słowa pokrywającego tekstu x. Algorytm jest efektywny ponieważ liczy dodatkową tablicę Zakres. W i-tej iteracji algorytmu pamiętany jest znany dotychczas zakres każdego minimalnego słowa pokrywającego.


Rysunek 1: i-ta iteracja algorytmu dla i=15 oraz słowa x=abaabababaababa. Tuż przed rozpoczęciem tej iteracji mamy P[i]=8, S[8]=2, Zakres[3]=13. Zatem spełniony jest warunek iZakres[S[P[i]]S[P[i]]. Po zakończeniu i-tej iteracji mamy S[15]=3,Zakres[3]=15.

Algorytm Rozmiar-Minimalnego-Pokrycia


[[pascal,1]]
for i:=2 to n do begin
   Zakres[i]=i; S[i]=i;
end;
for i:=2 to n do
  if P[i]>0 and i-Zakres[S[P[i]]] <= S[P[i]] then begin
    S[i] := S[P[i]];  Zakres[S[P[i]] := i
  end;
return S[n];


Algorytmy Knutha-Morrisa-Pratta i Morrisa-Pratta

Przedstawimy klasyczne algorytmy Knutha-Morrisa-Pratta (w skrócie KMP) oraz Morrisa-Pratta (w skrócie MP) dla problemu string-matchingu:   obliczyć w w tekście y wszystkie (lub pierwsze) wystąpienia danego tekstu x, zwanego wzorcem.

Algorytmy MP i KMP różnią się jedynie tym że jeden używa tablicy P a drugi P'. Tablica P' jest bardziej skomplikowana, będziemy się zatem głównie koncentrować na algorytmie MP, poza wersją on-line (gdzie waśnie P' ma przewagę).

Oznaczmy m=|x|,n=|y|, gdzie mn.

Zaczniemy od obliczania jedynie pierwszego wystąpienia. Algorytm MP przegląda tekst y od lewej do prawej, sprawdzając, czy jest zgodność na pozycji j+1 we wzorcu x oraz na pozycji i+j+1 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.


Algorytm Algorytm MP


1  i:=0; j:=0;
2  while inm do begin
3    while j<m and x[j+1]=y[i+j+1] do j=j+1;
4    if j=m then return(true);
5    i:=i+jP[j]; j:=max(0,P[j])
6  end;


Uwaga: Algorytm działa podobnie gdy zamiast prefikso-sufiksów użyjemy tablicy P' silnych prefisko-sufksów. Algorytm w całości jest wtedy bardziej skomplikowany ze względu na trudniejszy preprocessing (liczenie P' jest trudniejsze od P).

Algorytm MP z tablicą P' zamiast P nazywamy algorytmem Knutha-Morrisa-Pratta i oznaczamy przez KMP.

Operacją dominującą w algorytmach KMP i MP jest operacja porównania symboli: x[j+1]=y[i+j+1]. Algorytmy KMP i MP wykonują 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 i 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ń.
Poniższa animacja pokazuje przykładowe działanie algorytmu KMP.


<flash>file=KMP.swf|width=450|height=220</flash>

Algorytm dla x=ab, y=aa..a wykonuje 2n-2porównania, zatem 2n-m jest dolną i jednocześnie górną granicą na liczbę porównań w algorytmie.

Obserwacja. 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.



Rysunek 1: Jedna iteracja algorytmu KMP. Przesunięcie shift=jP[j] potencjalnego początku wystąpienia wzorca gdy x[j+1]y[i+j+1].

Wersja on-line algorytmu KMP

Przedstawimy teraz wersję on-line algorytmu KMP. Wczytujemy kolejne symbole y 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


0  j:=0;
1  repeat forever
2    read(symbol);
3    while j>1 and x[j+1]symbol do j:=P[j];
4    j:=j+1;
5    if j=m then 
6      write(1); j:=P[m];
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 x=aaaaa oraz y=am1b, to delay(m)=O(1), delay(m)=Θ(m).

Z lematu o okresowości wynika, że zachodzi następujący fakt:

delay(m)=Θ(logm)

Uzasadnienie pozostawiamy jako ćwiczenie.



Obliczanie Tablicy Silnych Prefikso-Sufiksów

Algorytm liczenia silnych prefikso-sufiksów bazuje na następującej relacji między P a P':

(t=P[j] oraz x[t+1]x[j+1])  P[j]=t


(t=P[j], t0, oraz x[t+1]=x[j+1])  P[j]=P[t]


Nie musimy obliczać tablicy P; potrzebna jest jedynie ostatnia wartość t=P[j], którą obliczamy on-line.


Algorytm Silne-Prefikso-Sufiksy


1  P[0]:=1; t:=1;
2 for j:= 1 to m do
3 while t0 and x[t+1]x[j]do 4 t:=P[t]; 5 t:=t+1; 6 if j=m or x[t+1]x[j+1] 7 then P[j]:=t else P[j]:=P[t];


Gdy weźmiemy x=abam2 to
P[0]=1, P[1]=0, P[2]=1, oraz dla 3jm P[j]=1.
Jest to jest pesymistyczny przypadek dla algorytmu Silne-Prefikso-Sufiksy, algorytm wykonuje wtedy 3m5 porównań symboli.