]> mj.ucw.cz Git - ads2.git/commitdiff
KMP: Korektury, rusime konvenci o velkych pismenech, byla obludna
authorMartin Mares <mj@ucw.cz>
Mon, 30 Jan 2012 21:26:19 +0000 (22:26 +0100)
committerMartin Mares <mj@ucw.cz>
Mon, 30 Jan 2012 21:26:19 +0000 (22:26 +0100)
1-kmp/1-kmp.tex

index 6aec1bb69b8fa7efdf75bff96dfa5046ffeda442..ae58d31771eec93cc1c4ddf18c24ad206210ffee 100644 (file)
@@ -4,14 +4,18 @@
 
 \h{Jehla v~kupce sena}
 
-Uva¾ujme následující problém: máme nìjaký text~$\sigma$ délky~$S$ (seno), chceme v~nìm najít
-v¹echny výskyty nìjakého podøetìzce~$\iota$ délky~$J$ (jehly). Seno pøitom bude øádovì del¹í
+Uva¾ujme následující úlohu: máme nìjaký text~$\sigma$ délky~$S$ (budeme mu øíkat {\I seno}) a
+chceme v~nìm najít v¹echny výskyty nìjakého podøetìzce~$\iota$ délky~$J$ ({\I jehly}). Seno pøitom bude øádovì del¹í
 ne¾ jehla.
 
+Kupøíkladu v~senì |bananas| se jehla |ana| vyskytuje hned dvakrát, pøièem¾
+výskyty se pøekrývají.
+
 Triviální øe¹ení pøesnì podle definice by vypadalo následovnì: Zkusíme v¹echny mo¾né pozice,
 kde by se v~senì mohla jehla nacházet, a pro ka¾dou z~nich otestujeme, zda tam opravdu je.
 Pozic je øádovì~$S$, ka¾dé porovnání stojí a¾~$J$, celkovì tedy algoritmus bì¾í v~èase
-$\O(SJ)$. (Rozmyslete si, jak by vypadaly vstupy, pro které skuteènì spotøebujeme tolik èasu.)
+$\O(SJ)$. (Rozmyslete si, jak by vypadaly vstupy, pro které skuteènì spotøebujeme tolik èasu
+-- viz cvièení \exref{naivejs}.)
 
 Zkusme jiný pøístup: nalezneme v~senì první znak jehly a od tohoto místa budeme porovnávat
 dal¹í znaky. Pokud se pøestanou shodovat, pøepneme opìt na hledání prvního znaku. Jen¾e odkud?
@@ -30,8 +34,8 @@ v~terminologii okolo 
 
 \s{Definice:}
 \itemize\ibull
-\:{\I Abeceda $\Sigma$} je nìjaká koneèná mno¾ina {\I znakù,} z~nich¾ se
-  skládají na¹e øetìzce.
+\:{\I Abeceda $\Sigma$} je nìjaká koneèná mno¾ina, jejím prvkùm budeme øíkat {\I znaky}
+  (nìkdy té¾ {\I písmena}).
 \:{\I $\Sigma^*$} je mno¾ina v¹ech {\I slov} neboli {\I øetìzcù} nad abecedou~$\Sigma$,
   co¾ jsou koneèné posloupnosti znakù ze~$\Sigma$.
 \endlist
@@ -41,7 +45,7 @@ Abeceda m
 Potkáme ov¹em i rozlehlej¹í abecedy: napøíklad dnes bì¾ná znaková sada UniCode
 má $2^{16}=65\,536$ znakù, v~novìj¹ích verzích dokonce $2^{31}\approx 2\cdot 10^9$ znakù. Je¹tì extrémnìj¹ím
 zpùsobem pou¾ívají øetìzce lingvisté: na èeský text se nìkdy dívají jako na~øetìzec
-nad abecedou, její¾ prvky jsou èeská slova.
+nad abecedou, její¾ znaky jsou èeská slova.
 
 Pro na¹e úèely budeme pøedpokládat, ¾e abeceda je \uv{rozumnì malá}, èím¾ myslíme, ¾e
 její velikost je konstantní a navíc dostateènì malá na to, abychom si mohli dovolit
@@ -50,9 +54,8 @@ ukl
 \s{Znaèení:}
 \itemize\ibull
 \:{\I Slova} budeme znaèit malými písmenky øecké abecedy $\alpha$, $\beta$, \dots
-\:{\I Znaky} oznaèíme malými písmeny latinky $a$, $b$, \dots{} \hfil\break
+\:{\I Znaky} abecedy oznaèíme malými písmeny latinky |a|, |b|, \dots{} \hfil\break
   Znak budeme pou¾ívat i ve~smyslu jednoznakového øetìzce.
-\:{\I Èísla} budeme znaèit velkými písmeny $A$, $B$, \dots
 \:{\I Délka slova} $\vert \alpha  \vert$ udává, kolika znaky je slovo tvoøeno.
 \:{\I Prázdné slovo} znaèíme písmenem $\varepsilon$, je to jediné slovo délky~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$.
@@ -65,15 +68,15 @@ pr
 \:$\alpha[:] = \alpha$.
 \endlist
 
-\>Dodejme je¹tì, ¾e prázdné slovo je prefixem, suffixem i~podslovem jakéhokoliv slova vèetnì sebe sama.
-Ka¾dé slovo je také prefixem, suffixem i~podslovem sebe sama. To se ne v¾dy hodí, pak budeme hovoøit
-o~{\I vlastním} prefixu, suffixu èi podslovì, èím¾ myslíme, ¾e alespoò jeden znak nebude obsahovat.
+\>Dodejme je¹tì, ¾e ka¾dé slovo je prefixem sebe sama a prázdné slovo je
+prefixem ka¾dého slova. Pokud budeme hovoøit o~{\I vlastním} suffixu, budeme
+tím myslet suffix rùzný od~celého slova. Analogicky pro prefixy a podslova.
 
 \h{Inkrementální algoritmus}
 
-Vra»me se tedy zpìt k~pùvodnímu problému hledání podøetìzcù. Nejprve si
+Vra»me se nyní zpìt k~pùvodnímu problému hledání podøetìzcù. Nejprve si
 ujasnìme, co má být výstupem algoritmu. Budeme chtít nalézt mno¾inu v¹ech
