]> mj.ucw.cz Git - ads2.git/commitdiff
Vyhledavani v textu: korektury od Petra Jankovskeho.
authorMartin Mares <mj@ucw.cz>
Thu, 4 Feb 2010 12:05:39 +0000 (13:05 +0100)
committerMartin Mares <mj@ucw.cz>
Thu, 4 Feb 2010 12:05:39 +0000 (13:05 +0100)
6-kmp/6-kmp.tex
6-kmp/barb.eps
6-kmp/barb.svg

index 223d92dbeab3ff3e04e599ead43531170313973a..9719f787f55ca6b86838236a01cd6facf26b4201 100644 (file)
@@ -2,18 +2,18 @@
 
 \prednaska{6}{Vyhledávání v~textu}{(zapsal: Petr Jankovský)}
 
-Nyní se budeme vìnovat následujícímu problému. V~textu délky $S$ (Senu) budeme chtít najít v¹echny výskyty hledaného slova délky $J$ (Jehly). Nejprve se podívejme na~jeden primitivní algoritmus, který nefunguje. Je ale zajímavé rozmyslet si, proè.
+Nyní se budeme vìnovat následujícímu problému: v~textu délky $S$ (senì) budeme chtít najít v¹echny výskyty hledaného slova délky $J$ (jehly). Nejprve se podívejme na~jeden primitivní algoritmus, který nefunguje. Je ale zajímavé rozmyslet si, proè.
 
 \h{Hloupý algoritmus} 
 Zaèneme prvním písmenkem hledaného slova a~budeme postupnì procházet text, a¾ najdeme první výskyt poèáteèního písmenka. Poté budeme testovat, zda souhlasí i~písmenka dal¹í. Pokud nastane neshoda, v~hledaném slovì se vrátíme na~zaèátek a~v~textu pokraèujeme znakem, ve~kterém neshoda nastala. Podívejme se na~pøíklad.
 
-\s{Pøíklad:} Budeme hledat slovo |jehla| v~textu |jevkupcejejehla|. Vezmeme si tedy první písmenko |j| v~hledaném slovì a~zjistíme, ¾e v~textu se nachází hned na~zaèátku. Vezmeme tedy dal¹í písmenko |e|, které se vyskytuje jako druhé i~v~textu. Pøi tøetím písmenku ale narazíme na~neshodu. V~tuto chvíli tedy zresetujeme a~opìt hledáme výskyt písmenka |j|, tentokrát v¹ak a¾ od~tøetího písmene v~textu. Takto postupujeme postupnì dál, a¾ narazíme na~dal¹í |je|, které ov¹em není následováno písmenem |h|, tudí¾ opìt zresetujeme a~nakonec najdeme shodu s~celým hledaným øetìzcem. V~tomto pøípadì tedy algoritmus na¹el hledané slovo.
+\s{Pøíklad:} Budeme hledat slovo |jehla| v~textu |jevkupcejejehla|. Vezmeme si tedy první písmenko |j| v~hledaném slovì a~zjistíme, ¾e v~textu se nachází hned na~zaèátku. Vezmeme tedy dal¹í písmenko |e|, které se vyskytuje jako druhé i~v~textu. Pøi tøetím písmenku ale narazíme na~neshodu. V~tuto chvíli tedy zresetujeme a~opìt hledáme výskyt písmenka |j|, tentokrát v¹ak a¾ od~tøetího písmene v~textu. Takto postupujeme postupnì dál, a¾ narazíme na~dal¹í |je|, které ov¹em není následováno písmenem~|h|, tudí¾ opìt zresetujeme a~nakonec najdeme shodu s~celým hledaným øetìzcem. V~tomto pøípadì tedy algoritmus na¹el hledané slovo.
 
-Tento algoritmus v¹ak zjevnì mù¾e hanebnì selhat. Mù¾e se stát, ¾e zaèneme porovnávat, a¾ v~jednu chvíli narazíme na~neshodu. Celý tento kus tedy pøeskoèíme. Pøi tom se ale v~tomto kusu textu mohl vyskytovat nìjaký pøekrývající se výskyt hledané \uv{jehly}. Hledejme napøíklad øetìzec |kokos| v~textu |clanekokokosu|. Algoritmus tedy zaène porovnávat. Ve~chvíli kdy najde prefix |koko| a~na~vstupu dostane |k|, dochází k~neshodì. Proto algoritmus zresetuje a~pokraèuje v~hledání od~tohoto znaku. Najde sice je¹tì výskyt |ko|, ov¹em s~dal¹ím písmenkem |s| ji¾ dochází k~neshodì a~algotimus sel¾e. Nesprávnì se toti¾ \uv{upnul} na~první nalezené |koko| a~s~dal¹ím |k| pak \uv{zahodil} i~správný zaèátek.
+Tento algoritmus v¹ak zjevnì mù¾e hanebnì selhat. Mù¾e se stát, ¾e zaèneme porovnávat, a¾ v~jednu chvíli narazíme na~neshodu. Celý tento kus tedy pøeskoèíme. Pøi tom se ale v~tomto kusu textu mohl vyskytovat nìjaký pøekrývající se výskyt hledané \uv{jehly}. Hledejme napøíklad øetìzec |kokos| v~textu |clanekokokosu|. Algoritmus tedy zaène porovnávat. Ve~chvíli kdy najde prefix |koko| a~na~vstupu dostane~|k|, dochází k~neshodì. Proto algoritmus zresetuje a~pokraèuje v~hledání od~tohoto znaku. Najde sice je¹tì výskyt |ko|, ov¹em s~dal¹ím písmenkem |s| ji¾ dochází k~neshodì a~algotimus sel¾e. Nesprávnì se toti¾ \uv{upnul} na~první nalezené |koko| a~s~dal¹ím~|k| pak \uv{zahodil} i~správný zaèátek.
 
-Máme tedy algoritmus, který i~kdy¾ je ¹patnì, tak funguje urèitì kdykoli se první písmenko hledaného slova v~tomto slovì u¾ nikde jinde nevyskytuje - co¾ |jehla| splòovala, ale |kokos| u¾ ne.
+Máme tedy algoritmus, který i~kdy¾ je ¹patnì, tak funguje urèitì kdykoli se první písmenko hledaného slova v~tomto slovì u¾ nikde jinde nevyskytuje -- co¾ |jehla| splòovala, ale |kokos| u¾ ne.
 
-{\I Hloupý algoritmus} se na~ka¾dé písmenko textu podívá jednou, tudí¾ èasová slo¾itost bude lineární s~délkou textu ve~kterém hledáme - tedy $\O(S)$.
+{\I Hloupý algoritmus} se na~ka¾dé písmenko textu podívá jednou, tudí¾ èasová slo¾itost bude lineární s~délkou textu ve~kterém hledáme -- tedy $\O(S)$.
 
 \h{Pomalý algoritmus}
 Zkusíme algoritmus vylep¹it tak, aby fungoval správnì: pokud nastane nìjaká neshoda, vrátíme se zpátky tìsnì za~zaèátek toho, kdy se nám to zaèalo shodovat. To je ov¹em vlastnì skoro toté¾, jako brát postupnì v¹echny mo¾né zaèátky v~\uv{senì} a~pro ka¾dý z~nìj ovìøit, jestli se tam \uv{jehla} nachází èi nikoliv.
@@ -25,15 +25,15 @@ Nyn
 \h{Chytrý algoritmus}
 Ne¾ vlastní algoritmus vybudujeme, zkusíme se cestou nauèit pøemý¹let o~øetìzcích obèas trochu pøekrouceným zpùsobem. Podívejme se na~je¹tì jeden pøíklad.
 
-\s{Pøíklad:}Vezmìme si napøíklad staré italské pøízvisko |barbarossa|, které znamená Rudovous. Pøedstavme si, ¾e takovéto slovo hledáme v~nìjakém textu, který zaèíná |barbar|. Víme, ¾e a¾ sem se nám hledaný øetìzec shodoval. Øeknìme, ¾e dal¹í písmenko textu se shodovat pøestane - místo |o| naèteme napøíklad opìt |b|. {\I Hloupý algoritmus} by velil vrátit se k~|a| a~od~nìj hledat dál. Uvìdomme si ale, ¾e kdy¾ se vracíme z~|barbar| do~|arbar| (tedy øetìzce, který ji¾ známe), mù¾eme si pøedpoèítat, jak dopadne hledání, kdy¾ ho pustíme na~nìj. V~pøedpoèítaném bychom tedy chtìli ukládat, ¾e kdy¾ máme øtìzec |arbar|, tak |ar| a~|r| nám do~hledaného nepasuje a~a¾~|bar| se bude shodovat. Tedy místo toho, abychom spustili nové hledání od~|a|, mù¾eme ho spustit a¾~od~|b|. Co víc, my dokonce víme, jak dopadne to - pokud toti¾ nastane neshoda po~pøeètení |barbar|, je to stejné, jako kdybychom pøeèetli pouze |bar|, na~které se (pùvodne neshodující se) |b| u¾ navázat dá. Kdyby se nedalo navázat ani tam, tak bychom opìt zkracovali... Nejen, ¾e tedy víme, kam se máme vrátit, ale víme dokonce i~to, co tam najdeme. 
+\s{Pøíklad:} Vezmìme si napøíklad staré italské pøízvisko |barbarossa|, které znamená Rudovous. Pøedstavme si, ¾e takovéto slovo hledáme v~nìjakém textu, který zaèíná |barbar|. Víme, ¾e a¾ sem se nám hledaný øetìzec shodoval. Øeknìme, ¾e dal¹í písmenko textu se shodovat pøestane -- místo |o| naèteme napøíklad opìt |b|. {\I Hloupý algoritmus} by velil vrátit se k~|a| a~od~nìj hledat dál. Uvìdomme si ale, ¾e kdy¾ se vracíme z~|barbar| do~|arbar| (tedy øetìzce, který ji¾ známe), mù¾eme si pøedpoèítat, jak dopadne hledání, kdy¾ ho pustíme na~nìj. V~pøedpoèítaném bychom tedy chtìli ukládat, ¾e kdy¾ máme øetìzec |arbar|, tak |ar| a~|r| nám do~hledaného nepasuje a~a¾~|bar| se bude shodovat. Tedy místo toho, abychom spustili nové hledání od~|a|, mù¾eme ho spustit a¾~od~|b|. Co víc, my dokonce víme, jak dopadne to -- pokud toti¾ nastane neshoda po~pøeètení |barbar|, je to stejné, jako kdybychom pøeèetli pouze |bar|, na~které se (pùvodne neshodující se) |b| u¾ navázat dá. Kdyby se nedalo navázat ani tam, tak bychom opìt zkracovali... Nejen, ¾e tedy víme, kam se máme vrátit, ale víme dokonce i~to, co tam najdeme. 
 
 My¹lenka, ke které míøíme, je pøedpoèítat si nìjakou tabulku, která nám bude øíkat, jak se máme pøi hledání vracet a~jak to dopadne, a~pak u¾ jenom prohlédávat s~pou¾itím této tabulky. 
 
-Aby se nám o~pøepisových algoritmech lépe mluvilo a~pøedev¹ím psalo, pojïme si povìdìt nìkolik definic.
+Aby se nám o~tìchto algoritmech lépe mluvilo a~pøedev¹ím psalo, pojïme si povìdìt nìkolik definic.
  
 \s{Definice:}
 \itemize\ibull
