1078 lines
30 KiB
TeX
1078 lines
30 KiB
TeX
\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<y$, jos joko $x$ on $y$:n aito alkuosa
|
|
tai on olemassa kohta $k$ niin,
|
|
että $x[i]=y[i]$, kun $i<k$, ja $x[k]<y[k]$.
|
|
|
|
\section{Trie-rakenne}
|
|
|
|
\index{trie@trie}
|
|
|
|
\key{Trie} on puurakenne,
|
|
joka pitää yllä joukkoa merkkijonoja.
|
|
Merkkijonot tallennetaan puuhun
|
|
juuresta lähtevinä merkkien ketjuina.
|
|
Jos useammalla merkkijonolla on sama alkuosa,
|
|
niiden ketjun alkuosa on yhteinen.
|
|
|
|
Esimerkiksi joukkoa
|
|
$\{\texttt{APILA},\texttt{APINA},\texttt{SUU},\texttt{SUURI}\}$
|
|
vastaa seuraava trie:
|
|
|
|
\begin{center}
|
|
\begin{tikzpicture}[scale=0.9]
|
|
\node[draw, circle] (1) at (0,20) {$\phantom{1}$};
|
|
\node[draw, circle] (2) at (-1.5,19) {$\phantom{1}$};
|
|
\node[draw, circle] (3) at (1.5,19) {$\phantom{1}$};
|
|
\node[draw, circle] (4) at (-1.5,17.5) {$\phantom{1}$};
|
|
\node[draw, circle] (5) at (-1.5,16) {$\phantom{1}$};
|
|
\node[draw, circle] (6) at (-2.5,14.5) {$\phantom{1}$};
|
|
\node[draw, circle] (7) at (-0.5,14.5) {$\phantom{1}$};
|
|
\node[draw, circle] (8) at (-2.5,13) {*};
|
|
\node[draw, circle] (9) at (-0.5,13) {*};
|
|
\node[draw, circle] (10) at (1.5,17.5) {$\phantom{1}$};
|
|
\node[draw, circle] (11) at (1.5,16) {*};
|
|
\node[draw, circle] (12) at (1.5,14.5) {$\phantom{1}$};
|
|
\node[draw, circle] (13) at (1.5,13) {*};
|
|
|
|
\path[draw,thick,->] (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. |