-indexù~$K$ takových, ¾e $\sigma[K:K+\vert\iota\vert] = \iota$. To je dostateènì
+indexù~$k$ takových, ¾e $\sigma[k:k+\vert\iota\vert] = \iota$. To je dostateènì
 kompaktní výstup (nejvý¹e lineární s~délkou sena), a~pøitom obsahuje informace
 o~v¹ech výskytech.
 
@@ -82,25 +85,28 @@ Na hled
 budeme pøidávat znak na konec sena a zapoèítáme pøípadný nový výskyt jehly, který
 konèí tímto znakem.
 
-Abychom toho dosáhli, budeme si prùbì¾nì udr¾ovat informaci o~tom, jaký nejdel¹í
-prefix jehly konèí právì pøidaným znakem. Tomu budeme øíkat {\I stav algoritmu.}
-A~jakmile bude tento prefix roven celé jehle, ohlásíme výskyt.
+Abychom toho dosáhli, budeme si prùbì¾nì udr¾ovat informaci o~tom, jakým nejdel¹ím
+prefixem jehly konèí zatím pøeètená èást sena. Tomu budeme øíkat {\I stav
+algoritmu.} A~jakmile bude tento prefix roven celé jehle, ohlásíme výskyt.
+
+V~na¹em \uv{kokosovém} pøíkladì se tedy po~pøeètení sena |clanekoko| nacházíme
+ve~stavu |koko|, následují stavy |kok|, |koko| a |kokos|.
 
-Pøedstavme si tedy, ¾e jsme pøeèetli øetìzec~$\sigma$, který konèil stavem~$\alpha$.
+Pøedstavme si nyní obecnì, ¾e jsme pøeèetli øetìzec~$\sigma$, který konèil stavem~$\alpha$.
 Teï vstup roz¹íøíme o~znak~$x$ na~$\sigma x$. V~jakém stavu se nyní máme
 nacházet? Pokud to nebude prázdný øetìzec, musí konèit na~$x$, tedy ho mù¾eme
 napsat ve~tvaru $\alpha'x$.
 
 V¹imneme si, ¾e $\alpha'$ musí být suffixem slova~$\alpha$: Jeliko¾ $\alpha' x$
 je prefix jehly, je $\alpha'$ také prefix jehly. A~proto¾e $\alpha'x$ je suffixem~$\sigma x$,
-musí $\alpha'$ být suffixem~$\sigma$. Tedy jak $\alpha$, tak $\alpha'$ jsou suffixy slova~$\alpha$,
+musí $\alpha'$ být suffixem~$\sigma$. Tedy jak~$\alpha$, tak $\alpha'$ jsou suffixy slova~$\alpha$,
 které jsou souèasnì prefixy jehly. Ov¹em stav~$\alpha$ jsme vybrali jako nejdel¹í slovo
 s~touto vlastností, tak¾e $\alpha'$~musí být nejvý¹e tak dlouhé, a~tudí¾ je prefixem~$\alpha$.
 
 Staèilo by tedy probrat v¹echny suffixy slova~$\alpha$, které jsou prefixem jehly,
-a~vybrat z~nich nejdel¹í, který po roz¹íøení o~znak~$x$ je stále prefixem jehly.
+a~vybrat z~nich nejdel¹í, který po roz¹íøení o~znak~$x$ stále je prefixem jehly.
 
-Abychom ale nemuseli suffixy procházet v¹echny, pøedpoèítáme {\I zpìtnou funkci~$z$.}
+Abychom ale nemuseli suffixy procházet v¹echny, pøedpoèítáme si {\I zpìtnou funkci~$z$.}
 Ta nám pro ka¾dý prefix jehly øekne, jaký je jeho nejdel¹í vlastní suffix,
 který je opìt prefixem jehly. To nám umo¾ní procházet rovnou kandidáty na nový stav:
 staèí probrat øetìzce $\alpha$, $z(\alpha)$, $z(z(\alpha))$, \dots{} a pou¾ít první,
@@ -120,30 +126,30 @@ vlastn
 \figure{barb.eps}{Vyhledávací automat pro slovo |barbarossa|}{4.1in}
 
 Reprezentace automatu bude pøímoèará: stavy oèíslujeme od~0 do~$J$, dopøedná hrana
-povede v¾dy ze stavu~$S$ do~$S+1$ a bude odpovídat roz¹íøení prefixu o~pøíslu¹ný
-znak jehly, tedy $\iota[S]$. Zpìtné hrany si budeme pamatovat v~poli~$Z$, tedy
-$Z[S]$ bude øíkat èíslo stavu, do~nìj¾ vede zpìtná hrana ze~stavu~$S$, pøípadnì
+povede v¾dy ze stavu~$s$ do~$s+1$ a bude odpovídat roz¹íøení prefixu o~pøíslu¹ný
+znak jehly, tedy o~$\iota[s]$. Zpìtné hrany si budeme pamatovat v~poli~$Z$, tedy
+$Z[s]$ bude øíkat èíslo stavu, do~nìj¾ vede zpìtná hrana ze~stavu~$s$, pøípadnì
 bude nedefinované, pokud taková hrana neexistuje.
 
 Kdybychom takový automat mìli, mohli bychom pomocí nìj inkrementální algoritmus
 z~pøedchozí sekce popsat následovnì:
 
 \proc{KmpKrok} \cmt{Jeden krok automatu}
-\algin Jsme ve~stavu~$I$, pøeèetli jsme znak~$x$.
-\:Dokud $\iota[I] \neq x~\&~I \neq 0: I \= Z[I]$.
-\:Pokud $\iota[I] = x$, pak $I \= I + 1$.
-\algout Nový stav~$I$.
+\algin Jsme ve~stavu~$s$, pøeèetli jsme znak~$x$.
+\:Dokud $\iota[s] \neq x~\&~s \neq 0: s \= Z[s]$.
+\:Pokud $\iota[s] = x$, pak $s \= s + 1$.
+\algout Nový stav~$s$.
 \endalgo
 