-\:{\I Abeceda $\Sigma$} je koneèná mno¾ina znakù \foot{Mù¾eme pøi tom jít a¾~do~extrémù. Pøíkladem extrémních abeced je binární abeceda slo¾ená pouze z~nul a~jednièek. Pøíklad z~druhého konce (který rádi dìlají lingvisti) je abeceda, která má jako abecedu v¹echna èeská slova. V¹echny èeské vìty, pak nejsou nic jiného, ne¾ slova nad touto abecedou. Pou¾itá abeceda tedy mù¾e být i~relativnì obrovská. Dal¹ím takovým pøíkladem mù¾e být unicode. Pro na¹e potøeby ale zatím budeme pøedpokládat, ¾e abeceda je nejen konstantnì velká, ale i~rozumnì malá. Budeme si moci tedy dovolit napøíklad indexovat pole znakem abecedy (kdybychom nemohli, tak bychom místo pole pou¾ili napøíklad hashovací tabulku, èi nìco podobného\dots) .}, ze~kterých tvoøíme text, øetìzce, slova. 
+\:{\I Abeceda $\Sigma$} je koneèná mno¾ina znakù\foot{Mù¾eme pøi tom jít a¾~do~extrémù. Pøíkladem extrémních abeced je binární abeceda slo¾ená pouze z~nul a~jednièek. Pøíklad z~druhého konce (který rádi dìlají lingvisti) je abeceda, která má jako abecedu v¹echna èeská slova. V¹echny èeské vìty, pak nejsou nic jiného, ne¾ slova nad touto abecedou. Pou¾itá abeceda tedy mù¾e být i~relativnì obrovská. Dal¹ím takovým pøíkladem mù¾e být Unicode. Pro na¹e potøeby ale zatím budeme pøedpokládat, ¾e abeceda je nejen konstantnì velká, ale i~rozumnì malá. Budeme si moci tedy dovolit napøíklad indexovat pole znakem abecedy (kdybychom nemohli, tak bychom místo pole pou¾ili napøíklad hashovací tabulku, èi nìco podobného\dots).}, ze~kterých tvoøíme text, øetìzce, slova. 
 
 
 \:{\I $\Sigma^*$} je mno¾ina v¹ech slov nad abecedou $\Sigma$. Èili mno¾ina v¹ech neprázdných koneèných posloupností znakù ze $\Sigma$.
@@ -42,31 +42,32 @@ Aby se n
 Aby se nám nepletlo znaèení, budeme rozli¹ovat promìnné pro slova, promìnné pro písmenka a~promìnné pro èísla.
 
 \itemize\ibull
-\:{\I Slova} budeme znaèit malými písmenky øecké abecedy $\alpha$,$\beta$... .
+\:{\I Slova} budeme znaèit malými písmenky øecké abecedy $\alpha$, $\beta$, ... .
 \:$\iota$ bude oznaèovat \uv{jehlu}
 \:$\sigma$ bude oznaèovat \uv{seno}
-\:{\I Znaky} oznaèíme malými písmeny latinky $a$,$b$\dots .
-\:{\I Èísla} budeme znaèit velkými písmeny $A$, $B$\dots .
+\:{\I Znaky} oznaèíme malými písmeny latinky $a$, $b$, \dots .
+\:{\I Èísla} budeme znaèit velkými písmeny $A$, $B$\dots .
 \:{\I Délka slova} $\vert \alpha  \vert$ pro $\alpha \in \Sigma^*$ je poèet jeho znakù.
 \:{\I Prázdné slovo} znaèíme písmenem $\varepsilon$, $\vert \varepsilon \vert = 0$.
 \:{\I Zøetìzení} $\alpha\beta$ vznikne zapsáním slov $\alpha$ a~$\beta$ za sebe. Platí $\vert \alpha\beta  \vert=\vert \alpha \vert+\vert \beta \vert$, $\alpha\varepsilon=\varepsilon\alpha=\alpha$.
 \:$\alpha[k]$ je $k$-tý znak slova $\alpha$, indexujeme od~$0$.
-\:$\alpha[k:l]$ je podslovo zaèínající $k$-tým znakem a~$l$-tý znak je první, který v~nìm není. Jedná se tedy o~podslovo skládající se z~$\alpha[k]$,$\alpha[k+1]$,\dots,$\alpha[l-1]$. Platí tedy: $\alpha[k:k]=\varepsilon$, $\alpha[k:k+1]=\alpha[k]$. Jednu (èi obì) meze mù¾eme i~vynechat, tento zápis pak bude znamenat buï \uv{od zaèátku slova a¾ nìkam}, nebo \uv{od nìkud a¾ do~konce}.
-\:$\alpha[:k]$ je {\I prefix} obsahující prvních $k$ znakù slova $\alpha$ ($\alpha[0]$,\dots,$\alpha[k-1]$) .
+\:$\alpha[k:l]$ je podslovo zaèínající $k$-tým znakem a~$l$-tý znak je první, který v~nìm není. Jedná se tedy o~podslovo skládající se z~$\alpha[k]$,$\alpha[k+1]$,\dots,$\alpha[l-1]$. Platí tedy: $\alpha[k:k]=\varepsilon$, $\alpha[k:k+1]=\alpha[k]$. Jednu (èi obì) meze mù¾eme i~vynechat, tento zápis pak bude znamenat buï \uv{od zaèátku slova a¾ nìkam}, nebo \uv{odnìkud a¾ do~konce}:
+%TODO - zaøadit pod pøedchozí bod
+\:$\alpha[:k]$ je {\I prefix} obsahující prvních $k$ znakù slova $\alpha$ ($\alpha[0]$,\dots,$\alpha[k-1]$).
 \:$\alpha[k:]$ je {\I suffix} obsahující znaky slova $\alpha$ poèínaje $k$-tým znakem a¾ do~konce.
 \:$\alpha[:] = \alpha$
 \endlist
 
+
 V¹imnìme si, ¾e prázdné slovo je prefixem, suffixem i~podslovem jekéhokoliv slova vèetnì sebe sama.
-Ka¾dé slovo je také prefixem, suffixem i~podslovem sebe sama.
-To se nám nìkdy nebude hodit. Nìkdy budeme chtít øíct, ¾e nìjaké slovo je {\I vlastním} prefixem nebo suffixem. To bude znamenat, ¾e to nebude celé slovo.
+Ka¾dé slovo je také prefixem, suffixem i~podslovem sebe sama. To se ne v¾dy hodí. Nìkdy budeme chtít øíct, ¾e nìjaké slovo je {\I vlastním} prefixem nebo suffixem. To bude znamenat, ¾e to nebude celé slovo.
 
 \> $\alpha$ je {\I vlastní prefix} slova $\beta \equiv \alpha$ je prefix $\beta~\&~\alpha \neq \beta$.
 
 \h{Vyhledávací automat (Knuth, Morris, Pratt)}
-{\I Vyhledávací automat} bude graf, jeho¾ vrcholùm budeme øíkat stavy. Jejich jména budou prefixy hledaného slova a~hrany budou odpovídat tomu, jak jeden prefix mù¾eme získat z~pøedchozího prefixu pøidáním jednoho stavu. Poèáteèní stav je prázdné slovo $\varepsilon$ a~koncový je celá $\iota$. Dopøedné hrany grafu budou popisovat pøechod mezi stavy ve~smyslu zvìt¹ení délky jména stavu (dopøedná funkce $h(\alpha)$, urèující znak na~dopøedné hranì z~$\alpha$). Zpìtné hrany grafu budou popisovat pøechod (zpìtná funkce $z(\alpha)$) mezi stavem $\alpha$ a~nejdel¹ím vlastním suffixem $\alpha$, který je prefixem $\iota$, kdy¾ nastane neshoda.
+{\I Vyhledávací automat} bude graf, jeho¾ vrcholùm budeme øíkat {\I stavy}. Jejich jména budou prefixy hledaného slova a~hrany budou odpovídat tomu, jak jeden prefix mù¾eme získat z~pøedchozího prefixu pøidáním jednoho stavu. Poèáteèní stav je prázdné slovo $\varepsilon$ a~koncový je celá $\iota$. Dopøedné hrany grafu budou popisovat pøechod mezi stavy ve~smyslu zvìt¹ení délky jména stavu (dopøedná funkce $h(\alpha)$, urèující znak na~dopøedné hranì z~$\alpha$). Zpìtné hrany grafu budou popisovat pøechod (zpìtná funkce $z(\alpha)$) mezi stavem $\alpha$ a~nejdel¹ím vlastním suffixem $\alpha$, který je prefixem $\iota$, kdy¾ nastane neshoda.
 
-\figure{barb.eps}{Vyhledávací automat.}{4.2in}
+\figure{barb.eps}{Vyhledávací automat.}{4.1in}
 
 \s{Hledej($\sigma$):}
 \algo
@@ -78,39 +79,40 @@ To se n
 \endalgo
 
 \>Vstupem je $\iota$, hledané slovo (jehla) délky $J=\vert \iota \vert$ a~$\sigma$, text (seno) délky $S=\vert \sigma \vert$.
-\>Výstupem jsou v¹echny výskyty hledaného slova $\iota$ v~textu $\sigma$: $\left\{ k\vert \sigma[k:k+J]=\iota \right\}$
+\>Výstupem jsou v¹echny výskyty hledaného slova $\iota$ v~textu $\sigma$, tedy mno¾ina $\left\{ k \mid \sigma[k:k+J]=\iota \right\}$
 
 Pojïme nyní dokázat, ¾e tento algoritmus správnì ohlásí v¹echny výskyty.
 
 \s{Definice}: $\alpha(\tau) := $ stav automatu po~pøeètení $\tau$
 
 \s{Invariant:} Pokud algoritmus pøeète nìjaký vstup, nachází se ve~stavu, který je nejdel¹ím suffixem pøeèteného vstupu, který je nìjakým stavem.
-$\alpha(\tau) =$ nejdel¹í stav (nejdel¹í prefix jehly), který je suffixem $\tau$ (pøeèteného vstupu). 
+$\alpha(\tau) =$ nejdel¹í stav (nejdel¹í prefix jehly), který je suffixem $\tau$ (pøeèteného vstupu).
+
 Pojïme si rozmyslet, ¾e z~tohoto invariantu ihnet plyne, ¾e algoritmus najde to, co má. Kdykoli toti¾ ohlásí nìjaký výskyt, tak tam tento výskyt opravdu je. Kdykoli pak má nìjaký výskyt ohlásit, tak se v~této situaci jako suffix toho právì pøeèteného textu vyskytuje hledané slovo, pøièem¾ hledané slovo je urèitì stav a~zároveò nejdel¹í ze v¹ech existujících stavù. Tak¾e invariant opravdu øíká, ¾e jsme právì v~koncovém stavu a~algoritmus nám tedy ohlásí výskyt.
 
-\proof
-Indukcí podle kroku algoritmu. Na~zaèátku pro prázdný naètený vstup invariant triviálnì platí, tedy prázdný suffix $\tau$ je prefixem $\iota$. V~kroku $n$ máme naètený vstup $\tau$ a~k~nìmu naèteme znak $x$. Invariant nám øíká, ¾e nejdel¹í stav, který je suffixem, je nejdel¹í suffix, který je stavem. Nyní se ptáme, jaký je nejdel¹í stav, který se dá \uv{napasovat} na~konec øetìzce $\tau x$. Kdykoli v¹ak takovýto suffix máme, tak z~nìj mù¾eme $x$ na~konci odebrat, èím¾ dostaneme suffix slova $\tau$.
+\proof {\I (invariantu)}
+Indukcí podle kroku algoritmu. Na~zaèátku pro prázdný naètený vstup invariant triviálnì platí, tedy prázdný suffix $\tau$ je prefixem $\iota$. V~kroku $n$ máme naètený vstup $\tau$ a~k~nìmu pøipojíme znak $x$. Invariant nám øíká, ¾e nejdel¹í stav, který je suffixem, je nejdel¹í suffix, který je stavem. Nyní se ptáme, jaký je nejdel¹í stav, který se dá \uv{napasovat} na~konec øetìzce $\tau x$. Kdykoli v¹ak takovýto suffix máme, tak z~nìj mù¾eme $x$ na~konci odebrat, èím¾ dostaneme suffix slova $\tau$.
 
 \>Tedy: pokud $\beta$ je neprázdným suffixem slova $\tau x$, pak $\beta = \gamma x$, kde $\gamma$ je suffix $\tau$.
 
