\chapter{String algorithms} \index{merkkijono@merkkijono} \index{aakkosto@aakkosto} Merkkijonon $s$ merkit ovat $s[1],s[2],\ldots,s[n]$, missä $n$ on merkkijonon pituus. \key{Aakkosto} sisältää ne merkit, joita merkkijonossa voi esiintyä. Esimerkiksi aakkosto $\{\texttt{A},\texttt{B},\ldots,\texttt{Z}\}$ sisältää englannin kielen suuret kirjaimet. \index{osajono@osajono} \key{Osajono} sisältää merkkijonon merkit yhtenäiseltä väliltä. Merkkijonon osa\-jonojen määrä on $n(n+1)/2$. Esimerkiksi merkkijonon \texttt{ALGORITMI} yksi osajono on \texttt{ORITM}, joka muodostuu valitsemalla välin \texttt{ALG\underline{ORITM}I}. \index{alijono@alijono} \key{Alijono} on osajoukko merkkijonon merkeistä. Merkkijonon alijonojen määrä on $2^n-1$. Esimerkiksi merkkijonon \texttt{ALGORITMI} yksi alijono on \texttt{LGRMI}, joka muodostuu valitsemalla merkit \texttt{A\underline{LG}O\underline{R}IT\underline{MI}}. \index{alkuosa@alkuosa} \index{loppuosa@loppuosa} \index{prefiksi@prefiksi} \index{suffiksi@suffiksi} \key{Alkuosa} on merkkijonon alusta alkava osajono, ja \key{loppuosa} on merkkijonon loppuun päättyvä osajono. Esimerkiksi merkkijonon \texttt{KISSA} alkuosat ovat \texttt{K}, \texttt{KI}, \texttt{KIS}, \texttt{KISS} ja \texttt{KISSA} ja loppuosat ovat \texttt{A}, \texttt{SA}, \texttt{SSA}, \texttt{ISSA} ja \texttt{KISSA}. Alkuosa tai loppuosa on \key{aito}, jos se ei ole koko merkkijono. \index{kierto@kierto} \key{Kierto} syntyy siirtämällä jokin alkuosa merkkijonon loppuun tai jokin loppuosa merkkijonon alkuun. Esimerkiksi merkkijonon \texttt{APILA} kierrot ovat \texttt{APILA}, \texttt{PILAA}, \texttt{ILAAP}, \texttt{LAAPI} ja \texttt{AAPIL}. \index{jakso@jakso} \key{Jakso} on alkuosa, jota toistamalla merkkijono muodostuu. Jakson viimeinen toistokerta voi olla osittainen niin, että siinä on vain jakson alkuosa. Usein on kiinnostavaa selvittää, mikä on merkkijonon \key{lyhin jakso}. Esimerkiksi merkkijonon \texttt{ABCABCA} lyhin jakso on \texttt{ABC}. Tässä tapauksessa merkkijono syntyy toistamalla jaksoa ensin kahdesti kokonaan ja sitten kerran osittain. \key{Reuna} on merkkijono, joka on sekä alkuosa että loppuosa. Esimerkiksi merkkijonon \texttt{ABADABA} reunat ovat \texttt{A}, \texttt{ABA} ja \texttt{ABADABA}. Usein halutaan etsiä \key{pisin reuna}, joka ei ole koko merkkijono. \index{leksikografinen jxrjestys@leksikografinen järjestys} Merkkijonojen vertailussa käytössä on yleensä \key{leksikografinen järjestys}, joka vastaa aakkosjärjestystä. Siinä $x] (1) -- node[font=\small,label=\texttt{A}] {} (2); \path[draw,thick,->] (1) -- node[font=\small,label=\texttt{S}] {} (3); \path[draw,thick,->] (2) -- node[font=\small,label=left:\texttt{P}] {} (4); \path[draw,thick,->] (4) -- node[font=\small,label=left:\texttt{I}] {} (5); \path[draw,thick,->] (5) -- node[font=\small,label=left:\texttt{L}] {} (6); \path[draw,thick,->] (5) -- node[font=\small,label=right:\texttt{N}] {} (7); \path[draw,thick,->] (6) -- node[font=\small,label=left:\texttt{A}] {}(8); \path[draw,thick,->] (7) -- node[font=\small,label=right:\texttt{A}] {} (9); \path[draw,thick,->] (3) -- node[font=\small,label=right:\texttt{U}] {} (10); \path[draw,thick,->] (10) -- node[font=\small,label=right:\texttt{U}] {} (11); \path[draw,thick,->] (11) -- node[font=\small,label=right:\texttt{R}] {} (12); \path[draw,thick,->] (12) -- node[font=\small,label=right:\texttt{I}] {} (13); \end{tikzpicture} \end{center} Merkki * solmussa tarkoittaa, että jokin merkkijono päättyy kyseiseen solmuun. Tämä merkki on tarpeen, koska merkkijono voi olla toisen merkkijonon alkuosa, kuten tässä puussa merkkijono \texttt{SUU} on merkkijonon \texttt{SUURI} alkuosa. Triessä merkkijonon lisääminen ja hakeminen vievät aikaa $O(n)$, kun $n$ on merkkijonon pituus. Molemmat operaatiot voi toteuttaa lähtemällä liikkeelle juuresta ja kulkemalla alaspäin ketjua merkkien mukaisesti. Tarvittaessa puuhun lisätään uusia solmuja. Triestä on mahdollista etsiä sekä merkkijonoja että merkkijonojen alkuosia. Lisäksi puun solmuissa voi pitää kirjaa, monessako merkkijonossa on solmua vastaava alkuosa, mikä lisää trien käyttömahdollisuuksia. Trie on kätevää tallentaa taulukkona \begin{lstlisting} int t[N][A]; \end{lstlisting} missä $N$ on solmujen suurin mahdollinen määrä (eli tallennettavien merkkijonojen yhteispituus) ja $A$ on aakkoston koko. Trien solmut numeroidaan $1,2,3,\ldots$ niin, että juuren numero on 1, ja taulukon kohta $\texttt{t}[s][c]$ kertoo, mihin solmuun solmusta $s$ pääsee merkillä $c$. \section{Merkkijonohajautus} \index{hajautus@hajautus} \index{merkkijonohajautus@merkkijonohajautus} \key{Merkkijonohajautus} on tekniikka, jonka avulla voi esikäsittelyn jälkeen tarkastaa tehokkaasti, ovatko kaksi merkkijonon osajonoa samat. Ideana on verrata toisiinsa osajonojen hajautusarvoja, mikä on tehokkaampaa kuin osajonojen vertaaminen merkki kerrallaan. \subsubsection*{Hajautusarvon laskeminen} \index{hajautusarvo@hajautusarvo} \index{polynominen hajautus@polynominen hajautus} Merkkijonon \key{hajautusarvo} on luku, joka lasketaan merkkijonon merkeistä etukäteen valitulla tavalla. Jos kaksi merkkijonoa ovat samat, myös niiden hajautusarvot ovat samat, minkä ansiosta merkkijonoja voi vertailla niiden hajautusarvojen kautta. Tavallinen tapa toteuttaa merkkijonohajautus on käyttää polynomista hajautusta. Siinä hajautusarvo lasketaan kaavalla \[(c[1] A^{n-1} + c[2] A^{n-2} + \cdots + c[n] A^0) \bmod B ,\] missä merkkijonon merkkien koodit ovat $c[1],c[2],\ldots,c[n]$ ja $A$ ja $B$ ovat etukäteen valitut vakiot. Esimerkiksi merkkijonon \texttt{KISSA} merkkien koodit ovat: \begin{center} \begin{tikzpicture}[scale=0.7] \draw (0,0) grid (5,2); \node at (0.5, 1.5) {\texttt{K}}; \node at (1.5, 1.5) {\texttt{I}}; \node at (2.5, 1.5) {\texttt{S}}; \node at (3.5, 1.5) {\texttt{S}}; \node at (4.5, 1.5) {\texttt{A}}; \node at (0.5, 0.5) {75}; \node at (1.5, 0.5) {73}; \node at (2.5, 0.5) {83}; \node at (3.5, 0.5) {83}; \node at (4.5, 0.5) {65}; \end{tikzpicture} \end{center} Jos $A=3$ ja $B=97$, merkkijonon \texttt{KISSA} hajautusarvoksi tulee \[(75 \cdot 3^4 + 73 \cdot 3^3 + 83 \cdot 3^2 + 83 \cdot 3^1 + 65 \cdot 3^0) \bmod 97 = 59.\] \subsubsection*{Esikäsittely} Merkkijonohajautuksen esikäsittely muodostaa tietoa, jonka avulla voi laskea tehokkaasti merkkijonon osajonojen hajautusarvoja. Osoittautuu, että polynomisessa hajautuksessa $O(n)$-aikaisen esikäsittelyn jälkeen voi laskea minkä tahansa osajonon hajautusarvon ajassa $O(1)$. Ideana on muodostaa taulukko $h$, jossa $h[k]$ on hajautusarvo merkkijonon alkuosalle kohtaan $k$ asti. Taulukon voi muodostaa rekursiolla seuraavasti: \[ \begin{array}{lcl} h[0] & = & 0 \\ h[k] & = & (h[k-1] A + c[k]) \bmod B \\ \end{array} \] Lisäksi muodostetaan taulukko $p$, jossa $p[k]=A^k \bmod B$: \[ \begin{array}{lcl} p[0] & = & 1 \\ p[k] & = & (p[k-1] A) \bmod B. \\ \end{array} \] Näiden taulukoiden muodostaminen vie aikaa $O(n)$. Tämän jälkeen hajautusarvo merkkijonon osajonolle, joka alkaa kohdasta $a$ ja päättyy kohtaan $b$, voidaan laskea $O(1)$-ajassa kaavalla \[(h[b]-h[a-1] p[b-a+1]) \bmod B.\] \subsubsection*{Hajautuksen käyttö} Hajautusarvot tarjoavat nopean tavan merkkijonojen vertailemiseen. Ideana on vertailla merkkijonojen koko sisällön sijasta niiden hajautusarvoja. Jos hajautusarvot ovat samat, myös merkkijonot ovat \textit{todennäköisesti} samat, ja jos taas hajautusarvot eivät ole samat, merkkijonot eivät \textit{varmasti} ole samat. Hajautuksen avulla voi usein tehostaa raa'an voiman algoritmia niin, että siitä tulee tehokas. Tarkastellaan esimerkkinä raa'an voiman algoritmia, joka laskee, montako kertaa merkkijono $p$ esiintyy osajonona merkkijonossa $s$. Algoritmi käy läpi kaikki kohdat, joissa $p$ voi esiintyä, ja vertailee merkkijonoja merkki merkiltä. Tällaisen algoritmin aikavaativuus on $O(n^2)$. Voimme kuitenkin tehostaa algoritmia hajautuksen avulla, koska algoritmissa vertaillaan merkkijonojen osajonoja. Hajautusta käyttäen kukin vertailu vie aikaa vain $O(1)$, koska vertailua ei tehdä merkki merkiltä vaan suoraan hajautusarvon perusteella. Tuloksena on algoritmi, jonka aikavaativuus on $O(n)$, joka on paras mahdollinen aikavaativuus tehtävään. Yhdistämällä hajautus ja \emph{binäärihaku} on mahdollista myös selvittää logaritmisessa ajassa, kumpi kahdesta osajonosta on suurempi aakkosjärjestyksessä. Tämä onnistuu tutkimalla ensin binäärihaulla, kuinka pitkä on merkkijonojen yhteinen alkuosa, minkä jälkeen yhteisen alkuosan jälkeinen merkki kertoo, kumpi merkkijono on suurempi. \subsubsection*{Törmäykset ja parametrit} \index{tzzmxys@törmäys} Ilmeinen riski hajautusarvojen vertailussa on \key{törmäys}, joka tarkoittaa, että kahdessa merkkijonossa on eri sisältö mutta niiden hajautusarvot ovat samat. Tällöin hajautusarvojen perusteella merkkijonot näyttävät samalta, vaikka todellisuudessa ne eivät ole samat, ja algoritmi voi toimia väärin. Törmäyksen riski on aina olemassa, koska erilaisia merkkijonoja on enemmän kuin erilaisia hajautusarvoja. Riskin saa kuitenkin pieneksi valitsemalla hajautuksen vakiot $A$ ja $B$ huolellisesti. Vakioiden valinnassa on kaksi tavoitetta: hajautusarvojen tulisi jakautua tasaisesti merkkijonoille ja erilaisten hajautusarvojen määrän tulisi olla riittävän suuri. Hyvä ratkaisu on valita vakioiksi suuria satunnaislukuja. Tavallinen tapa on valita vakiot läheltä lukua $10^9$, esimerkiksi \[ \begin{array}{lcl} A & = & 911382323 \\ B & = & 972663749 \\ \end{array} \] Tällainen valinta takaa sen, että hajautusarvot jakautuvat riittävän tasaisesti välille $0 \ldots B-1$. Suuruusluokan $10^9$ etuna on, että \texttt{long long} -tyyppi riittää hajautusarvojen käsittelyyn koodissa, koska tulot $AB$ ja $BB$ mahtuvat \texttt{long long} -tyyppiin. Mutta onko $10^9$ riittävä määrä hajautusarvoja? Tarkastellaan nyt kolmea hajautuksen käyttötapaa: \textit{Tapaus 1:} Merkkijonoja $x$ ja $y$ verrataan toisiinsa. Törmäyksen todennäköisyys on $1/B$ olettaen, että kaikki hajautusarvot esiintyvät yhtä usein. \textit{Tapaus 2:} Merkkijonoa $x$ verrataan merkkijonoihin $y_1,y_2,\ldots,y_n$. Yhden tai useamman törmäyksen todennäköisyys on \[1-(1-1/B)^n.\] \textit{Tapaus 3:} Merkkijonoja $x_1,x_2,\ldots,x_n$ verrataan kaikkia keskenään. Yhden tai useamman törmäyksen todennäköisyys on \[ 1 - \frac{B \cdot (B-1) \cdot (B-2) \cdots (B-n+1)}{B^n}.\] Seuraava taulukko sisältää törmäyksen todennäköisyydet, kun vakion $B$ arvo vaihtelee ja $n=10^6$: \begin{center} \begin{tabular}{rrrr} vakio $B$ & tapaus 1 & tapaus 2 & tapaus 3 \\ \hline $10^3$ & $0.001000$ & $1.000000$ & $1.000000$ \\ $10^6$ & $0.000001$ & $0.632121$ & $1.000000$ \\ $10^9$ & $0.000000$ & $0.001000$ & $1.000000$ \\ $10^{12}$ & $0.000000$ & $0.000000$ & $0.393469$ \\ $10^{15}$ & $0.000000$ & $0.000000$ & $0.000500$ \\ $10^{18}$ & $0.000000$ & $0.000000$ & $0.000001$ \\ \end{tabular} \end{center} Taulukosta näkee, että tapauksessa 1 törmäyksen riski on olematon valinnalla $B \approx 10^9$. Tapauksessa 2 riski on olemassa, mutta se on silti edelleen vähäinen. Tapauksessa 3 tilanne on kuitenkin täysin toinen: törmäys tapahtuu käytännössä varmasti vielä valinnalla $B \approx 10^9$. \index{syntymxpxivxparadoksi@syntymäpäiväparadoksi} Tapauksen 3 ilmiö tunnetaan nimellä \key{syntymäpäiväparadoksi}: jos huoneessa on $n$ henkilöä, on suuri todennäköisyys, että jollain kahdella henkilöllä on sama syntymäpäivä, vaikka $n$ olisi melko pieni. Vastaavasti hajautuksessa kun kaikkia hajautusarvoja verrataan keskenään, käy helposti niin, että jotkin kaksi ovat sattumalta samoja. Hyvä tapa pienentää törmäyksen riskiä on laskea \emph{useita} hajautusarvoja eri parametreilla ja verrata niitä kaikkia. On hyvin pieni todennäköisyys, että törmäys tapahtuisi samaan aikaan kaikissa hajautusarvoissa. Esimerkiksi kaksi hajautusarvoa parametrilla $B \approx 10^9$ vastaa yhtä hajautusarvoa parametrilla $B \approx 10^{18}$, mikä takaa hyvän suojan törmäyksiltä. Jotkut käyttävät hajautuksessa vakioita $B=2^{32}$ tai $B=2^{64}$, jolloin modulo $B$ tulee laskettua automaattisesti, kun muuttujan arvo pyörähtää ympäri. Tämä ei ole kuitenkaan hyvä valinta, koska muotoa $2^x$ olevaa moduloa vastaan pystyy tekemään testisyötteen, joka aiheuttaa varmasti törmäyksen\footnote{ J. Pachocki ja Jakub Radoszweski: ''Where to use and how not to use polynomial string hashing''. \textit{Olympiads in Informatics}, 2013. }. \section{Z-algoritmi} \index{Z-algoritmi} \index{Z-taulukko} \key{Z-algoritmi} muodostaa merkkijonosta \key{Z-taulukon}, joka kertoo kullekin merkkijonon kohdalle, mikä on pisin kyseisestä kohdasta alkava osajono, joka on myös merkkijonon alkuosa. Z-algoritmin avulla voi ratkaista tehokkaasti monia merkkijonotehtäviä. Z-algoritmi ja merkkijonohajautus ovat usein vaihtoehtoisia tekniikoita, ja on makuasia, kumpaa algoritmia käyttää. Toisin kuin hajautus, Z-algoritmi toimii varmasti oikein eikä siinä ole törmäysten riskiä. Toisaalta Z-algoritmi on vaikeampi toteuttaa eikä se sovellu kaikkeen samaan kuin hajautus. \subsubsection*{Algoritmin toiminta} Z-algoritmi muodostaa merkkijonolle Z-taulukon, jonka jokaisessa kohdassa lukee, kuinka pitkälle kohdasta alkava osajono vastaa merkkijonon alkuosaa. Esimerkiksi Z-taulukko merkkijonolle \texttt{ACBACDACBACBACDA} on seuraava: \begin{center} \begin{tikzpicture}[scale=0.7] \draw (0,0) grid (16,2); \node at (0.5, 1.5) {\texttt{A}}; \node at (1.5, 1.5) {\texttt{C}}; \node at (2.5, 1.5) {\texttt{B}}; \node at (3.5, 1.5) {\texttt{A}}; \node at (4.5, 1.5) {\texttt{C}}; \node at (5.5, 1.5) {\texttt{D}}; \node at (6.5, 1.5) {\texttt{A}}; \node at (7.5, 1.5) {\texttt{C}}; \node at (8.5, 1.5) {\texttt{B}}; \node at (9.5, 1.5) {\texttt{A}}; \node at (10.5, 1.5) {\texttt{C}}; \node at (11.5, 1.5) {\texttt{B}}; \node at (12.5, 1.5) {\texttt{A}}; \node at (13.5, 1.5) {\texttt{C}}; \node at (14.5, 1.5) {\texttt{D}}; \node at (15.5, 1.5) {\texttt{A}}; \node at (0.5, 0.5) {--}; \node at (1.5, 0.5) {0}; \node at (2.5, 0.5) {0}; \node at (3.5, 0.5) {2}; \node at (4.5, 0.5) {0}; \node at (5.5, 0.5) {0}; \node at (6.5, 0.5) {5}; \node at (7.5, 0.5) {0}; \node at (8.5, 0.5) {0}; \node at (9.5, 0.5) {7}; \node at (10.5, 0.5) {0}; \node at (11.5, 0.5) {0}; \node at (12.5, 0.5) {2}; \node at (13.5, 0.5) {0}; \node at (14.5, 0.5) {0}; \node at (15.5, 0.5) {1}; \footnotesize \node at (0.5, 2.5) {1}; \node at (1.5, 2.5) {2}; \node at (2.5, 2.5) {3}; \node at (3.5, 2.5) {4}; \node at (4.5, 2.5) {5}; \node at (5.5, 2.5) {6}; \node at (6.5, 2.5) {7}; \node at (7.5, 2.5) {8}; \node at (8.5, 2.5) {9}; \node at (9.5, 2.5) {10}; \node at (10.5, 2.5) {11}; \node at (11.5, 2.5) {12}; \node at (12.5, 2.5) {13}; \node at (13.5, 2.5) {14}; \node at (14.5, 2.5) {15}; \node at (15.5, 2.5) {16}; \end{tikzpicture} \end{center} Esimerkiksi kohdassa 7 on arvo 5, koska siitä alkava 5-merkkinen osajono \texttt{ACBAC} on merkkijonon alkuosa, mutta 6-merkkinen osajono \texttt{ACBACB} ei ole enää merkkijonon alkuosa. Z-algoritmi käy läpi merkkijonon vasemmalta oikealle ja laskee jokaisessa kohdassa, kuinka pitkälle kyseisestä kohdasta alkava osajono täsmää merkkijonon alkuun. Algoritmi laskee yhteisen alkuosan pituuden vertaamalla merkkijonon alkua ja osajonon alkua toisiinsa. Suoraviivaisesti toteutettuna tällaisen algoritmin aikavaativuus olisi $O(n^2)$, koska yhteiset alkuosat voivat olla pitkiä. Z-algoritmissa on kuitenkin yksi tärkeä optimointi, jonka ansiosta algoritmin aikavaativuus on vain $O(n)$. Ideana on pitää muistissa väliä $[x,y]$, joka on aiemmin laskettu merkkijonon alkuun täsmäävä väli, jossa $y$ on mahdollisimman suuri. Tällä välillä olevia merkkejä ei tarvitse koskaan verrata uudestaan merkkijonon alkuun, vaan niitä koskevan tiedon saa suoraan Z-taulukon lasketusta osasta. Z-algoritmin aikavaativuus on $O(n)$, koska algoritmi aloittaa merkki kerrallaan vertailemisen vasta kohdasta $y+1$. Jos merkit täsmäävät, kohta $y$ siirtyy eteenpäin eikä algoritmin tarvitse enää koskaan vertailla tätä kohtaa, vaan algoritmi pystyy hyödyntämään Z-taulukon alussa olevaa tietoa. \subsubsection*{Esimerkki} Katsotaan nyt, miten Z-algoritmi muodostaa seuraavan Z-taulukon: \begin{center} \begin{tikzpicture}[scale=0.7] \draw (0,0) grid (16,2); \node at (0.5, 1.5) {A}; \node at (1.5, 1.5) {C}; \node at (2.5, 1.5) {B}; \node at (3.5, 1.5) {A}; \node at (4.5, 1.5) {C}; \node at (5.5, 1.5) {D}; \node at (6.5, 1.5) {A}; \node at (7.5, 1.5) {C}; \node at (8.5, 1.5) {B}; \node at (9.5, 1.5) {A}; \node at (10.5, 1.5) {C}; \node at (11.5, 1.5) {B}; \node at (12.5, 1.5) {A}; \node at (13.5, 1.5) {C}; \node at (14.5, 1.5) {D}; \node at (15.5, 1.5) {A}; \node at (0.5, 0.5) {--}; \node at (1.5, 0.5) {?}; \node at (2.5, 0.5) {?}; \node at (3.5, 0.5) {?}; \node at (4.5, 0.5) {?}; \node at (5.5, 0.5) {?}; \node at (6.5, 0.5) {?}; \node at (7.5, 0.5) {?}; \node at (8.5, 0.5) {?}; \node at (9.5, 0.5) {?}; \node at (10.5, 0.5) {?}; \node at (11.5, 0.5) {?}; \node at (12.5, 0.5) {?}; \node at (13.5, 0.5) {?}; \node at (14.5, 0.5) {?}; \node at (15.5, 0.5) {?}; \footnotesize \node at (0.5, 2.5) {1}; \node at (1.5, 2.5) {2}; \node at (2.5, 2.5) {3}; \node at (3.5, 2.5) {4}; \node at (4.5, 2.5) {5}; \node at (5.5, 2.5) {6}; \node at (6.5, 2.5) {7}; \node at (7.5, 2.5) {8}; \node at (8.5, 2.5) {9}; \node at (9.5, 2.5) {10}; \node at (10.5, 2.5) {11}; \node at (11.5, 2.5) {12}; \node at (12.5, 2.5) {13}; \node at (13.5, 2.5) {14}; \node at (14.5, 2.5) {15}; \node at (15.5, 2.5) {16}; \end{tikzpicture} \end{center} Ensimmäinen mielenkiintoinen kohta tulee, kun yhteisen alkuosan pituus on 5. Silloin algoritmi laittaa muistiin välin $[7,11]$ seuraavasti: \begin{center} \begin{tikzpicture}[scale=0.7] \fill[color=lightgray] (6,0) rectangle (7,1); \draw (0,0) grid (16,2); \node at (0.5, 1.5) {A}; \node at (1.5, 1.5) {C}; \node at (2.5, 1.5) {B}; \node at (3.5, 1.5) {A}; \node at (4.5, 1.5) {C}; \node at (5.5, 1.5) {D}; \node at (6.5, 1.5) {A}; \node at (7.5, 1.5) {C}; \node at (8.5, 1.5) {B}; \node at (9.5, 1.5) {A}; \node at (10.5, 1.5) {C}; \node at (11.5, 1.5) {B}; \node at (12.5, 1.5) {A}; \node at (13.5, 1.5) {C}; \node at (14.5, 1.5) {D}; \node at (15.5, 1.5) {A}; \node at (0.5, 0.5) {--}; \node at (1.5, 0.5) {0}; \node at (2.5, 0.5) {0}; \node at (3.5, 0.5) {2}; \node at (4.5, 0.5) {0}; \node at (5.5, 0.5) {0}; \node at (6.5, 0.5) {5}; \node at (7.5, 0.5) {?}; \node at (8.5, 0.5) {?}; \node at (9.5, 0.5) {?}; \node at (10.5, 0.5) {?}; \node at (11.5, 0.5) {?}; \node at (12.5, 0.5) {?}; \node at (13.5, 0.5) {?}; \node at (14.5, 0.5) {?}; \node at (15.5, 0.5) {?}; \draw [decoration={brace}, decorate, line width=0.5mm] (6,3.00) -- (11,3.00); \node at (6.5,3.50) {$x$}; \node at (10.5,3.50) {$y$}; \footnotesize \node at (0.5, 2.5) {1}; \node at (1.5, 2.5) {2}; \node at (2.5, 2.5) {3}; \node at (3.5, 2.5) {4}; \node at (4.5, 2.5) {5}; \node at (5.5, 2.5) {6}; \node at (6.5, 2.5) {7}; \node at (7.5, 2.5) {8}; \node at (8.5, 2.5) {9}; \node at (9.5, 2.5) {10}; \node at (10.5, 2.5) {11}; \node at (11.5, 2.5) {12}; \node at (12.5, 2.5) {13}; \node at (13.5, 2.5) {14}; \node at (14.5, 2.5) {15}; \node at (15.5, 2.5) {16}; \end{tikzpicture} \end{center} Välin $[7,11]$ hyötynä on, että algoritmi voi sen avulla laskea seuraavat Z-taulukon arvot nopeammin. Koska välin $[7,11]$ merkit ovat samat kuin merkkijonon alussa, myös Z-taulukon arvoissa on vastaavuutta. Ensinnäkin kohdissa 8 ja 9 tulee olla samat arvot kuin kohdissa 2 ja 3, koska väli $[7,11]$ vastaa väliä $[1,5]$: \begin{center} \begin{tikzpicture}[scale=0.7] \fill[color=lightgray] (7,0) rectangle (9,1); \draw (0,0) grid (16,2); \node at (0.5, 1.5) {A}; \node at (1.5, 1.5) {C}; \node at (2.5, 1.5) {B}; \node at (3.5, 1.5) {A}; \node at (4.5, 1.5) {C}; \node at (5.5, 1.5) {D}; \node at (6.5, 1.5) {A}; \node at (7.5, 1.5) {C}; \node at (8.5, 1.5) {B}; \node at (9.5, 1.5) {A}; \node at (10.5, 1.5) {C}; \node at (11.5, 1.5) {B}; \node at (12.5, 1.5) {A}; \node at (13.5, 1.5) {C}; \node at (14.5, 1.5) {D}; \node at (15.5, 1.5) {A}; \node at (0.5, 0.5) {--}; \node at (1.5, 0.5) {0}; \node at (2.5, 0.5) {0}; \node at (3.5, 0.5) {2}; \node at (4.5, 0.5) {0}; \node at (5.5, 0.5) {0}; \node at (6.5, 0.5) {5}; \node at (7.5, 0.5) {0}; \node at (8.5, 0.5) {0}; \node at (9.5, 0.5) {?}; \node at (10.5, 0.5) {?}; \node at (11.5, 0.5) {?}; \node at (12.5, 0.5) {?}; \node at (13.5, 0.5) {?}; \node at (14.5, 0.5) {?}; \node at (15.5, 0.5) {?}; \draw [decoration={brace}, decorate, line width=0.5mm] (6,3.00) -- (11,3.00); \node at (6.5,3.50) {$x$}; \node at (10.5,3.50) {$y$}; \footnotesize \node at (0.5, 2.5) {1}; \node at (1.5, 2.5) {2}; \node at (2.5, 2.5) {3}; \node at (3.5, 2.5) {4}; \node at (4.5, 2.5) {5}; \node at (5.5, 2.5) {6}; \node at (6.5, 2.5) {7}; \node at (7.5, 2.5) {8}; \node at (8.5, 2.5) {9}; \node at (9.5, 2.5) {10}; \node at (10.5, 2.5) {11}; \node at (11.5, 2.5) {12}; \node at (12.5, 2.5) {13}; \node at (13.5, 2.5) {14}; \node at (14.5, 2.5) {15}; \node at (15.5, 2.5) {16}; \draw[thick,<->] (7.5,-0.25) .. controls (7,-1.25) and (2,-1.25) .. (1.5,-0.25); \draw[thick,<->] (8.5,-0.25) .. controls (8,-1.25) and (3,-1.25) .. (2.5,-0.25); \end{tikzpicture} \end{center} Seuraavaksi kohdasta 4 saa tietoa kohdan 10 arvon laskemiseksi. Koska kohdassa 4 on arvo 2, tämä tarkoittaa, että osajono täsmää kohtaan $y=11$ asti, mutta sen jälkeen on tutkimatonta aluetta merkkijonossa. \begin{center} \begin{tikzpicture}[scale=0.7] \fill[color=lightgray] (9,0) rectangle (10,1); \draw (0,0) grid (16,2); \node at (0.5, 1.5) {A}; \node at (1.5, 1.5) {C}; \node at (2.5, 1.5) {B}; \node at (3.5, 1.5) {A}; \node at (4.5, 1.5) {C}; \node at (5.5, 1.5) {D}; \node at (6.5, 1.5) {A}; \node at (7.5, 1.5) {C}; \node at (8.5, 1.5) {B}; \node at (9.5, 1.5) {A}; \node at (10.5, 1.5) {C}; \node at (11.5, 1.5) {B}; \node at (12.5, 1.5) {A}; \node at (13.5, 1.5) {C}; \node at (14.5, 1.5) {D}; \node at (15.5, 1.5) {A}; \node at (0.5, 0.5) {--}; \node at (1.5, 0.5) {0}; \node at (2.5, 0.5) {0}; \node at (3.5, 0.5) {2}; \node at (4.5, 0.5) {0}; \node at (5.5, 0.5) {0}; \node at (6.5, 0.5) {5}; \node at (7.5, 0.5) {0}; \node at (8.5, 0.5) {0}; \node at (9.5, 0.5) {?}; \node at (10.5, 0.5) {?}; \node at (11.5, 0.5) {?}; \node at (12.5, 0.5) {?}; \node at (13.5, 0.5) {?}; \node at (14.5, 0.5) {?}; \node at (15.5, 0.5) {?}; \draw [decoration={brace}, decorate, line width=0.5mm] (6,3.00) -- (11,3.00); \node at (6.5,3.50) {$x$}; \node at (10.5,3.50) {$y$}; \footnotesize \node at (0.5, 2.5) {1}; \node at (1.5, 2.5) {2}; \node at (2.5, 2.5) {3}; \node at (3.5, 2.5) {4}; \node at (4.5, 2.5) {5}; \node at (5.5, 2.5) {6}; \node at (6.5, 2.5) {7}; \node at (7.5, 2.5) {8}; \node at (8.5, 2.5) {9}; \node at (9.5, 2.5) {10}; \node at (10.5, 2.5) {11}; \node at (11.5, 2.5) {12}; \node at (12.5, 2.5) {13}; \node at (13.5, 2.5) {14}; \node at (14.5, 2.5) {15}; \node at (15.5, 2.5) {16}; \draw[thick,<->] (9.5,-0.25) .. controls (9,-1.25) and (4,-1.25) .. (3.5,-0.25); \end{tikzpicture} \end{center} Nyt algoritmi alkaa vertailla merkkejä kohdasta $y+1=12$ alkaen merkki kerrallaan. Algoritmi ei voi hyödyntää valmiina Z-taulukossa olevaa tietoa, koska se ei ole vielä aiemmin tutkinut merkkijonoa näin pitkälle. Tuloksena osajonon pituudeksi tulee 7 ja väli $[x,y]$ päivittyy vastaavasti: \begin{center} \begin{tikzpicture}[scale=0.7] \fill[color=lightgray] (9,0) rectangle (10,1); \draw (0,0) grid (16,2); \node at (0.5, 1.5) {A}; \node at (1.5, 1.5) {C}; \node at (2.5, 1.5) {B}; \node at (3.5, 1.5) {A}; \node at (4.5, 1.5) {C}; \node at (5.5, 1.5) {D}; \node at (6.5, 1.5) {A}; \node at (7.5, 1.5) {C}; \node at (8.5, 1.5) {B}; \node at (9.5, 1.5) {A}; \node at (10.5, 1.5) {C}; \node at (11.5, 1.5) {B}; \node at (12.5, 1.5) {A}; \node at (13.5, 1.5) {C}; \node at (14.5, 1.5) {D}; \node at (15.5, 1.5) {A}; \node at (0.5, 0.5) {--}; \node at (1.5, 0.5) {0}; \node at (2.5, 0.5) {0}; \node at (3.5, 0.5) {2}; \node at (4.5, 0.5) {0}; \node at (5.5, 0.5) {0}; \node at (6.5, 0.5) {5}; \node at (7.5, 0.5) {0}; \node at (8.5, 0.5) {0}; \node at (9.5, 0.5) {7}; \node at (10.5, 0.5) {?}; \node at (11.5, 0.5) {?}; \node at (12.5, 0.5) {?}; \node at (13.5, 0.5) {?}; \node at (14.5, 0.5) {?}; \node at (15.5, 0.5) {?}; \draw [decoration={brace}, decorate, line width=0.5mm] (9,3.00) -- (16,3.00); \node at (9.5,3.50) {$x$}; \node at (15.5,3.50) {$y$}; \footnotesize \node at (0.5, 2.5) {1}; \node at (1.5, 2.5) {2}; \node at (2.5, 2.5) {3}; \node at (3.5, 2.5) {4}; \node at (4.5, 2.5) {5}; \node at (5.5, 2.5) {6}; \node at (6.5, 2.5) {7}; \node at (7.5, 2.5) {8}; \node at (8.5, 2.5) {9}; \node at (9.5, 2.5) {10}; \node at (10.5, 2.5) {11}; \node at (11.5, 2.5) {12}; \node at (12.5, 2.5) {13}; \node at (13.5, 2.5) {14}; \node at (14.5, 2.5) {15}; \node at (15.5, 2.5) {16}; % \draw[thick,<->] (9.5,-0.25) .. controls (9,-1.25) and (4,-1.25) .. (3.5,-0.25); \end{tikzpicture} \end{center} Tämän jälkeen kaikkien seuraavien Z-taulukon arvojen laskemisessa pystyy hyödyntämään jälleen välin $[x,y]$ antamaa tietoa ja algoritmi saa Z-taulukon loppuun tulevat arvot suoraan Z-taulukon alusta: \begin{center} \begin{tikzpicture}[scale=0.7] \draw (0,0) grid (16,2); \node at (0.5, 1.5) {A}; \node at (1.5, 1.5) {C}; \node at (2.5, 1.5) {B}; \node at (3.5, 1.5) {A}; \node at (4.5, 1.5) {C}; \node at (5.5, 1.5) {D}; \node at (6.5, 1.5) {A}; \node at (7.5, 1.5) {C}; \node at (8.5, 1.5) {B}; \node at (9.5, 1.5) {A}; \node at (10.5, 1.5) {C}; \node at (11.5, 1.5) {B}; \node at (12.5, 1.5) {A}; \node at (13.5, 1.5) {C}; \node at (14.5, 1.5) {D}; \node at (15.5, 1.5) {A}; \node at (0.5, 0.5) {--}; \node at (1.5, 0.5) {0}; \node at (2.5, 0.5) {0}; \node at (3.5, 0.5) {2}; \node at (4.5, 0.5) {0}; \node at (5.5, 0.5) {0}; \node at (6.5, 0.5) {5}; \node at (7.5, 0.5) {0}; \node at (8.5, 0.5) {0}; \node at (9.5, 0.5) {7}; \node at (10.5, 0.5) {0}; \node at (11.5, 0.5) {0}; \node at (12.5, 0.5) {2}; \node at (13.5, 0.5) {0}; \node at (14.5, 0.5) {0}; \node at (15.5, 0.5) {1}; \draw [decoration={brace}, decorate, line width=0.5mm] (9,3.00) -- (16,3.00); \node at (9.5,3.50) {$x$}; \node at (15.5,3.50) {$y$}; \footnotesize \node at (0.5, 2.5) {1}; \node at (1.5, 2.5) {2}; \node at (2.5, 2.5) {3}; \node at (3.5, 2.5) {4}; \node at (4.5, 2.5) {5}; \node at (5.5, 2.5) {6}; \node at (6.5, 2.5) {7}; \node at (7.5, 2.5) {8}; \node at (8.5, 2.5) {9}; \node at (9.5, 2.5) {10}; \node at (10.5, 2.5) {11}; \node at (11.5, 2.5) {12}; \node at (12.5, 2.5) {13}; \node at (13.5, 2.5) {14}; \node at (14.5, 2.5) {15}; \node at (15.5, 2.5) {16}; \end{tikzpicture} \end{center} \subsubsection{Z-taulukon käyttäminen} Ratkaistaan esimerkkinä tehtävä, jossa laskettavana on, montako kertaa merkkijono $p$ esiintyy osajonona merkkijonossa $s$. Ratkaisimme tehtävän aiemmin tehokkaasti merkkijonohajautuksen avulla, ja nyt Z-algoritmi tarjoaa siihen vaihtoehtoisen lähestymistavan. Usein esiintyvä idea Z-algoritmin yhteydessä on muodostaa merkkijono, jonka osana on useita välimerkeillä erotettuja merkkijonoja. Tässä tehtävässä sopiva merkkijono on $p$\texttt{\#}$s$, jossa merkkijonojen $p$ ja $s$ välissä on erikoismerkki \texttt{\#}, jota ei esiinny merkkijonoissa. Nyt merkkijonoa $p$\texttt{\#}$s$ vastaava Z-taulukko kertoo, missä kohdissa merkkijonoa $p$ esiintyy merkkijono $s$. Tällaiset kohdat ovat tarkalleen ne Z-taulukon kohdat, joissa on merkkijonon $p$ pituus. \begin{samepage} Esimerkiksi jos $s=$\texttt{HATTIVATTI} ja $p=$\texttt{ATT}, niin Z-taulukosta tulee: \begin{center} \begin{tikzpicture}[scale=0.7] \draw (0,0) grid (14,2); \node at (0.5, 1.5) {A}; \node at (1.5, 1.5) {T}; \node at (2.5, 1.5) {T}; \node at (3.5, 1.5) {\#}; \node at (4.5, 1.5) {H}; \node at (5.5, 1.5) {A}; \node at (6.5, 1.5) {T}; \node at (7.5, 1.5) {T}; \node at (8.5, 1.5) {I}; \node at (9.5, 1.5) {V}; \node at (10.5, 1.5) {A}; \node at (11.5, 1.5) {T}; \node at (12.5, 1.5) {T}; \node at (13.5, 1.5) {I}; \node at (0.5, 0.5) {--}; \node at (1.5, 0.5) {0}; \node at (2.5, 0.5) {0}; \node at (3.5, 0.5) {0}; \node at (4.5, 0.5) {0}; \node at (5.5, 0.5) {3}; \node at (6.5, 0.5) {0}; \node at (7.5, 0.5) {0}; \node at (8.5, 0.5) {0}; \node at (9.5, 0.5) {0}; \node at (10.5, 0.5) {3}; \node at (11.5, 0.5) {0}; \node at (12.5, 0.5) {0}; \node at (13.5, 0.5) {0}; \footnotesize \node at (0.5, 2.5) {1}; \node at (1.5, 2.5) {2}; \node at (2.5, 2.5) {3}; \node at (3.5, 2.5) {4}; \node at (4.5, 2.5) {5}; \node at (5.5, 2.5) {6}; \node at (6.5, 2.5) {7}; \node at (7.5, 2.5) {8}; \node at (8.5, 2.5) {9}; \node at (9.5, 2.5) {10}; \node at (10.5, 2.5) {11}; \node at (11.5, 2.5) {12}; \node at (12.5, 2.5) {13}; \node at (13.5, 2.5) {14}; \end{tikzpicture} \end{center} \end{samepage} Taulukon kohdissa 6 ja 11 on luku 3, mikä tarkoittaa, että \texttt{ATT} esiintyy vastaavissa kohdissa merkkijonossa \texttt{HATTIVATTI}. Tuloksena olevan algoritmin aikavaativuus on $O(n)$, koska riittää muodostaa Z-taulukko ja käydä se läpi.