-\algo{Kmp} \cmt{Spu¹tìní automatu na øetìzec~$\sigma$.}
+\algo{KmpHledej} \cmt{Spu¹tìní automatu na øetìzec~$\sigma$.}
 \algin Seno~$\sigma$, zkonstruovaný automat.
-\:$I \= 0$.
+\:$s \= 0$.
 \:Pro znaky $x\in\sigma$ postupnì provádíme:
-\::$I \= \alg{KmpKrok}(I,x)$.
-\::Pokud $I = J$, ohlásíme výskyt.
+\::$s \= \alg{KmpKrok}(s,x)$.
+\::Pokud $s = J$, ohlásíme výskyt.
 \endalgo
 
-\s{Invariant:} Stav algoritmu~$I$ v~ka¾dém okam¾iku øíká, jaký nejdel¹í
+\s{Invariant:} Stav algoritmu~$s$ v~ka¾dém okam¾iku øíká, jaký nejdel¹í
 prefix jehly je suffixem zatím pøeètené èásti sena. (To u¾ víme z~úvah
 o~inkrementálním algoritmu.)
 
@@ -155,7 +161,7 @@ Jen mus
 algoritmus zeptá na~dopøednou hranu z~posledního stavu. Ta ale
 neexistuje. Napravíme to jednodu¹e: pøidáme fiktivní dopøednou hranu,
 na~ní¾ je napsán znak odli¹ný od~v¹ech skuteèných znakù. Tím zajistíme,
-¾e se po této hranì nikdy nevydáme. Staèí tedy vhodnì dodefinovat $\iota[i]$.%
+¾e se po této hranì nikdy nevydáme. Staèí tedy vhodnì dodefinovat $\iota[J]$.%
 \foot{V jazyce C mù¾eme zneu¾ít toho, ¾e ka¾dý øetìzec je ukonèen znakem
 s~nulovým kódem.}
 
@@ -170,9 +176,11 @@ doleva. Proto je v
 jsme pro¹li dopøedných hran, tak¾e také nejvý¹e~$S$.
 \qed
 
-\s{Konstrukce automatu:}
-Algoritmus je tedy lineární, ale potøebuje, aby mu nìkdo zkonstruoval 
-automat. Dopøedné hrany vytvoøíme snadno, ale jak si poøídit ty zpìtné?
+\ss{Konstrukce automatu}
+
+Hledání tedy pracuje v~lineárním èase, zbývá domyslet, jak v~lineárním èase
+sestrojit automat. Stavy a dopøedné hrany získáme triviálnì, se zpìtnými budeme
+mít trochu práce.
 
 Podnikneme my¹lenkový pokus: Pøedstavme si, ¾e automat u¾ máme hotový, ale nevidíme,
 jak vypadá uvnitø. Chtìli bychom zjistit, jak v~nìm vedou zpìtné hrany, ov¹em jediné,
@@ -194,32 +202,32 @@ jsme pro
 
 To je zajímavé pozorování, øeknete si, ale jak nám pomù¾e ke~konstrukci automatu,
 kdy¾ samo u¾ hotový automat potøebuje? Pomù¾e pìkný trik: pokud hledáme zpìtnou
-hranu z~$I$-tého stavu, spou¹tíme automat na slovo délky~$I-1$, tak¾e se mù¾eme
-dostat pouze do prvních~$I-1$ stavù a vùbec nám nevadí, ¾e v~tom $I$-tém je¹tì
+hranu z~$i$-tého stavu, spou¹tíme automat na slovo délky~$i-1$, tak¾e se mù¾eme
+dostat pouze do prvních~$i-1$ stavù a vùbec nám nevadí, ¾e v~tom $i$-tém je¹tì
 není zpìtná hrana hotova.%
 \foot{Konstruovat nìjaký objekt pomocí tého¾ objektu je osvìdèený postup, který
 si u¾ vyslou¾il i svùj vlastní název. V~angliètinì se mu øíká {\I bootstrapping}
-a z~tohoto názvu vzniklo bootování poèítaèù, proto¾e pøi nìm operaèní systém
-vlastnì do pamìti zavádí sám sebe. Kde se toto slovo vzalo? Bootstrap znamená èesky
+a z~tohoto názvu vzniklo bootování poèítaèù, proto¾e pøi nìm operaèní systém
+zavádí do pamìti sám sebe. Kde se toto slovo vzalo? Bootstrap znamená èesky
 {\I ¹truple} -- to je takové to oèko na patì boty, které usnadòuje nazouvání.
 A~v~jednom z~pøíbìhù o~baronu Prá¹ilovi sly¹íme barona vyprávìt, jak se uvíznuv
 v~ba¾inì zachránil tím, ¾e se vytáhl za ¹truple. Krásný popis bootování, není-li¾
 pravda?}
 
-Konstrukce automatu tedy bude vypadat tak, ¾e nejdøíve sestrojíme pouze dopøedné
-hrany, pak rozpracovaný automat spustíme na øetìzec $\iota[1:{}]$ a podle toho,
-jakými stavy prochází, doplòujeme zpìtné hrany. A~jeliko¾ vyhledávání je lineární,
-celá konstrukce trvá $\O(J)$.
+Pøi konstrukci automatu tedy nejdøíve sestrojíme dopøedné hrany, naèe¾
+rozpracovaný automat spustíme na øetìzec $\iota[1:{}]$ a podle toho,
+jakými stavy bude procházet, doplníme zpìtné hrany. Jak u¾ víme, vyhledávání má
+lineární slo¾itost, tak¾e celá konstrukce potrvá $\O(J)$.
 
-Hotový algoritmus mù¾eme zapsat následovnì:
+Hotový algoritmus pro konstrukci automatu mù¾eme zapsat následovnì:
 
 \algo{KmpKonstrukce}
 \algin Jehla~$\iota$ délky~$J$.
 \:$Z[0] \= ?$, $Z[1] \= 0$.
-\:$I \= 0$.
-\:Pro $K = 2, \ldots, J-1$:
-\::$I \= \alg{KmpKrok}(I, \iota[K])$.
-\::$Z[K] \= I$.
+\:$s \= 0$.
+\:Pro $i = 2, \ldots, J-1$:
+\::$s \= \alg{KmpKrok}(s, \iota[i])$.
+\::$Z[i] \= s$.
 \algout Pole zpìtných hran~$Z$.
 \endalgo
 