-Suffix, který máme sestrojit, tedy vznikne z~nìjakého suffixu slova $\tau$ pøipsáním~$x$. Chceme najít nejdel¹í suffix slova $\tau x$, který je stavem, tak¾e chceme najít i~nejdel¹í suffix pùvodního slova $\tau$, za který se dá pøidat $x$ tak, aby vy¹lo jméno stavu. Staèí tedy u¾ jen \uv{probírat} suffixy $\tau$ od~nejdel¹ího po~nejkrat¹í, zkou¹et k~nim pøidávat $x$ a~a¾ to pùjde, tak jsme na¹li nejdel¹í suffix $\tau x$. Pøesnì toto ov¹em algoritmus dìlá, nebo» zpìtná funkce mu v¾dy øekne nejbli¾¹í krat¹í suffix, který je stavem. Pokud pak nemù¾eme $x$ pøidat ani do~$\varepsilon$, pak je øe¹ením prázdný suffix. Algoritmus tedy funguje. \qed
+Suffix, který máme sestrojit, tedy vznikne z~nìjakého suffixu slova $\tau$ pøipsáním~$x$. Chceme najít nejdel¹í suffix slova $\tau x$, který je stavem, tak¾e chceme najít i~nejdel¹í suffix pùvodního slova $\tau$, za který se dá pøidat $x$ tak, aby vy¹lo jméno stavu. Staèí tedy u¾ jen \uv{probírat} suffixy slova $\tau$ od~nejdel¹ího po~nejkrat¹í, zkou¹et k~nim pøidávat $x$ a~a¾ to pùjde, tak jsme na¹li nejdel¹í suffix $\tau x$. Pøesnì toto ov¹em algoritmus dìlá, nebo» zpìtná funkce mu v¾dy øekne nejbli¾¹í krat¹í suffix, který je stavem. Pokud pak nemù¾eme $x$ pøidat ani do~$\varepsilon$, pak je øe¹ením prázdný suffix. Algoritmus tedy funguje. \qed
 
 Nyní pojïmì zkoumat to, jak je ve~skuteènosti ná¹ algoritmus rychlý. K tomu bychom si ale nejdøív mìli øíct, jak pøesnì budeme automat reprezentovat. V~algoritmu vystupují nìjaká porovnávání stavù, pøièem¾ není úplnì jasné, jak zaøídit, aby v¹e trvalo konstantnì dlouho. Vyjde nám to ale docela snadno. K reprezentaci automatu nám toti¾ budou staèit pouze dvì pole.
 
 \s{Reprezentace automatu:}
-Oèíslujeme si stavy délkami pøíslu¹ných prefixù tedy $0 \dots J$. Poté je¹tì potøebujeme nìjakým zpùsobem zakódovat dopøedné a~zpìtné hrany. Vzhledem k~tomu, ¾e z~ka¾dého vrcholu vede v¾dy nejvý¹e jedna dopøedná a~nejvý¹e jedna zpìtná, tak nám evidentnì staèí pamatovat si pro ka¾dý typ hran pouze jedno èíslo na~vrchol. Budeme mít tedy nìjaké pole dopøedných hran, které nám pro ka¾dý stav øekne, jakým písmenkem je nadepsaná dopøedná hrana ze stavu $I$ do~$I+1$. To jsou ale pøesnì písmenka jehly, tak¾e si staèí pamatovat jehlu samotnou. Èili z~$I$ do~$I+1$ vede hrana nadepsaná $\iota [I]$. Pro zpìtné hrany pak budeme potøebovat pole $Z$, které nám pro stav $I$ øekne èíslo stavu, do~kterého vede zpìtná hrana. Tedy $Z[I]$ je cíl zpìtné hrany ze stavu $I$.
+Oèíslujeme si stavy délkami pøíslu¹ných prefixù, tedy $0 \dots J$. Poté je¹tì potøebujeme nìjakým zpùsobem zakódovat dopøedné a~zpìtné hrany. Vzhledem k~tomu, ¾e z~ka¾dého vrcholu vede v¾dy nejvý¹e jedna dopøedná a~nejvý¹e jedna zpìtná, tak nám evidentnì staèí pamatovat si pro ka¾dý typ hran pouze jedno èíslo na~vrchol. Budeme mít tedy nìjaké pole dopøedných hran, které nám pro ka¾dý stav øekne, jakým písmenkem je nadepsaná dopøedná hrana ze stavu $I$ do~$I+1$. To jsou ale pøesnì písmenka jehly, tak¾e si staèí pamatovat jehlu samotnou. Èili z~$I$ do~$I+1$ vede hrana nadepsaná $\iota [I]$. Pro zpìtné hrany pak budeme potøebovat pole $Z$, které nám pro stav $I$ øekne èíslo stavu, do~kterého vede zpìtná hrana. Tedy $Z[I]$ je cíl zpìtné hrany ze stavu $I$.
 S~touto reprezentací ji¾ doká¾eme na¹i hledací proceduru pøímoèaøe pøepsat tak, aby sahala pouze do~tìchto dvou polí:
 \algo
 \:$I \leftarrow 0$.
 \:Pro znaky $x$ z~textu:
 \:$\indent$Dokud $\iota[I] \neq x~\&~I \neq 0: I \leftarrow Z[I]$.
-\:$\indent$Pokud $\iota[I] = x, pak I \leftarrow I + 1$.
+\:$\indent$Pokud $\iota[I] = x$, pak $I \leftarrow I + 1$.
 \:$\indent$Pokud $I = J$, ohlásíme výskyt.
 \endalgo
 
-Zatím se v~algoritmu je¹tì skrývá drobná chyba -- toti¾ algoritmus se obèas zeptá na~dopøednou hranu z~posledního stavu. Pokud jsme právì ohlásili výskyt (jsme tedy v~posledním stavu) a~pøijde nìjaký dal¹í znak, algoritmus se ptá, zda je roven tomu, co je na~dopøedné hranì z~posledního stavu. Ta ale ov¹em neexistuje. Jednodu¹e to ale napravíme tak, ¾e si pøidáme fiktivní hranu, na~které se vyskytuje nìjaké \uv{nepísmenko} -- nìco co se nerovná ¾ádnému jinému písmenku. Zajistíme tak, ¾e se po~této hranì nikdy nevydáme. Dodefinujeme tedy $\iota[J]$ odli¹nì od~v¹ech znakù. (V jazyce C se toto dodefinování provede vlastnì zadarmo, nebo» ka¾dý øetìzec je v~nìm ukonèen znakem s~kódem nula, který se ve~vstupu nevyskytne\dots Algoritmus bude tedy fungovat i~bez tohoto dodefinování. V jiných jazycích je ale tøeba na~nìj nezapomenout!)
+Zatím se v~algoritmu je¹tì skrývá drobná chyba -- toti¾ algoritmus se obèas zeptá na~dopøednou hranu z~posledního stavu. Pokud jsme právì ohlásili výskyt (jsme tedy v~posledním stavu) a~pøijde nìjaký dal¹í znak, algoritmus se ptá, zda je roven tomu, co je na~dopøedné hranì z~posledního stavu. Ta ale ov¹em neexistuje. Jednodu¹e to ale napravíme tak, ¾e si pøidáme fiktivní hranu, na~které se vyskytuje nìjaké \uv{nepísmenko} -- nìco, co se nerovná ¾ádnému jinému písmenku. Zajistíme tak, ¾e se po~této hranì nikdy nevydáme. Dodefinujeme tedy $\iota[J]$ odli¹nì od~v¹ech znakù.\foot{V jazyce C se toto dodefinování provede vlastnì zadarmo, nebo» ka¾dý øetìzec je v~nìm ukonèen znakem s~kódem nula, který se ve~vstupu nevyskytne\dots Algoritmus bude tedy fungovat i~bez tohoto dodefinování. V jiných jazycích je ale tøeba na~nìj nezapomenout!}
 
-\s{Lemma:} Funkce Hledej bì¾í v~èase $\O(S)$.
+\s{Lemma:} Funkce {\I Hledej} bì¾í v~èase $\O(S)$.
 
 \proof
 Funkce {\I Hledej} chodí po~dopøedných a~zpìtných hranách. Dopøedných hran projdeme urèitì maximálnì tolik, kolik je délka sena. Pro ka¾dý znak pøeètený ze sena toti¾ jdeme nejvý¹e jednou po~dopøedné hranì. Se zpìtnými hranami se to má tak, ¾e na~jeden pøeètený znak z~textu se mù¾eme po~zpìtné hranì vracet maximálnì $J$-krát. Z~tohoto by nám v¹ak vy¹la slo¾itost $\O(JS)$, èím¾ bychom si nepomohli. Zachrání nás ale pøímoèarý potenciál. Uvìdomme si, ¾e chùze po~dopøedné hranì zvý¹í $I$ o~jedna a~chùze po~zpìtné hranì $I$ sní¾í alespoò o~jedna. Vzhledem k~tomu, ¾e $I$ není nikdy záporné a~na~zaèátku je nulové, zjistíme, ¾e krokù zpìt mù¾e být maximálnì tolik, kolik krokù dopøedu. Èasová slo¾itost hledání je tedy lineární vzhledem k~délce sena. \qed
@@ -119,149 +121,157 @@ Nyn
 
 \s{Pozorování:}
 Pøedstavme si, ¾e automat u¾ máme hotový a~tím, ¾e budeme sledovat jeho chování, chceme zjistit, jak v~nìm vedou zpìtné hrany.
-Vezmìme si nìjaký stav~$\beta$. To, kam z~nìj vede zpìtná hrana zjistíme tak, ¾e spustíme automat na~øetìzec $\beta$~bez prvního písmenka a~stav, ve~kterém se automat zastaví je pøesnì ten, kam má vést i~zpìtná hrana z~$\beta$. Jinými slovy víme, ¾e $z(\beta) = \alpha (\beta[1:])$. 
+Vezmìme si nìjaký stav~$\beta$. To, kam z~nìj vede zpìtná hrana zjistíme tak, ¾e spustíme automat na~øetìzec $\beta$~bez prvního písmenka a~stav, ve~kterém se automat zastaví, je pøesnì ten, kam má vést i~zpìtná hrana z~$\beta$. Jinými slovy víme, ¾e $z(\beta) = \alpha (\beta[1:])$. 
 Proè takováto vìc funguje? V¹imìme si, ¾e definice $z$ a~to, co nám o~$\alpha$ øíká invariant je témìø toto¾ná -- $z(\beta)$ je nejdel¹í vlastní suffix $\beta$, který je stavem, $\alpha(\beta)$ je nejdel¹í suffix $\beta$, který je stavem. Jediná odli¹nost je v~tom, ¾e definice $z$ narozdíl od~definice $\alpha$ zakazuje nevlastní suffixy. Jak nyní vylouèit suffix $\beta$, který by byl roven $\beta$ samotné? Zkrátíme $\beta$ o~první znak. Tím pádem v¹echny suffixy $\beta$ bez prvního znaku jsou stejné jako v¹echny vlastní suffixy $\beta$.
 
