From 4781975ce0a7e3e797695efce50312e281822814 Mon Sep 17 00:00:00 2001 From: Antti H S Laaksonen Date: Tue, 3 Jan 2017 19:41:30 +0200 Subject: [PATCH] Static queries --- luku09.tex | 220 ++++++++++++++++++++++++++--------------------------- 1 file changed, 108 insertions(+), 112 deletions(-) diff --git a/luku09.tex b/luku09.tex index 920936e..a04bd0c 100644 --- a/luku09.tex +++ b/luku09.tex @@ -1,20 +1,20 @@ \chapter{Range queries} -\index{vxlikysely@välikysely} -\index{summakysely@summakysely} -\index{minimikysely@minimikysely} -\index{maksimikysely@maksimikysely} +\index{range query} +\index{sum query} +\index{minimum query} +\index{maximum query} -\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: +In a \key{range query}, a range of an array +is given and we should calculate some value from the +elements in the range. Typical range queries are: \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]$ +\item \key{sum query}: calculate the sum of elements in range $[a,b]$ +\item \key{minimum query}: find the smallest element in range $[a,b]$ +\item \key{maximum query}: find the largest element in range $[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: +For example, in range $[4,7]$ of the following array, +the sum is $4+6+1+3=14$, the minimum is 1 and the maximum is 6: \begin{center} \begin{tikzpicture}[scale=0.7] \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{center} -Helppo tapa vastata välikyselyyn on -käydä läpi kaikki välin alkiot silmukalla. -Esimerkiksi seuraava funktio toteuttaa summakyselyn: +An easy way to answer a range query is +to iterate through all the elements in the range. +For example, we can answer a sum query as follows: \begin{lstlisting} -int summa(int a, int b) { +int sum(int a, int b) { int s = 0; for (int i = a; i <= b; i++) { s += t[i]; @@ -55,34 +55,36 @@ int summa(int a, int b) { } \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. +The above function handles a sum query +in $O(n)$ time, which is slow if the array is large +and there are a lot of queries. +In this chapter we will learn how +range queries can be answered much more efficiently. -\section{Staattisen taulukon kyselyt} +\section{Static array queries} -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. +We will first focus on a simple case where +the array is \key{static}, i.e., +the elements never change between the queries. +In this case, it suffices to process the +contents of the array beforehand and construct +a data structure that can be used for answering +any possible range query efficiently. -\subsubsection{Summakysely} +\subsubsection{Sum query} -\index{summataulukko@summataulukko} +\index{prefix sum array} -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. +Sum queries can be answered efficiently +by constructing a \key{prefix sum array} +that contains the sum of the range $[1,k]$ +for each $k=1,2,\ldots,n$. +After this, the sum of any range $[a,b]$ of the +original array +can be calculated in $O(1)$ time using the +prefix sum array. -Esimerkiksi taulukon +For example, for the array \begin{center} \begin{tikzpicture}[scale=0.7] %\fill[color=lightgray] (3,0) rectangle (7,1); @@ -108,7 +110,7 @@ Esimerkiksi taulukon \node at (7.5,1.4) {$8$}; \end{tikzpicture} \end{center} -summataulukko on seuraava: +the corresponding prefix sum array is as follows: \begin{center} \begin{tikzpicture}[scale=0.7] %\fill[color=lightgray] (3,0) rectangle (7,1); @@ -135,32 +137,30 @@ summataulukko on seuraava: \node at (7.5,1.4) {$8$}; \end{tikzpicture} \end{center} - -Seuraava koodi muodostaa taulukosta \texttt{t} -summataulukon \texttt{s} ajassa $O(n)$: +The following code constructs a prefix sum +array \texttt{s} from array \texttt{t} in $O(n)$ time: \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: +After this, the following function answers +a sum query in $O(1)$ time: \begin{lstlisting} -int summa(int a, int b) { +int sum(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$. +The function calculates the sum of range $[a,b]$ +by subtracting the sum of range $[1,a-1]$ +from the sum of range $[1,b]$. +Thus, only two values from the prefix sum array +are needed, and the query takes $O(1)$ time. +Note that thanks to the one-based indexing, +the function also works when $a=1$ if $\texttt{s}[0]=0$. -Tarkastellaan esimerkiksi väliä $[4,7]$: +As an example, let us consider the range $[4,7]$: \begin{center} \begin{tikzpicture}[scale=0.7] \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$}; \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: +The sum of the range $[4,7]$ is $8+6+1+4=19$. +This can be calculated from the prefix sum array +using the sums $[1,3]$ and $[1,7]$: \begin{center} \begin{tikzpicture}[scale=0.7] \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$}; \end{tikzpicture} \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ää -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. +We can also generalize the idea of prefix sum array +for a two-dimensional array. +In this case, it will be possible to calculate the sum of +any rectangular subarray in $O(1)$ time. +The prefix sum array will contain sums +of all subarrays that begin from the upper-left corner. \begin{samepage} -Seuraava ruudukko havainnollistaa asiaa: +The following picture illustrates the idea: \begin{center} \begin{tikzpicture}[scale=0.55] \draw[fill=lightgray] (3,2) rectangle (7,5); @@ -241,23 +240,25 @@ Seuraava ruudukko havainnollistaa asiaa: \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. +The sum inside the gray subarray can be calculated +using the formula +\[S(A) - S(B) - S(C) + S(D)\] +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 -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. +It is also possible to answer a minimum query +in $O(1)$ after preprocessing, though it is +more difficult than answer a sum query. +Note that minimum and maximum queries can always +be implemented using same techniques, +so it suffices to focus on the minimum query. -Ideana on laskea etukäteen taulukon jokaiselle -$2^k$-kokoiselle välille, mikä on kyseisen välin minimi. -Esimerkiksi taulukosta +The idea is to find the minimum element for each range +of size $2^k$ in the array. +For example, in the array \begin{center} \begin{tikzpicture}[scale=0.7] @@ -283,13 +284,13 @@ Esimerkiksi taulukosta \node at (7.5,1.4) {$8$}; \end{tikzpicture} \end{center} -lasketaan seuraavat minimit: +the following minima will be calculated: \begin{center} \begin{tabular}{ccc} \begin{tabular}{ccc} -väli & koko & minimi \\ +range & size & min \\ \hline $[1,1]$ & 1 & 1 \\ $[2,2]$ & 1 & 3 \\ @@ -304,7 +305,7 @@ $[8,8]$ & 1 & 2 \\ & \begin{tabular}{ccc} -väli & koko & minimi \\ +range & size & min \\ \hline $[1,2]$ & 2 & 1 \\ $[2,3]$ & 2 & 3 \\ @@ -319,7 +320,7 @@ $[7,8]$ & 2 & 2 \\ & \begin{tabular}{ccc} -väli & koko & minimi \\ +range & size & min \\ \hline $[1,4]$ & 4 & 1 \\ $[2,5]$ & 4 & 3 \\ @@ -335,24 +336,23 @@ $[1,8]$ & 8 & 1 \\ \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ä. +The number of $2^k$ ranges in an array is $O(n \log n)$ +because there are $O(\log n)$ ranges that begin +from each array index. +The minima for all $2^k$ ranges can be calculated +in $O(n \log n)$ time because each $2^k$ range +consists of two $2^{k-1}$ ranges, so the minima +can be calculated recursively. -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]$. +After this, the minimum of any range $[a,b]$c +can be calculated in $O(1)$ time as a minimum of +two $2^k$ ranges where $k=\lfloor \log_2(b-a+1) \rfloor$. +The first range begins from index $a$, +and the second range ends to index $b$. +The parameter $k$ is so chosen that +two $2^k$ ranges cover the range $[a,b]$ entirely. -Tarkastellaan esimerkiksi väliä $[2,7]$: +As an example, consider the range $[2,7]$: \begin{center} \begin{tikzpicture}[scale=0.7] \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$}; \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]$: +The length of the range $[2,7]$ is 6, +and $\lfloor \log_2(6) \rfloor = 2$. +Thus, the minimum can be calculated +from two ranges of length 4. +The ranges are $[2,5]$ and $[4,7]$: \begin{center} \begin{tikzpicture}[scale=0.7] \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$}; \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. +The minimum of the range $[2,5]$ is 3, +and the minimum of the range $[4,7]$ is 1. +Thus, the minimum of the range $[2,7]$ is 1. \section{Binääri-indeksipuu}