@@ -232,25 +240,23 @@ Line
 s~délkou sena pak potøebujeme na~samotné vyhledání.
 \qed
 
-\h{Hledání více øetìzcù najednou: algoritmus Aho-Corasicková}
+\h{Hledání více øetìzcù najednou: algoritmus Aho-Corasicková (AC)}
 
-Nyní si zahrajeme tuté¾ hru, ov¹em v~trochu slo¾itìj¹ích kulisách. Tentokrát
-bude jehel vícero: $\iota_1, \ldots, \iota_N$, jejich délky oznaèíme $J_I = \vert \iota_I \vert $.
-Opìt dostaneme nìjaké seno~$\sigma$ délky $S=\vert \sigma \vert$ a chceme nalézt
+Nyní si zahrajeme tuté¾ hru v~trochu slo¾itìj¹ích kulisách. Tentokrát
+bude jehel vícero: $\iota_1, \ldots, \iota_N$, jejich délky oznaèíme $J_i = \vert \iota_i \vert $.
+Opìt dostaneme nìjaké seno~$\sigma$ délky~$S$ a chceme nalézt
 v¹echny výskyty jehel v~senì.
 
 Pøedtím, ne¾ se pustíme do~vlastního vyhledávacího algoritmu, mìli bychom si
-ujasnit, co bude jeho výstupem. Dokud byla jehla jedna jediná, bylo to jasné --
+opìt ujasnit, co bude jeho výstupem. Dokud byla jehla jedna jediná, bylo to zøejmé --
 chtìli jsme nalézt mno¾inu v¹ech pozic v~senì, na~kterých zaèínaly výskyty
-jehly. Jak tomu bude zde? Budeme chtít vìdìt, která jehla se vyskytuje na které
-pozici. Jinými slovy budeme chtít vypsat v¹echny dvojice $(K,I)$ takové,
-¾e $\sigma[K:K+J_I]= \iota_I$.
-
-Zde se v¹ak skrývá jedna drobná zrada. Budeme se asi muset vzdát nadìje
-na 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í. Pokud je toti¾ jedna
-jehla suffixem druhé, na jedné pozici v~senì mohou konèit výskyty obou.
-Proto budeme hledat algoritmus, který bude lineární v~délce vstupu plus
+jehly. Jak tomu bude zde? Chceme se dozvìdìt, která jehla se vyskytuje na které
+pozici. Jinými slovy vypsat v¹echny dvojice $(k,i)$ takové, ¾e $\sigma[k:k+J_i]= \iota_i$.
+
+Tìchto dvojic mù¾e být pomìrnì hodnì. Pokud je toti¾ jedna jehla suffixem druhé,
+na jedné pozici v~senì mohou konèit výskyty obou. Celková velikost výstupu
+tak mù¾e být vìt¹í ne¾ lineární v~délce vstupu (viz cvièení~\exref{acslout}).
+Budeme proto hledat 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, objevili v~roce 1975 pan Aho a~paní
@@ -259,10 +265,14 @@ v
 
 \s{Vyhledávací automat:}
 Opìt se budeme sna¾it sestrojit vyhledávací automat, jeho¾ stavy budou
-odpovídat prefixùm jehel a dopøedné hrany roz¹iøování prefixù o~jeden znak.
-Tentokrát navíc nebude jasnì definovaný koncový stav, oznaèíme si proto
-v¹echny stavy, které odpovídají nìkteré z~jehel (na pøíkladu na obrázku
-je vidìt, ¾e to nemusí být jen listy).
+odpovídat prefixùm jehel a dopøedné hrany budou popisovat roz¹iøování
+prefixù o~jeden znak. Hrany tedy budou tvoøit strom orientovaný smìrem
+od~koøene (písmenkový strom neboli trii pro daný slovník).
+
+Ka¾dý list stromu bude odpovídat nìkteré z~jehel, ale jak je vidìt
+na obrázku, nìkteré jehly se mohou vyskytovat i ve~vnitøních vrcholech
+(pokud je jedna jehla prefixem jiné). Výskyty jehel ve~stromu si tedy
+nìjak oznaèíme, pøíslu¹ným stavùm budeme øíkat {\I koncové.}
 
 \figure{ara_strom_zkr.eps}{Vyhledávací automat pro slova |ara|, |bar|, |arab|, |baraba| a |barbara|.}{1.25in}
 
@@ -270,13 +280,13 @@ D
 Jejich definice bude úplnì stejná jako u~automatu KMP.
 Z~ka¾dého stavu pùjde zpìtná hrana do~jeho nejdel¹ího vlastního suffixu, který je také
 stavem. Èili se budeme sna¾it jméno stavu zkracovat zleva tak dlouho, ne¾ dostaneme
-opìt jméno stavu. Z~koøene -- prázdného stavu -- pak evidentnì ¾ádná zpìtná hrana nepovede.
+jméno dal¹ího stavu. Z~koøene -- prázdného stavu -- pak evidentnì ¾ádná zpìtná hrana nepovede.
 
 Funkce pro hledání v~senì bude vypadat stejnì jako u~KMP: zaène v~poèáteèním
-stavu (to je koøen stromu), postupnì bude roz¹iøovat seno o~dal¹í písmenka,
-poka¾dé zkusí jít dopøednou hranou a pokud to nejde, bude se vracet po~zpìtných
-hranách tak dlouho, a¾ buïto bude existovat vhodná dopøedná hrana, nebo se vrátí
-do koøene stromu a tehdy nový znak zahodí.
+stavu (to je koøen stromu) a postupnì bude roz¹iøovat seno o~dal¹í písmenka.
+Poka¾dé zkusí jít dopøednou hranou a pokud to nepùjde, bude se vracet po~zpìtných
+hranách. Pøitom se buïto dostane do vrcholu, kde vhodná dopøedná hrana existuje,
+nebo se vrátí a¾ do koøene stromu a tehdy nový znak zahodí.
 
 Stejnì jako u~KMP nahlédneme, ¾e procházení sena trvá $\O(S)$ a ¾e platí analogický
 invariant, toti¾ ¾e se v~ka¾dém okam¾iku nacházíme ve~stavu, který odpovídá nejdel¹ímu