-K èemu je toto pozorování dobré? Rozmysleme si, ¾e pomocí nìj u¾ doká¾eme zkonstruovat zpìtné hrany. Není to ale trochu divné, kdy¾ pøi simulování automatu na~øetìzec bez prvního znaku u¾ zpìtné hrany potøebujeme? Není. Za chvíli zjistíme, ¾e takto mù¾eme zji¹»ovat zpìtné hrany postupnì s~tím, ¾e pou¾íváme v¾dy jenom ty, které jsme u¾ sestrojili. 
-Takovémuhle pøístupu, kdy pøi konstruování chtìného u¾ pou¾íváme to, co chceme sestrojit, ale pouze ten kousek, který ji¾ máme hotový, se v~angliètinì øíká {\I bootstrapping}\foot{Z~tohoto slova vzniklo i~{\I bootování} poèítaèù, kdy operaèní systém v~podstatì zavádí sám seme. Bootstrap znamená èesky ¹truple -- tedy oèko na~konci boty, které slou¾í k~usnadnìní nazouvání. A~jak souvisí ¹truple s~algoritmem? To se zase musíme vrátit k~pøíbìhùm o~baronu Prá¹ilovi, mezi nimi¾ je i~ten, ve~kterém baron Prá¹il vypráví o~tom, jak sám sebe vytáhl z~ba¾iny za ¹truple. Stejnì tak i~my budeme algoritmus konstruovat tím, ¾e se budeme sami vytahovat za ¹truple, tedy bootstrappovat.}.
+K èemu je toto pozorování dobré? Rozmysleme si, ¾e pomocí nìj u¾ doká¾eme zkonstruovat zpìtné hrany. Není to ale trochu divné, kdy¾ pøi simulování automatu na~øetìzec bez prvního znaku u¾ zpìtné hrany potøebujeme? Není. Za chvíli zjistíme, ¾e takto mù¾eme zji¹»ovat zpìtné hrany postupnì -- a~to tak, ¾e pou¾íváme v¾dy jenom ty, které jsme u¾ sestrojili.
+Takovémuhle pøístupu, kdy pøi konstruování chtìného u¾ pou¾íváme to, co chceme sestrojit, ale pouze ten kousek, který ji¾ máme hotový, se v~angliètinì øíká {\I bootstrapping}\foot{Z~tohoto slova vzniklo i~{\I bootování} poèítaèù, kdy operaèní systém v~podstatì zavádí sám sebe. Bootstrap znamená èesky ¹truple -- tedy oèko na~konci boty, které slou¾í k~usnadnìní nazouvání. A~jak souvisí ¹truple s~algoritmem? To se zase musíme vrátit k~pøíbìhùm o~baronu Prá¹ilovi, mezi nimi¾ je i~ten, ve~kterém baron Prá¹il vypráví o~tom, jak sám sebe vytáhl z~ba¾iny za ¹truple. Stejnì tak i~my budeme algoritmus konstruovat tím, ¾e se budeme sami vytahovat za ¹truple, tedy bootstrappovat.}.
 V¹imnìme si, ¾e pøi výpoètu se vstupem $\beta$ projde automat jenom prvních $\vert \beta  \vert$ stavù. Automat se evidentnì nemù¾e dostat dál, proto¾e na~ka¾dý krok dopøedu (doprava) spotøebuje písmenko $\beta$. Tak¾e krokù doprava je maximálnì tolik, kolik je  $\vert \beta  \vert$. Jinými slovy kdybychom ji¾ mìli zkonstruované zpìtné hrany pro prvních  $\vert \beta  \vert$ stavù (tedy $0 \dots \vert \beta  \vert - 1$), tak pøi tomto výpoètu, který potøebujeme na~zkonstruování zpìtné hrany z~$\beta$, je¹tì tuto zpìtnou hranu nemù¾eme potøebovat. Vystaèíme si s~tìmi, které ji¾ máme zkonstruované.
-Nabízí se tedy zaèít zpìtnou hranou z~prvního znaku (která vede evidentnì do~$\varepsilon$), pak postupnì brát dal¹í stavy a~pro ka¾dý z~nich si spoèítáme, kdy spustíme automat na~jméno stavu bez prvního znaku a~tím získáme dal¹í zpìtnou hranu. Toto funguje, ale je to kvadratické \dots. Máme toti¾ $J$ stavù a~pro ka¾dý z~nich nám automat bì¾í v~èase a¾ lineárním s~$J$. Jak z~toho ven?
-Z~prvního stavu povede zpìtná funkce do~$\varepsilon$. Pro dal¹í stavy chceme spoèítat zpìtnou funkci. Z~druhého stavu $\iota[0:2]$ tedy automat spustíme na~$\iota[1:2]$, dále pak na~$\iota[1:3]$, $\iota[1:4]$, atd. Ty øetìzce, pro které potøebujeme spo¹tìt automat, abychom dostali zpìtné hrany, jsou tedy ve~skuteènosti takové, ¾e ka¾dý dal¹í dostaneme roz¹íøením pøedchozího o~jeden znak. To jsou ale pøesnì ty stavy, kterými projde automat pøi spracovávání øetezce $\iota$ od~prvního znaku dál. Jedním prùchodem automatu nad jehlou bez prvního písmenka, se tím pádem rovnou dozvíme v¹echny údaje, které potøebujeme.
-Z~pøedchozího pozorování plyne, ¾e nikdy nebudeme potøebovat zpìtnou hranu, kterou jsme je¹tì nezkonstruovali a~jeliko¾ víme, ¾e jedno prohledání trvá lineárnì s~délkou toho v~èem hledáme, tak toto celé pobì¾í v~lineárním èase. Dostaneme tedy následující algoritmus:
+
+Nabízí se tedy zaèít zpìtnou hranou z~prvního znaku (která vede evidentnì do~$\varepsilon$), pak postupnì brát dal¹í stavy a~pro ka¾dý z~nich si spoèítat, kdy spustíme automat na~jméno stavu bez prvního znaku a~tím získáme dal¹í zpìtnou hranu. Toto funguje, ale je to kvadratické \dots. Máme toti¾ $J$ stavù a~pro ka¾dý z~nich nám automat bì¾í v~èase a¾ lineárním s~$J$. Jak z~toho ven?
+
+Z~prvního stavu povede zpìtná funkce do~$\varepsilon$. Pro dal¹í stavy chceme spoèítat zpìtnou funkci. Z~druhého stavu $\iota[0:2]$ tedy automat spustíme na~$\iota[1:2]$, dále pak na~$\iota[1:3]$, $\iota[1:4]$, atd. Ty øetìzce, pro které potøebujeme spo¹tìt automat, abychom dostali zpìtné hrany, jsou tedy ve~skuteènosti takové, ¾e ka¾dý dal¹í dostaneme roz¹íøením pøedchozího o~jeden znak. To jsou ale pøesnì ty stavy, kterými projde automat pøi zpracovávání øetezce $\iota$ od~prvního znaku dál. Jedním prùchodem automatu nad jehlou bez prvního písmenka se tím pádem rovnou dozvíme v¹echny údaje, které potøebujeme.
+Z~pøedchozího pozorování plyne, ¾e nikdy nebudeme potøebovat zpìtnou hranu, kterou jsme je¹tì nezkonstruovali a~jeliko¾ víme, ¾e jedno prohledání trvá lineárnì s~délkou toho, v~èem hledáme, tak toto celé pobì¾í v~lineárním èase. Dostaneme tedy následující algoritmus:
 
 \s{Konstrukce zpìtné funkce:}
 \algo
 \:$Z[0] \leftarrow ?$, $Z[1] \leftarrow 0$.
 \:$I \leftarrow 0$.
-\:pro $k = 2$ do~$J$.
-\:$\indent$$I \leftarrow krok( I , \iota [k])$.
-\:$\indent$$Z[k] \leftarrow I$.
+\:Pro $k = 2 \dots J$:
+\::$I \leftarrow Krok( I , \iota [k])$.
+\::$Z[k] \leftarrow I$.
 \endalgo
 
-Zaèínáme tím, ¾e nastavíme zpìtnou hranu z~prvních dvou stavù, pøièem¾ $z[0]$ je nedefinované, proto¾e tuto zpìtnou hranu nikdy nepou¾íváme. Dále postupnì simulujeme výpoèet automatu nad slovem bez prvního znaku a~po ka¾dém kroku se dozvíme novou zpìtnou hranu. {\I Krokem} automatu pak není nic jiného ne¾ vnitøek (3. a~4. bod) na¹í hledací procedury. To, kam jsme se dostali pak zaznamenáme jako zpìtnou funkci z~$k$.
+Zaèínáme tím, ¾e nastavíme zpìtnou hranu z~prvních dvou stavù, pøièem¾ $z[0]$ je nedefinované, proto¾e tuto zpìtnou hranu nikdy nepou¾íváme. Dále postupnì simulujeme výpoèet automatu nad slovem bez prvního znaku a~po ka¾dém kroku se dozvíme novou zpìtnou hranu. {\I Krokem} automatu pak není nic jiného ne¾ vnitøek (3. a~4. bod) na¹í hledací procedury. To, kam jsme se dostali, pak zaznamenáme jako zpìtnou funkci z~$k$.
 Èili pou¹tíme automat na~jehlu bez prvního písmenka, provedeme v¾dy jeden krok automatu (pøes dal¹í písmenko jehly) a~zapamatujeme si, jakou zpìtnou funkci jsme zrovna dostali. Díky pozorováním navíc víme, ¾e zpìtné hrany konstruujeme správnì, nikdy nepou¾ijeme zpìtnou hranu, kterou jsme je¹tì nesestrojili a~víme i~to, ¾e celou konstrukci zvládneme v~lineárním èase s~délkou jehly.
 
 \s{Vìta:} Algoritmus KMP najde v¹echny výskyty v~èase $O(J+S)$.
+
+\proof
 Linéární èas s~délkou jehly potøebujeme na~postavení automatu, lineární èas s~délkou sena pak potøebujeme na~samotné vyhledání.
 
+\h{Rabinùv-Karpùv algoritmus}
+
 Nyní si uká¾eme je¹tì jeden algoritmus na~hledání jedné jehly, který nebude mít v~nejhor¹ím pøípadì lineární slo¾itost, ale bude ji mít prùmìrnì. Bude daleko jednodu¹¹í a~uká¾e se, ¾e je v~praxi daleko rychlej¹í. Bude to algoritmus zalo¾ený na~hashování.
 
-\h{Rabinùv -- Karpùv algoritmus}
 
-Pøedstavme si, ¾e máme seno délky $S$ a~jehlu délky $J$ a~vezmìme si nìjakou hashovací funkci, které dáme na~vstup $J$-tice znakù (tedy podslova dlouhá jako jehla). Tato hashovací funkce nám je pak zobrazí do~nìjaké velké mno¾iny èísel. Jak nám toto pomù¾e pøi hledání jehly? Vezmeme si libovolné \uv{okénko} délky $J$ a~ne¾ budeme zji¹»ovat, zda se v~nìm jehla vyskytuje, tak si spoèítáme hashovací fci a~porovnáme jí s~hashem jehly. Èili ptáme se, jestli je hash ze sena od~nìjaké pozice $I$ do~pozice $I+J$ roven hashi jehly -- formálnì: $h(\sigma [I: I+J ]) = h(\iota)$. Teprve tehdy, kdy¾ zjistíme, ¾e se hodnota hashovací fce shoduje, tak zaèneme doopravdy porovnávat øetìzce.
+Pøedstavme si, ¾e máme seno délky $S$ a~jehlu délky $J$, a~vezmìme si nìjakou hashovací funkci, které dáme na~vstup $J$-tici znakù (tedy podslova dlouhá jako jehla). Tato hashovací funkce nám je pak zobrazí do~nìjaké velké mno¾iny èísel. Jak nám toto pomù¾e pøi hledání jehly? Vezmeme si libovolné \uv{okénko} délky $J$ a~ne¾ budeme zji¹»ovat, zda se v~nìm jehla vyskytuje, tak si spoèítáme hashovací funkci a~porovnáme ji s~hashem jehly. Èili ptáme se, jestli je hash ze sena od~nìjaké pozice $I$ do~pozice $I+J$ roven hashi jehly -- formálnì: $h(\sigma [I: I+J ]) = h(\iota)$. Teprve tehdy, kdy¾ zjistíme, ¾e se hodnota hashovací fce shoduje, zaèneme doopravdy porovnávat øetìzce.
 
