cphb/luku08.tex

840 lines
23 KiB
TeX

\chapter{Amortized analysis}
\index{tasoitettu analyysi@tasoitettu analyysi}
Monen algoritmin aikavaativuuden pystyy laskemaan
suoraan katsomalla algoritmin rakennetta:
mitä silmukoita algoritmissa on ja miten monta
kertaa niitä suoritetaan.
Joskus kuitenkaan näin suoraviivainen analyysi ei
riitä antamaan todellista kuvaa algoritmin tehokkuudesta.
\key{Tasoitettu analyysi} soveltuu sellaisten
algoritmien analyysiin, joiden osana on jokin operaatio,
jonka ajankäyttö vaihtelee.
Ideana on tarkastella yksittäisen operaation
sijasta kaikkia operaatioita algoritmin
aikana ja laskea niiden ajankäytölle yhteinen raja.
\section{Kahden osoittimen tekniikka}
\index{kahden osoittimen tekniikka}
\key{Kahden osoittimen tekniikka} on taulukon käsittelyssä
käytettävä menetelmä, jossa taulukkoa käydään läpi
kahden osoittimen avulla.
Molemmat osoittimet liikkuvat algoritmin aikana,
mutta rajoituksena on, että ne voivat liikkua vain
yhteen suuntaan, mikä takaa, että algoritmi toimii tehokkaasti.
Tutustumme seuraavaksi kahden osoittimen tekniikkaan
kahden esimerkkitehtävän kautta.
\subsubsection{Alitaulukon summa}
Annettuna on taulukko, jossa on $n$ positiivista kokonaislukua.
Tehtävänä on selvittää, onko taulukossa alitaulukkoa,
jossa lukujen summa on $x$.
Esimerkiksi taulukossa
\begin{center}
\begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$3$};
\node at (2.5,0.5) {$2$};
\node at (3.5,0.5) {$5$};
\node at (4.5,0.5) {$1$};
\node at (5.5,0.5) {$1$};
\node at (6.5,0.5) {$2$};
\node at (7.5,0.5) {$3$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
on alitaulukko, jossa lukujen summa on 8:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (2,0) rectangle (5,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$3$};
\node at (2.5,0.5) {$2$};
\node at (3.5,0.5) {$5$};
\node at (4.5,0.5) {$1$};
\node at (5.5,0.5) {$1$};
\node at (6.5,0.5) {$2$};
\node at (7.5,0.5) {$3$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Osoittautuu, että tämän tehtävän voi ratkaista
ajassa $O(n)$ kahden osoittimen tekniikalla.
Ideana on käydä taulukkoa läpi kahden osoittimen
avulla, jotka rajaavat välin taulukosta.
Joka vuorolla vasen osoitin liikkuu
yhden askeleen eteenpäin ja oikea osoitin
liikkuu niin kauan eteenpäin kuin summa on enintään $x$.
Jos välin summaksi tulee tarkalleen $x$, ratkaisu on löytynyt.
Tarkastellaan esimerkkinä algoritmin toimintaa
seuraavassa taulukossa, kun tavoitteena on muodostaa summa $x=8$:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$3$};
\node at (2.5,0.5) {$2$};
\node at (3.5,0.5) {$5$};
\node at (4.5,0.5) {$1$};
\node at (5.5,0.5) {$1$};
\node at (6.5,0.5) {$2$};
\node at (7.5,0.5) {$3$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Aluksi osoittimet rajaavat taulukosta välin,
jonka summa on $1+3+2=6$.
Väli ei voi olla tätä suurempi,
koska seuraava luku 5 veisi summan yli $x$:n.
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (0,0) rectangle (3,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$3$};
\node at (2.5,0.5) {$2$};
\node at (3.5,0.5) {$5$};
\node at (4.5,0.5) {$1$};
\node at (5.5,0.5) {$1$};
\node at (6.5,0.5) {$2$};
\node at (7.5,0.5) {$3$};
\draw[thick,->] (0.5,-0.7) -- (0.5,-0.1);
\draw[thick,->] (2.5,-0.7) -- (2.5,-0.1);
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Seuraavaksi vasen osoitin siirtyy askeleen eteenpäin.
Oikea osoitin säilyy paikallaan, koska muuten
summa kasvaisi liian suureksi.
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (1,0) rectangle (3,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$3$};
\node at (2.5,0.5) {$2$};
\node at (3.5,0.5) {$5$};
\node at (4.5,0.5) {$1$};
\node at (5.5,0.5) {$1$};
\node at (6.5,0.5) {$2$};
\node at (7.5,0.5) {$3$};
\draw[thick,->] (1.5,-0.7) -- (1.5,-0.1);
\draw[thick,->] (2.5,-0.7) -- (2.5,-0.1);
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Vasen osoitin siirtyy taas askeleen eteenpäin
ja tällä kertaa oikea osoitin siirtyy kolme askelta
eteenpäin. Muodostuu summa $2+5+1=8$ eli taulukosta
on löytynyt väli, jonka lukujen summa on $x$.
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (2,0) rectangle (5,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$3$};
\node at (2.5,0.5) {$2$};
\node at (3.5,0.5) {$5$};
\node at (4.5,0.5) {$1$};
\node at (5.5,0.5) {$1$};
\node at (6.5,0.5) {$2$};
\node at (7.5,0.5) {$3$};
\draw[thick,->] (2.5,-0.7) -- (2.5,-0.1);
\draw[thick,->] (4.5,-0.7) -- (4.5,-0.1);
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
% Algoritmin toteutus näyttää seuraavalta:
%
% \begin{lstlisting}
% int s = 0, b = 0;
% for (int a = 1; a <= n; a++) {
% while (b<n && s+t[b+1] <= x) {
% b++;
% s += t[b];
% }
% if (s == x) {
% // ratkaisu löytyi
% }
% s -= t[a];
% }
% \end{lstlisting}
%
% Muuttujat $a$ ja $b$ sisältävät vasemman ja oikean
% osoittimen kohdan.
% Muuttuja $s$ taas laskee lukujen summan välillä.
% Joka askeleella $a$ liikkuu askeleen eteenpäin
% ja $b$ liikkuu niin kauan kuin summa on enintään $x$.
Algoritmin aikavaativuus riippuu siitä,
kauanko oikean osoittimen liikkuminen vie aikaa.
Tämä vaihtelee, koska oikea osoitin voi liikkua
minkä tahansa matkan eteenpäin taulukossa.
Kuitenkin oikea osoitin liikkuu \textit{yhteensä}
$O(n)$ askelta algoritmin aikana, koska se voi
liikkua vain eteenpäin.
Koska sekä vasen että oikea osoitin liikkuvat
$O(n)$ askelta algoritmin aikana,
algoritmin aikavaativuus on $O(n)$.
\subsubsection{Kahden luvun summa}
\index{2SUM-ongelma}
Annettuna on taulukko, jossa on $n$ kokonaislukua,
sekä kokonaisluku $x$.
Tehtävänä on etsiä taulukosta kaksi lukua,
joiden summa on $x$, tai todeta,
että tämä ei ole mahdollista.
Tämä ongelma tunnetaan tunnetaan nimellä
\key{2SUM} ja se ratkeaa tehokkaasti
kahden osoittimen tekniikalla.
Taulukon luvut järjestetään ensin
pienimmästä suurimpaan, minkä jälkeen
taulukkoa aletaan käydä läpi kahdella osoittimella,
jotka lähtevät liikkelle taulukon molemmista päistä.
Vasen osoitin aloittaa taulukon alusta ja
liikkuu joka vaiheessa askeleen eteenpäin.
Oikea osoitin taas aloittaa taulukon lopusta
ja peruuttaa vuorollaan taaksepäin, kunnes osoitinten
määrittämän välin lukujen summa on enintään $x$.
Jos summa on tarkalleen $x$, ratkaisu on löytynyt.
Tarkastellaan algoritmin toimintaa
seuraavassa taulukossa, kun tavoitteena on muodostaa
summa $x=12$:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$4$};
\node at (2.5,0.5) {$5$};
\node at (3.5,0.5) {$6$};
\node at (4.5,0.5) {$7$};
\node at (5.5,0.5) {$9$};
\node at (6.5,0.5) {$9$};
\node at (7.5,0.5) {$10$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Seuraavassa on algoritmin aloitustilanne.
Lukujen summa on $1+10=11$, joka on pienempi
kuin $x$:n arvo.
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (0,0) rectangle (1,1);
\fill[color=lightgray] (7,0) rectangle (8,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$4$};
\node at (2.5,0.5) {$5$};
\node at (3.5,0.5) {$6$};
\node at (4.5,0.5) {$7$};
\node at (5.5,0.5) {$9$};
\node at (6.5,0.5) {$9$};
\node at (7.5,0.5) {$10$};
\draw[thick,->] (0.5,-0.7) -- (0.5,-0.1);
\draw[thick,->] (7.5,-0.7) -- (7.5,-0.1);
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Seuraavaksi vasen osoitin liikkuu askeleen eteenpäin.
Oikea osoitin peruuttaa kolme askelta, minkä jälkeen
summana on $4+7=11$.
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (1,0) rectangle (2,1);
\fill[color=lightgray] (4,0) rectangle (5,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$4$};
\node at (2.5,0.5) {$5$};
\node at (3.5,0.5) {$6$};
\node at (4.5,0.5) {$7$};
\node at (5.5,0.5) {$9$};
\node at (6.5,0.5) {$9$};
\node at (7.5,0.5) {$10$};
\draw[thick,->] (1.5,-0.7) -- (1.5,-0.1);
\draw[thick,->] (4.5,-0.7) -- (4.5,-0.1);
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Sitten vasen osoitin siirtyy jälleen askeleen eteenpäin.
Oikea osoitin pysyy paikallaan ja ratkaisu $5+7=12$ on löytynyt.
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (2,0) rectangle (3,1);
\fill[color=lightgray] (4,0) rectangle (5,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$4$};
\node at (2.5,0.5) {$5$};
\node at (3.5,0.5) {$6$};
\node at (4.5,0.5) {$7$};
\node at (5.5,0.5) {$9$};
\node at (6.5,0.5) {$9$};
\node at (7.5,0.5) {$10$};
\draw[thick,->] (2.5,-0.7) -- (2.5,-0.1);
\draw[thick,->] (4.5,-0.7) -- (4.5,-0.1);
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Algoritmin alussa taulukon järjestäminen vie
aikaa $O(n \log n)$.
Tämän jälkeen vasen osoitin liikkuu $O(n)$ askelta
eteenpäin ja oikea osoitin liikkuu $O(n)$ askelta
taaksepäin, mihin kuluu aikaa $O(n)$.
Algoritmin kokonaisaikavaativuus on siis $O(n \log n)$.
Huomaa, että tehtävän voi ratkaista myös
toisella tavalla ajassa
$O(n \log n)$ binäärihaun avulla.
Tässä ratkaisussa jokaiselle taulukon luvulle
etsitään binäärihaulla toista lukua niin,
että lukujen summa olisi yhteensä $x$.
Binäärihaku suoritetaan $n$ kertaa ja
jokainen binäärihaku vie aikaa $O(\log n)$.
\index{3SUM-ongelma}
Hieman vaikeampi ongelma on \key{3SUM},
jossa taulukosta tuleekin etsiä kolme lukua,
joiden summa on $x$.
Tämä ongelma on mahdollista ratkaista ajassa $O(n^2)$.
Keksitkö, miten se tapahtuu?
\section{Lähin pienempi edeltäjä}
\index{lzhin pienempi edeltxjx@lähin pienempi edeltäjä}
Tasoitetun analyysin avulla arvioidaan usein
tietorakenteeseen kohdistuvien operaatioiden määrää.
Algoritmin operaatiot voivat jakautua epätasaisesti
niin, että useimmat operaatiot tehdään tietyssä
algoritmin vaiheessa, mutta operaatioiden
yhteismäärä on kuitenkin rajoitettu.
Tarkastellaan esimerkkinä ongelmaa,
jossa tehtävänä on etsiä kullekin taulukon
alkiolle
\key{lähin pienempi edeltäjä} eli
lähinnä oleva pienempi alkio taulukon alkuosassa.
On mahdollista, ettei tällaista alkiota ole olemassa,
jolloin algoritmin tulee huomata asia.
Osoittautuu, että tehtävä on mahdollista ratkaista
tehokkaasti ajassa $O(n)$ sopivan tietorakenteen avulla.
Tehokas ratkaisu tehtävään on käydä
taulukko läpi alusta loppuun ja pitää samalla yllä ketjua,
jonka ensimmäinen luku on käsiteltävä taulukon luku
ja jokainen seuraava luku on luvun lähin
pienempi edeltäjä.
Jos ketjussa on vain yksi luku,
käsiteltävällä luvulla ei ole pienempää edeltäjää.
Joka askeleella ketjun alusta poistetaan lukuja
niin kauan, kunnes ketjun ensimmäinen luku on
pienempi kuin käsiteltävä taulukon luku tai ketju on tyhjä.
Tämän jälkeen käsiteltävä luku lisätään ketjun alkuun.
Tarkastellaan esimerkkinä algoritmin toimintaa
seuraavassa taulukossa:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$3$};
\node at (2.5,0.5) {$4$};
\node at (3.5,0.5) {$2$};
\node at (4.5,0.5) {$5$};
\node at (5.5,0.5) {$3$};
\node at (6.5,0.5) {$4$};
\node at (7.5,0.5) {$2$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Aluksi luvut 1, 3 ja 4 liittyvät ketjuun, koska jokainen luku on
edellistä suurempi. Siis luvun 4 lähin pienempi edeltäjä on luku 3,
jonka lähin pienempi edeltäjä on puolestaan luku 1. Tilanne näyttää tältä:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (2,0) rectangle (3,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$3$};
\node at (2.5,0.5) {$4$};
\node at (3.5,0.5) {$2$};
\node at (4.5,0.5) {$5$};
\node at (5.5,0.5) {$3$};
\node at (6.5,0.5) {$4$};
\node at (7.5,0.5) {$2$};
\draw[thick,->] (2.5,-0.25) .. controls (2.25,-1.00) and (1.75,-1.00) .. (1.6,-0.25);
\draw[thick,->] (1.4,-0.25) .. controls (1.25,-1.00) and (0.75,-1.00) .. (0.5,-0.25);
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Taulukon seuraava luku 2 on pienempi kuin ketjun kaksi ensimmäistä lukua 4 ja 3.
Niinpä luvut 4 ja 3 poistetaan ketjusta, minkä jälkeen luku 2
lisätään ketjun alkuun. Sen lähin pienempi edeltäjä on luku 1:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (3,0) rectangle (4,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$3$};
\node at (2.5,0.5) {$4$};
\node at (3.5,0.5) {$2$};
\node at (4.5,0.5) {$5$};
\node at (5.5,0.5) {$3$};
\node at (6.5,0.5) {$4$};
\node at (7.5,0.5) {$2$};
\draw[thick,->] (3.5,-0.25) .. controls (3.00,-1.00) and (1.00,-1.00) .. (0.5,-0.25);
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Seuraava luku 5 on suurempi kuin luku 2,
joten se lisätään suoraan ketjun alkuun ja
sen lähin pienempi edeltäjä on luku 2:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (4,0) rectangle (5,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$1$};
\node at (1.5,0.5) {$3$};
\node at (2.5,0.5) {$4$};
\node at (3.5,0.5) {$2$};
\node at (4.5,0.5) {$5$};
\node at (5.5,0.5) {$3$};
\node at (6.5,0.5) {$4$};
\node at (7.5,0.5) {$2$};
\draw[thick,->] (3.4,-0.25) .. controls (3.00,-1.00) and (1.00,-1.00) .. (0.5,-0.25);
\draw[thick,->] (4.5,-0.25) .. controls (4.25,-1.00) and (3.75,-1.00) .. (3.6,-0.25);
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Algoritmi jatkaa samalla tavalla taulukon loppuun
ja selvittää jokaisen luvun lähimmän
pienemmän edeltäjän.
Mutta kuinka tehokas algoritmi on?
Algoritmin tehokkuus riippuu siitä,
kauanko ketjun käsittelyyn kuluu aikaa yhteensä.
Jos uusi luku on suurempi kuin ketjun ensimmäinen
luku, se vain lisätään ketjun alkuun,
mikä on tehokasta.
Joskus taas ketjussa voi olla useita
suurempia lukuja, joiden poistaminen vie aikaa.
Oleellista on kuitenkin, että jokainen
taulukossa oleva luku liittyy
tarkalleen kerran ketjuun ja poistuu
korkeintaan kerran ketjusta.
Niinpä jokainen luku aiheuttaa $O(1)$
ketjuun liittyvää operaatiota
ja algoritmin kokonaisaikavaativuus on $O(n)$.
\section{Liukuvan ikkunan minimi}
\index{liukuva ikkuna}
\index{liukuvan ikkunan minimi@liukuvan ikkunan minimi}
\key{Liukuva ikkuna} on taulukon halki kulkeva
aktiivinen alitaulukko, jonka pituus on vakio.
Jokaisessa liukuvan ikkunan sijainnissa
halutaan tyypillisesti laskea jotain tietoa
ikkunan alueelle osuvista alkioista.
Kiinnostava tehtävä on pitää yllä
\key{liukuvan ikkunan minimiä}.
Tämä tarkoittaa, että jokaisessa liukuvan ikkunan
sijainnissa tulee ilmoittaa pienin alkio
ikkunan alueella.
Liukuvan ikkunan minimit voi laskea
lähes samalla tavalla kuin lähimmät
pienimmät edeltäjät.
Ideana on pitää yllä ketjua, jonka alussa
on ikkunan viimeinen luku ja jossa jokainen
luku on edellistä pienempi. Joka vaiheessa
ketjun viimeinen luku on ikkunan pienin luku.
Kun liukuva ikkuna liikkuu eteenpäin ja välille
tulee uusi luku, ketjusta poistetaan kaikki luvut,
jotka ovat uutta lukua suurempia.
Tämän jälkeen uusi luku lisätään ketjun alkuun.
Lisäksi jos ketjun viimeinen luku ei enää kuulu
välille, se poistetaan ketjusta.
Tarkastellaan esimerkkinä, kuinka algoritmi selvittää
minimit seuraavassa taulukossa,
kun ikkunan koko $k=4$.
\begin{center}
\begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$2$};
\node at (1.5,0.5) {$1$};
\node at (2.5,0.5) {$4$};
\node at (3.5,0.5) {$5$};
\node at (4.5,0.5) {$3$};
\node at (5.5,0.5) {$4$};
\node at (6.5,0.5) {$1$};
\node at (7.5,0.5) {$2$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\end{tikzpicture}
\end{center}
Liukuva ikkuna aloittaa matkansa taulukon vasemmasta reunasta.
Ensimmäisessä ikkunan sijainnissa pienin luku on 1:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (0,0) rectangle (4,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$2$};
\node at (1.5,0.5) {$1$};
\node at (2.5,0.5) {$4$};
\node at (3.5,0.5) {$5$};
\node at (4.5,0.5) {$3$};
\node at (5.5,0.5) {$4$};
\node at (6.5,0.5) {$1$};
\node at (7.5,0.5) {$2$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\draw[thick,->] (3.5,-0.25) .. controls (3.25,-1.00) and (2.75,-1.00) .. (2.6,-0.25);
\draw[thick,->] (2.4,-0.25) .. controls (2.25,-1.00) and (1.75,-1.00) .. (1.5,-0.25);
\end{tikzpicture}
\end{center}
Kun ikkuna siirtyy eteenpäin, mukaan tulee luku 3,
joka on pienempi kuin luvut 5 ja 4 ketjun alussa.
Niinpä luvut 5 ja 4 poistuvat ketjusta ja luku 3
siirtyy sen alkuun. Pienin luku on edelleen 1.
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (1,0) rectangle (5,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$2$};
\node at (1.5,0.5) {$1$};
\node at (2.5,0.5) {$4$};
\node at (3.5,0.5) {$5$};
\node at (4.5,0.5) {$3$};
\node at (5.5,0.5) {$4$};
\node at (6.5,0.5) {$1$};
\node at (7.5,0.5) {$2$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\draw[thick,->] (4.5,-0.25) .. controls (4.25,-1.00) and (1.75,-1.00) .. (1.5,-0.25);
\end{tikzpicture}
\end{center}
Ikkuna siirtyy taas eteenpäin, minkä seurauksena pienin luku 1
putoaa pois ikkunasta. Niinpä se poistetaan ketjun lopusta
ja uusi pienin luku on 3. Lisäksi uusi ikkunaan tuleva luku 4
lisätään ketjun alkuun.
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (2,0) rectangle (6,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$2$};
\node at (1.5,0.5) {$1$};
\node at (2.5,0.5) {$4$};
\node at (3.5,0.5) {$5$};
\node at (4.5,0.5) {$3$};
\node at (5.5,0.5) {$4$};
\node at (6.5,0.5) {$1$};
\node at (7.5,0.5) {$2$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\draw[thick,->] (5.5,-0.25) .. controls (5.25,-1.00) and (4.75,-1.00) .. (4.5,-0.25);
\end{tikzpicture}
\end{center}
Seuraavaksi ikkunaan tuleva luku 1 on pienempi
kuin kaikki ketjussa olevat luvut.
Tämän seurauksena koko ketju tyhjentyy ja
siihen jää vain luku 1:
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (3,0) rectangle (7,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$2$};
\node at (1.5,0.5) {$1$};
\node at (2.5,0.5) {$4$};
\node at (3.5,0.5) {$5$};
\node at (4.5,0.5) {$3$};
\node at (5.5,0.5) {$4$};
\node at (6.5,0.5) {$1$};
\node at (7.5,0.5) {$2$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\fill[color=black] (6.5,-0.25) circle (0.1);
%\draw[thick,->] (5.5,-0.25) .. controls (5.25,-1.00) and (4.75,-1.00) .. (4.5,-0.25);
\end{tikzpicture}
\end{center}
Lopuksi ikkuna saapuu viimeiseen sijaintiinsa.
Luku 2 lisätään ketjun alkuun,
mutta ikkunan pienin luku on edelleen 1.
\begin{center}
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (4,0) rectangle (8,1);
\draw (0,0) grid (8,1);
\node at (0.5,0.5) {$2$};
\node at (1.5,0.5) {$1$};
\node at (2.5,0.5) {$4$};
\node at (3.5,0.5) {$5$};
\node at (4.5,0.5) {$3$};
\node at (5.5,0.5) {$4$};
\node at (6.5,0.5) {$1$};
\node at (7.5,0.5) {$2$};
\footnotesize
\node at (0.5,1.4) {$1$};
\node at (1.5,1.4) {$2$};
\node at (2.5,1.4) {$3$};
\node at (3.5,1.4) {$4$};
\node at (4.5,1.4) {$5$};
\node at (5.5,1.4) {$6$};
\node at (6.5,1.4) {$7$};
\node at (7.5,1.4) {$8$};
\draw[thick,->] (7.5,-0.25) .. controls (7.25,-1.00) and (6.75,-1.00) .. (6.5,-0.25);
\end{tikzpicture}
\end{center}
Tässäkin algoritmissa jokainen taulukon luku lisätään
ketjuun tarkalleen kerran ja poistetaan ketjusta korkeintaan kerran,
joko ketjun alusta tai ketjun lopusta.
Niinpä algoritmin kokonaisaikavaativuus on $O(n)$.