@@ -286,29 +296,28 @@ suffixu zat
 Jediné, co se bude od KMP li¹it, je, kdy ohlásit výskyt. U~KMP to bylo snadné: kdykoliv
 jsme dospìli do posledního stavu, znamenalo to nalezení jehly. Nabízí se hlásit
 výskyt, kdykoliv dojdeme do stavu oznaèeného jako koncový. To ale nefunguje:
-pokud ná¹ ukázkový automat pøeète seno |bara|, skonèí ve stavu~|bara|, a~pøitom
-by mìl ohlásit výskyt jehly |ara|. Stejnì tak pøeèteme-li |barbara|, nev¹imneme
-si, ¾e na tém¾e místì konèí i |ara|.
+pokud ná¹ ukázkový automat pøeète seno |bara|, skonèí ve stavu~|bara|, který není
+koncový, a~pøitom by zde mìl ohlásit výskyt jehly |ara|. Stejnì tak pøeèteme-li |barbara|,
+nev¹imneme si, ¾e na tém¾e místì konèí i |ara|.
 
 Platí ale, ¾e v¹echna slova, která bychom mìli v~daném stavu ohlásit, jsou suffixy
-jména tohoto stavu. Mohli bychom je tedy najít tak, ¾e se vydáme po zpìtných hranách
-a¾ do koøene a kdykoliv projdeme pøes koncový vrchol, ohlásíme výskyt. To ov¹em
+jména tohoto stavu. Mohli bychom se tedy vydat po zpìtných hranách
+a¾ do koøene a kdykoliv projdeme pøes koncový vrchol, ohlásit výskyt. To ov¹em
 trvá pøíli¹ dlouho -- jistì by se stávalo, ¾e bychom podnikli dlouhou cestu do koøene
-a nena¹li na ní ani jeden výskyt.
+a nena¹li na ní nic.
 
-Dal¹í, co se nabízí, je pøedpoèítat si pro ka¾dý stav~$\beta$ mno¾inu slov ${\cal S}(\beta)$,
+Dal¹í, co se nabízí, je pøedpoèítat si pro ka¾dý stav~$\beta$ mno¾inu slov $M(\beta)$,
 jejich¾ výskyty máme v~tomto stavu hlásit. To by fungovalo, ale existují mno¾iny jehel,
-pro které bude celková velikost mno¾in ${\cal S}(\beta)$ superlineární. Museli bychom se
-tedy vzdát lákavé mo¾nosti stavby automatu v~lineárním èase. (Rozmyslete si, jak by
-takové jehly vypadaly.)
+pro které bude celková velikost mno¾in $M(\beta)$ superlineární (viz cvièení~\exref{acslm}).
+Museli bychom se tedy vzdát lákavé mo¾nosti stavby automatu v~lineárním èase.
 
-Jak to tedy vyøe¹íme? Zavedeme zkratky (na obrázku zelenì):
+Jak to tedy vyøe¹íme? Zavedeme zkratky (na obrázku vyznaèeny zelenì):
 
 \s{Definice:}
-{\I Zkratková hrana} ze~stavu~$\alpha$ vede do nejbli¾¹ího stavu $\zeta(\alpha)$ dosa¾itelného
-z~$\alpha$ po zpìtných hranách, který je koncový.
+{\I Zkratková hrana} ze~stavu~$\alpha$ vede do nejbli¾¹ího koncového stavu $\zeta(\alpha)$
+dosa¾itelného z~$\alpha$ po zpìtných hranách.
 
-Jinými slovy, $\zeta(\alpha)$ nám øekne, jaký je nejdel¹í vlastní suffix slova~$\alpha$, který
+Jinými slovy, zkratka $\zeta(\alpha)$ nám øekne, jaký je nejdel¹í vlastní suffix slova~$\alpha$, který
 je jehlou. Pokud takový suffix neexistuje, ¾ádná zkratková hrana ze~stavu~$\alpha$ nepovede.
 Pomocí zkratkových hran mù¾eme snadno vyjmenovat v¹echny výskyty. Budeme postupovat stejnì,
 jako bychom procházeli po v¹ech zpìtných hranách, jen budeme dlouhé úseky zpìtných hran, na~nich¾
@@ -316,55 +325,55 @@ nen
 
 \s{Reprezentace automatu:}
 Vyhledávací automat se tedy sestává ze stromu dopøedných hran, ze zpìtných
-hran a ze~zkratkových hran. Ne¾ vyslovíme samotný algoritmus AC, rozmysleme si, jak automat
-ulo¾it do pamìti. Pro ka¾dý stav si budeme pamatovat:
+hran a ze~zkratkových hran. Rozmysleme si, jak v¹e ulo¾it do pamìti.
+Pro ka¾dý stav si budeme pamatovat:
 \itemize\ibull