-Není to ale nìjaká hloupost? Mù¾e nám vùbec takováto konstrukce pomoct? Není to tak, ¾e na~spoèítání hashovací funkce z~$J$ znakù, potøebujeme tìch $J$ znakù pøeèíst, co¾ je stejnì rychlé, jako rovnou øetìzce porovnávat? Pou¾ijeme trik, který bude spoèívat v~tom, ¾e si zvolíme ¹ikovnou hashovací funkci. Udìláme to tak, abychom jí mohli pøi posunutí \uv {okénka} o~jedna doprava v~konstantním èase pøepoèítat. Chceme umìt z~$h(x_1 \dots x_j)$ spoèítat $h(x_2 \dots x_{j+1})$.
-Na~zaèátku si tedy spoèítáme hash jehly a~první $J$-tice znakù sena. Pak ji¾ jenom posouváme \uv {okénko} o~jedna, pøepoèítáme hashovací funkci a~kdy¾ se shoduje s~hashem jehly, tak porovnáme. Budeme pøitom vìøit tomu, ¾e pokud se tam jehla nevyskytuje, pak máme hashovací funkci natolik rovnomìrnou, ¾e pravdìpodobnost toho, ¾e se pøesto strefíme do~hashe od~jehly, je $1/N$. Jinými slovy jenom v~jednom z~øádovì $N$ pøípadù budeme porovnávat fale¹nì -- tedy provedeme porovnání a~vyjde nám, ¾e výsledek je neshoda. V~prùmìrném pøípadì tedy mù¾eme stlaèit slo¾itost a¾ témìø k~lineární.
+Není to ale nìjaká hloupost? Mù¾e nám vùbec takováto konstrukce pomoci? Není to tak, ¾e na~spoèítání hashovací funkce z~$J$ znakù, potøebujeme tìch $J$ znakù pøeèíst, co¾ je stejnì rychlé, jako rovnou øetìzce porovnávat? Pou¾ijeme trik, který bude spoèívat v~tom, ¾e si zvolíme ¹ikovnou hashovací funkci. Udìláme to tak, abychom ji mohli pøi posunutí \uv {okénka} o~jeden znak doprava v~konstantním èase pøepoèítat. Chceme umìt z~$h(x_1 \dots x_j)$ spoèítat $h(x_2 \dots x_{j+1})$.
+Na~zaèátku si tedy spoèítáme hash jehly a~první $J$-tice znakù sena. Pak ji¾ jenom posouváme \uv {okénko} o~jedna, pøepoèítáme hashovací funkci a~kdy¾ se shoduje s~hashem jehly, tak porovnáme. Budeme pøitom vìøit tomu, ¾e pokud se tam jehla nevyskytuje, pak máme hashovací funkci natolik rovnomìrnou, ¾e pravdìpodobnost toho, ¾e se pøesto strefíme do~hashe jehly, je $1/N$. Jinými slovy jenom v~jednom z~øádovì $N$ pøípadù budeme porovnávat fale¹nì -- tedy provedeme porovnání a~vyjde nám, ¾e výsledek je neshoda. V~prùmìrném pøípadì tedy mù¾eme stlaèit slo¾itost a¾ témìø k~lineární.
 
-Podívejme se teï na~prùmìrnou èasovou slo¾itost. Budeme urèitì potøebovat èas na~projití jehly a~sena. Navíc strávíme nìjaký èas nad fale¹nými porovnáními, kterých bude v~prùmìru na~ka¾dý $N$-tý znak sena jedno porovnání s~jehlou -- tedy $SJ / N$, pøièem¾ $N$ mù¾eme zvolit dost velké na~to, abychom tento èlen dostali pod nìjakou rozumnou konstantu... Nakonec budeme potøebovat jedno porovnání na~ka¾dý opravdový výskyt, èemu¾ se nevyhneme. Pøipoèteme tedy je¹tì $J \cdot \sharp výskytù$. Dostáváme tedy: $ \O(J+S+SJ/N+J \cdot \sharp výskytù)$.
+Podívejme se teï na~prùmìrnou èasovou slo¾itost. Budeme urèitì potøebovat èas na~projití jehly a~sena. Navíc strávíme nìjaký èas nad fale¹nými porovnáními, kterých bude v~prùmìru na~ka¾dý $N$-tý znak sena jedno porovnání s~jehlou -- tedy $SJ / N$, pøièem¾ $N$ mù¾eme zvolit dost velké na~to, abychom tento èlen dostali pod nìjakou rozumnou konstantu... Nakonec budeme potøebovat jedno porovnání na~ka¾dý opravdový výskyt, èemu¾ se nevyhneme. Pøipoèteme tedy je¹tì $J \cdot$ {\I $\sharp$výskytù}. Dostáváme tedy: $ \O(J+S+SJ/N+J \cdot$ {\I $\sharp$výskytù}).
 
 Zbývá malièkost -- toti¾ kde vzít hashovací funkci, která toto v¹e splòuje. Jednu si uká¾eme. Bude to vlastnì takový hezký polynom:
-$$h(x_1 \dots x_j) := (\sum_{I=1}^{J} x_I \cdot p^{J-I})~mod~N$$
+$$h(x_1 \dots x_j) := \left(\sum_{I=1}^{J} x_I \cdot p^{J-I}\right) \bmod N.$$
 Jinak zapsáno se tedy jedná o:
-$$(x_1 \cdot p^{J-1} + x_2 \cdot p^{J-2} + \dots + x_J \cdot p^0 )~mod~N$$
-Po posunutí okénka o~jedna, pak chceme dostat:
-$$(x_2 \cdot p^{J-1} + x_3 \cdot p^{J-2} + \dots + x_J \cdot p^1 + x_{J+1} \cdot p^0 )~mod~N$$
-Kdy¾ se ale podíváme na~èleny tìchto dvou polynomù, zjistíme, ¾e se li¹í jen o~málo. Pùvodní polynom staèí pøenásobit $p$, odeèíst první èlen s~$x_1$ a~naopak pøièíst chybìjící èlen $x_{J+1}$. Dostáváme tedy:
-$$h(x_2 \dots x_{J+1}) = (p \cdot h(x_1 \dots x_J) - x_1 \cdot p^J + x_{J+1})~mod~N$$
-Pøepoèítání hashovací funkce tedy není nc jiného, ne¾ pøenásobení té minulé $p$, odeètení nìjakého násobku toho znaku, který vypadl z~okénka a~pøiètení toho znaku, o~který se okénko posunulo. Pokud tedy máme k~dispozici aritmetické operace v~konstantním èase, zvládneme konstantnì pøepoèítávat i~hashovací funkci.
+$$(x_1 \cdot p^{J-1} + x_2 \cdot p^{J-2} + \dots + x_J \cdot p^0 ) \bmod N.$$
+Po posunutí okénka o~jedna chceme dostat:
+$$(x_2 \cdot p^{J-1} + x_3 \cdot p^{J-2} + \dots + x_J \cdot p^1 + x_{J+1} \cdot p^0 ) \bmod N.$$
+Kdy¾ se ale podíváme na~èleny tìchto dvou polynomù, zjistíme, ¾e se li¹í jen o~málo. Pùvodní polynom staèí pøenásobit~$p$, odeèíst první èlen s~$x_1$ a~naopak pøièíst chybìjící èlen $x_{J+1}$. Dostáváme tedy:
+$$h(x_2 \dots x_{J+1}) = (p \cdot h(x_1 \dots x_J) - x_1 \cdot p^J + x_{J+1}) \bmod N.$$
+Pøepoèítání hashovací funkce tedy není nic jiného, ne¾ pøenásobení té minulé~$p$, odeètení nìjakého násobku toho znaku, který vypadl z~okénka a~pøiètení toho znaku, o~který se okénko posunulo. Pokud tedy máme k~dispozici aritmetické operace v~konstantním èase, zvládneme konstantnì pøepoèítávat i~hashovací funkci.
 
-Tato hashovací funkce se dokonce nejen hezky poèítá, ale dokonce se i~opravdu \uv{hezky} chová (tedy \uv{rozumnì} náhodnì), pokud zvolíme vhodné $p$. To bychom mìli zvoli tak, aby bylo rozhodnì nesoudìlné s~$N$ -- tedy $NSD(p, N) = 1$. Aby se nám navíc dobøe projevilo modulo obsa¾ené v~hashovací funkci, mìlo by být $p$ relativnì velké (lze dopoèítat, ¾e optimum je mezi 2/3 a~3/4 $N$). S~takto zvoleným $p$ se tato hashovací funkce chová velmi pøíznivì a~v~praxi má celý algoritmus takøka lineární èasovou slo¾itost (prùmìrnou).
+Tato hashovací funkce se dokonce nejen hezky poèítá, ale dokonce se i~opravdu \uv{hezky} chová (tedy \uv{rozumnì} náhodnì), pokud zvolíme vhodné~$p$. To bychom mìli zvoli tak, aby bylo rozhodnì nesoudìlné s~$N$ -- tedy $\<NSD>(p, N) = 1$. Aby se nám navíc dobøe projevilo modulo obsa¾ené v~hashovací funkci, mìlo by být~$p$ relativnì velké (lze dopoèítat, ¾e optimum je mezi $2/3 \cdot N$ a~$3/4 \cdot N$). S~takto zvoleným~$p$ se tato hashovací funkce chová velmi pøíznivì a~v~praxi má celý algoritmus takøka lineární èasovou slo¾itost (prùmìrnou).
 
-\h{Více jehel}
+\h{Hledání více øetìzcù najednou}
 Nyní si zahrajeme tuté¾ hru, ov¹em v~trochu slo¾itìj¹ích kulisách. Podíváme se na~algoritmus, který si poradí i~s více ne¾ jednou jehlou. 
 Mìjme tedy jehly $\iota_1 \dots \iota_n$, a~jejich délky $J_i = \vert \iota_i \vert $. Dále budeme potøebovat seno $\sigma$ délky $S=\vert \sigma \vert$.
 
 Pøedtím, ne¾ se pustíme do~vlastního vyhledávacího algoritmu, mo¾ná bychom si mìli ujasnit, co vlastnì bude jeho výstupem. U problému hledání jedné jehly to bylo jasné -- byla to nìjaká mno¾ina pozic v~senì, na~kterých zaèínaly výskyty jehly. Jak tomu ale bude zde? Sice bychom také mohly vrátit pouze mno¾inu pozic, ale my budeme chtít malièko víc. Budeme toti¾ chtít vìdìt i~to, která jehla se na~které pozici vyskytuje. Výstup tedy bude vypadat následovnì: $V = \{(i,j)~\vert~\sigma[i:i+J_j]= \iota_j \}$.
 
-Zde se v¹ak skrývá jedna drobná zrada. Budeme se asi muset vzdát nadìje, ¾e najdeme algoritmus, jeho¾ slo¾itost je lineární v~délce v¹ech jehel a~sena. Výstup toti¾ mù¾e být del¹í ne¾ lineární. Mù¾e se nám klidnì stát, ¾e na~jedné pozici v~senì se bude vyskytovat více rùzných jehel -- pokud bude jedna jehla prefixem jiné (co¾ jsme nikde nezakázali), tak máme povinost ohlásit oba výskyty. Vzhledem k~tomu budeme hledat takový algoritmus, který bude lineární v~délce vstupu plus délce výstupu, co¾ je evidentnì to nejlep¹í, èeho mù¾eme dosáhnout.
+Zde se v¹ak skrývá jedna drobná zrada. Budeme se asi muset vzdát nadìje, ¾e najdeme algoritmus, jeho¾ slo¾itost je lineární v~celkové délce v¹ech jehel a~sena. Výstup toti¾ mù¾e být del¹í ne¾ lineární. Mù¾e se nám klidnì stát, ¾e na~jedné pozici v~senì se bude vyskytovat více rùzných jehel -- pokud bude jedna jehla prefixem jiné (co¾ jsme nikde nezakázali), tak máme povinost ohlásit oba výskyty. Vzhledem k~tomu budeme hledat takový algoritmus, který bude lineární v~délce vstupu plus délce výstupu, co¾ je evidentnì to nejlep¹í, èeho mù¾eme dosáhnout.
 
-Algoritmus, který si nyní uká¾eme, vymysleli nìkdy v~70. letech pan Aho a~paní Corasicková. Bude to takové zobecnìní Knuthova-Morrisova-Prattova algoritmu.
+Algoritmus, který si nyní uká¾eme, vymysleli nìkdy v~roce 1975 pan Aho a~paní Corasicková. Bude to takové zobecnìní Knuthova-Morrisova-Prattova algoritmu.
 
 \h{Algoritmus Aho-Corasicková}
 
-Opìt se budeme sna¾it sestrojit nìjaký vyhledávací automat a~nìjakým zpùsobem tento automat pou¾ít k~procházení sena. Podívejme se nejprve na~pøíklad. Budeme chtít vyhledávat tato slova: {\I ARA, BAR, ARAB, BARABA, BARBARA}. Mìjme tedy tìchto pìt jehel a~rozmysleme si, jak by vypadal nìjaký automat, který by tato slova umìl zatím jenom rozpoznávat. Pro jedno slovo automat vypadal jako cestièka, zde u¾ to bude strom. 
+Opìt se budeme sna¾it sestrojit nìjaký vyhledávací automat a~nìjakým zpùsobem tento automat pou¾ít k~procházení sena. Podívejme se nejprve na~pøíklad. Budeme chtít vyhledávat tato slova: |ara|, |bar|, |arab|, |baraba|, |barbara|. Mìjme tedy tìchto pìt jehel a~rozmysleme si, jak by vypadal nìjaký automat, který by tato slova umìl zatím jenom rozpoznávat. Pro jedno slovo automat vypadal jako cesta, zde u¾ to bude strom. (viz obrázek).
 
 \figure{ara_strom_blank.eps}{Vyhledávací automat -- strom.}{1in}
 
