Static queries

This commit is contained in:
Antti H S Laaksonen 2017-01-03 19:41:30 +02:00
parent 8794316213
commit 4781975ce0
1 changed files with 108 additions and 112 deletions

View File

@ -1,20 +1,20 @@
\chapter{Range queries} \chapter{Range queries}
\index{vxlikysely@välikysely} \index{range query}
\index{summakysely@summakysely} \index{sum query}
\index{minimikysely@minimikysely} \index{minimum query}
\index{maksimikysely@maksimikysely} \index{maximum query}
\key{Välikysely} kohdistuu taulukon välille $[a,b]$, In a \key{range query}, a range of an array
ja tehtävänä on laskea haluttu tieto välillä olevista alkioista. is given and we should calculate some value from the
Tavallisia välikyselyitä ovat: elements in the range. Typical range queries are:
\begin{itemize} \begin{itemize}
\item \key{summakysely}: laske välin $[a,b]$ summa \item \key{sum query}: calculate the sum of elements in range $[a,b]$
\item \key{minimikysely}: etsi pienin alkio välillä $[a,b]$ \item \key{minimum query}: find the smallest element in range $[a,b]$
\item \key{maksimikysely}: etsi suurin alkio välillä $[a,b]$ \item \key{maximum query}: find the largest element in range $[a,b]$
\end{itemize} \end{itemize}
Esimerkiksi seuraavan taulukon välillä $[4,7]$ For example, in range $[4,7]$ of the following array,
summa on $4+6+1+3=14$, minimi on 1 ja maksimi on 6: the sum is $4+6+1+3=14$, the minimum is 1 and the maximum is 6:
\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);
@ -41,12 +41,12 @@ summa on $4+6+1+3=14$, minimi on 1 ja maksimi on 6:
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Helppo tapa vastata välikyselyyn on An easy way to answer a range query is
käydä läpi kaikki välin alkiot silmukalla. to iterate through all the elements in the range.
Esimerkiksi seuraava funktio toteuttaa summakyselyn: For example, we can answer a sum query as follows:
\begin{lstlisting} \begin{lstlisting}
int summa(int a, int b) { int sum(int a, int b) {
int s = 0; int s = 0;
for (int i = a; i <= b; i++) { for (int i = a; i <= b; i++) {
s += t[i]; s += t[i];
@ -55,34 +55,36 @@ int summa(int a, int b) {
} }
\end{lstlisting} \end{lstlisting}
Yllä oleva funktio toteuttaa summakyselyn The above function handles a sum query
ajassa $O(n)$, mikä on hidasta, in $O(n)$ time, which is slow if the array is large
jos taulukko on suuri ja kyselyitä tulee paljon. and there are a lot of queries.
Tässä luvussa opimme, miten välikyselyitä pystyy In this chapter we will learn how
toteuttamaan huomattavasti nopeammin. range queries can be answered much more efficiently.
\section{Staattisen taulukon kyselyt} \section{Static array queries}
Aloitamme yksinkertaisesta We will first focus on a simple case where
tilanteesta, jossa taulukko on \key{staattinen} the array is \key{static}, i.e.,
eli sen sisältö ei muutu kyselyiden välillä. the elements never change between the queries.
Tällöin riittää muodostaa ennen kyselyitä In this case, it suffices to process the
taulukon pohjalta tietorakenne, contents of the array beforehand and construct
josta voi selvittää tehokkaasti vastauksen mihin tahansa väliin a data structure that can be used for answering
kohdistuvaan kyselyyn. any possible range query efficiently.
\subsubsection{Summakysely} \subsubsection{Sum query}
\index{summataulukko@summataulukko} \index{prefix sum array}
Summakyselyyn on mahdollista vastata tehokkaasti Sum queries can be answered efficiently
muodostamalla taulukosta etukäteen \key{summataulukko}, by constructing a \key{prefix sum array}
jonka kohdassa $k$ on taulukon välin $[1,k]$ summa. that contains the sum of the range $[1,k]$
Tämän jälkeen minkä tahansa välin $[a,b]$ for each $k=1,2,\ldots,n$.
summan saa laskettua $O(1)$-ajassa After this, the sum of any range $[a,b]$ of the
summataulukkoa käyttäen. original array
can be calculated in $O(1)$ time using the
prefix sum array.
Esimerkiksi taulukon For example, for the array
\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);
@ -108,7 +110,7 @@ Esimerkiksi taulukon
\node at (7.5,1.4) {$8$}; \node at (7.5,1.4) {$8$};
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
summataulukko on seuraava: the corresponding prefix 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);
@ -135,32 +137,30 @@ summataulukko on seuraava:
\node at (7.5,1.4) {$8$}; \node at (7.5,1.4) {$8$};
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
The following code constructs a prefix sum
Seuraava koodi muodostaa taulukosta \texttt{t} array \texttt{s} from array \texttt{t} in $O(n)$ time:
summataulukon \texttt{s} ajassa $O(n)$:
\begin{lstlisting} \begin{lstlisting}
for (int i = 1; i <= n; i++) { for (int i = 1; i <= n; i++) {
s[i] = s[i-1]+t[i]; s[i] = s[i-1]+t[i];
} }
\end{lstlisting} \end{lstlisting}
Tämän jälkeen summakyselyyn voi vastata After this, the following function answers
ajassa $O(1)$ seuraavasti: a sum query in $O(1)$ time:
\begin{lstlisting} \begin{lstlisting}
int summa(int a, int b) { int sum(int a, int b) {
return s[b]-s[a-1]; return s[b]-s[a-1];
} }
\end{lstlisting} \end{lstlisting}
Funktio laskee välin $[a,b]$ summan The function calculates the sum of range $[a,b]$
vähentämällä välin $[1,b]$ summasta by subtracting the sum of range $[1,a-1]$
välin $[1,a-1]$ summan. from the sum of range $[1,b]$.
Summataulukosta riittää siis hakea kaksi arvoa Thus, only two values from the prefix sum array
ja aikaa kuluu vain $O(1)$. are needed, and the query takes $O(1)$ time.
Huomaa, että 1-indeksoinnin ansiosta Note that thanks to the one-based indexing,
funktio toimii myös tapauksessa $a=1$, the function also works when $a=1$ if $\texttt{s}[0]=0$.
kunhan $\texttt{s}[0]=0$.
Tarkastellaan esimerkiksi väliä $[4,7]$: As an example, let us 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);
@ -186,9 +186,9 @@ Tarkastellaan esimerkiksi väliä $[4,7]$:
\node at (7.5,1.4) {$8$}; \node at (7.5,1.4) {$8$};
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Välin $[4,7]$ summa on $8+6+1+4=19$. The sum of the range $[4,7]$ is $8+6+1+4=19$.
Tämän saa laskettua tehokkaasti summataulukosta This can be calculated from the prefix sum array
etsimällä välien $[1,3]$ ja $[1,7]$ summat: using the sums $[1,3]$ and $[1,7]$:
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (2,0) rectangle (3,1); \fill[color=lightgray] (2,0) rectangle (3,1);
@ -216,18 +216,17 @@ etsimällä välien $[1,3]$ ja $[1,7]$ summat:
\node at (7.5,1.4) {$8$}; \node at (7.5,1.4) {$8$};
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Välin $[4,7]$ summa on siis $27-8=19$. Thus, the sum of the range $[4,7]$ is $27-8=19$.
Summataulukon idean voi yleistää We can also generalize the idea of prefix sum array
myös kaksiulotteiseen taulukkoon, for a two-dimensional array.
jolloin summataulukosta voi laskea In this case, it will be possible to calculate the sum of
minkä tahansa suorakulmaisen alueen any rectangular subarray in $O(1)$ time.
summan $O(1)$-ajassa. The prefix sum array will contain sums
Tällöin summataulukkoon tallennetaan summia of all subarrays that begin from the upper-left corner.
alueista, jotka alkavat taulukon vasemmasta yläkulmasta.
\begin{samepage} \begin{samepage}
Seuraava ruudukko havainnollistaa asiaa: The following picture illustrates the idea:
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.55] \begin{tikzpicture}[scale=0.55]
\draw[fill=lightgray] (3,2) rectangle (7,5); \draw[fill=lightgray] (3,2) rectangle (7,5);
@ -241,23 +240,25 @@ Seuraava ruudukko havainnollistaa asiaa:
\end{center} \end{center}
\end{samepage} \end{samepage}
Harmaan suorakulmion summan saa laskettua kaavalla The sum inside the gray subarray can be calculated
\[S(A) - S(B) - S(C) + S(D),\] using the formula
missä $S(X)$ tarkoittaa summaa vasemmasta \[S(A) - S(B) - S(C) + S(D)\]
yläkulmasta kirjaimen $X$ osoittamaan kohtaan asti. where $S(X)$ denotes the sum in a rectangular
subarray from the upper-left corner
to the position of letter $X$.
\subsubsection{Minimikysely} \subsubsection{Minimum query}
Myös minimikyselyyn on mahdollista It is also possible to answer a minimum query
vastata $O(1)$-ajassa sopivan esikäsittelyn avulla, in $O(1)$ after preprocessing, though it is
joskin tämä on vaikeampaa kuin summakyselyssä. more difficult than answer a sum query.
Huomaa, että minimikysely ja maksimikysely on Note that minimum and maximum queries can always
mahdollista toteuttaa aina samalla tavalla, be implemented using same techniques,
joten riittää keskittyä minimikyselyn toteutukseen. so it suffices to focus on the minimum query.
Ideana on laskea etukäteen taulukon jokaiselle The idea is to find the minimum element for each range
$2^k$-kokoiselle välille, mikä on kyseisen välin minimi. of size $2^k$ in the array.
Esimerkiksi taulukosta For example, in the array
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
@ -283,13 +284,13 @@ Esimerkiksi taulukosta
\node at (7.5,1.4) {$8$}; \node at (7.5,1.4) {$8$};
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
lasketaan seuraavat minimit: the following minima will be calculated:
\begin{center} \begin{center}
\begin{tabular}{ccc} \begin{tabular}{ccc}
\begin{tabular}{ccc} \begin{tabular}{ccc}
väli & koko & minimi \\ range & size & min \\
\hline \hline
$[1,1]$ & 1 & 1 \\ $[1,1]$ & 1 & 1 \\
$[2,2]$ & 1 & 3 \\ $[2,2]$ & 1 & 3 \\
@ -304,7 +305,7 @@ $[8,8]$ & 1 & 2 \\
& &
\begin{tabular}{ccc} \begin{tabular}{ccc}
väli & koko & minimi \\ range & size & min \\
\hline \hline
$[1,2]$ & 2 & 1 \\ $[1,2]$ & 2 & 1 \\
$[2,3]$ & 2 & 3 \\ $[2,3]$ & 2 & 3 \\
@ -319,7 +320,7 @@ $[7,8]$ & 2 & 2 \\
& &
\begin{tabular}{ccc} \begin{tabular}{ccc}
väli & koko & minimi \\ range & size & min \\
\hline \hline
$[1,4]$ & 4 & 1 \\ $[1,4]$ & 4 & 1 \\
$[2,5]$ & 4 & 3 \\ $[2,5]$ & 4 & 3 \\
@ -335,24 +336,23 @@ $[1,8]$ & 8 & 1 \\
\end{center} \end{center}
Taulukon $2^k$-välien määrä on $O(n \log n)$, The number of $2^k$ ranges in an array is $O(n \log n)$
koska jokaisesta taulukon kohdasta alkaa because there are $O(\log n)$ ranges that begin
$O(\log n)$ väliä. from each array index.
Kaikkien $2^k$-välien minimit pystytään laskemaan The minima for all $2^k$ ranges can be calculated
ajassa $O(n \log n)$, koska jokainen $2^k$-väli in $O(n \log n)$ time because each $2^k$ range
muodostuu kahdesta $2^{k-1}$ välistä ja consists of two $2^{k-1}$ ranges, so the minima
$2^k$-välin minimi on pienempi $2^{k-1}$-välien minimeistä. can be calculated recursively.
Tämän jälkeen minkä tahansa välin $[a,b]$ minimin After this, the minimum of any range $[a,b]$c
saa laskettua $O(1)$-ajassa miniminä kahdesta $2^k$-välistä, can be calculated in $O(1)$ time as a minimum of
missä $k=\lfloor \log_2(b-a+1) \rfloor$. two $2^k$ ranges where $k=\lfloor \log_2(b-a+1) \rfloor$.
Ensimmäinen väli alkaa kohdasta $a$ The first range begins from index $a$,
ja toinen väli päättyy kohtaan $b$. and the second range ends to index $b$.
Parametri $k$ on valittu niin, The parameter $k$ is so chosen that
että kaksi $2^k$-kokoista väliä two $2^k$ ranges cover the range $[a,b]$ entirely.
kattaa koko välin $[a,b]$.
Tarkastellaan esimerkiksi väliä $[2,7]$: As an example, consider the range $[2,7]$:
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (1,0) rectangle (7,1); \fill[color=lightgray] (1,0) rectangle (7,1);
@ -378,10 +378,11 @@ Tarkastellaan esimerkiksi väliä $[2,7]$:
\node at (7.5,1.4) {$8$}; \node at (7.5,1.4) {$8$};
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Välin $[2,7]$ pituus on 6 ja $\lfloor \log_2(6) \rfloor = 2$. The length of the range $[2,7]$ is 6,
Niinpä välin minimin saa selville kahden 4-pituisen and $\lfloor \log_2(6) \rfloor = 2$.
välin minimistä. Thus, the minimum can be calculated
Välit ovat $[2,5]$ ja $[4,7]$: from two ranges of length 4.
The ranges are $[2,5]$ and $[4,7]$:
\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);
@ -433,14 +434,9 @@ Välit ovat $[2,5]$ ja $[4,7]$:
\node at (7.5,1.4) {$8$}; \node at (7.5,1.4) {$8$};
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Välin $[2,5]$ minimi on 3 ja välin $[4,7]$ minimi on 1. The minimum of the range $[2,5]$ is 3,
Tämän seurauksena välin $[2,7]$ minimi on pienempi näistä eli 1. and the minimum of the range $[4,7]$ is 1.
% Thus, the minimum of the range $[2,7]$ is 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} \section{Binääri-indeksipuu}