-\:$I$ -- poøadové èíslo stavu (tøeba v~poøadí, jak vrcholy vznikaly),
-\:$\<Zpìt>(I)$ -- kam z~nìj vede zpìtná hrana (vyu¾íváme toho, ze mù¾e být nejvý¹e jedna, tak¾e si zapamatujeme
+\:$s$ -- poøadové èíslo stavu (tøeba podle toho, jak vrcholy vznikaly),
+\:$\<Zpìt>(s)$ -- kam z~nìj vede zpìtná hrana (vyu¾íváme toho, ze mù¾e být nejvý¹e jedna, tak¾e si zapamatujeme
   èíslo stavu, do nìj¾ vede),
-\:$\<Zkratka>(I)$ -- kam z~nìj vede zkratková hrana (takté¾),
-\:$\<Slovo>(I)$ -- zda tu konèí nìjaké slovo (a~pokud ano, tak které),
-\:$\<Dopøedu>(I,x)$ -- kam vede dopøedná hrana oznaèená písmenem~$x$ (pro malé abecedy si to
-  mù¾eme pamatovat v~poli, pro velké tøeba v~he¹ovací tabulce nebo stromu).
+\:$\<Zkratka>(s)$ -- kam z~nìj vede zkratková hrana (takté¾),
+\:$\<Slovo>(s)$ -- zda tu konèí nìjaké slovo (a~pokud ano, tak které),
+\:$\<Dopøedu>(s,x)$ -- kam vede dopøedná hrana oznaèená písmenem~$x$ (pro malé abecedy si to
+  mù¾eme pamatovat v~poli, pro velké viz cvièení~\exref{bigsigma}).
 \endlist
 
 \>Celý algoritmus pro zpracování sena automatem pak bude vypadat takto:
 
 \proc{AcKrok} \cmt{Jeden krok automatu}
-\algin Jsme ve~stavu~$I$, pøeèetli jsme znak~$x$.
-\:Dokud $\<Dopøedu>(I, x) = \emptyset~\&~I \ne \<koøen>$: $I \= \<Zpìt>(I)$.
-\:Pokud $\<Dopøedu>(I, x) \ne \emptyset$: $I \= \<Dopøedu>(I,x)$.
-\algout Nový stav~$I$.
+\algin Jsme ve~stavu~$s$, pøeèetli jsme znak~$x$.
+\:Dokud $\<Dopøedu>(s, x) = \emptyset~\&~s \ne \<koøen>$: $s \= \<Zpìt>(s)$.
+\:Pokud $\<Dopøedu>(s, x) \ne \emptyset$: $s \= \<Dopøedu>(s,x)$.
+\algout Nový stav~$s$.
 \endalgo
 
 \algo{AcHledej} \cmt{Spu¹tìní automatu na daný øetìzec}
 \algin Seno~$\sigma$, zkonstruovaný automat.
-\:$I \= \<koøen>$.
+\:$s \= \<koøen>$.
 \:Pro znaky $x\in\sigma$ postupnì provádíme:
-\::$I \= \alg{AcKrok}(I, x)$.
-\::$K \= I$.
-\::Dokud $K \neq \emptyset$:
-\:::Je-li $\<Slovo>(K) \neq \emptyset$:
-\::::Ohlásíme $\<Slovo>(K)$.
-\:::$K \= \<Zkratka>(K)$.
+\::$s \= \alg{AcKrok}(s, x)$.
+\::$j \= s$.
+\::Dokud $j \neq \emptyset$:
+\:::Je-li $\<Slovo>(j) \neq \emptyset$:
+\::::Ohlásíme $\<Slovo>(j)$.
+\:::$j \= \<Zkratka>(j)$.
 \endalgo
 
-\>Jak u¾ jsme nahlédli, v¹echny kroky automatu dohromady trvají $\O(S)$.
+\>Stejným argumentem jako u~KMP zdùvodníme, ¾e v¹echny kroky automatu dohromady trvají $\O(S)$.
 Mimo to je¹tì hlásíme výskyty, co¾ trvá $\O(\<poèet výskytù>)$. Zbývá
 ukázat, jak automat sestrojit.
 
 \s{Konstrukce automatu:}
-Stejnì jako u~KMP, i zde nahlédneme, ¾e zpìtná hrana ze stavu $\beta$ vede tam,
-kam by se dostal automat pøi hledání slova $\beta$ bez prvního znaku. Opìt bychom
-tedy chtìli zaèít sestrojením dopøedných hran a pak spu¹tìním je¹tì nehotového
-automatu doplòovat hrany zpìtné, doufajíce, ¾e si vystaèíme s~u¾ sestrojenou
+Opìt se inspirujeme algoritmem KMP a nahlédneme, ¾e zpìtná hrana ze~stavu~$\beta$ vede tam,
+kam by se automat dostal pøi hledání slova~$\beta$ bez prvního znaku. Chtìli bychom
+tedy zaèít sestrojením dopøedných hran a pak spou¹tìním je¹tì nehotového automatu
+na jednotlivé jehly doplòovat zpìtné hrany, doufajíce, ¾e si vystaèíme s~u¾ sestrojenou
 èástí automatu.
 
-Pøi konstrukci zpìtných hran tedy chceme automatem vyhledávat v¹echny jehly
-bez prvních znakù. To v¹ak nemù¾eme dìlat jednu jehlu po druhé, proto¾e zpìtné
-hrany mohou vést køí¾em mezi jednotlivými vìtvemi stromu. 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. Proto tento postup sel¾e.
+Kdybychom v¹ak automat spou¹tìli na jednu jehlu po~druhé (poka¾dé bez prvního
+znaku), dostali bychom se do úzkých, proto¾e zpìtné hrany mohou vést køí¾em mezi
+jednotlivými vìtvemi stromu. 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. Proto 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ý¹, a strom konstruovat po~hladinách. To si lze pøedstavit tak, ¾e
@@ -372,34 +381,36 @@ paraleln
 jeden krok ka¾dého z~tìchto hledání, co¾ nám dá zpìtné hrany v~dal¹ím patøe stromu.
 
 Kdykoliv vytvoøíme zpìtnou hranu, sestrojíme také zkratkovou hranu z~tého¾
-vrcholu. Pokud vede zpìtná hrana z~$K$ do~$Q$ a $\<Slovo>(Q) \ne \emptyset$,
-musí vést zkratka z~$K$ také do~$Q$. Pokud v~$Q$ ¾ádné slovo neskonèí, musí
-zkratka z~$K$ vést do tého¾ vrcholu, jako zkratka z~$Q$.
+vrcholu. Pokud vede zpìtná hrana ze stavu~$s$ do stavu~$z$ a $\<Slovo>(z)$ je definováno,
+musí vést zkratka z~$s$ také do~$z$. Pokud v~$z$ ¾ádné slovo nekonèí, musí
+zkratka z~$s$ vést do tého¾ vrcholu, kam vede zkratka ze~$z$.
 
 \algo{AcKonstrukce}
-\algin Slova $\iota_1,\ldots,\iota_N$.
-\:Zalo¾íme prázdný strom, $R \=$ jeho koøen.
-\:Vlo¾íme do~stromu slova $\iota_1 \dots \iota_N$, nastavíme $\<Slovo>$ ve~v¹ech stavech.
-\:$\<Zpìt>(R) \= \emptyset$, $\<Zkratka>(R) \= \emptyset$.
+\algin Slova $\iota_1,\ldots,\iota_n$.
+\:Zalo¾íme strom, který obsahuje pouze koøen~$r$.
+\:Vlo¾íme do~stromu slova $\iota_1 \dots \iota_n$, nastavíme $\<Slovo>$ ve~v¹ech stavech.
+\:$\<Zpìt>(r) \= \emptyset$, $\<Zkratka>(r) \= \emptyset$.
 \:Zalo¾íme frontu $F$ a~vlo¾íme do~ní syny koøene.
-\:Pro v¹echny syny~$K$ koøene: $\<Zpìt>(K) \= R$, $\<Zkratka>(K) \= \emptyset$.
+\:Pro v¹echny syny~$s$ koøene: $\<Zpìt>(s) \= r$, $\<Zkratka>(s) \= \emptyset$.
 \:Dokud $F \neq \emptyset$:
-\::Vybereme $I$ z~fronty $F$.
-\::Pro v¹echny syny $K$ vrcholu $I$:
-\:::$Q \= \alg{AcKrok}(\<Zpìt>(I), \hbox{písmeno na~hranì $IK$})$.
-\:::$\<Zpìt>(K) \= Q$.
-\:::Pokud $\<Slovo>(Q) \neq \emptyset$: $\<Zkratka>(K) \= Q$.
-\:::Jinak $\<Zkratka>(K) \= \<Zkratka>(Q)$.
-\:::Vlo¾íme $K$ do~fronty $F$.
+\::Vybereme $i$ z~fronty $F$.
+\::Pro v¹echny syny $s$ vrcholu $i$:
+\:::$z \= \alg{AcKrok}(\<Zpìt>(s), \hbox{písmeno na~hranì $is$})$.
+\:::$\<Zpìt>(s) \= z$.
+\:::Pokud $\<Slovo>(z) \neq \emptyset$: $\<Zkratka>(s) \= z$.
+\:::Jinak $\<Zkratka>(s) \= \<Zkratka>(z)$.
+\:::Vlo¾íme $s$ do~fronty $F$.
 \algout Strom, pole \<Slovo>, \<Zpìt> a \<Zkratka>.
 \endalgo
 
-Jeliko¾ tento algoritmus pouze napøeskáèku hledá v¹echny jehly, musí být jeho
-èasová slo¾itost shora omezena souètem slo¾itostí hledání jehel, co¾, jak u¾
-víme, je pro ka¾dou jehlu lineární s~její délkou. Dokonce mù¾e dobìhnout i
-rychleji, jeliko¾ jsou-li dva ze simulovaných výpoètù v~tém¾e stavu, poèítáme
-krok automatu pouze jednou (to je vidìt tøeba na spoleèném zaèátku slov |araba|
-a |arbara|).
+Pro rozbor èasové slo¾itosti si uvìdomíme, ¾e konstrukce zpìtných hran
+hledá v¹echny jehly, jen kroky jednotlivých hledání vhodným zpùsobem støídá
+(jakoby je provádìla paralelnì).
+Její èasovou slo¾itost tedy mù¾eme shora omezit souètem slo¾itostí hledání
+jehel, co¾, jak u¾ víme, je lineární v~délce jehel.
+Konstrukce dokonce mù¾e dobìhnout i rychleji, jsou-li toti¾ dva z~provádìných
+výpoètù v~tém¾e stavu, poèítáme krok automatu pouze jednou. To je vidìt tøeba
+na spoleèném zaèátku slov |araba| a |arbara|.
 
 Chování celého algoritmu shrneme do následující vìty:
 
@@ -418,16 +429,16 @@ P
 he¹ovací funkci~$H$, které $J$-ticím znakù pøiøazuje èísla z~mno¾iny $\{0,\ldots,N-1\}$
 pro nìjaké dost velké~$N$. Budeme posouvat okénko délky~$J$ po~senì, pro ka¾dou
 jeho polohu si spoèteme he¹ znakù uvnitø okénka, porovnáme s~he¹em jehly a pokud
-se rovnají, porovnáme je znak po~znaku.
+se rovnají, porovnáme okénko s~jehlou znak po~znaku.
 
 Pokud je he¹ovací funkce \uv{kvalitní}, málokdy se stane, ¾e by se he¹e rovnaly,
 tak¾e místo èasu $\Theta(J)$ na porovnáváním øetìzcù si vystaèíme s~porovnáním
 he¹ù v~konstantním èase. Jen¾e ouha, èas $\Theta(J)$ potøebujeme i na vypoètení
-he¹e pro ka¾dou polohu okénka, tak¾e jsme si nepomohli.
+he¹e pro ka¾dou polohu okénka. Jak z~toho ven?
 
-Poøídíme si proto he¹ovací funkci, kterou lze pøi posunutí okénka o~jednu
+Poøídíme si he¹ovací funkci, kterou lze pøi posunutí okénka o~jednu
 pozici doprava v~konstantním èase pøepoèítat. Tyto po¾adavky splòuje tøeba
-polynom
+funkce
 $$
 H(x_1,\ldots,x_J) = (x_1P^{J-1} + x_2P^{J-2} + \ldots + x_{J-1}P^1 + x_JP^0) \bmod N,
 $$
@@ -444,14 +455,14 @@ Cel
 
 \algo{RabinKarp}
 \algin Jehla $\iota$ délky~$J$, seno~$\sigma$ délky~$S$.
-\:$X \= H(\iota)$. \cmt{he¹ jehly}
-\:$Y \= H(\sigma[{}:J])$. \cmt{he¹ první pozice okénka}
+\:$j \= H(\iota)$. \cmt{he¹ jehly}
+\:$h \= H(\sigma[{}:J])$. \cmt{he¹ první pozice okénka}
 \:Zvolíme~$P$ a~$N$ a pøedpoèítáme $P^J \bmod N$.
-\:Pro $I$ od~$0$ do~$S-J$: \cmt{mo¾né pozice okénka}
-\::Je-li $X=Y$:
-\:::Pokud $\sigma[I:I+J] = \iota$, ohlásíme výskyt na pozici~$I$.
-\::Pokud $I<S-J$: \cmt{pøepoèítáme he¹}
-\:::$Y \= (P\cdot Y - \sigma[I]\cdot P^J + \sigma[I+J]) \bmod N$.
+\:Pro $i$ od~$0$ do~$S-J$: \cmt{mo¾né pozice okénka}
+\::Je-li $h=j$:
+\:::Pokud $\sigma[i:i+J] = \iota$, ohlásíme výskyt na pozici~$i$.
+\::Pokud $i<S-J$: \cmt{pøepoèítáme he¹}
+\:::$h \= (P\cdot h - \sigma[i]\cdot P^J + \sigma[i+J]) \bmod N$.
 \endalgo
 
 \s{Analýza:}
@@ -474,29 +485,33 @@ algoritmus pob
 porovnává øetìzce, má èasovou slo¾itost $\O(JS)$. Mù¾e být opravdu tak pomalý,
 uvá¾íme-li, ¾e porovnávání øetìzcù skonèí, jakmile najde první neshodu? Sestrojte
 vstup, na kterém algoritmus pobì¾í $\Theta(JS)$ krokù, pøesto¾e nic nenajde.}