-Navíc budeme muset do~automatu zanést, kde nìjaké slovo konèí. V~pùvodním automatu pro jedno slovo to bylo jednoduché -- ono jedno jediné slovo odpovídalo poslednímu vrcholu cesty. Tady se v¹ak slova mohou vyskytovat vícekrát a~konèit nejenom v~listech ale i~v~nìjakém vnitøním vrcholu (co¾ se stane tehdy, pokud je jedno hledané slovo prefixem jiného hledaného slova). Formálnì to nebudeme dokazovat, ale snadno nahlédneme, ¾e listy stromu urèitì odpovídají hledaným slovùm, ale opaènì to neplatí.
+Navíc budeme muset do~automatu zanést, kde nìjaké slovo konèí. V~pùvodním automatu pro jedno slovo to bylo jednoduché -- ono jedno jediné slovo odpovídalo poslednímu vrcholu cesty. Tady se v¹ak slova mohou vyskytovat vícekrát a~konèit nejenom v~listech ale i~v~nìjakém vnitøním vrcholu (co¾ se stane tehdy, pokud je jedno hledané slovo prefixem jiného hledaného slova). Formálnì to nebudeme dokazovat, ale snadno nahlédneme, ¾e listy stromu odpovídají hledaným slovùm, ale opaènì to neplatí.
 
 \figure{ara_strom_end.eps}{Vyhledávací automat s~konci slov.}{1in}
 
-Dále bychom mìli do~automatu pøidat zpìtné hrany. Jejich definice bude úplnì stejná jako u automatu pro hledání jednoho slova. Jinými slovy z~ka¾dého stavu pùjde zpìtná hrana do~nejdel¹ího vlastního suffixu, který je stavem. Èili kdy¾ budeme mít nìjaké jméno stavu, budeme se ho sna¾it co nejménì (ale alespoò o~znak) zkrátit zleva, abychom zase dostali jméno stavu. Z~koøene -- prázdného stavu pak evidentnì ¾ádná zpìtná hrana nepovede.
+Dále bychom mìli do~automatu pøidat zpìtné hrany. Jejich definice bude úplnì stejná jako u automatu pro hledání jednoho slova. Jinými slovy z~ka¾dého stavu pùjde zpìtná hrana do~nejdel¹ího vlastního suffixu, který je stavem. Èili kdy¾ budeme mít nìjaké jméno stavu, budeme se ho sna¾it co nejménì (ale alespoò o~znak) zkrátit zleva, abychom zase dostali jméno stavu. Z~koøene -- prázdného stavu -- pak evidentnì ¾ádná zpìtná hrana nepovede.
 
-\figure{ara_strom_final.eps}{Vyhledávací automat se zpìtnými hranami.}{1in}
+\figure{ara_strom_final.eps}{Vyhledávací automat se zpìtnými hranami.}{1,25in}
 
-Zbývá nám si je¹tì rozmyslet, jakým zpùsobem bude ná¹ automat hlásit výstup. Opìt smìøujeme k~tomu, aby se automat po~pøeètení nìjakého kusu textu, nacházel ve~stavu odpovídajícímu nejdel¹ímu mo¾nému suffixu toho textu. Zatímco u hledání pouze jedné jehly bylo hlá¹ení výskytù jednoduché a~to prostì tím, ¾e jsme se dostali na~konec \uv{automatové cestièky}, tady to bude opìt slo¾itìj¹í.
+Zbývá nám je¹tì si rozmyslet, jakým zpùsobem bude ná¹ automat hlásit výstup. Opìt smìøujeme k~tomu, aby se automat po~pøeètení nìjakého kusu textu nacházel ve~stavu odpovídajícímu nejdel¹ímu mo¾nému suffixu toho textu. Zatímco u hledání jediné jehly bylo hlá¹ení výskytù jednoduché -- kdykoliv jsme se dostali na~konec \uv{automatové cestièky} tady to bude opìt slo¾itìj¹í.
 
-První, co se nabízí, je vyu¾ít toho, ¾e jsme si oznaèili nìjaké vrcholy, kde hledaná slova konèí. Co tedy zkusit hlásit výskyt tohoto slova v¾dy, kdy¾ pøijdeme do~nìjakého oznaèeného vrcholu? Tento zpùsob v¹ak nefunguje, pokud se uvnitø nìkteré jehly skrývá jehla vnoøená. Napøíklad po~pøeètení slova {\I BARA}, nám ná¹ souèasný automat neøíká, ¾e bychom mìli nìjaké slovo ohlásit a~pøitom tam evidentì konèí podøetìzec {\I ARA}. Stejnì tak pokud pøeèteme {\I BARBARA}, u¾ si nev¹imneme toho, ¾e tam konèí zároveò i~{\I ARA}. Pouhé \uv{hlá¹ení teèek} tedy nefunguje.
+První, co se nabízí, je vyu¾ít toho, ¾e jsme si oznaèili nìjaké vrcholy, kde hledaná slova konèí. Co tedy zkusit hlásit výskyt tohoto slova v¾dy, kdy¾ pøijdeme do~nìjakého oznaèeného vrcholu? Tento zpùsob v¹ak nefunguje, pokud se uvnitø nìkteré jehly skrývá jehla vnoøená. Napøíklad po~pøeètení slova |bara|, nám ná¹ souèasný automat neøíká, ¾e bychom mìli nìjaké slovo ohlásit, a~pøitom tam evidentì konèí podøetìzec |ara|. Stejnì tak pokud pøeèteme |barbara|, u¾ si nev¹imneme toho, ¾e tam konèí zároveò i~|ara|. Pouhé \uv{hlá¹ení teèek} tedy nefunguje.
 
-Dále si mù¾eme v¹imnout toho, ¾e v¹echna slova, která by se mìla v~daném stavu hlásit, jsou suffixy toho jména stavu. Pøi tom víme, ¾e zpìtná hrana nám zkracuje zleva. Tak¾e speciálnì v¹echny suffixy daného stavu, které jsou také stavy, se dají najít tak, ¾e se z~toho stavu, kde právì jsme, vydáme po~zpìtných hranách do~koøene. Nabízí se tedy v¾dy projít cestu po~zpìtných hranách a¾ do~koøene a~hlásit v¹echny \uv{teèky}. Tento zpùsob by nám v¹ak celý algoritmus znaènì zpomalilo, proto¾e cestièka do~koøene mù¾e být relativnì dlouhá, ale teèek na~ní mù¾e být jen málo.
+Dále si mù¾eme v¹imnout toho, ¾e v¹echna slova, která by se mìla v~daném stavu hlásit, jsou suffixy jména tohoto stavu. Pøi tom víme, ¾e zpìtná hrana jméno stavu zkracuje zleva. Tak¾e speciálnì v¹echny suffixy daného stavu, které jsou také stavy, se dají najít tak, ¾e se vydáme po~zpìtných hranách do~koøene. Nabízí se tedy v¾dy projít cestu po~zpìtných hranách a¾ do~koøene a~hlásit v¹echny \uv{teèky}. Tento zpùsob by nám v¹ak celý algoritmus znaènì zpomalilo, proto¾e cesta do~koøene mù¾e být relativnì dlouhá, ale teèek na~ní obvykle bude málo.
 
-Mohli bychom také zkusit si pro ka¾dý stav $\beta$ pøedpoèítat mno¾inu $cache(\beta)$, která by obsahovala v¹echna slova, která máme hlásit, kdy¾ se ve~stavu $\beta$ nacházíme. Pokud pak do~tohoto stavu vstoupíme, podíváme se na~tuto mno¾inu a~vypí¹eme v¹e, co v~ní je. Výpis nám bude evidentì trvat lineárnì k~velikosti mno¾inky, celkovì pak tedy lineárnì k~velikosti výstupu. Problém je ale ten, ¾e jednotlivé cache mohou být hodnì velké.
+Mohli bychom také zkusit si pro ka¾dý stav $\beta$ pøedpoèítat mno¾inu $cache(\beta)$, která by obsahovala v¹echna slova, která máme hlásit, kdy¾ se ve~stavu $\beta$ nacházíme. Pokud pak do~tohoto stavu vstoupíme, podíváme se na~tuto mno¾inu a~vypí¹eme v¹e, co v~ní je. Výpis nám bude evidentnì trvat lineárnì k~velikosti mno¾iny, celkovì tedy lineárnì k~velikosti výstupu. Problém je ale ten, ¾e jednotlivé cache mohou být hodnì velké, tak¾e je nestihneme sestrojit v lineárním èase. (Rozmyslete si pøíklad slovníku, kdy se to stane.)
 
 To, co nám ale ji¾ opravdu pomù¾e, bude zavedení zkratek. V¹imli jsme si, ¾e po~zpìtných hranách mù¾eme projít do~koøene a~hlásit v¹echny nalezené teèky. Vadilo nám ale, ¾e se mù¾e stát, ¾e budeme dlouho po~cestì chodit a~pøi tom ¾ádné teèky nenalézat. Zavedeme si proto zkratky k~nejbli¾¹í teèce. 
 
 \s{Definice} (zkratková hrana):
-Budeme mít tedy nìjakou funkci $slovo(\beta) :=$ slovo, které konèí ve~stavu $\beta$ (nebo $\emptyset$, pokud ¾ádné takové slovo není). Dále pak funkci $out(\beta) :=$ nejbli¾¹í vrchol dosa¾itelný po~zpìtných hranách, èili nejdel¹í vlastní suffix stavu $\beta$, v~nìm¾ je definovaná funkce $slovo$. Trochu lid¹tìji øeèeno, ten nejbli¾¹í dosa¾itelný vrchol, v~kterém je teèka.
+Budeme mít tedy nìjakou funkci $slovo(\beta) :=$ slovo, které konèí ve~stavu $\beta$ (nebo $\emptyset$, pokud ¾ádné takové slovo není). Dále pak funkci $out(\beta) :=$ nejbli¾¹í vrchol dosa¾itelný po~zpìtných hranách, èili nejdel¹í vlastní suffix stavu $\beta$, v~nìm¾ je definovaná funkce $slovo$. Trochu lid¹tìji øeèeno, ten nejbli¾¹í dosa¾itelný vrchol, ve~kterém je teèka.
 
-Po pøidání tìchto zkratkových hran ji¾ máme reprezentaci, v~které opravdu umíme v~daném stavu vyjmenovat v¹echna slova, která máme vypsat a~to v~èase lineárním s~tím, kolik tìch slov je.
+Po pøidání tìchto zkratkových hran ji¾ máme reprezentaci, ve~které opravdu umíme v~daném stavu vyjmenovat v¹echna slova, která máme vypsat, a~to v~èase lineárním s~tím, kolik tìch slov je.
 
 \s{Definice:}
 Vyhledávací automat sestává ze stromu dopøedných hran (vrcholy jsou prefixy jehel, hrany odpovídají roz¹íøení o~písmenko), zpìtných hran ($z(\beta) :=$ nejdel¹í vlastní suffix slova $\beta$, který je stavem) a~zkratkových hran.
 
 Automat pak bude na~na¹em pøíkladu vypadat takto (zkratkové hrany jsou znázornìny zelenì):
 
-\figure{ara_strom_zkr.eps}{Vyhledávací automat se zkratkovými hranami.}{1in}
+\figure{ara_strom_zkr.eps}{Vyhledávací automat se zkratkovými hranami.}{1,25in}
 
