Binary indexed tree

This commit is contained in:
Antti H S Laaksonen 2017-01-03 20:43:51 +02:00
parent 4781975ce0
commit db7af1212d
1 changed files with 88 additions and 86 deletions

View File

@ -76,13 +76,13 @@ any possible range query efficiently.
\index{prefix sum array} \index{prefix sum array}
Sum queries can be answered efficiently Sum queries can be answered efficiently
by constructing a \key{prefix sum array} by constructing a \key{sum array}
that contains the sum of the range $[1,k]$ that contains the sum of the range $[1,k]$
for each $k=1,2,\ldots,n$. for each $k=1,2,\ldots,n$.
After this, the sum of any range $[a,b]$ of the After this, the sum of any range $[a,b]$ of the
original array original array
can be calculated in $O(1)$ time using the can be calculated in $O(1)$ time using the
prefix sum array. precalculated sum array.
For example, for the array For example, for the array
\begin{center} \begin{center}
@ -110,7 +110,7 @@ For example, for the array
\node at (7.5,1.4) {$8$}; \node at (7.5,1.4) {$8$};
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
the corresponding prefix sum array is as follows: the corresponding sum array is as follows:
\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);
@ -155,12 +155,12 @@ int sum(int a, int b) {
The function calculates the sum of range $[a,b]$ The function calculates the sum of range $[a,b]$
by subtracting the sum of range $[1,a-1]$ by subtracting the sum of range $[1,a-1]$
from the sum of range $[1,b]$. from the sum of range $[1,b]$.
Thus, only two values from the prefix sum array Thus, only two values from the sum array
are needed, and the query takes $O(1)$ time. are needed, and the query takes $O(1)$ time.
Note that thanks to the one-based indexing, Note that thanks to the one-based indexing,
the function also works when $a=1$ if $\texttt{s}[0]=0$. the function also works when $a=1$ if $\texttt{s}[0]=0$.
As an example, let us consider the range $[4,7]$: As an example, consider the range $[4,7]$:
\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);
@ -187,7 +187,7 @@ As an example, let us consider the range $[4,7]$:
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
The sum of the range $[4,7]$ is $8+6+1+4=19$. The sum of the range $[4,7]$ is $8+6+1+4=19$.
This can be calculated from the prefix sum array This can be calculated from the sum array
using the sums $[1,3]$ and $[1,7]$: using the sums $[1,3]$ and $[1,7]$:
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
@ -218,12 +218,12 @@ using the sums $[1,3]$ and $[1,7]$:
\end{center} \end{center}
Thus, the sum of the range $[4,7]$ is $27-8=19$. Thus, the sum of the range $[4,7]$ is $27-8=19$.
We can also generalize the idea of prefix sum array We can also generalize the idea of a sum array
for a two-dimensional array. for a two-dimensional array.
In this case, it will be possible to calculate the sum of In this case, it will be possible to calculate the sum of
any rectangular subarray in $O(1)$ time. any rectangular subarray in $O(1)$ time.
The prefix sum array will contain sums The sum array will contain sums
of all subarrays that begin from the upper-left corner. 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:
@ -250,7 +250,7 @@ to the position of letter $X$.
\subsubsection{Minimum query} \subsubsection{Minimum query}
It is also possible to answer a minimum query It is also possible to answer a minimum query
in $O(1)$ after preprocessing, though it is in $O(1)$ time after preprocessing, though it is
more difficult than answer a sum query. more difficult than answer a sum query.
Note that minimum and maximum queries can always Note that minimum and maximum queries can always
be implemented using same techniques, be implemented using same techniques,
@ -438,36 +438,36 @@ The minimum of the range $[2,5]$ is 3,
and the minimum of the range $[4,7]$ is 1. and the minimum of the range $[4,7]$ is 1.
Thus, the minimum of the range $[2,7]$ is 1. Thus, the minimum of the range $[2,7]$ is 1.
\section{Binääri-indeksipuu} \section{Binary indexed tree}
\index{binxxri-indeksipuu@binääri-indeksipuu} \index{binary indexed tree}
\index{Fenwick-puu} \index{Fenwick tree}
\key{Binääri-indeksipuu} eli \key{Fenwick-puu} on A \key{binary indexed tree} or a \key{Fenwick tree}
summataulukkoa muistuttava tietorakenne, is a data structure that resembles a sum array.
joka toteuttaa kaksi operaatiota: The supported operations are answering
taulukon välin $[a,b]$ summakysely a sum query for range $[a,b]$,
sekä taulukon kohdassa $k$ olevan luvun päivitys. and updating the element at index $k$.
Kummankin operaation aikavaativuus on $O(\log n)$. The time complexity for both of the operations is $O(\log n)$.
Binääri-indeksipuun etuna summataulukkoon verrattuna on, Unlike a sum array, a binary indexed tree
että taulukkoa pystyy päivittämään tehokkaasti can be efficiently updated between the sum queries.
summakyselyiden välissä. This would not be possible using a sum array
Summataulukossa tämä ei olisi mahdollista, because we should build the whole sum array again
vaan koko summataulukko tulisi muodostaa uudestaan $O(n)$-ajassa in $O(n)$ time after each update.
taulukon päivityksen jälkeen.
\subsubsection{Rakenne} \subsubsection{Structure}
Binääri-indeksipuu on taulukko, jonka A binary indexed tree can be represented as an array
kohdassa $k$ on kohtaan $k$ päättyvän välin lukujen summa where index $k$ contains the sum of a range in the
alkuperäisessä taulukossa. original array that ends to index $k$.
Välin pituus on suurin 2:n potenssi, jolla $k$ on jaollinen. The length of the range is the largest power of two
Esimerkiksi jos $k=6$, välin pituus on 2, koska that divides $k$.
6 on jaollinen 2:lla mutta ei ole jaollinen 4:llä. For example, if $k=6$, the length of the range is $2$
because $2$ divides $6$ but $4$ doesn't divide $6$.
\begin{samepage} \begin{samepage}
Esimerkiksi taulukkoa For example, for 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);
@ -493,7 +493,7 @@ Esimerkiksi taulukkoa
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
\end{samepage} \end{samepage}
vastaava binääri-indeksipuu on seuraava: the corresponding binary indexed tree is as follows:
\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);
@ -538,18 +538,21 @@ vastaava binääri-indeksipuu on seuraava:
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Esimerkiksi binääri-indeksipuun kohdassa 6 on luku 7, For example, the binary indexed tree
koska välin $[5,6]$ lukujen summa on $6+1=7$. contains the value 7 at index 6
because the sum of the elements in the range $[5,6]$
of the original array is $6+1=7$.
\subsubsection{Summakysely} \subsubsection{Sum query}
Binääri-indeksipuun perusoperaatio on välin $[1,k]$ The basic operation in a binary indexed tree is
summan laskeminen, missä $k$ on mikä tahansa taulukon kohta. calculating the sum of a range $[1,k]$ where $k$
Tällaisen summan pystyy muodostamaan aina laskemalla yhteen is any index in the array.
puussa olevia välien summia. The sum of any range can be constructed by combining
sums of subranges in the tree.
Esimerkiksi välin $[1,7]$ For example, the range $[1,7]$ will be divided
summa muodostuu seuraavista summista: into three subranges:
\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);
@ -594,25 +597,26 @@ summa muodostuu seuraavista summista:
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Välin $[1,7]$ summa on siis $16+7+4=27$. Thus, the sum of the range $[1,7]$ is $16+7+4=27$.
Binääri-indeksipuun rakenteen ansiosta Because of the structure of the binary indexed tree,
jokainen summaan kuuluva väli on eripituinen. the length of each subrange inside a range is distinct,
Niinpä summa muodostuu aina $O(\log n)$ välin summasta. so the sum of a range
always consists of sums of $O(\log n)$ subranges.
Summataulukon tavoin binääri-indeksipuusta voi laskea Using the same technique that we previously used
tehokkaasti minkä tahansa taulukon välin summan, with a sum array,
koska välin $[a,b]$ summa saadaan vähentämällä we can efficiently calculate the sum of any range
välin $[1,b]$ summasta välin $[1,a-1]$ summa. $[a,b]$ by substracting the sum of the range $[1,a-1]$
Aikavaativuus on edelleen $O(\log n)$, from the sum of the range $[1,b]$.
koska riittää laskea kaksi $[1,k]$-välin summaa. The time complexity remains $O(\log n)$
because it suffices to calculate two sums of $[1,k]$ ranges.
\subsubsection{Taulukon päivitys} \subsubsection{Array update}
Kun taulukon kohdassa $k$ oleva luku muuttuu, When an element in the original array changes,
tämä vaikuttaa useaan several sums in the binary indexed tree change.
binääri-indeksi\-puussa olevaan summaan. For example, if the value at index 3 changes,
Esimerkiksi jos kohdassa 3 oleva luku muuttuu, the sums of the following ranges change:
seuraavat välien summat muuttuvat:
\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);
@ -657,33 +661,30 @@ seuraavat välien summat muuttuvat:
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Myös tässä tapauksessa kaikki välit, Also in this case, the length of each range is distinct,
joihin muutos vaikuttaa, ovat eripituisia, so $O(\log n)$ ranges will be updated in the binary indexed tree.
joten muutos kohdistuu $O(\log n)$ kohtaan
binääri-indeksipuussa.
\subsubsection{Toteutus} \subsubsection{Implementation}
Binääri-indeksipuun operaatiot on mahdollista toteuttaa The operations of a binary indexed tree can be implemented
lyhyesti ja tehokkaasti bittien käsittelyn avulla. in an elegant and efficient way using bit manipulation.
Oleellinen bittioperaatio on $k \& -k$, The bit operation needed is $k \& -k$ that
joka eristää luvusta $k$ viimeisenä olevan ykkösbitin. returns the last bit one from number $k$.
Esimerkiksi $6 \& -6=2$, koska luku $6$ on bittimuodossa 110 For example, $6 \& -6=2$ because the number $6$
ja luku $2$ on bittimuodossa on 10. corresponds to 110 and the number $2$ corresponds to 10.
Osoittautuu, että summan laskemisessa It turns out that when calculating a range sum,
binääri-indeksipuun kohtaa $k$ tulee muuttaa joka askeleella niin, the index $k$ in the binary indexed tree should be
että siitä poistetaan luku $k \& -k$. decreased by $k \& -k$ at every step.
Vastaavasti taulukon päivityksessä kohtaa $k$ tulee muuttaa joka askeleella niin, Correspondingly, when updating the array,
että siihen lisätään luku $k \& -k$. the index $k$ should be increased by $k \& -k$ at every step.
The following functions assume that the binary indexed tree
is stored to array \texttt{b} and it consists of indices $1 \ldots n$.
Seuraavat funktiot olettavat, että binääri-indeksipuu on The function \texttt{sum} calculates the sum of the range $[1,k]$:
tallennettu taulukkoon \texttt{b} ja se muodostuu kohdista $1 \ldots n$.
Funktio \texttt{summa} laskee välin $[1,k]$ summan:
\begin{lstlisting} \begin{lstlisting}
int summa(int k) { int sum(int k) {
int s = 0; int s = 0;
while (k >= 1) { while (k >= 1) {
s += b[k]; s += b[k];
@ -693,9 +694,9 @@ int summa(int k) {
} }
\end{lstlisting} \end{lstlisting}
Funktio \texttt{lisaa} kasvattaa taulukon kohtaa $k$ arvolla $x$: 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) {
while (k <= n) { while (k <= n) {
b[k] += x; b[k] += x;
k += k&-k; k += k&-k;
@ -703,10 +704,11 @@ void lisaa(int k, int x) {
} }
\end{lstlisting} \end{lstlisting}
Kummankin yllä olevan funktion aikavaativuus on $O(\log n)$, The time complexity of both above functions is
koska funktiot muuttavat $O(\log n)$ kohtaa $O(\log n)$ because the functions change $O(\log n)$
binääri-indeksipuussa ja uuteen kohtaan siirtyminen values in the binary indexed tree and each move
vie aikaa $O(1)$ bittioperaation avulla. to the next index
takes $O(1)$ time using the bit operation.
\section{Segmenttipuu} \section{Segmenttipuu}