+\exid{naivejs}
 
 \ex{Naleznìte pøíklad jehel a sena, v~nìm¾ se nachází superlineární poèet
 výskytù vzhledem k~celkové velikosti vstupu (souèet velikostí jehel a délky
 sena).}
+\exid{acslout}
 
 \ex{Uva¾ujme zjednodu¹ený algoritmus AC, který nepou¾ívá zkratkové hrany
 a v¾dy projde po zpìtných hranách a¾ do koøene. Uka¾te pøíklad vstupu,
 na~kterém je tento algoritmus asymptoticky pomalej¹í.}
 
-\ex{Oznaème $M(S)$ mno¾inu øetìzcù, jejich¾ výskyty algoritmus AC ohlásí
-ve~stavu~$S$. Uka¾te, ¾e kdybychom si mìli v¹echny mno¾iny $M(S)$ pamatovat
-explicitnì, budeme pro nìkteré vstupy potøebovat superlineární mno¾ství
+\ex{Oznaème $M(s)$ mno¾inu øetìzcù, jejich¾ výskyty algoritmus AC ohlásí
+ve~stavu~$s$. Uka¾te, ¾e kdybychom si mìli v¹echny mno¾iny $M(s)$ pamatovat
+explicitnì, budeme pro nìkteré sady jehel potøebovat superlineární mno¾ství
 prostoru vzhledem k~velikosti vstupu, tak¾e automat nepùjde sestrojit
 v~lineárním èase.}
+\exid{acslm}
 
 \ex{Rozmyslete si, ¾e mno¾iny $M(S)$ z~pøedchozího pøíkladu by bylo mo¾né
 reprezentovat jako srùstající spojové seznamy -- tedy takové, kde si ka¾dý
 prvek pamatuje ukazatel na svého následníka, který ov¹em mù¾e le¾et v~jiném
 seznamu. Uka¾te, ¾e námi zavedené zkratkové hrany lze interpretovat jako
-v~takových seznamech.}
+ukazatele v~takových seznamech.}
 
 \ex{Rozmyslete si, jak algoritmy z~této kapitoly upravit, aby si poradily
 s~velkými abecedami.}