-Nyní u¾ nám zbývá jenom vlastní algoritmus -- nejdøív popí¹eme algoritmus, který bude hledat pomocí takového automatu a~potom se pustíme do~toho, jak se takový automat staví.
+Nyní u¾ nám zbývá jenom vlastní algoritmus -- nejdøív popí¹eme algoritmus, který bude hledat pomocí takového automatu, a~potom se pustíme do~toho, jak se takový automat staví.
 
-Nejprve si nadefinujeme, jak vypadá jeden krok automatu. Bude to vlastnì nìjaká funkce, která dostane stav a~písmenko. Ona nás pak pomocí tohoto písmenka posune po~automatu. ($f(\alpha, x)$ bude dopøedná hrana ze stavu $\alpha$ oznaèená písm$x$)
+Nejprve si nadefinujeme, jak vypadá jeden krok automatu. Bude to vlastnì nìjaká funkce, která dostane stav a~písmenko. Ona nás pak pomocí tohoto písmenka posune po~automatu. ($f(\alpha, x)$ bude dopøedná hrana ze stavu $\alpha$ oznaèená písmenem~$x$)
 
 \s{Krok ($\alpha$, $x$):}
 \algo
-\:Dokud $f(\alpha, x) = \emptyset~\&~\alpha \neq koøen:~~\alpha \leftarrow z(\alpha)$.
+\:Dokud $f(\alpha, x) = \emptyset~\&~\alpha \neq \<koøen:>~~\alpha \leftarrow z(\alpha)$.
 \:Pokud $f(\alpha, x) \neq \emptyset:~~\alpha \leftarrow f(\alpha, x)$.
 \:Vrátíme výsledek.
 \endalgo
 
 \s{Hledání:}
 \algo
-\:$\alpha \leftarrow koøen$.
+\:$\alpha \leftarrow \<koøen>$.
 \:Pro znaky $x$ ze slova $\sigma$:
-\::$\alpha \leftarrow krok(\alpha, x)$.
-\::Dokud $\alpha \neq \emptyset$:
-\:::Je-li $slovo(\alpha) \neq \emptyset$:
-\::::ohlásíme $slovo(\alpha)$.
-\::::$\alpha \leftarrow out(\alpha)$.
+\::$\alpha \leftarrow Krok(\alpha, x)$.
+\::$\beta \leftarrow \alpha$
+\::Dokud $\beta \neq \emptyset$:
+\:::Je-li $\<slovo>(\beta) \neq \emptyset$:
+\::::Ohlásíme $\<slovo>(\beta)$.
+\::::$\beta \leftarrow \<out>(\beta)$.
 \endalgo
 
-Algoritmus hledání vlastnì není nic jiného, ne¾ prosté projití po~zelených zkratkových hranách ze stavu $\alpha$ ve~kterém právì jsme a~ohlá¹ení v¹eho, co po~cestì najdeme.
+Algoritmus hledání vlastnì není nic jiného, ne¾ prosté projití po~zelených zkratkových hranách ze stavu $\alpha$, ve~kterém právì jsme, a~ohlá¹ení v¹eho, co po~cestì najdeme.
 
-V ka¾dém okam¾iku se automat nachází ve~stavu, který odpovídá nejmen¹ímu mo¾nému suffixu toho, co jsme u¾ pøeèetli. Dùkaz tohoto invariantu je stejný jako u verze automatu pro hledání pouze jedné jehly, nebo» vychází pouze z~definice zpìtných hran. Podobnì nahlédneme, ¾e èasová slo¾itost vyhledávací procedury je lineární v~délce sena plus to, co spotøebujeme na~hlá¹ení výskytù. Nejprve na~chvíli zapomeneme, ¾e nìjaké výskyty hlásíme a~spoèítáme jenom kroky. Ty mohou vézt dopøedu a~zpátky. Kroky dopøedu prodlu¾uje jméno stavu o~jedna, krok dozadu zkracuje aspoò o~jedna. Tudí¾ krokù dozadu je maximálnì tolik, co krokù dopøedu a~krokù dopøedu je maximálnì tolik, kolik je délka sena. V¹echny kroky dohromady tedy trvají $\O(S)$. Hlá¹ení výskytù pak trvá $\O(S~+ \vert V \vert)$. Velé hledání tedy trvá lineárnì v~délce vstupu a~výstupu.
+V ka¾dém okam¾iku se automat nachází ve~stavu, který odpovídá nejmen¹ímu mo¾nému suffixu toho, co jsme u¾ pøeèetli. Dùkaz tohoto invariantu je stejný jako u verze automatu pro hledání pouze jedné jehly, nebo» vychází pouze z~definice zpìtných hran. Podobnì nahlédneme, ¾e èasová slo¾itost vyhledávací procedury je lineární v~délce sena plus to, co spotøebujeme na~hlá¹ení výskytù. Nejprve na~chvíli zapomeneme, ¾e nìjaké výskyty hlásíme a~spoèítáme jenom kroky. Ty mohou vést dopøedu a~zpátky. Krok dopøedu prodlu¾uje jméno stavu o~jedna, krok dozadu zkracuje aspoò o~jedna. Tudí¾ krokù dozadu je maximálnì tolik, co krokù dopøedu a~krokù dopøedu je maximálnì tolik, kolik je délka sena. V¹echny kroky dohromady tedy trvají $\O(S)$. Hlá¹ení výskytù pak trvá $\O(S~+ \vert V \vert)$. Velé hledání tedy trvá lineárnì v~délce vstupu a~výstupu.
 
-Zbývá nám u¾ jen konstrukce automatu. Opìt pou¾ijeme trik, ¾e zpìtná hrana ze stavu $\beta$ vede tam, kam by se dostal automat pøi hledání $\beta$ bez prvního písmenka. Tak¾e zase chceme nìco, jako simulovat výpoèet toho automatu na~slovech bez prvního písmenka a~doufat v~to, ¾e si vystaèíme s~tou èástí automatu, kterou jsme u¾ postavili. Tentokrát to v¹ak nemù¾eme dìlat jedno slovo po~druhém, proto¾e zpìtné hrany mohou vést rùznì køí¾em mezi jednotlivými vìtvemi automatu. Mohlo by se nám tedy klidnì stát, ¾e pøi hledání nìjakého slova potøebujeme zpìtnou hranu, která vede vlastnì do~jiného slova, které jmse je¹tì nezkonstruovali. Tak¾e tento postup sel¾e. Mù¾eme v¹ak vyu¾ít toho, ¾e ka¾dá zpìtná hrana vede ve~stromu alespoò o~jednu hladinu vý¹. Mù¾eme tak strom konstruovat po~hladinách. Lze si to tedy pøedstavit tak, ¾e paralelnì spustíme vyhledávání v¹ech slov bez prvních písmenek a~v¾dycky udìláme jeden podkrok ka¾dého z~tìch hledání, co¾ nám dá zpìtné hrany z~dal¹ího patra stromu.
+Zbývá nám u¾ jen konstrukce automatu. Opìt vyu¾ijeme faktu, ¾e zpìtná hrana ze stavu $\beta$ vede tam, kam by se dostal automat pøi hledání $\beta$ bez prvního písmenka. Tak¾e zase chceme nìco, jako simulovat výpoèet toho automatu na~slovech bez prvního písmenka a~doufat v~to, ¾e si vystaèíme s~tou èástí automatu, kterou jsme u¾ postavili. Tentokrát to v¹ak nemù¾eme dìlat jedno slovo po~druhém, proto¾e zpìtné hrany mohou vést køí¾em mezi jednotlivými vìtvemi automatu. Mohlo by se nám tedy stát, ¾e pøi hledání nìjakého slova potøebujeme zpìtnou hranu, která vede do~jiného slova, které jsme je¹tì nezkonstruovali. Tak¾e tento postup sel¾e. Mù¾eme v¹ak vyu¾ít toho, ¾e ka¾dá zpìtná hrana vede ve~stromu alespoò o~jednu hladinu vý¹. Mù¾eme tak strom konstruovat po~hladinách. Lze si to tedy pøedstavit tak, ¾e paralelnì spustíme vyhledávání v¹ech slov bez prvních písmenek a~v¾dycky udìláme jeden podkrok ka¾dého z~tìch hledání, co¾ nám dá zpìtné hrany z~dal¹ího patra stromu.
 
 \s{Konstrukce automatu:}
 \algo
-\:Zalo¾íme prázdný strom, $r \leftarrow$ jeho koøen
-\:Vlo¾íme do~stromu slova $\iota_1 \dots \iota_n$, nastavíme $slovo(*)$
-\:$z(r) \leftarrow \emptyset$, $out(r) \leftarrow \emptyset$
-\:Zalo¾íme frontu $F$ a~vlo¾íme do~ní syny koøene
-\:$\forall v~\in F:~~z(v) \leftarrow r, out(v) \leftarrow \emptyset$
+\:Zalo¾íme prázdný strom, $r \leftarrow$ jeho koøen.
+\:Vlo¾íme do~stromu slova $\iota_1 \dots \iota_n$, nastavíme $slovo(*)$.
+\:$z(r) \leftarrow \emptyset$, $out(r) \leftarrow \emptyset$.
+\:Zalo¾íme frontu $F$ a~vlo¾íme do~ní syny koøene.
+\:$\forall v~\in F:~~z(v) \leftarrow r, \<out>(v) \leftarrow \emptyset$.
 \:Dokud $F \neq \emptyset$:
-\::Vybereme $u$ z~forny $F$
+\::Vybereme $u$ z~fronty $F$.
 \::Pro v¹echny syny $v$ vrcholu $u$:
-\:::$q \leftarrow krok(z(u), písmeno na~hranì uv)$
-\:::$z(v) \leftarrow q$
-\:::Pokud $slovo(q) \neq \emptyset$, pak $out(v) \leftarrow q$
-\::::jinak $out(v) \leftarrow out(q)$.
+\:::$q \leftarrow \<Krok>(z(u), \<písmeno na~hranì uv>)$.
+\:::$z(v) \leftarrow q$.
+\:::Pokud $slovo(q) \neq \emptyset$, pak $out(v) \leftarrow q$.
+\::::Jinak $out(v) \leftarrow out(q)$.
 \endalgo
 
-To, ¾e tento algoritmus zkonstruuje zpìtné hrany jak má, vyplývá z~toho, ¾e nedìláme nic jiného, ne¾ ¾e spou¹tíme výpoèty po~hladinách na~v¹echna hledaná slova bez prvního písmenka. Stejnì tak to, ¾e dobìhne v~lineárním èase je takté¾ dùsledkem toho, ¾e efektivnì spou¹tíme v¹echny tyto výpoèty. Jen nìkdy udìláme najednou krok dvou èi více výpoètù (napøíklad $ARABA$ a~$ARBARA$ se poèítají na~zaèátku, dokud jsou stejné, jen jednou). Èasová slo¾itost této konstrukce je tedy men¹í nebo rovna souètu èasových slo¾itostí výpoètu nad v¹emi tìmi slovy. To u¾ ale víme, ¾e je lineární v~celkové délce tìch slov. Konstrukce automatu tedy trvá ménì nebo rovno tomu, co hledání v¹ech $\iota_i$, co¾ je $\O(\sum_{i} \iota_i)$.
+To, ¾e tento algoritmus zkonstruuje zpìtné hrany jak má, vyplývá z~toho, ¾e nedìláme nic jiného, ne¾ ¾e spou¹tíme výpoèty po~hladinách na~v¹echna hledaná slova bez prvního písmenka. Stejnì tak to, ¾e dobìhne v~lineárním èase je takté¾ dùsledkem toho, ¾e efektivnì spou¹tíme v¹echny tyto výpoèty. Jen nìkdy udìláme najednou krok dvou èi více výpoètù (napøíklad |araba| a~|arbara| se poèítají na~zaèátku, dokud jsou stejné, jen jednou). Èasová slo¾itost této konstrukce je tedy men¹í nebo rovna souètu èasových slo¾itostí výpoètù nad v¹emi tìmi slovy. To u¾ ale víme, ¾e je lineární v~celkové délce tìchto slov. Konstrukce automatu tedy trvá nejvý¹e tolik, co hledání v¹ech $\iota_i$, co¾ je $\O(\sum_{i} \iota_i)$.
 
