diff --git a/luku26.tex b/luku26.tex index 30bbcf0..c8c09fb 100644 --- a/luku26.tex +++ b/luku26.tex @@ -392,7 +392,7 @@ using different parameters. It is very unlikely that a collision would occur in all hash values at the same time. For example, two hash values with parameter -$B \approx 10^9$ corresponds to one hash +$B \approx 10^9$ correspond to one hash value with parameter $B \approx 10^{18}$, which makes the probability of a collision very small. @@ -401,40 +401,39 @@ which is convenient, because operations with 32 and 64 bit integers are calculated modulo $2^{32}$ and $2^{64}$. However, this is not a good choice, because it is possible to construct inputs that always generate collisions when -remainders of the form $2^x$ are used\footnote{ -J. Pachocki ja Jakub Radoszweski: +constants of the form $2^x$ are used\footnote{ +J. Pachocki and Jakub Radoszweski: ''Where to use and how not to use polynomial string hashing''. \textit{Olympiads in Informatics}, 2013. }. -\section{Z-algoritmi} +\section{Z-algorithm} -\index{Z-algoritmi} -\index{Z-taulukko} +\index{Z-algorithm} +\index{Z-array} -\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ä. +The \key{Z-algorithm} generates a \key{Z-array} +for the string, that contains for each index $k$ +in the string the length of the longest substring +that begins at index $k$ and is a prefix of the string. +Many string problems can be efficiently solved +using the Z-algorithm. -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. +It is often a matter of taste whether to use +the Z-algorithm or string hashing. +Unlike hashing, the Z-algorithm always works +and there is no risk for collisions. +On the other hand, the Z-algorithm is more difficult +to implement and some problems can only be solved +using hashing. -\subsubsection*{Algoritmin toiminta} +\subsubsection*{Description} -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: +The Z-algorithm constructs a Z-array that +indicates for each position the length of the +longest substring that is also a prefix of the string. +For example, the Z-array for the string +\texttt{ACBACDACBACBACDA} is as follows: \begin{center} \begin{tikzpicture}[scale=0.7] @@ -495,52 +494,45 @@ merkkijonolle \texttt{ACBACDACBACBACDA} on seuraava: \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. +For example, the position 7 contains the value 5, +because the substring \texttt{ACBAC} of length 5 +is a prefix of the string, +but the substring \texttt{ACBACB} of length 6 +is not a prefix of the string. -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. +The Z-algorithm scans the string from the left +to the right, and calculates for each position +the length of the longest substring that +is a prefix of the string. +The algorithm compares the first characters +of the string +and the active substring with each other to +find the length of the common prefix. -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)$. +A straightforward implementation would yield +an algorithm with time complexity $O(n^2)$ +because the common prefixes may be long. +However, the Z-algorithm has one important +optimization which ensures that the time complexity +is only $O(n)$. +The idea is to maintain a range $[x,y]$ such that +the substring from $x$ to $y$ is a prefix of +the string and $y$ is as large as possible. +Since the Z-array already contains information +about the characters in the range $[x,y]$, +it is not needed to process them again later in the algorithm. -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. +The time complexity of the Z-algorithm is $O(n)$, +because the algorithm always compares substrings +character by character only from index $y+1$. +If the characters match, the value of $y$ increases, +and it is not needed to inspect the character again, +but the information in the Z-array can be used. -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*{Example} -\subsubsection*{Esimerkki} - -Katsotaan nyt, miten Z-algoritmi muodostaa -seuraavan Z-taulukon: +Let's construct the following Z-array using +the Z-algorithm: \begin{center} \begin{tikzpicture}[scale=0.7] @@ -601,10 +593,9 @@ seuraavan Z-taulukon: \end{tikzpicture} \end{center} -Ensimmäinen mielenkiintoinen kohta tulee, -kun yhteisen alkuosan pituus on 5. -Silloin algoritmi laittaa muistiin -välin $[7,11]$ seuraavasti: +The first interesting position is 7 where the +length of the common prefix is 5. +The corresponding range in the string is $[7,11]$: \begin{center} \begin{tikzpicture}[scale=0.7] @@ -672,18 +663,14 @@ välin $[7,11]$ seuraavasti: \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]$: +The benefit in the range $[7,11]$ is that the +algorithm can calculate the subsequent values +for the Z-array more efficiently. +Since the ranges $[1,5]$ and $[7,11]$ contain +the same characters, also the Z-array will +contain similar values. +First, the values at indices 8 and 9 +correspond to the values at indices 2 and 3: \begin{center} \begin{tikzpicture}[scale=0.7] @@ -755,13 +742,13 @@ vastaa väliä $[1,5]$: \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. +After this, the value for index 10 can be +calculated using the value at index 4. +The value at index 4 is 2, +so the first two characters +in the substring match the beginning of the string. +However, the characters after index $y=11$ have +not been inspected yet. \begin{center} \begin{tikzpicture}[scale=0.7] @@ -830,13 +817,13 @@ aluetta merkkijonossa. \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: +The algorithm compares the substring +beginning at index $y+1=12$ character by character. +The previous values in the Z-array cannot be used, +because this is the first time the characters +after index 11 are inspected. +It turns out that the length of the common +prefix is 7, and the range $[x,y]$ will be updated: \begin{center} \begin{tikzpicture}[scale=0.7] @@ -905,11 +892,10 @@ ja väli $[x,y]$ päivittyy vastaavasti: \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: +After this, all subsequent values in the Z-array +can be calculated using the information in +the range $[x,y]$. All the remaining values can be +directly retrieved from the beginning of the Z-array: \begin{center} \begin{tikzpicture}[scale=0.7] @@ -976,37 +962,33 @@ arvot suoraan Z-taulukon alusta: \end{tikzpicture} \end{center} -\subsubsection{Z-taulukon käyttäminen} +\subsubsection{Using the Z-array} -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. +As an example, let's solve a problem +where our task is to calculate +the number of times a string $p$ +occurs as a substring in a string $s$. +Previously, we solved this problem +using string hashing, but the Z-algorithm +provides another way to solve the problem. -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 +A usual idea when using the Z-algorithm +is to construct a string that consists of +several strings separated by special characters. +In this problem, we can construct a string $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. +where $p$ and $s$ are separated by a special +character \texttt{\#} that doesn't occur +in the strings. +After this, the Z-array for the string +$p$\texttt{\#}$s$ indicates the positions +where $p$ occurs in $s$. +Such positions are those positions in the Z-array +that contain the value $p$. \begin{samepage} -Esimerkiksi jos $s=$\texttt{HATTIVATTI} ja $p=$\texttt{ATT}, -niin Z-taulukosta tulee: +For example, if $s=$\texttt{HATTIVATTI} and $p=$\texttt{ATT}, +the Z-array is as follows: \begin{center} \begin{tikzpicture}[scale=0.7] @@ -1060,11 +1042,11 @@ niin Z-taulukosta tulee: \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}. +The positions 6 and 11 contain the value 3, +which means that the substring \texttt{ATT} +occurs in the corresponding positions +in the string \texttt{HATTIVATTI}. -Tuloksena olevan algoritmin aikavaativuus on -$O(n)$, koska riittää muodostaa Z-taulukko -ja käydä se läpi. \ No newline at end of file +The time complexity of the resulting algorithm +is $O(n)$, because it suffices to construct and +go through the Z-array. \ No newline at end of file