+\exid{bigsigma}
 
 \ex{Upravte algoritmus AC, aby pro ka¾dou pozici v~senì vypsal nejdel¹í
 tam konèící jehlu, a~to efektivnìji ne¾ vyjmenováním v¹ech jehel. Jak se
@@ -516,17 +531,17 @@ v~line
 Jak o~dvou øetìzcích zjistit, zda je jeden rotací druhého?}
 
 \ex{Jak v~lineárním èase zrotovat øetìzec, dostaèuje-li pamì» poèítaèe jen
-na ulo¾ení jedhoho øetìzce a $\O(1)$ pomocných promìnných?}
+na ulo¾ení jednoho øetìzce a $\O(1)$ pomocných promìnných?}
 
-\ex{Navrhnìte algoritmus, který v~lineárním èase nalezne tu z~rotací zadaného
+\exx{Navrhnìte algoritmus, který v~lineárním èase nalezne tu z~rotací zadaného
 øetìzce, je¾ je lexikograficky minimální.}
 
 \ex{Navrhnìte datovou strukturu pro básníky, která si bude pamatovat slovník
 a bude umìt hledat rýmy. Tedy pro libovolné zadané slovo najde takové slovo
 ve~slovníku, které má se zadaným nejdel¹í spoleèný suffix.}
 
-\exx{Upravte datovou strukturu z~pøedchozího cvièení, aby je-li nejlep¹ích rýmù
-více, vypsala lexikograficky nejmen¹í z~nich.}
+\exx{Upravte datovou strukturu z~pøedchozího cvièení, aby v~pøípadì, ¾e nejlep¹ích rýmù
+je více, vypsala lexikograficky nejmen¹í z~nich.}
 
 \ex{Jak reprezentovat slovník, abyste umìli rychle vyhledávat v¹echny
 pøesmyèky zadaného slova?}
@@ -541,12 +556,12 @@ nejdel
 podøetìzec.}
 
 \ex{Definujme Fibonacciho slova takto: $F_0=|a|$, $F_1=|b|$, $F_{n+2}=F_nF_{n+1}$.
-Jak pro zadaný øetìzec nad abecedou $\{|a|,|b|\}$ najít nejdel¹í Fibonacciho podslovo?
+Jak v~zadaném øetìzci nad abecedou $\{|a|,|b|\}$ najít nejdel¹í Fibonacciho podslovo?
 }
 
-\exx{Pokraèujme v~pøedchozím cvièení. Dostaneme-li øetìzec nad nìjakou obecnou abecedou,
-jak nalézt jeho nejdel¹í podøetìzec, který je isomorfní s~nìjakým Fibonacciho slovem
-(li¹í se pouze substitucí jiných znakù za |a| a~|b|)?}
+\exx{Pokraèujme v~pøedchozím cvièení. Dostaneme øetìzec nad nìjakou obecnou abecedou,
+chceme nalézt jeho nejdel¹í podøetìzec, který je isomorfní s~nìjakým Fibonacciho slovem
+(li¹í se pouze substitucí jiných znakù za |a| a~|b|).}
 
 \ex{Je dáno slovo. Chceme nalézt jeho nejdel¹í prefix, který je souèasnì suffixem.}