-\s{Vìta:} Algoritmus Aho-Corasicková najde v¹echny výskyty v~èase $\O(\sum_i~\iota_i~+~S~+~\sharp výskytù)$
+\s{Vìta:} Algoritmus Aho-Corasicková najde v¹echny výskyty v~èase 
+$$\O\left(\sum_i~\iota_i~+~S~+~\sharp\<výskytù>\right).$$
 
-Je¹tì se na~závìr zamysleme, jak bychom si takový automat ukládali do~pamìti. Urèitì se nám bude hodit si stavy nìjak oèíslovat (tøeba v~poøadí v~jakém budou vznikat). Potom funkce pro zpìtné a~zkratkové hrany mohou být reprezentované polem indexovaným èíslem stavu. Funkce slovo, která øíká, jaké slovo ve~stavu konèí zase mù¾e být pouze pole indexované stavem, které nám øekne poøadové èíslo slova ve~slovníku. A~pro dopøedné hrany v~ka¾dém vrcholu mít pole indexované písmenky abecedy, které nám pro ka¾dé písmenko øekne buï taková hrana není, nebo nám øekne, kam vede. Je vidìt, ¾e takovéto pole se hodí pro pomìrnì malé abecedy. U¾ pro abecedu A-Z~bude velikosti 26 a~zvìt¹iny bude prázdné, tak¾e bychom plýtvali pamìtí. V praxi se proto èasto pou¾ívá hashovací tabulka. Pøípadnì bychom mohli mít i~jen jednu velkou spoleènou hashovací tabulku, která bude reprezentovat funkci celou, ve~které budou zahashované dvojice stav-písmenko. Tìchto dvojic je evidentnì tolik, kolik hran stromu, èili lineárnì s~velikostí slovníku a~je to asi nejkompaktnìj¹í reprezentace.
+Je¹tì se na~závìr zamysleme, jak bychom si takový automat ukládali do~pamìti. Urèitì se nám bude hodit si stavy nìjak oèíslovat (tøeba v~poøadí, v~jakém budou vznikat). Potom funkce pro zpìtné a~zkratkové hrany mohou být reprezentované polem indexovaným èíslem stavu. Funkce {\I Slovo}, která øíká, jaké slovo ve~stavu konèí, zase mù¾e být pole indexované stavem, které nám øekne poøadové èíslo slova ve~slovníku. Pro dopøedné hrany v~ka¾dém vrcholu pak mù¾eme mít pole indexované písmenky abecedy, které nám pro ka¾dé písmenko øekne, buï ¾e taková hrana není, nebo nám øekne, kam tato hrana vede. Je vidìt, ¾e takovéto pole se hodí pro pomìrnì malé abecedy. U¾ pro abecedu A-Z~bude velikosti 26 a~z~vìt¹iny bude prázdné, tak¾e bychom plýtvali pamìtí. V praxi se proto èasto pou¾ívá hashovací tabulka. Pøípadnì bychom mohli mít i~jen jednu velkou spoleènou hashovací tabulku, která bude reprezentovat funkci celou, ve~které budou zahashované dvojice (stav, písmenko). Tìchto dvojic je evidentnì tolik, kolik hran stromu, èili lineárnì s~velikostí slovníku, a~je to asi nejkompaktnìj¹í reprezentace.
 
 
 \bye
index a136c1f946b7e2915d921f4b7cbc02ec9071b7f4..0c93b8a087db18e93cefea5adcfca5586d819bf2 100644 (file)
@@ -2,8 +2,8 @@
 %%Creator: inkscape 0.46
 %%Pages: 1
 %%Orientation: Portrait
-%%BoundingBox: 1 666 595 775
-%%HiResBoundingBox: 1.6 666.74151 594.29565 774.11124
+%%BoundingBox: -9 662 595 775
+%%HiResBoundingBox: -9.4114437 662.8626 594.29565 774.11124
 %%EndComments
 %%Page: 1 1
 0 842 translate
@@ -2547,45 +2547,16 @@ closepath
 eofill
 grestore
 grestore
-gsave
-0 0 0 setrgbcolor
-newpath
-525.80261 196.2317 moveto
-524.66882 197.14577 lineto
-523.94811 196.60085 523.27429 196.32839 522.64734 196.32838 curveto
-522.13171 196.32839 521.75378 196.46609 521.51355 196.74147 curveto
-521.27917 197.01687 521.16198 197.31277 521.16199 197.62917 curveto
-521.16198 198.03933 521.33777 198.3821 521.68933 198.65749 curveto
-522.04089 198.92702 522.55651 199.06179 523.23621 199.06178 curveto
-523.32409 199.06179 523.47058 199.05886 523.67566 199.05299 curveto
-523.67566 200.38014 lineto
-523.48815 200.37429 523.34753 200.37136 523.25378 200.37135 curveto
-522.41589 200.37136 521.83288 200.53249 521.50476 200.85475 curveto
-521.18249 201.17702 521.02136 201.5403 521.02136 201.9446 curveto
-521.02136 202.36061 521.18542 202.71218 521.51355 202.99928 curveto
-521.84167 203.28053 522.23132 203.42116 522.6825 203.42116 curveto
-523.50866 203.42116 524.2821 203.03737 525.00281 202.26979 curveto
-526.1366 203.2278 lineto
-525.19909 204.22389 524.03308 204.72194 522.63855 204.72194 curveto
-521.54284 204.72194 520.72546 204.44362 520.1864 203.88698 curveto
-519.64734 203.33034 519.37781 202.68288 519.37781 201.9446 curveto
-519.37781 200.88405 519.89343 200.13112 520.92468 199.68581 curveto
-520.06335 199.19948 519.63269 198.52859 519.63269 197.67311 curveto
-519.63269 196.97585 519.88757 196.35183 520.39734 195.80104 curveto
-520.9071 195.24441 521.68933 194.96609 522.74402 194.96608 curveto
-523.95104 194.96609 524.97057 195.38796 525.80261 196.2317 curveto
-fill
-grestore
 0 0 0 setrgbcolor
 [] 0 setdash
-1 setlinewidth
+0.99999982 setlinewidth
 0 setlinejoin
 0 setlinecap
 newpath
-553.1751 160.12402 moveto
-557.10653 179.41906 543.9716 187.33653 528.05932 193.40244 curveto
+551.6176 159.60837 moveto
+426.17515 260.43382 -84.115921 225.79475 -0.53486469 163.7889 curveto
 stroke
-gsave [1.0278486 -0.39182551 0.39182551 1.0278486 529.08717 193.01061] concat
+gsave [-0.88343752 0.65539127 -0.65539127 -0.88343752 -1.4183022 164.44429] concat
 gsave
 0 0 0 setrgbcolor
 newpath
@@ -2603,42 +2574,20 @@ grestore
 0 setlinejoin
 0 setlinecap
 newpath
-631.03403 159.49613 moveto
-609.41147 191.856 574.44081 198.85447 536.22195 199.68138 curveto
+631.09082 161.52172 moveto
+584.4479 211.71214 440.8137 218.21863 316.61606 220.87291 curveto
 stroke
-gsave [1.0997426 -0.023794225 0.023794225 1.0997426 537.32169 199.65759] concat
-gsave
-0 0 0 setrgbcolor
-newpath
-8.7185878 4.0337352 moveto
--2.2072895 0.016013256 lineto
-8.7185884 -4.0017078 lineto
-6.97309 -1.6296469 6.9831476 1.6157441 8.7185878 4.0337352 curveto
-closepath
-eofill
-grestore
-grestore
 0 0 0 setrgbcolor
 [] 0 setdash
-1 setlinewidth
+0.99999994 setlinewidth
 0 setlinejoin
 0 setlinecap
 newpath
-713.28821 160.75192 moveto
-670.74734 221.47065 606.06284 227.41306 531.82669 209.72769 curveto
+711.15927 159.63036 moveto
+703.71296 189.73566 643.62165 205.468 570.98179 213.59973 curveto
+531.11935 218.06216 487.47795 220.2357 446.68392 221.23953 curveto
+366.0505 223.2237 296.54156 220.6379 289.32855 222.12506 curveto
 stroke
-gsave [1.070054 0.2549203 -0.2549203 1.070054 532.89674 209.98261] concat
-gsave
-0 0 0 setrgbcolor
-newpath
-8.7185878 4.0337352 moveto
--2.2072895 0.016013256 lineto
-8.7185884 -4.0017078 lineto
-6.97309 -1.6296469 6.9831476 1.6157441 8.7185878 4.0337352 curveto
-closepath
-eofill
-grestore
-grestore
 grestore
 showpage
 %%EOF
index 467c6ab0736fffe1758c235ecb577303b9695a02..e2a9bfd0c5873ba4b655b6449de7862fd599b1b8 100644 (file)
    inkscape:output_extension="org.inkscape.output.svg.inkscape">
   <defs
      id="defs3">
+    <marker
+       inkscape:stockid="Arrow1Lstart"
+       orient="auto"
+       refY="0.0"
+       refX="0.0"
+       id="Arrow1Lstart"
+       style="overflow:visible">
+      <path
+         id="path3561"
+         d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+         transform="scale(0.8) translate(12.5,0)" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow2Send"
+       orient="auto"
+       refY="0.0"
+       refX="0.0"
+       id="Arrow2Send"
+       style="overflow:visible;">
+      <path
+         id="path3594"
+         style="font-size:12.0;fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"
+         d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+         transform="scale(0.3) rotate(180) translate(-2.3,0)" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Lend"
+       orient="auto"
+       refY="0.0"
+       refX="0.0"
+       id="Arrow1Lend"
+       style="overflow:visible;">
+      <path
+         id="path3564"
+         d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+         transform="scale(0.8) rotate(180) translate(12.5,0)" />
+    </marker>
     <marker
        inkscape:stockid="Arrow2Lstart"
        orient="auto"
      borderopacity="1.0"
      inkscape:pageopacity="0.0"
      inkscape:pageshadow="2"
-     inkscape:zoom="1.5926241"
-     inkscape:cx="285.31172"
-     inkscape:cy="911.02563"
+     inkscape:zoom="1.5972792"
+     inkscape:cx="365.66562"
+     inkscape:cy="893.05351"
      inkscape:document-units="px"
      inkscape:current-layer="layer1"
      showgrid="false"
        id="path10778"
        inkscape:connector-type="polyline"
        sodipodi:nodetypes="cc" />
-    <text
-       xml:space="preserve"
-       style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Arial;-inkscape-font-specification:Arial"
-       x="518.51648"
-       y="204.511"
-       id="text10780"><tspan
-         sodipodi:role="line"
-         id="tspan10782"
-         x="518.51648"
-         y="204.511"
-         style="font-size:18px">ε</tspan></text>
     <path
-       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow2Lend)"
-       d="M 553.1751,160.12402 C 557.10653,179.41906 543.9716,187.33653 528.05932,193.40244"
-       id="path10786"
-       inkscape:connector-type="polyline"
+       style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999982px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:url(#Arrow2Lend);stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="M 551.6176,159.60837 C 426.17515,260.43382 -84.115921,225.79475 -0.53486469,163.7889"
+       id="path9778"
        sodipodi:nodetypes="cc" />
     <path
-       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow2Lend)"
-       d="M 631.03403,159.49613 C 609.41147,191.856 574.44081,198.85447 536.22195,199.68138"
-       id="path11315"
-       inkscape:connector-type="polyline"
+       style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="M 631.09082,161.52172 C 584.4479,211.71214 440.8137,218.21863 316.61606,220.87291"
+       id="path10874"
        sodipodi:nodetypes="cc" />
     <path
-       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow2Lend)"
-       d="M 713.28821,160.75192 C 670.74734,221.47065 606.06284,227.41306 531.82669,209.72769"
-       id="path11844"
-       inkscape:connector-type="polyline"
-       sodipodi:nodetypes="cc" />
+       style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="M 711.15927,159.63036 C 703.71296,189.73566 643.62165,205.468 570.98179,213.59973 C 531.11935,218.06216 487.47795,220.2357 446.68392,221.23953 C 366.0505,223.2237 296.54156,220.6379 289.32855,222.12506"
+       id="path10880"
+       sodipodi:nodetypes="cssc" />
   </g>
 </svg>