Static queries
This commit is contained in:
parent
8794316213
commit
4781975ce0
220
luku09.tex
220
luku09.tex
|
@ -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}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue