Chapter 27 first version

This commit is contained in:
Antti H S Laaksonen 2017-01-25 23:13:05 +02:00
parent 986de0d086
commit e422e2652f
1 changed files with 218 additions and 209 deletions

View File

@ -1,37 +1,37 @@
\chapter{Square root algorithms} \chapter{Square root algorithms}
\index{nelizjuurialgoritmi@neliöjuurialgoritmi} \index{square root algorithm}
\key{Neliöjuurialgoritmi} on algoritmi, A \key{square root algorithm} is an algorithm
jonka aikavaativuudessa esiintyy neliöjuuri. that has a square root in its time complexity.
Neliöjuurta voi ajatella ''köyhän miehen logaritmina'': A square root can be seen as a ''poor man's logarithm'':
aikavaativuus $O(\sqrt n)$ on parempi kuin $O(n)$ the complexity $O(\sqrt n)$ is better than $O(n)$
mutta huonompi kuin $O(\log n)$. but worse than $O(\log n)$.
Toisaalta neliöjuurialgoritmit toimivat Still, many square root algorithms are fast in practice
käytännössä hyvin ja niiden vakiokertoimet ovat pieniä. and have small constant factors.
Tarkastellaan esimerkkinä tuttua ongelmaa, As an example, let's consider the problem of
jossa toteutettavana on summakysely taulukkoon. handling sum queries in an array.
Halutut operaatiot ovat: The required operations are:
\begin{itemize} \begin{itemize}
\item muuta kohdassa $x$ olevaa lukua \item change the value at index $x$
\item laske välin $[a,b]$ lukujen summa \item calculate the sum in the range $[a,b]$
\end{itemize} \end{itemize}
Olemme aiemmin ratkaisseet tehtävän We have previously solved the problem using
binääri-indeksipuun ja segmenttipuun avulla, a binary indexed tree and a segment tree,
jolloin kummankin operaation aikavaativuus on $O(\log n)$. that support both operations in $O(\log n)$ time.
Nyt ratkaisemme tehtävän toisella However, now we will solve the problem
tavalla neliöjuurirakennetta käyttäen, in another way using a square root structure
jolloin summan laskenta vie aikaa $O(\sqrt n)$ so that we can calculate sums in $O(\sqrt n)$ time
ja luvun muuttaminen vie aikaa $O(1)$. and modify values in $O(1)$ time.
Ideana on jakaa taulukko $\sqrt n$-kokoisiin The idea is to divide the array into segments
väleihin niin, että jokaiseen väliin of size $\sqrt n$ so that each segment contains
tallennetaan lukujen summa välillä. the sum of values inside the segment.
Seuraavassa on esimerkki taulukosta ja The following example shows an array and the
sitä vastaavista $\sqrt n$-väleistä: corresponding segments:
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
@ -67,9 +67,9 @@ sitä vastaavista $\sqrt n$-väleistä:
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Kun taulukon luku muuttuu, When a value in the array changes,
tämän yhteydessä täytyy laskea uusi summa we have to calculate the sum in the corresponding
vastaavalle $\sqrt n$-välille: segment again:
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
@ -107,9 +107,9 @@ vastaavalle $\sqrt n$-välille:
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Välin summan laskeminen taas tapahtuu muodostamalla Any sum in the array can be calculated as a combination
summa reunoissa olevista yksittäisistä luvuista of single values in the array and the sums of the
sekä keskellä olevista $\sqrt n$-väleistä: segments between them:
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
@ -152,207 +152,213 @@ sekä keskellä olevista $\sqrt n$-väleistä:
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Luvun muuttamisen aikavaativuus on We can change a value in $O(1)$ time,
$O(1)$, koska riittää muuttaa yhden $\sqrt n$-välin summaa. because we only have to change the sum of a single segment.
Välin summa taas lasketaan kolmessa osassa: A sum in a range consists of three parts:
\begin{itemize} \begin{itemize}
\item vasemmassa reunassa on $O(\sqrt n)$ yksittäistä lukua \item first, there are $O(\sqrt n)$ single values
\item keskellä on $O(\sqrt n)$ peräkkäistä $\sqrt n$-väliä \item then, there are $O(\sqrt n)$ consecutive segments
\item oikeassa reunassa on $O(\sqrt n)$ yksittäistä lukua \item finally, there are $O(\sqrt n)$ single values
\end{itemize} \end{itemize}
Jokaisen osan summan laskeminen vie aikaa $O(\sqrt n)$, Calculating each sum takes $O(\sqrt n)$ time,
joten summan laskemisen aikavaativuus on yhteensä $O(\sqrt n)$. so the total complexity for calculating the sum
of values in any range is $O(\sqrt n)$.
Neliöjuurialgoritmeissa parametri $\sqrt n$ The reason why we use the parameter $\sqrt n$ is that
johtuu siitä, että se saattaa kaksi asiaa tasapainoon: it balances two things:
esimerkiksi $n$ alkion taulukko jakautuu for example, an array of $n$ elements is divided
$\sqrt n$ osaan, joista jokaisessa on $\sqrt n$ alkiota. into $\sqrt n$ segments, each of which contains
Käytännössä algoritmeissa $\sqrt n$ elements.
ei ole kuitenkaan pakko käyttää In practice, it is not needed to use exactly
tarkalleen parametria $\sqrt n$, the parameter $\sqrt n$ in algorithms, but it may be better to
vaan voi olla parempi valita toiseksi use parameters $k$ and $n/k$ where $k$ is
parametriksi $k$ ja toiseksi $n/k$, larger or smaller than $\sqrt n$.
missä $k$ on pienempi tai suurempi kuin $\sqrt n$.
Paras parametri selviää usein kokeilemalla The best parameter depends on the problem
ja riippuu tehtävästä ja syötteestä. and input.
Esimerkiksi jos taulukkoa käsittelevä algoritmi For example, if an algorithm often goes through
käy usein läpi välit mutta harvoin välin sisällä segments but rarely iterates the elements inside
olevia alkioita, taulukko voi olla järkevää the segments, it may be good to divide the array into
jakaa $k < \sqrt n$ väliin, $k < \sqrt n$ segments, each of which contains $n/k > \sqrt n$
joista jokaisella on $n/k > \sqrt n$ alkiota. elements.
\section{Eräkäsittely} \section{Batch processing}
\index{erxkxsittely@eräkäsittely} \index{batch processing}
\key{Eräkäsittelyssä} algoritmin suorittamat In \key{batch processing}, the operations in the
operaatiot jaetaan eriin, algorithm are divided into batches,
jotka käsitellään omina kokonaisuuksina. and each batch will be processed separately.
Erien välissä tehdään yksittäinen työläs toimenpide, Between the batches some precalculation is done
joka auttaa tulevien operaatioiden käsittelyä. to process the future operations more efficiently.
Neliöjuurialgoritmi syntyy, kun $n$ operaatiota In a square root algorithm, $n$ operations are
jaetaan $O(\sqrt n)$-kokoisiin eriin, divided into batches of size $O(\sqrt n)$,
jolloin sekä eriä että operaatioita kunkin erän and the number of both batches and operations in each
sisällä on $O(\sqrt n)$. batch is $O(\sqrt n)$.
Tämä tasapainottaa sitä, miten usein erien välinen This balances the precalculation time between
työläs toimenpide tapahtuu sekä miten paljon työtä the batches and the time needed for processing
erän sisällä täytyy tehdä. the batches.
Tarkastellaan esimerkkinä tehtävää, jossa As an example, let's consider a problem
ruudukossa on $k \times k$ ruutua, where a grid of size $k \times k$
jotka ovat aluksi valkoisia. initially consists of white squares.
Tehtävänä on suorittaa ruudukkoon Our task is to perform $n$ operations,
$n$ operaatiota, each of which is one of the following:
joista jokainen on jompikumpi seuraavista:
\begin{itemize} \begin{itemize}
\item \item
väritä ruutu $(y,x)$ mustaksi paint square $(y,x)$ black
\item \item
etsi ruudusta $(y,x)$ lähin find the nearest black square to
musta ruutu, kun square $(y,x)$ where the distance
ruutujen $(y_1,x_1)$ ja $(y_2,x_2)$ between squares $(y_1,x_1)$ and $(y_2,x_2)$
etäisyys on $|y_1-y_2|+|x_1-x_2|$ is $|y_1-y_2|+|x_1-x_2|$
\end{itemize} \end{itemize}
Ratkaisuna on jakaa operaatiot $O(\sqrt n)$ erään, The solution is to divide the operations into
joista jokaisessa on $O(\sqrt n)$ operaatiota. $O(\sqrt n)$ batches, each of which consists
Kunkin erän alussa jokaiseen ruudukon ruutuun of $O(\sqrt n)$ operations.
lasketaan pienin etäisyys mustaan ruutuun. At the beginning of each batch,
Tämä onnistuu ajassa $O(k^2)$ leveyshaun avulla. we calculate for each square in the grid
the smallest distance to a black square.
This can be done in $O(k^2)$ time using breadth-first search.
Kunkin erän käsittelyssä pidetään yllä listaa ruuduista, When processing a batch, we maintain a list of squares
jotka on muutettu mustaksi tässä erässä. that have been painted black in the current batch.
Nyt etäisyys ruudusta lähimpään mustaan ruutuun Now, the distance from a square to the nearest black
on joko erän alussa laskettu etäisyys tai sitten square is either the precalculated distance or the distance
etäisyys johonkin listassa olevaan tämän erän aikana mustaksi to a square that has been painted black in the current batch.
muutettuun ruutuun.
Algoritmi vie aikaa $O((k^2+n) \sqrt n)$, The algorithm works in
koska erien välissä tehdään $O(\sqrt n)$ kertaa $O((k^2+n) \sqrt n)$ time.
$O(k^2)$-aikainen läpikäynti, ja First, between the batches,
erissä käsitellään yhteensä $O(n)$ solmua, there are $O(\sqrt n)$ searches that each take
joista jokaisen kohdalla käydään läpi $O(k^2)$ time.
$O(\sqrt n)$ solmua listasta. Second, the total number of processed
squares is $O(n)$, and at each square,
we go through a list of $O(\sqrt n)$ squares
in a batch.
Jos algoritmi tekisi leveyshaun jokaiselle operaatiolle, If the algorithm would perform a breadth-first search
aikavaativuus olisi $O(k^2 n)$. at each operation, the complexity would be
Jos taas algoritmi kävisi kaikki muutetut ruudut läpi $O(k^2 n)$.
jokaisen operaation kohdalla, And if the algorithm would go through all painted
aikavaativuus olisi $O(n^2)$. squares at each operation,
Neliöjuurialgoritmi yhdistää nämä aikavaativuudet the complexity would be $O(n^2)$.
ja muuttaa kertoimen $n$ kertoimeksi $\sqrt n$. The square root algorithm combines these complexities,
and turns the factor $n$ into $\sqrt n$.
\section{Tapauskäsittely} \section{Case processing}
\index{tapauskxsittely@tapauskäsittely} \index{case processing}
\key{Tapauskäsittelyssä} algoritmissa on useita In \key{case processing}, an algorithm has
toimintatapoja, jotka aktivoituvat syötteen specialized subalgorithms for different cases that
ominaisuuksista riippuen. may appear during the algorithm.
Tyypillisesti yksi algoritmin osa on tehokas Typically, one part is efficient for
pienellä parametrilla small parameters, and another part is efficient
ja toinen osa on tehokas suurella parametrilla, for large parameters, and the turning point is
ja sopiva jakokohta kulkee suunnilleen arvon $\sqrt n$ kohdalla. about $\sqrt n$.
Tarkastellaan esimerkkinä tehtävää, jossa As an example, let's consider a problem where
puussa on $n$ solmua, joista jokaisella on tietty väri. we are given a tree that contains $n$ nodes,
Tavoitteena on etsiä puusta kaksi solmua, each with some color. Our task is to find two nodes
jotka ovat samanvärisiä ja mahdollisimman that have the same color and the distance
kaukana toisistaan. between them is as large as possible.
Tehtävän voi ratkaista The problem can be solved by going through all
käymällä läpi värit yksi kerrallaan ja colors one after another, and for each color,
etsimällä kullekin värille kaksi solmua, jotka ovat finding two nodes of that color whose distance is
mahdollisimman kaukana toisistaan. maximum.
Tietyllä värillä algoritmin toiminta riippuu siitä, For a fixed color, a subalgorithm will be used
montako kyseisen väristä solmua puussa on. that depends on the number of nodes of that color.
Oletetaan nyt, että käsittelyssä on väri $x$ Let's assume that the current color is $x$
ja puussa on $c$ solmua, joiden väri on $x$. and there are $c$ nodes whose color is $x$.
Tapaukset ovat seuraavat: There are two cases:
\subsubsection*{Tapaus 1: $c \le \sqrt n$} \subsubsection*{Case 1: $c \le \sqrt n$}
Jos $x$-värisiä solmuja on vähän, If the number of nodes is small,
käydään läpi kaikki $x$-väristen solmujen parit we go through all pairs of nodes whose
ja valitaan pari, jonka etäisyys on suurin. color is $x$ and select the pair that
Jokaisesta solmusta täytyy has the maximum distance.
laskea etäisyys $O(\sqrt n)$ muuhun solmuun (ks. luku 18.3), For each node, we have calculate the distance
joten kaikkien tapaukseen 1 osuvien solmujen to $O(\sqrt n)$ other nodes (see 18.3),
käsittely vie aikaa yhteensä $O(n \sqrt n)$. so the total time needed for processing all
nodes in case 1 is $O(n \sqrt n)$.
\subsubsection*{Tapaus 2: $c > \sqrt n$} \subsubsection*{Case 2: $c > \sqrt n$}
Jos $x$-värisiä solmuja on paljon, If the number of nodes is large,
käydään koko puu läpi ja we traverse through the whole tree
lasketaan suurin etäisyys kahden and calculate the maximum distance between
$x$-värisen solmun välillä. two nodes with color $x$.
Läpikäynnin aikavaativuus on $O(n)$, The time complexity of the tree traversal is $O(n)$,
ja tapaus 2 aktivoituu korkeintaan $O(\sqrt n)$ and this will be done at most $O(\sqrt n)$ times,
värille, joten tapauksen 2 solmut so the total time needed for case 2 is
tuottavat aikavaativuuden $O(n \sqrt n)$.\\\\ $O(n \sqrt n)$.\\\\
\noindent \noindent
Algoritmin kokonaisaikavaativuus on $O(n \sqrt n)$, The time complexity of the algorithm is $O(n \sqrt n)$,
koska sekä tapaus 1 että tapaus 2 vievät aikaa because both case 1 and case 2 take $O(n \sqrt n)$ time.
yhteensä $O(n \sqrt n)$.
\section{Mo'n algoritmi} \section{Mo's algorithm}
\index{Mo'n algoritmi} \index{Mo's algorithm}
\key{Mo'n algoritmi} soveltuu tehtäviin, \key{Mo's algorithm} can be used in many problems
joissa taulukkoon tehdään välikyselyitä ja where we are asked to process range queries in
taulukon sisältö kaikissa kyselyissä on sama. a \emph{static} array.
Algoritmi järjestää The algorithm handles the queries in a special order
kyselyt uudestaan niin, so that it is efficient to process them.
että niiden käsittely on tehokasta.
Algoritmi pitää yllä taulukon väliä, The algorithm maintains a range in the array,
jolle on laskettu kyselyn vastaus. and the answer for a query for that range.
Kyselystä toiseen siirryttäessä algoritmi When moving from a range to another range,
muuttaa väliä askel kerrallaan niin, the algorithm modifies the range step by step
että vastaus uuteen kyselyyn saadaan laskettua. so that the answer for the next range can be
Algoritmin aikavaativuus on $O(n \sqrt n f(n))$, calculated.
kun kyselyitä on $n$ ja The time complexity of the algorithm is
yksi välin muutosaskel vie aikaa $f(n)$. $O(n \sqrt n f(n))$ when there are $n$ queries
and each step takes $f(n)$ time.
Algoritmin toiminta perustuu järjestykseen, The algorithm processes the queries in a special
jossa kyselyt käsitellään. order which makes the algorithm efficient.
Kun kyselyjen välit ovat muotoa $[a,b]$, When the queries correspond to ranges of the form $[a,b]$,
algoritmi järjestää ne ensisijaisesti arvon they are primarily sorted according to
$\lfloor a/\sqrt n \rfloor$ mukaan ja toissijaisesti arvon $b$ mukaan. the value $\lfloor a/\sqrt n \rfloor$,
Algoritmi suorittaa siis peräkkäin kaikki kyselyt, and secondarily according to the value $b$.
joiden alkukohta on tietyllä $\sqrt n$-välillä. Hence, all queries whose starting index
is in a fixed segment
are processed after each other.
Osoittautuu, että tämän järjestyksen ansiosta It turns out that using this order, the algorithm
algoritmi tekee yhteensä vain $O(n \sqrt n)$ muutosaskelta. only performs $O(n \sqrt n)$ steps.
Tämä johtuu siitä, että välin vasen reuna liikkuu The reason for this is that the left border of
$n$ kertaa $O(\sqrt n)$ askelta, the range moves $n$ times $O(\sqrt n)$ steps,
kun taas välin oikea reuna liikkuu $\sqrt n$ and the right border of the range moves
kertaa $O(n)$ askelta. Molemmat reunat liikkuvat $\sqrt n$ times $O(n)$ steps. Thus, both the
siis yhteensä $O(n \sqrt n)$ askelta. borders move a total of $O(n \sqrt n)$ steps.
\subsubsection*{Esimerkki} \subsubsection*{Example}
Tarkastellaan esimerkkinä tehtävää, As an example, let's consider a problem
jossa annettuna on joukko välejä taulukossa where we are given a set of ranges in an array,
ja tehtävänä on selvittää kullekin välille, and our task is to calculate for each range
montako eri lukua taulukossa on kyseisellä välillä. the number of distinct elements in the range.
Mo'n algoritmissa kyselyt järjestetään aina samalla In Mo's algorithm, the queries are always sorted
tavalla, ja tehtävästä riippuva osa on, in the same way, but it depends on the problem
miten kyselyn vastausta pidetään yllä. how the answer for queries is maintained.
Tässä tehtävässä luonteva tapa on In this problem, we can maintain an array
pitää muistissa kyselyn vastausta sekä \texttt{c} where $\texttt{c}[x]$
taulukkoa \texttt{c}, jossa $\texttt{c}[x]$ indicates how many times an element $x$
on alkion $x$ lukumäärä aktiivisella välillä. occurs in the active range.
Kyselystä toiseen siirryttäessä taulukon aktiivinen
väli muuttuu. Esimerkiksi jos nykyinen kysely koskee väliä
When we move from a query to another query,
the active range changes.
For example, if the current range is
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (1,0) rectangle (5,1); \fill[color=lightgray] (1,0) rectangle (5,1);
@ -368,7 +374,7 @@ väli muuttuu. Esimerkiksi jos nykyinen kysely koskee väliä
\node at (8.5, 0.5) {4}; \node at (8.5, 0.5) {4};
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
ja seuraava kysely koskee väliä and the next range is
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (2,0) rectangle (7,1); \fill[color=lightgray] (2,0) rectangle (7,1);
@ -384,22 +390,25 @@ ja seuraava kysely koskee väliä
\node at (8.5, 0.5) {4}; \node at (8.5, 0.5) {4};
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
niin tapahtuu kolme muutosaskelta: there will be three steps:
välin vasen reuna siirtyy askeleen oikealle the left border moves one step to the left,
ja välin oikea reuna siirtyy kaksi askelta oikealle. and the right border moves two steps to the right.
Jokaisen muutosaskeleen jälkeen täytyy After each step, we should update the
päivittää taulukkoa \texttt{c}. array \texttt{c}.
Jos väliin tulee alkio $x$, If an element $x$ is added to the range,
arvo $\texttt{c}[x]$ kasvaa 1:llä, the value
ja jos välistä poistuu alkio $x$, $\texttt{c}[x]$ increases by one,
arvo $\texttt{c}[x]$ vähenee 1:llä. and if a value $x$ is removed from the range,
Jos lisäyksen jälkeen $\texttt{c}[x]=1$, the value $\texttt{c}[x]$ decreases by one.
kyselyn vastaus kasvaa 1:llä, If after an insertion
ja jos poiston jälkeen $\texttt{c}[x]=0$, $\texttt{c}[x]=1$,
kyselyn vastaus vähenee 1:llä. the answer for the query increases by one,
and if after a removel $\texttt{c}[x]=0$,
the answer for the query decreases by one.
Tässä tapauksessa muutosaskeleen aikavaativuus on $O(1)$, In this problem, the time needed to perform
joten algoritmin kokonaisaikavaativuus on $O(n \sqrt n)$. each step is $O(1)$, so the total time complexity
of the algorithm is $O(n \sqrt n)$.