Segment tree

This commit is contained in:
Antti H S Laaksonen 2017-01-03 22:51:20 +02:00
parent db7af1212d
commit fe87f1f1e3
1 changed files with 144 additions and 156 deletions

View File

@ -228,7 +228,7 @@ for all subarrays that begin from the upper-left corner.
\begin{samepage} \begin{samepage}
The following picture illustrates the idea: The following picture illustrates the idea:
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.55] \begin{tikzpicture}[scale=0.54]
\draw[fill=lightgray] (3,2) rectangle (7,5); \draw[fill=lightgray] (3,2) rectangle (7,5);
\draw (0,0) grid (10,7); \draw (0,0) grid (10,7);
%\draw[line width=2pt] (3,2) rectangle (7,5); %\draw[line width=2pt] (3,2) rectangle (7,5);
@ -710,36 +710,38 @@ values in the binary indexed tree and each move
to the next index to the next index
takes $O(1)$ time using the bit operation. takes $O(1)$ time using the bit operation.
\section{Segmenttipuu} \section{Segment tree}
\index{segmenttipuu@segmenttipuu} \index{segment tree}
\key{Segmenttipuu} on tietorakenne, A \key{segment tree} is a data structure
jonka operaatiot ovat taulukon välin $[a,b]$ välikysely whose supported operations are
sekä kohdan $k$ arvon päivitys. handling a range query for range $[a,b]$
Segmenttipuun avulla voi toteuttaa summakyselyn, and updating the element at index $k$.
minimikyselyn ja monia muitakin kyselyitä niin, Using a segment tree, we can implement sum
että kummankin operaation aikavaativuus on $O(\log n)$. queries, minimum queries and many other
queries so that both operations work in $O(\log n)$ time.
Segmenttipuun etuna binääri-indeksipuuhun verrattuna on, Compared to a binary indexed tree,
että se on yleisempi tietorakenne. the advantage of a segment tree is that it is
Binääri-indeksipuulla voi toteuttaa vain summakyselyn, a more general data structure.
mutta segmenttipuu sallii muitakin kyselyitä. While binary indexed trees only support
Toisaalta segmenttipuu vie enemmän muistia ja sum queries, segment trees also support other queries.
on hieman vaikeampi toteuttaa kuin binääri-indeksipuu. On the other hand, a segment tree requires more
memory and is a bit more difficult to implement.
\subsubsection{Rakenne} \subsubsection{Structure}
Segmenttipuussa on $2n-1$ solmua niin, A segment tree contains $2n-1$ nodes
että alimmalla tasolla on $n$ solmua, so that the bottom $n$ nodes correspond
jotka kuvaavat taulukon sisällön, to the original array and the other nodes
ja ylemmillä tasoilla on välikyselyihin contain information needed for range queries.
tarvittavaa tietoa. The values in a segment tree depend on
Segmenttipuun sisältö riippuu siitä, the supported query type.
mikä välikysely puun tulee toteuttaa. We will first assume that the supported
Oletamme aluksi, että välikysely on tuttu summakysely. query is the sum query.
Esimerkiksi taulukkoa For example, the array
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1); \draw (0,0) grid (8,1);
@ -764,7 +766,7 @@ Esimerkiksi taulukkoa
\node at (7.5,1.4) {$8$}; \node at (7.5,1.4) {$8$};
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
vastaa seuraava segmenttipuu: corresponds to the following segment tree:
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1); \draw (0,0) grid (8,1);
@ -804,34 +806,32 @@ vastaa seuraava segmenttipuu:
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Jokaisessa segmenttipuun solmussa on tietoa Each internal node in the segment tree contains
$2^k$-kokoisesta välistä taulukossa. information about a range of size $2^k$
Tässä tapauksessa solmussa oleva arvo kertoo, in the original array.
mikä on taulukon lukujen summa solmua vastaavalla In the above tree, the value of each internal
välillä. node is the sum of the corresponding array elements,
Kunkin solmun arvo saadaan laskemalla yhteen and it can be calculated as the sum of
solmun alapuolella vasemmalla ja oikealla the values of its left and right child node.
olevien solmujen arvot.
Segmenttipuu on mukavinta rakentaa niin, It is convenient to build a segment tree
että taulukon koko on 2:n potenssi, when the size of the array is a power of two
jolloin tuloksena on täydellinen binääripuu. and the tree is a complete binary tree.
Jatkossa oletamme aina, In the sequel, we will assume that the tree
että taulukko täyttää tämän vaatimuksen. is built like this.
Jos taulukon koko ei ole 2:n potenssi, If the size of the array is not a power of two,
sen loppuun voi lisätä tyhjää niin, we can always extend it using zero elements.
että koosta tulee 2:n potenssi.
\subsubsection{Välikysely} \subsubsection{Range query}
Segmenttipuussa vastaus välikyselyyn lasketaan In a segment tree, the answer for a range query
väliin kuuluvista solmuista, is calculated from nodes that belong to the range
jotka ovat mahdollisimman korkealla puussa. and are as high as possible in the tree.
Jokainen solmu antaa vastauksen väliin kuuluvalle osavälille, Each node gives the answer for a subrange,
ja vastaus kyselyyn selviää yhdistämällä and the answer for the entire range can be
segmenttipuusta saadut osavälejä koskeva tiedot. calculated by combining these values.
Tarkastellaan esimerkiksi seuraavaa taulukon väliä: For example, consider the following range:
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
\fill[color=gray!50] (2,0) rectangle (8,1); \fill[color=gray!50] (2,0) rectangle (8,1);
@ -857,9 +857,10 @@ Tarkastellaan esimerkiksi seuraavaa taulukon väliä:
\node at (7.5,1.4) {$8$}; \node at (7.5,1.4) {$8$};
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Lukujen summa välillä $[3,8]$ on $6+3+2+7+2+6=26$. The sum of elements in the range $[3,8]$ is
Segmenttipuusta summa saadaan laskettua seuraavien $6+3+2+7+2+6=26$.
osasummien avulla: The sum can be calculated from the segment tree
using the following subranges:
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1); \draw (0,0) grid (8,1);
@ -898,27 +899,26 @@ osasummien avulla:
\path[draw,thick,-] (m) -- (j); \path[draw,thick,-] (m) -- (j);
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Taulukon välin summaksi tulee osasummista $9+17=26$. Thus, the sum of the range is $9+17=26$.
Kun vastaus välikyselyyn lasketaan mahdollisimman When the answer for a range query is
korkealla segmenttipuussa olevista solmuista, calculated using as high nodes as possible,
väliin kuuluu enintään kaksi solmua at most two nodes on each level
jokaiselta segmenttipuun tasolta. of the segment tree are needed.
Tämän ansiosta välikyselyssä Because of this, the total number of nodes
tarvittavien solmujen yhteismäärä on vain $O(\log n)$. examined is only $O(\log n)$.
\subsubsection{Taulukon päivitys} \subsubsection{Array update}
Kun taulukossa oleva arvo muuttuu, When an element in the array changes,
segmenttipuussa täytyy päivittää we should update all nodes in the segment tree
kaikkia solmuja, joiden arvo whose value depends on the changed element.
riippuu muutetusta taulukon kohdasta. This can be done by travelling from the bottom
Tämä tapahtuu kulkemalla puuta ylöspäin huipulle to the top in the tree and updating the nodes along the path.
asti ja tekemällä muutokset.
\begin{samepage} \begin{samepage}
Seuraava kuva näyttää, mitkä solmut segmenttipuussa muuttuvat, The following picture shows which nodes in the segment tree
jos taulukon luku 7 muuttuu. change if the element 7 in the array changes.
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
@ -961,28 +961,26 @@ jos taulukon luku 7 muuttuu.
\end{center} \end{center}
\end{samepage} \end{samepage}
Polku segmenttipuun pohjalta huipulle muodostuu aina $O(\log n)$ solmusta, The path from the bottom of the segment tree to the top
joten taulukon arvon muuttuminen vaikuttaa $O(\log n)$ solmuun puussa. always consists of $O(\log n)$ nodes,
so updating the array affects $O(\log n)$ nodes in the tree.
\subsubsection{Puun tallennus} \subsubsection{Storing the tree}
Segmenttipuun voi tallentaa muistiin A segment tree can be stored as an array
$2N$ alkion taulukkona, of $2N$ elements where $N$ is a power of two.
jossa $N$ on riittävän suuri 2:n potenssi. From now on, we will assume that the indices
Tällaisen segmenttipuun avulla voi ylläpitää of the original array are between $0$ and $N-1$.
taulukkoa, jonka indeksialue on $[0,N-1]$.
Segmenttipuun taulukon The element at index 1 in the segment tree array
kohdassa 1 on puun ylimmän solmun arvo, contains the top node of the tree,
kohdat 2 ja 3 sisältävät seuraavan tason the elements at indices 2 and 3 correspond to
solmujen arvot, jne. the second level of the tree, and so on.
Segmenttipuun alin taso eli varsinainen Finally, the elements beginning from index $N$
taulukon sisältä tallennetaan contain the bottom level of the tree, i.e.,
kohdasta $N$ alkaen. the actual content of the original array.
Niinpä taulukon kohdassa $k$ oleva alkio
on segmenttipuun taulukossa kohdassa $k+N$.
Esimerkiksi segmenttipuun For example, the segment tree
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1); \draw (0,0) grid (8,1);
@ -1021,7 +1019,7 @@ Esimerkiksi segmenttipuun
\path[draw,thick,-] (m) -- (j); \path[draw,thick,-] (m) -- (j);
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
voi tallentaa taulukkoon seuraavasti ($N=8$): can be stored as follows ($N=8$):
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
%\fill[color=lightgray] (3,0) rectangle (7,1); %\fill[color=lightgray] (3,0) rectangle (7,1);
@ -1061,30 +1059,24 @@ voi tallentaa taulukkoon seuraavasti ($N=8$):
\node at (14.5,1.4) {$15$}; \node at (14.5,1.4) {$15$};
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Tätä tallennustapaa käyttäen kohdassa $k$ Using this representation,
olevalle solmulle pätee, että for a node at index $k$,
\begin{itemize} \begin{itemize}
\item ylempi solmu on kohdassa $\lfloor k/2 \rfloor$, \item the parent node is at index $\lfloor k/2 \rfloor$,
\item vasen alempi solmu on kohdassa $2k$ ja \item the left child node is at index $2k$, and
\item oikea alempi solmu on kohdassa $2k+1$. \item the right child node is at index $2k+1$.
\end{itemize} \end{itemize}
Huomaa, että tämän seurauksena solmun kohta on parillinen, Note that this implies that the index of a node
jos se on vasemmalla ylemmästä solmusta katsoen, is even if it is a left child and odd if it is a right child.
ja pariton, jos se on oikealla.
\subsubsection{Toteutus} \subsubsection{Functions}
Tarkastellaan seuraavaksi välikyselyn ja päivityksen We assume that the segment tree is stored
toteutusta segmenttipuuhun. in the array \texttt{p}.
Seuraavat funktiot olettavat, että segmenttipuu The following function calculates the sum of range $[a,b]$:
on tallennettu $2n-1$-kokoi\-seen taulukkoon $\texttt{p}$
edellä kuvatulla tavalla.
Funktio \texttt{summa} laskee summan
välillä $a \ldots b$:
\begin{lstlisting} \begin{lstlisting}
int summa(int a, int b) { int sum(int a, int b) {
a += N; b += N; a += N; b += N;
int s = 0; int s = 0;
while (a <= b) { while (a <= b) {
@ -1096,17 +1088,18 @@ int summa(int a, int b) {
} }
\end{lstlisting} \end{lstlisting}
Funktio aloittaa summan laskeminen segmenttipuun The function begins from the bottom of the tree
pohjalta ja liikkuu askel kerrallaan ylemmille tasoille. and moves step by step upwards in the tree.
Funktio laskee välin summan muuttujaan $s$ The function calculates the range sum to
yhdistämällä puussa olevia osasummia. the variable $s$ by combining the sums in the tree nodes.
Välin reunalla oleva osasumma lisätään summaan The value of a node is added to the sum if
aina silloin, kun se ei kuulu ylemmän tason osasummaan. the parent node doesn't belong to the range.
Funktio \texttt{lisaa} kasvattaa kohdan $k$ arvoa $x$:llä: The function \texttt{add} increases the value
of element $k$ by $x$:
\begin{lstlisting} \begin{lstlisting}
void lisaa(int k, int x) { void add(int k, int x) {
k += N; k += N;
p[k] += x; p[k] += x;
for (k /= 2; k >= 1; k /= 2) { for (k /= 2; k >= 1; k /= 2) {
@ -1114,35 +1107,32 @@ void lisaa(int k, int x) {
} }
} }
\end{lstlisting} \end{lstlisting}
Ensin funktio tekee muutoksen puun alimmalle First the function updates the bottom level
tasolle taulukkoon. of the tree that corresponds to the original array.
Tämän jälkeen se päivittää kaikki osasummat After this, the function updates the values of all
puun huipulle asti. internal nodes in the tree, until it reaches
Taulukon \texttt{p} indeksoinnin ansiosta the root node of the tree.
kohdasta $k$ alemmalla tasolla
ovat kohdat $2k$ ja $2k+1$.
Molemmat segmenttipuun operaatiot toimivat ajassa Both operations in the segment tree work
$O(\log n)$, koska $n$ lukua sisältävässä in $O(\log n)$ time because a segment tree
segmenttipuussa on $O(\log n)$ tasoa of $n$ elements consists of $O(\log n)$ levels,
ja operaatiot siirtyvät askel kerrallaan and the operations move one level forward at each step.
segmenttipuun tasoja ylöspäin.
\subsubsection{Muut kyselyt} \subsubsection{Other queries}
Segmenttipuu mahdollistaa summan lisäksi minkä Besides the sum query,
tahansa välikyselyn, the segment tree can support any range query
jossa vierekkäisten välien $[a,b]$ ja $[b+1,c]$ where the answer for range $[a,b]$
tuloksista pystyy laskemaan tehokkaasti can be efficiently calculated
välin $[a,c]$ tuloksen. from ranges $[a,c]$ and $[c+1,b]$ where
Tällaisia kyselyitä $c$ is some element between $a$ and $b$.
ovat esimerkiksi minimi ja maksimi, Such queries are, for example,
suurin yhteinen tekijä minimum and maximum, greatest common divisor,
sekä bittioperaatiot and, or ja xor. and bit operations.
\begin{samepage} \begin{samepage}
Esimerkiksi seuraavan segmenttipuun avulla voi laskea For example, the following segment tree
taulukon välien minimejä: supports minimum queries:
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
@ -1184,27 +1174,25 @@ taulukon välien minimejä:
\end{center} \end{center}
\end{samepage} \end{samepage}
Tässä segmenttipuussa jokainen puun solmu kertoo, In this segment tree, every node in the tree
mikä on pienin luku sen alapuolella olevassa contains the smallest element in the corresponding
taulukon osassa. range of the original array.
Segmenttipuun ylin luku on pienin luku The top node of the tree contains the smallest
koko taulukon alueella. element in the array.
Puun toteutus on samanlainen kuin summan laskemisessa, The tree can be implemented like previously,
mutta joka kohdassa pitää laskea summan sijasta but instead of sums, minima are calculated.
lukujen minimi.
\subsubsection{Binäärihaku puussa} \subsubsection{Binary search in tree}
Segmenttipuun sisältämää tietoa voi käyttää The structure of the segment tree makes it possible
binäärihaun kaltaisesti aloittamalla to use binary search.
haun puun huipulta. For example, if the tree supports the minimum query,
Näin on mahdollista selvittää esimerkiksi we can find the index of the smallest
minimisegmenttipuusta $O(\log n)$-ajassa, element in $O(\log n)$ time.
missä kohdassa on taulukon pienin luku.
Esimerkiksi seuraavassa puussa pienin For example, in the following tree the
alkio on 1, jonka sijainti löytyy smallest element is 1 that can be found
kulkemalla puussa huipulta alaspäin: by following a path downwards from the top node:
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]