cphb/luku09.tex

1426 lines
40 KiB
TeX

\chapter{Range queries}
\index{vxlikysely@välikysely}
\index{summakysely@summakysely}
\index{minimikysely@minimikysely}
\index{maksimikysely@maksimikysely}
\key{Välikysely} kohdistuu taulukon välille $[a,b]$,
ja tehtävänä on laskea haluttu tieto välillä olevista alkioista.
Tavallisia välikyselyitä ovat:
\begin{itemize}
\item \key{summakysely}: laske välin $[a,b]$ summa
\item \key{minimikysely}: etsi pienin alkio välillä $[a,b]$
\item \key{maksimikysely}: etsi suurin alkio välillä $[a,b]$
\end{itemize}
Esimerkiksi seuraavan taulukon välillä $[4,7]$
summa on $4+6+1+3=14$, minimi on 1 ja maksimi on 6:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (3,0) rectangle (7,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$3$};
\node at (2.5,0.5) {$8$};
\node at (3.5,0.5) {$4$};
\node at (4.5,0.5) {$6$};
\node at (5.5,0.5) {$1$};
\node at (6.5,0.5) {$3$};
\node at (7.5,0.5) {$4$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Helppo tapa vastata välikyselyyn on
käydä läpi kaikki välin alkiot silmukalla.
Esimerkiksi seuraava funktio toteuttaa summakyselyn:
\begin{lstlisting}
int summa(int a, int b) {
int s = 0;
for (int i = a; i <= b; i++) {
s += t[i];
}
return s;
}
\end{lstlisting}
Yllä oleva funktio toteuttaa summakyselyn
ajassa $O(n)$, mikä on hidasta,
jos taulukko on suuri ja kyselyitä tulee paljon.
Tässä luvussa opimme, miten välikyselyitä pystyy
toteuttamaan huomattavasti nopeammin.
\section{Staattisen taulukon kyselyt}
Aloitamme yksinkertaisesta
tilanteesta, jossa taulukko on \key{staattinen}
eli sen sisältö ei muutu kyselyiden välillä.
Tällöin riittää muodostaa ennen kyselyitä
taulukon pohjalta tietorakenne,
josta voi selvittää tehokkaasti vastauksen mihin tahansa väliin
kohdistuvaan kyselyyn.
\subsubsection{Summakysely}
\index{summataulukko@summataulukko}
Summakyselyyn on mahdollista vastata tehokkaasti
muodostamalla taulukosta etukäteen \key{summataulukko},
jonka kohdassa $k$ on taulukon välin $[1,k]$ summa.
Tämän jälkeen minkä tahansa välin $[a,b]$
summan saa laskettua $O(1)$-ajassa
summataulukkoa käyttäen.
Esimerkiksi taulukon
\begin{center}
\begin{tikzpicture}[scale=0.7]
%\fill[color=lightgray] (3,0) rectangle (7,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$3$};
\node at (2.5,0.5) {$4$};
\node at (3.5,0.5) {$8$};
\node at (4.5,0.5) {$6$};
\node at (5.5,0.5) {$1$};
\node at (6.5,0.5) {$4$};
\node at (7.5,0.5) {$2$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
summataulukko on seuraava:
\begin{center}
\begin{tikzpicture}[scale=0.7]
%\fill[color=lightgray] (3,0) rectangle (7,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$4$};
\node at (2.5,0.5) {$8$};
\node at (3.5,0.5) {$16$};
\node at (4.5,0.5) {$22$};
\node at (5.5,0.5) {$23$};
\node at (6.5,0.5) {$27$};
\node at (7.5,0.5) {$29$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Seuraava koodi muodostaa taulukosta \texttt{t}
summataulukon \texttt{s} ajassa $O(n)$:
\begin{lstlisting}
for (int i = 1; i <= n; i++) {
s[i] = s[i-1]+t[i];
}
\end{lstlisting}
Tämän jälkeen summakyselyyn voi vastata
ajassa $O(1)$ seuraavasti:
\begin{lstlisting}
int summa(int a, int b) {
return s[b]-s[a-1];
}
\end{lstlisting}
Funktio laskee välin $[a,b]$ summan
vähentämällä välin $[1,b]$ summasta
välin $[1,a-1]$ summan.
Summataulukosta riittää siis hakea kaksi arvoa
ja aikaa kuluu vain $O(1)$.
Huomaa, että 1-indeksoinnin ansiosta
funktio toimii myös tapauksessa $a=1$,
kunhan $\texttt{s}[0]=0$.
Tarkastellaan esimerkiksi väliä $[4,7]$:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (3,0) rectangle (7,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$3$};
\node at (2.5,0.5) {$4$};
\node at (3.5,0.5) {$8$};
\node at (4.5,0.5) {$6$};
\node at (5.5,0.5) {$1$};
\node at (6.5,0.5) {$4$};
\node at (7.5,0.5) {$2$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Välin $[4,7]$ summa on $8+6+1+4=19$.
Tämän saa laskettua tehokkaasti summataulukosta
etsimällä välien $[1,3]$ ja $[1,7]$ summat:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (2,0) rectangle (3,1);
\fill[color=lightgray] (6,0) rectangle (7,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$4$};
\node at (2.5,0.5) {$8$};
\node at (3.5,0.5) {$16$};
\node at (4.5,0.5) {$22$};
\node at (5.5,0.5) {$23$};
\node at (6.5,0.5) {$27$};
\node at (7.5,0.5) {$29$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Välin $[4,7]$ summa on siis $27-8=19$.
Summataulukon idean voi yleistää
myös kaksiulotteiseen taulukkoon,
jolloin summataulukosta voi laskea
minkä tahansa suorakulmaisen alueen
summan $O(1)$-ajassa.
Tällöin summataulukkoon tallennetaan summia
alueista, jotka alkavat taulukon vasemmasta yläkulmasta.
\begin{samepage}
Seuraava ruudukko havainnollistaa asiaa:
\begin{center}
\begin{tikzpicture}[scale=0.55]
\draw[fill=lightgray] (3,2) rectangle (7,5);
\draw (0,0) grid (10,7);
%\draw[line width=2pt] (3,2) rectangle (7,5);
\node[anchor=center] at (6.5, 2.5) {$A$};
\node[anchor=center] at (2.5, 2.5) {$B$};
\node[anchor=center] at (6.5, 5.5) {$C$};
\node[anchor=center] at (2.5, 5.5) {$D$};
\end{tikzpicture}
\end{center}
\end{samepage}
Harmaan suorakulmion summan saa laskettua kaavalla
\[S(A) - S(B) - S(C) + S(D),\]
missä $S(X)$ tarkoittaa summaa vasemmasta
yläkulmasta kirjaimen $X$ osoittamaan kohtaan asti.
\subsubsection{Minimikysely}
Myös minimikyselyyn on mahdollista
vastata $O(1)$-ajassa sopivan esikäsittelyn avulla,
joskin tämä on vaikeampaa kuin summakyselyssä.
Huomaa, että minimikysely ja maksimikysely on
mahdollista toteuttaa aina samalla tavalla,
joten riittää keskittyä minimikyselyn toteutukseen.
Ideana on laskea etukäteen taulukon jokaiselle
$2^k$-kokoiselle välille, mikä on kyseisen välin minimi.
Esimerkiksi taulukosta
\begin{center}
\begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$3$};
\node at (2.5,0.5) {$4$};
\node at (3.5,0.5) {$8$};
\node at (4.5,0.5) {$6$};
\node at (5.5,0.5) {$1$};
\node at (6.5,0.5) {$4$};
\node at (7.5,0.5) {$2$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
lasketaan seuraavat minimit:
\begin{center}
\begin{tabular}{ccc}
\begin{tabular}{ccc}
väli & koko & minimi \\
\hline
$[1,1]$ & 1 & 1 \\
$[2,2]$ & 1 & 3 \\
$[3,3]$ & 1 & 4 \\
$[4,4]$ & 1 & 8 \\
$[5,5]$ & 1 & 6 \\
$[6,6]$ & 1 & 1 \\
$[7,7]$ & 1 & 4 \\
$[8,8]$ & 1 & 2 \\
\end{tabular}
&
\begin{tabular}{ccc}
väli & koko & minimi \\
\hline
$[1,2]$ & 2 & 1 \\
$[2,3]$ & 2 & 3 \\
$[3,4]$ & 2 & 4 \\
$[4,5]$ & 2 & 6 \\
$[5,6]$ & 2 & 1 \\
$[6,7]$ & 2 & 1 \\
$[7,8]$ & 2 & 2 \\
\\
\end{tabular}
&
\begin{tabular}{ccc}
väli & koko & minimi \\
\hline
$[1,4]$ & 4 & 1 \\
$[2,5]$ & 4 & 3 \\
$[3,6]$ & 4 & 1 \\
$[4,7]$ & 4 & 1 \\
$[5,8]$ & 4 & 1 \\
$[1,8]$ & 8 & 1 \\
\\
\\
\end{tabular}
\end{tabular}
\end{center}
Taulukon $2^k$-välien määrä on $O(n \log n)$,
koska jokaisesta taulukon kohdasta alkaa
$O(\log n)$ väliä.
Kaikkien $2^k$-välien minimit pystytään laskemaan
ajassa $O(n \log n)$, koska jokainen $2^k$-väli
muodostuu kahdesta $2^{k-1}$ välistä ja
$2^k$-välin minimi on pienempi $2^{k-1}$-välien minimeistä.
Tämän jälkeen minkä tahansa välin $[a,b]$ minimin
saa laskettua $O(1)$-ajassa miniminä kahdesta $2^k$-välistä,
missä $k=\lfloor \log_2(b-a+1) \rfloor$.
Ensimmäinen väli alkaa kohdasta $a$
ja toinen väli päättyy kohtaan $b$.
Parametri $k$ on valittu niin,
että kaksi $2^k$-kokoista väliä
kattaa koko välin $[a,b]$.
Tarkastellaan esimerkiksi väliä $[2,7]$:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (1,0) rectangle (7,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$3$};
\node at (2.5,0.5) {$4$};
\node at (3.5,0.5) {$8$};
\node at (4.5,0.5) {$6$};
\node at (5.5,0.5) {$1$};
\node at (6.5,0.5) {$4$};
\node at (7.5,0.5) {$2$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Välin $[2,7]$ pituus on 6 ja $\lfloor \log_2(6) \rfloor = 2$.
Niinpä välin minimin saa selville kahden 4-pituisen
välin minimistä.
Välit ovat $[2,5]$ ja $[4,7]$:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (1,0) rectangle (5,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$3$};
\node at (2.5,0.5) {$4$};
\node at (3.5,0.5) {$8$};
\node at (4.5,0.5) {$6$};
\node at (5.5,0.5) {$1$};
\node at (6.5,0.5) {$4$};
\node at (7.5,0.5) {$2$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (3,0) rectangle (7,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$3$};
\node at (2.5,0.5) {$4$};
\node at (3.5,0.5) {$8$};
\node at (4.5,0.5) {$6$};
\node at (5.5,0.5) {$1$};
\node at (6.5,0.5) {$4$};
\node at (7.5,0.5) {$2$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Välin $[2,5]$ minimi on 3 ja välin $[4,7]$ minimi on 1.
Tämän seurauksena välin $[2,7]$ minimi on pienempi näistä eli 1.
%
% Mainittakoon, että $O(1)$-aikaiset minimikyselyt pystyy
% toteuttamaan myös niin, että esikäsittely vie aikaa
% vain $O(n)$ eikä $O(n \log n)$.
% Tämä on kuitenkin selvästi vaikeampaa, eikä
% sillä ole merkitystä kisakoodauksessa.
\section{Binääri-indeksipuu}
\index{binxxri-indeksipuu@binääri-indeksipuu}
\index{Fenwick-puu}
\key{Binääri-indeksipuu} eli \key{Fenwick-puu} on
summataulukkoa muistuttava tietorakenne,
joka toteuttaa kaksi operaatiota:
taulukon välin $[a,b]$ summakysely
sekä taulukon kohdassa $k$ olevan luvun päivitys.
Kummankin operaation aikavaativuus on $O(\log n)$.
Binääri-indeksipuun etuna summataulukkoon verrattuna on,
että taulukkoa pystyy päivittämään tehokkaasti
summakyselyiden välissä.
Summataulukossa tämä ei olisi mahdollista,
vaan koko summataulukko tulisi muodostaa uudestaan $O(n)$-ajassa
taulukon päivityksen jälkeen.
\subsubsection{Rakenne}
Binääri-indeksipuu on taulukko, jonka
kohdassa $k$ on kohtaan $k$ päättyvän välin lukujen summa
alkuperäisessä taulukossa.
Välin pituus on suurin 2:n potenssi, jolla $k$ on jaollinen.
Esimerkiksi jos $k=6$, välin pituus on 2, koska
6 on jaollinen 2:lla mutta ei ole jaollinen 4:llä.
\begin{samepage}
Esimerkiksi taulukkoa
\begin{center}
\begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$3$};
\node at (2.5,0.5) {$4$};
\node at (3.5,0.5) {$8$};
\node at (4.5,0.5) {$6$};
\node at (5.5,0.5) {$1$};
\node at (6.5,0.5) {$4$};
\node at (7.5,0.5) {$2$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
\end{samepage}
vastaava binääri-indeksipuu on seuraava:
\begin{center}
\begin{tikzpicture}[scale=0.7]
%\fill[color=lightgray] (3,0) rectangle (7,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$4$};
\node at (2.5,0.5) {$4$};
\node at (3.5,0.5) {$16$};
\node at (4.5,0.5) {$6$};
\node at (5.5,0.5) {$7$};
\node at (6.5,0.5) {$4$};
\node at (7.5,0.5) {$29$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\draw[->,thick] (0.5,-0.9) -- (0.5,-0.1);
\draw[->,thick] (2.5,-0.9) -- (2.5,-0.1);
\draw[->,thick] (4.5,-0.9) -- (4.5,-0.1);
\draw[->,thick] (6.5,-0.9) -- (6.5,-0.1);
\draw[->,thick] (1.5,-1.9) -- (1.5,-0.1);
\draw[->,thick] (5.5,-1.9) -- (5.5,-0.1);
\draw[->,thick] (3.5,-2.9) -- (3.5,-0.1);
\draw[->,thick] (7.5,-3.9) -- (7.5,-0.1);
\draw (0,-1) -- (1,-1) -- (1,-1.5) -- (0,-1.5) -- (0,-1);
\draw (2,-1) -- (3,-1) -- (3,-1.5) -- (2,-1.5) -- (2,-1);
\draw (4,-1) -- (5,-1) -- (5,-1.5) -- (4,-1.5) -- (4,-1);
\draw (6,-1) -- (7,-1) -- (7,-1.5) -- (6,-1.5) -- (6,-1);
\draw (0,-2) -- (2,-2) -- (2,-2.5) -- (0,-2.5) -- (0,-2);
\draw (4,-2) -- (6,-2) -- (6,-2.5) -- (4,-2.5) -- (4,-2);
\draw (0,-3) -- (4,-3) -- (4,-3.5) -- (0,-3.5) -- (0,-3);
\draw (0,-4) -- (8,-4) -- (8,-4.5) -- (0,-4.5) -- (0,-4);
\end{tikzpicture}
\end{center}
Esimerkiksi binääri-indeksipuun kohdassa 6 on luku 7,
koska välin $[5,6]$ lukujen summa on $6+1=7$.
\subsubsection{Summakysely}
Binääri-indeksipuun perusoperaatio on välin $[1,k]$
summan laskeminen, missä $k$ on mikä tahansa taulukon kohta.
Tällaisen summan pystyy muodostamaan aina laskemalla yhteen
puussa olevia välien summia.
Esimerkiksi välin $[1,7]$
summa muodostuu seuraavista summista:
\begin{center}
\begin{tikzpicture}[scale=0.7]
%\fill[color=lightgray] (3,0) rectangle (7,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$4$};
\node at (2.5,0.5) {$4$};
\node at (3.5,0.5) {$16$};
\node at (4.5,0.5) {$6$};
\node at (5.5,0.5) {$7$};
\node at (6.5,0.5) {$4$};
\node at (7.5,0.5) {$29$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\draw[->,thick] (0.5,-0.9) -- (0.5,-0.1);
\draw[->,thick] (2.5,-0.9) -- (2.5,-0.1);
\draw[->,thick] (4.5,-0.9) -- (4.5,-0.1);
\draw[->,thick] (6.5,-0.9) -- (6.5,-0.1);
\draw[->,thick] (1.5,-1.9) -- (1.5,-0.1);
\draw[->,thick] (5.5,-1.9) -- (5.5,-0.1);
\draw[->,thick] (3.5,-2.9) -- (3.5,-0.1);
\draw[->,thick] (7.5,-3.9) -- (7.5,-0.1);
\draw (0,-1) -- (1,-1) -- (1,-1.5) -- (0,-1.5) -- (0,-1);
\draw (2,-1) -- (3,-1) -- (3,-1.5) -- (2,-1.5) -- (2,-1);
\draw (4,-1) -- (5,-1) -- (5,-1.5) -- (4,-1.5) -- (4,-1);
\draw[fill=lightgray] (6,-1) -- (7,-1) -- (7,-1.5) -- (6,-1.5) -- (6,-1);
\draw (0,-2) -- (2,-2) -- (2,-2.5) -- (0,-2.5) -- (0,-2);
\draw[fill=lightgray] (4,-2) -- (6,-2) -- (6,-2.5) -- (4,-2.5) -- (4,-2);
\draw[fill=lightgray] (0,-3) -- (4,-3) -- (4,-3.5) -- (0,-3.5) -- (0,-3);
\draw (0,-4) -- (8,-4) -- (8,-4.5) -- (0,-4.5) -- (0,-4);
\end{tikzpicture}
\end{center}
Välin $[1,7]$ summa on siis $16+7+4=27$.
Binääri-indeksipuun rakenteen ansiosta
jokainen summaan kuuluva väli on eripituinen.
Niinpä summa muodostuu aina $O(\log n)$ välin summasta.
Summataulukon tavoin binääri-indeksipuusta voi laskea
tehokkaasti minkä tahansa taulukon välin summan,
koska välin $[a,b]$ summa saadaan vähentämällä
välin $[1,b]$ summasta välin $[1,a-1]$ summa.
Aikavaativuus on edelleen $O(\log n)$,
koska riittää laskea kaksi $[1,k]$-välin summaa.
\subsubsection{Taulukon päivitys}
Kun taulukon kohdassa $k$ oleva luku muuttuu,
tämä vaikuttaa useaan
binääri-indeksi\-puussa olevaan summaan.
Esimerkiksi jos kohdassa 3 oleva luku muuttuu,
seuraavat välien summat muuttuvat:
\begin{center}
\begin{tikzpicture}[scale=0.7]
%\fill[color=lightgray] (3,0) rectangle (7,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$4$};
\node at (2.5,0.5) {$4$};
\node at (3.5,0.5) {$16$};
\node at (4.5,0.5) {$6$};
\node at (5.5,0.5) {$7$};
\node at (6.5,0.5) {$4$};
\node at (7.5,0.5) {$29$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\draw[->,thick] (0.5,-0.9) -- (0.5,-0.1);
\draw[->,thick] (2.5,-0.9) -- (2.5,-0.1);
\draw[->,thick] (4.5,-0.9) -- (4.5,-0.1);
\draw[->,thick] (6.5,-0.9) -- (6.5,-0.1);
\draw[->,thick] (1.5,-1.9) -- (1.5,-0.1);
\draw[->,thick] (5.5,-1.9) -- (5.5,-0.1);
\draw[->,thick] (3.5,-2.9) -- (3.5,-0.1);
\draw[->,thick] (7.5,-3.9) -- (7.5,-0.1);
\draw (0,-1) -- (1,-1) -- (1,-1.5) -- (0,-1.5) -- (0,-1);
\draw[fill=lightgray] (2,-1) -- (3,-1) -- (3,-1.5) -- (2,-1.5) -- (2,-1);
\draw (4,-1) -- (5,-1) -- (5,-1.5) -- (4,-1.5) -- (4,-1);
\draw (6,-1) -- (7,-1) -- (7,-1.5) -- (6,-1.5) -- (6,-1);
\draw (0,-2) -- (2,-2) -- (2,-2.5) -- (0,-2.5) -- (0,-2);
\draw (4,-2) -- (6,-2) -- (6,-2.5) -- (4,-2.5) -- (4,-2);
\draw[fill=lightgray] (0,-3) -- (4,-3) -- (4,-3.5) -- (0,-3.5) -- (0,-3);
\draw[fill=lightgray] (0,-4) -- (8,-4) -- (8,-4.5) -- (0,-4.5) -- (0,-4);
\end{tikzpicture}
\end{center}
Myös tässä tapauksessa kaikki välit,
joihin muutos vaikuttaa, ovat eripituisia,
joten muutos kohdistuu $O(\log n)$ kohtaan
binääri-indeksipuussa.
\subsubsection{Toteutus}
Binääri-indeksipuun operaatiot on mahdollista toteuttaa
lyhyesti ja tehokkaasti bittien käsittelyn avulla.
Oleellinen bittioperaatio on $k \& -k$,
joka eristää luvusta $k$ viimeisenä olevan ykkösbitin.
Esimerkiksi $6 \& -6=2$, koska luku $6$ on bittimuodossa 110
ja luku $2$ on bittimuodossa on 10.
Osoittautuu, että summan laskemisessa
binääri-indeksipuun kohtaa $k$ tulee muuttaa joka askeleella niin,
että siitä poistetaan luku $k \& -k$.
Vastaavasti taulukon päivityksessä kohtaa $k$ tulee muuttaa joka askeleella niin,
että siihen lisätään luku $k \& -k$.
Seuraavat funktiot olettavat, että binääri-indeksipuu on
tallennettu taulukkoon \texttt{b} ja se muodostuu kohdista $1 \ldots n$.
Funktio \texttt{summa} laskee välin $[1,k]$ summan:
\begin{lstlisting}
int summa(int k) {
int s = 0;
while (k >= 1) {
s += b[k];
k -= k&-k;
}
return s;
}
\end{lstlisting}
Funktio \texttt{lisaa} kasvattaa taulukon kohtaa $k$ arvolla $x$:
\begin{lstlisting}
void lisaa(int k, int x) {
while (k <= n) {
b[k] += x;
k += k&-k;
}
}
\end{lstlisting}
Kummankin yllä olevan funktion aikavaativuus on $O(\log n)$,
koska funktiot muuttavat $O(\log n)$ kohtaa
binääri-indeksipuussa ja uuteen kohtaan siirtyminen
vie aikaa $O(1)$ bittioperaation avulla.
\section{Segmenttipuu}
\index{segmenttipuu@segmenttipuu}
\key{Segmenttipuu} on tietorakenne,
jonka operaatiot ovat taulukon välin $[a,b]$ välikysely
sekä kohdan $k$ arvon päivitys.
Segmenttipuun avulla voi toteuttaa summakyselyn,
minimikyselyn ja monia muitakin kyselyitä niin,
että kummankin operaation aikavaativuus on $O(\log n)$.
Segmenttipuun etuna binääri-indeksipuuhun verrattuna on,
että se on yleisempi tietorakenne.
Binääri-indeksipuulla voi toteuttaa vain summakyselyn,
mutta segmenttipuu sallii muitakin kyselyitä.
Toisaalta segmenttipuu vie enemmän muistia ja
on hieman vaikeampi toteuttaa kuin binääri-indeksipuu.
\subsubsection{Rakenne}
Segmenttipuussa on $2n-1$ solmua niin,
että alimmalla tasolla on $n$ solmua,
jotka kuvaavat taulukon sisällön,
ja ylemmillä tasoilla on välikyselyihin
tarvittavaa tietoa.
Segmenttipuun sisältö riippuu siitä,
mikä välikysely puun tulee toteuttaa.
Oletamme aluksi, että välikysely on tuttu summakysely.
Esimerkiksi taulukkoa
\begin{center}
\begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$5$};
\node at (1.5,0.5) {$8$};
\node at (2.5,0.5) {$6$};
\node at (3.5,0.5) {$3$};
\node at (4.5,0.5) {$2$};
\node at (5.5,0.5) {$7$};
\node at (6.5,0.5) {$2$};
\node at (7.5,0.5) {$6$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
vastaa seuraava segmenttipuu:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1);
\node[anchor=center] at (0.5, 0.5) {5};
\node[anchor=center] at (1.5, 0.5) {8};
\node[anchor=center] at (2.5, 0.5) {6};
\node[anchor=center] at (3.5, 0.5) {3};
\node[anchor=center] at (4.5, 0.5) {2};
\node[anchor=center] at (5.5, 0.5) {7};
\node[anchor=center] at (6.5, 0.5) {2};
\node[anchor=center] at (7.5, 0.5) {6};
\node[draw, circle] (a) at (1,2.5) {13};
\path[draw,thick,-] (a) -- (0.5,1);
\path[draw,thick,-] (a) -- (1.5,1);
\node[draw, circle,minimum size=22pt] (b) at (3,2.5) {9};
\path[draw,thick,-] (b) -- (2.5,1);
\path[draw,thick,-] (b) -- (3.5,1);
\node[draw, circle,minimum size=22pt] (c) at (5,2.5) {9};
\path[draw,thick,-] (c) -- (4.5,1);
\path[draw,thick,-] (c) -- (5.5,1);
\node[draw, circle,minimum size=22pt] (d) at (7,2.5) {8};
\path[draw,thick,-] (d) -- (6.5,1);
\path[draw,thick,-] (d) -- (7.5,1);
\node[draw, circle] (i) at (2,4.5) {22};
\path[draw,thick,-] (i) -- (a);
\path[draw,thick,-] (i) -- (b);
\node[draw, circle] (j) at (6,4.5) {17};
\path[draw,thick,-] (j) -- (c);
\path[draw,thick,-] (j) -- (d);
\node[draw, circle] (m) at (4,6.5) {39};
\path[draw,thick,-] (m) -- (i);
\path[draw,thick,-] (m) -- (j);
\end{tikzpicture}
\end{center}
Jokaisessa segmenttipuun solmussa on tietoa
$2^k$-kokoisesta välistä taulukossa.
Tässä tapauksessa solmussa oleva arvo kertoo,
mikä on taulukon lukujen summa solmua vastaavalla
välillä.
Kunkin solmun arvo saadaan laskemalla yhteen
solmun alapuolella vasemmalla ja oikealla
olevien solmujen arvot.
Segmenttipuu on mukavinta rakentaa niin,
että taulukon koko on 2:n potenssi,
jolloin tuloksena on täydellinen binääripuu.
Jatkossa oletamme aina,
että taulukko täyttää tämän vaatimuksen.
Jos taulukon koko ei ole 2:n potenssi,
sen loppuun voi lisätä tyhjää niin,
että koosta tulee 2:n potenssi.
\subsubsection{Välikysely}
Segmenttipuussa vastaus välikyselyyn lasketaan
väliin kuuluvista solmuista,
jotka ovat mahdollisimman korkealla puussa.
Jokainen solmu antaa vastauksen väliin kuuluvalle osavälille,
ja vastaus kyselyyn selviää yhdistämällä
segmenttipuusta saadut osavälejä koskeva tiedot.
Tarkastellaan esimerkiksi seuraavaa taulukon väliä:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=gray!50] (2,0) rectangle (8,1);
\draw (0,0) grid (8,1);
\node[anchor=center] at (0.5, 0.5) {5};
\node[anchor=center] at (1.5, 0.5) {8};
\node[anchor=center] at (2.5, 0.5) {6};
\node[anchor=center] at (3.5, 0.5) {3};
\node[anchor=center] at (4.5, 0.5) {2};
\node[anchor=center] at (5.5, 0.5) {7};
\node[anchor=center] at (6.5, 0.5) {2};
\node[anchor=center] at (7.5, 0.5) {6};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Lukujen summa välillä $[3,8]$ on $6+3+2+7+2+6=26$.
Segmenttipuusta summa saadaan laskettua seuraavien
osasummien avulla:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1);
\node[anchor=center] at (0.5, 0.5) {5};
\node[anchor=center] at (1.5, 0.5) {8};
\node[anchor=center] at (2.5, 0.5) {6};
\node[anchor=center] at (3.5, 0.5) {3};
\node[anchor=center] at (4.5, 0.5) {2};
\node[anchor=center] at (5.5, 0.5) {7};
\node[anchor=center] at (6.5, 0.5) {2};
\node[anchor=center] at (7.5, 0.5) {6};
\node[draw, circle] (a) at (1,2.5) {13};
\path[draw,thick,-] (a) -- (0.5,1);
\path[draw,thick,-] (a) -- (1.5,1);
\node[draw, circle,fill=gray!50,minimum size=22pt] (b) at (3,2.5) {9};
\path[draw,thick,-] (b) -- (2.5,1);
\path[draw,thick,-] (b) -- (3.5,1);
\node[draw, circle,minimum size=22pt] (c) at (5,2.5) {9};
\path[draw,thick,-] (c) -- (4.5,1);
\path[draw,thick,-] (c) -- (5.5,1);
\node[draw, circle,minimum size=22pt] (d) at (7,2.5) {8};
\path[draw,thick,-] (d) -- (6.5,1);
\path[draw,thick,-] (d) -- (7.5,1);
\node[draw, circle] (i) at (2,4.5) {22};
\path[draw,thick,-] (i) -- (a);
\path[draw,thick,-] (i) -- (b);
\node[draw, circle,fill=gray!50] (j) at (6,4.5) {17};
\path[draw,thick,-] (j) -- (c);
\path[draw,thick,-] (j) -- (d);
\node[draw, circle] (m) at (4,6.5) {39};
\path[draw,thick,-] (m) -- (i);
\path[draw,thick,-] (m) -- (j);
\end{tikzpicture}
\end{center}
Taulukon välin summaksi tulee osasummista $9+17=26$.
Kun vastaus välikyselyyn lasketaan mahdollisimman
korkealla segmenttipuussa olevista solmuista,
väliin kuuluu enintään kaksi solmua
jokaiselta segmenttipuun tasolta.
Tämän ansiosta välikyselyssä
tarvittavien solmujen yhteismäärä on vain $O(\log n)$.
\subsubsection{Taulukon päivitys}
Kun taulukossa oleva arvo muuttuu,
segmenttipuussa täytyy päivittää
kaikkia solmuja, joiden arvo
riippuu muutetusta taulukon kohdasta.
Tämä tapahtuu kulkemalla puuta ylöspäin huipulle
asti ja tekemällä muutokset.
\begin{samepage}
Seuraava kuva näyttää, mitkä solmut segmenttipuussa muuttuvat,
jos taulukon luku 7 muuttuu.
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=gray!50] (5,0) rectangle (6,1);
\draw (0,0) grid (8,1);
\node[anchor=center] at (0.5, 0.5) {5};
\node[anchor=center] at (1.5, 0.5) {8};
\node[anchor=center] at (2.5, 0.5) {6};
\node[anchor=center] at (3.5, 0.5) {3};
\node[anchor=center] at (4.5, 0.5) {2};
\node[anchor=center] at (5.5, 0.5) {7};
\node[anchor=center] at (6.5, 0.5) {2};
\node[anchor=center] at (7.5, 0.5) {6};
\node[draw, circle] (a) at (1,2.5) {13};
\path[draw,thick,-] (a) -- (0.5,1);
\path[draw,thick,-] (a) -- (1.5,1);
\node[draw, circle,minimum size=22pt] (b) at (3,2.5) {9};
\path[draw,thick,-] (b) -- (2.5,1);
\path[draw,thick,-] (b) -- (3.5,1);
\node[draw, circle,minimum size=22pt,fill=gray!50] (c) at (5,2.5) {9};
\path[draw,thick,-] (c) -- (4.5,1);
\path[draw,thick,-] (c) -- (5.5,1);
\node[draw, circle,minimum size=22pt] (d) at (7,2.5) {8};
\path[draw,thick,-] (d) -- (6.5,1);
\path[draw,thick,-] (d) -- (7.5,1);
\node[draw, circle] (i) at (2,4.5) {22};
\path[draw,thick,-] (i) -- (a);
\path[draw,thick,-] (i) -- (b);
\node[draw, circle,fill=gray!50] (j) at (6,4.5) {17};
\path[draw,thick,-] (j) -- (c);
\path[draw,thick,-] (j) -- (d);
\node[draw, circle,fill=gray!50] (m) at (4,6.5) {39};
\path[draw,thick,-] (m) -- (i);
\path[draw,thick,-] (m) -- (j);
\end{tikzpicture}
\end{center}
\end{samepage}
Polku segmenttipuun pohjalta huipulle muodostuu aina $O(\log n)$ solmusta,
joten taulukon arvon muuttuminen vaikuttaa $O(\log n)$ solmuun puussa.
\subsubsection{Puun tallennus}
Segmenttipuun voi tallentaa muistiin
$2N$ alkion taulukkona,
jossa $N$ on riittävän suuri 2:n potenssi.
Tällaisen segmenttipuun avulla voi ylläpitää
taulukkoa, jonka indeksialue on $[0,N-1]$.
Segmenttipuun taulukon
kohdassa 1 on puun ylimmän solmun arvo,
kohdat 2 ja 3 sisältävät seuraavan tason
solmujen arvot, jne.
Segmenttipuun alin taso eli varsinainen
taulukon sisältä tallennetaan
kohdasta $N$ alkaen.
Niinpä taulukon kohdassa $k$ oleva alkio
on segmenttipuun taulukossa kohdassa $k+N$.
Esimerkiksi segmenttipuun
\begin{center}
\begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1);
\node[anchor=center] at (0.5, 0.5) {5};
\node[anchor=center] at (1.5, 0.5) {8};
\node[anchor=center] at (2.5, 0.5) {6};
\node[anchor=center] at (3.5, 0.5) {3};
\node[anchor=center] at (4.5, 0.5) {2};
\node[anchor=center] at (5.5, 0.5) {7};
\node[anchor=center] at (6.5, 0.5) {2};
\node[anchor=center] at (7.5, 0.5) {6};
\node[draw, circle] (a) at (1,2.5) {13};
\path[draw,thick,-] (a) -- (0.5,1);
\path[draw,thick,-] (a) -- (1.5,1);
\node[draw, circle,minimum size=22pt] (b) at (3,2.5) {9};
\path[draw,thick,-] (b) -- (2.5,1);
\path[draw,thick,-] (b) -- (3.5,1);
\node[draw, circle,minimum size=22pt] (c) at (5,2.5) {9};
\path[draw,thick,-] (c) -- (4.5,1);
\path[draw,thick,-] (c) -- (5.5,1);
\node[draw, circle,minimum size=22pt] (d) at (7,2.5) {8};
\path[draw,thick,-] (d) -- (6.5,1);
\path[draw,thick,-] (d) -- (7.5,1);
\node[draw, circle] (i) at (2,4.5) {22};
\path[draw,thick,-] (i) -- (a);
\path[draw,thick,-] (i) -- (b);
\node[draw, circle] (j) at (6,4.5) {17};
\path[draw,thick,-] (j) -- (c);
\path[draw,thick,-] (j) -- (d);
\node[draw, circle] (m) at (4,6.5) {39};
\path[draw,thick,-] (m) -- (i);
\path[draw,thick,-] (m) -- (j);
\end{tikzpicture}
\end{center}
voi tallentaa taulukkoon seuraavasti ($N=8$):
\begin{center}
\begin{tikzpicture}[scale=0.7]
%\fill[color=lightgray] (3,0) rectangle (7,1);
\draw (0,0) grid (15,1);
\node at (0.5,0.5) {$39$};
\node at (1.5,0.5) {$22$};
\node at (2.5,0.5) {$17$};
\node at (3.5,0.5) {$13$};
\node at (4.5,0.5) {$9$};
\node at (5.5,0.5) {$9$};
\node at (6.5,0.5) {$8$};
\node at (7.5,0.5) {$5$};
\node at (8.5,0.5) {$8$};
\node at (9.5,0.5) {$6$};
\node at (10.5,0.5) {$3$};
\node at (11.5,0.5) {$2$};
\node at (12.5,0.5) {$7$};
\node at (13.5,0.5) {$2$};
\node at (14.5,0.5) {$6$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\node at (8.5,1.4) {$9$};
\node at (9.5,1.4) {$10$};
\node at (10.5,1.4) {$11$};
\node at (11.5,1.4) {$12$};
\node at (12.5,1.4) {$13$};
\node at (13.5,1.4) {$14$};
\node at (14.5,1.4) {$15$};
\end{tikzpicture}
\end{center}
Tätä tallennustapaa käyttäen kohdassa $k$
olevalle solmulle pätee, että
\begin{itemize}
\item ylempi solmu on kohdassa $\lfloor k/2 \rfloor$,
\item vasen alempi solmu on kohdassa $2k$ ja
\item oikea alempi solmu on kohdassa $2k+1$.
\end{itemize}
Huomaa, että tämän seurauksena solmun kohta on parillinen,
jos se on vasemmalla ylemmästä solmusta katsoen,
ja pariton, jos se on oikealla.
\subsubsection{Toteutus}
Tarkastellaan seuraavaksi välikyselyn ja päivityksen
toteutusta segmenttipuuhun.
Seuraavat funktiot olettavat, että segmenttipuu
on tallennettu $2n-1$-kokoi\-seen taulukkoon $\texttt{p}$
edellä kuvatulla tavalla.
Funktio \texttt{summa} laskee summan
välillä $a \ldots b$:
\begin{lstlisting}
int summa(int a, int b) {
a += N; b += N;
int s = 0;
while (a <= b) {
if (a%2 == 1) s += p[a++];
if (b%2 == 0) s += p[b--];
a /= 2; b /= 2;
}
return s;
}
\end{lstlisting}
Funktio aloittaa summan laskeminen segmenttipuun
pohjalta ja liikkuu askel kerrallaan ylemmille tasoille.
Funktio laskee välin summan muuttujaan $s$
yhdistämällä puussa olevia osasummia.
Välin reunalla oleva osasumma lisätään summaan
aina silloin, kun se ei kuulu ylemmän tason osasummaan.
Funktio \texttt{lisaa} kasvattaa kohdan $k$ arvoa $x$:llä:
\begin{lstlisting}
void lisaa(int k, int x) {
k += N;
p[k] += x;
for (k /= 2; k >= 1; k /= 2) {
p[k] = p[2*k]+p[2*k+1];
}
}
\end{lstlisting}
Ensin funktio tekee muutoksen puun alimmalle
tasolle taulukkoon.
Tämän jälkeen se päivittää kaikki osasummat
puun huipulle asti.
Taulukon \texttt{p} indeksoinnin ansiosta
kohdasta $k$ alemmalla tasolla
ovat kohdat $2k$ ja $2k+1$.
Molemmat segmenttipuun operaatiot toimivat ajassa
$O(\log n)$, koska $n$ lukua sisältävässä
segmenttipuussa on $O(\log n)$ tasoa
ja operaatiot siirtyvät askel kerrallaan
segmenttipuun tasoja ylöspäin.
\subsubsection{Muut kyselyt}
Segmenttipuu mahdollistaa summan lisäksi minkä
tahansa välikyselyn,
jossa vierekkäisten välien $[a,b]$ ja $[b+1,c]$
tuloksista pystyy laskemaan tehokkaasti
välin $[a,c]$ tuloksen.
Tällaisia kyselyitä
ovat esimerkiksi minimi ja maksimi,
suurin yhteinen tekijä
sekä bittioperaatiot and, or ja xor.
\begin{samepage}
Esimerkiksi seuraavan segmenttipuun avulla voi laskea
taulukon välien minimejä:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1);
\node[anchor=center] at (0.5, 0.5) {5};
\node[anchor=center] at (1.5, 0.5) {8};
\node[anchor=center] at (2.5, 0.5) {6};
\node[anchor=center] at (3.5, 0.5) {3};
\node[anchor=center] at (4.5, 0.5) {1};
\node[anchor=center] at (5.5, 0.5) {7};
\node[anchor=center] at (6.5, 0.5) {2};
\node[anchor=center] at (7.5, 0.5) {6};
\node[draw, circle,minimum size=22pt] (a) at (1,2.5) {5};
\path[draw,thick,-] (a) -- (0.5,1);
\path[draw,thick,-] (a) -- (1.5,1);
\node[draw, circle,minimum size=22pt] (b) at (3,2.5) {3};
\path[draw,thick,-] (b) -- (2.5,1);
\path[draw,thick,-] (b) -- (3.5,1);
\node[draw, circle,minimum size=22pt] (c) at (5,2.5) {1};
\path[draw,thick,-] (c) -- (4.5,1);
\path[draw,thick,-] (c) -- (5.5,1);
\node[draw, circle,minimum size=22pt] (d) at (7,2.5) {2};
\path[draw,thick,-] (d) -- (6.5,1);
\path[draw,thick,-] (d) -- (7.5,1);
\node[draw, circle,minimum size=22pt] (i) at (2,4.5) {3};
\path[draw,thick,-] (i) -- (a);
\path[draw,thick,-] (i) -- (b);
\node[draw, circle,minimum size=22pt] (j) at (6,4.5) {1};
\path[draw,thick,-] (j) -- (c);
\path[draw,thick,-] (j) -- (d);
\node[draw, circle,minimum size=22pt] (m) at (4,6.5) {1};
\path[draw,thick,-] (m) -- (i);
\path[draw,thick,-] (m) -- (j);
\end{tikzpicture}
\end{center}
\end{samepage}
Tässä segmenttipuussa jokainen puun solmu kertoo,
mikä on pienin luku sen alapuolella olevassa
taulukon osassa.
Segmenttipuun ylin luku on pienin luku
koko taulukon alueella.
Puun toteutus on samanlainen kuin summan laskemisessa,
mutta joka kohdassa pitää laskea summan sijasta
lukujen minimi.
\subsubsection{Binäärihaku puussa}
Segmenttipuun sisältämää tietoa voi käyttää
binäärihaun kaltaisesti aloittamalla
haun puun huipulta.
Näin on mahdollista selvittää esimerkiksi
minimisegmenttipuusta $O(\log n)$-ajassa,
missä kohdassa on taulukon pienin luku.
Esimerkiksi seuraavassa puussa pienin
alkio on 1, jonka sijainti löytyy
kulkemalla puussa huipulta alaspäin:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\draw (8,0) grid (16,1);
\node[anchor=center] at (8.5, 0.5) {9};
\node[anchor=center] at (9.5, 0.5) {5};
\node[anchor=center] at (10.5, 0.5) {7};
\node[anchor=center] at (11.5, 0.5) {1};
\node[anchor=center] at (12.5, 0.5) {6};
\node[anchor=center] at (13.5, 0.5) {2};
\node[anchor=center] at (14.5, 0.5) {3};
\node[anchor=center] at (15.5, 0.5) {2};
%\node[anchor=center] at (1,2.5) {13};
\node[draw, circle,minimum size=22pt] (e) at (9,2.5) {5};
\path[draw,thick,-] (e) -- (8.5,1);
\path[draw,thick,-] (e) -- (9.5,1);
\node[draw, circle,minimum size=22pt] (f) at (11,2.5) {1};
\path[draw,thick,-] (f) -- (10.5,1);
\path[draw,thick,-] (f) -- (11.5,1);
\node[draw, circle,minimum size=22pt] (g) at (13,2.5) {2};
\path[draw,thick,-] (g) -- (12.5,1);
\path[draw,thick,-] (g) -- (13.5,1);
\node[draw, circle,minimum size=22pt] (h) at (15,2.5) {2};
\path[draw,thick,-] (h) -- (14.5,1);
\path[draw,thick,-] (h) -- (15.5,1);
\node[draw, circle,minimum size=22pt] (k) at (10,4.5) {1};
\path[draw,thick,-] (k) -- (e);
\path[draw,thick,-] (k) -- (f);
\node[draw, circle,minimum size=22pt] (l) at (14,4.5) {2};
\path[draw,thick,-] (l) -- (g);
\path[draw,thick,-] (l) -- (h);
\node[draw, circle,minimum size=22pt] (n) at (12,6.5) {1};
\path[draw,thick,-] (n) -- (k);
\path[draw,thick,-] (n) -- (l);
\path[draw=red,thick,->,line width=2pt] (n) -- (k);
\path[draw=red,thick,->,line width=2pt] (k) -- (f);
\path[draw=red,thick,->,line width=2pt] (f) -- (11.5,1);
\end{tikzpicture}
\end{center}
\section{Lisätekniikoita}
\subsubsection{Indeksien pakkaus}
Taulukon päälle rakennettujen tietorakenteiden
rajoituksena on, että alkiot on indeksoitu
kokonaisluvuin $1,2,3,$ jne.
Tästä seuraa ongelmia,
jos tarvittavat indeksit ovat suuria.
Esimerkiksi indeksin $10^9$ käyttäminen
vaatisi, että taulukossa olisi $10^9$ alkiota,
mikä ei ole realistista.
\index{indeksien pakkaus@indeksien pakkaus}
Tätä rajoitusta on kuitenkin mahdollista
kiertää usein käyttämällä \key{indeksien pakkausta},
jolloin indeksit jaetaan
uudestaan niin, että ne ovat
kokonaisluvut $1,2,3,$ jne.
Tämä on mahdollista silloin, kun kaikki
algoritmin aikana tarvittavat indeksit
ovat tiedossa algoritmin alussa.
Ideana on korvata jokainen alkuperäinen
indeksi $x$ indeksillä $p(x)$,
missä $p$ jakaa indeksit uudestaan.
Vaatimuksena on, että indeksien järjestys
ei muutu, eli jos $a<b$, niin $p(a)<p(b)$,
minkä ansiosta kyselyitä voi tehdä
melko tavallisesti indeksien pakkauksesta huolimatta.
Esimerkiksi jos alkuperäiset indeksit ovat
$555$, $10^9$ ja $8$, ne muuttuvat näin:
\[
\begin{array}{lcl}
p(8) & = & 1 \\
p(555) & = & 2 \\
p(10^9) & = & 3 \\
\end{array}
\]
\subsubsection{Välin muuttaminen}
Tähän asti olemme toteuttaneet tietorakenteita,
joissa voi tehdä tehokkaasti välikyselyitä
ja muuttaa yksittäisiä taulukon arvoja.
Tarkastellaan lopuksi käänteistä tilannetta,
jossa pitääkin muuttaa välejä ja
kysellä yksittäisiä arvoja.
Keskitymme operaatioon,
joka kasvattaa kaikkia välin $[a,b]$ arvoja $x$:llä.
Yllättävää kyllä,
voimme käyttää tämän luvun tietorakenteita myös tässä tilanteessa.
Tämä vaatii, että muutamme taulukkoa niin,
että jokainen taulukon arvo kertoo \textit{muutoksen}
edelliseen arvoon nähden.
Esimerkiksi taulukosta
\begin{center}
\begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$3$};
\node at (1.5,0.5) {$3$};
\node at (2.5,0.5) {$1$};
\node at (3.5,0.5) {$1$};
\node at (4.5,0.5) {$1$};
\node at (5.5,0.5) {$5$};
\node at (6.5,0.5) {$2$};
\node at (7.5,0.5) {$2$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
tulee seuraava:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$3$};
\node at (1.5,0.5) {$0$};
\node at (2.5,0.5) {$-2$};
\node at (3.5,0.5) {$0$};
\node at (4.5,0.5) {$0$};
\node at (5.5,0.5) {$4$};
\node at (6.5,0.5) {$-3$};
\node at (7.5,0.5) {$0$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Minkä tahansa vanhan arvon saa uudesta taulukosta
laskemalla summan taulukon alusta kyseiseen kohtaan asti.
Esimerkiksi kohdan 6 vanha arvo 5 saadaan
summana $3-2+4=5$.
Uuden tallennustavan etuna on,
että välin muuttamiseen riittää muuttaa
kahta taulukon kohtaa.
Esimerkiksi jos välille $2 \ldots 5$
lisätään luku 5,
taulukon kohtaan 2 lisätään 5
ja taulukon kohdasta 6 poistetaan 5.
Tulos on tässä:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$3$};
\node at (1.5,0.5) {$5$};
\node at (2.5,0.5) {$-2$};
\node at (3.5,0.5) {$0$};
\node at (4.5,0.5) {$0$};
\node at (5.5,0.5) {$-1$};
\node at (6.5,0.5) {$-3$};
\node at (7.5,0.5) {$0$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Yleisemmin kun taulukon välille $a \ldots b$
lisätään $x$, taulukon kohtaan $a$
lisätään $x$ ja taulukon kohdasta $b+1$
vähennetään $x$.
Tarvittavat operaatiot
ovat summan laskeminen
taulukon alusta tiettyyn kohtaan
sekä yksittäisen alkion muuttaminen,
joten voimme käyttää tuttuja menetelmiä tässäkin tilanteessa.
Hankalampi tilanne on, jos samaan aikaan pitää pystyä
sekä kysymään tietoa väleiltä että muuttamaan välejä.
Myöhemmin luvussa 28 tulemme näkemään,
että tämäkin on mahdollista.