\chapter{Directed graphs} Tässä luvussa tutustumme kahteen suunnattujen verkkojen alaluokkaan: \begin{itemize} \item \key{Syklitön verkko}: Tällaisessa verkossa ei ole yhtään sykliä, eli mistään solmusta ei ole polkua takaisin solmuun itseensä. \item \key{Seuraajaverkko}: Tällaisessa verkossa jokaisen solmun lähtöaste on 1 eli kullakin solmulla on yksikäsitteinen seuraaja. \end{itemize} Osoittautuu, että kummassakin tapauksessa verkon käsittely on yleistä verkkoa helpompaa ja voimme käyttää tehokkaita algoritmeja, jotka hyödyntävät oletuksia verkon rakenteesta. \section{Topologinen järjestys} \index{topologinen jxrjestys@topologinen järjestys} \index{sykli@sykli} \key{Topologinen järjestys} on tapa järjestää suunnatun verkon solmut niin, että jos solmusta $a$ pääsee solmuun $b$, niin $a$ on ennen $b$:tä järjestyksessä. Esimerkiksi verkon \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle] (1) at (1,5) {$1$}; \node[draw, circle] (2) at (3,5) {$2$}; \node[draw, circle] (3) at (5,5) {$3$}; \node[draw, circle] (4) at (1,3) {$4$}; \node[draw, circle] (5) at (3,3) {$5$}; \node[draw, circle] (6) at (5,3) {$6$}; \path[draw,thick,->,>=latex] (1) -- (2); \path[draw,thick,->,>=latex] (2) -- (3); \path[draw,thick,->,>=latex] (4) -- (1); \path[draw,thick,->,>=latex] (4) -- (5); \path[draw,thick,->,>=latex] (5) -- (2); \path[draw,thick,->,>=latex] (5) -- (3); \path[draw,thick,->,>=latex] (3) -- (6); \end{tikzpicture} \end{center} yksi topologinen järjestys on $[4,1,5,2,3,6]$: \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle] (1) at (-6,0) {$1$}; \node[draw, circle] (2) at (-3,0) {$2$}; \node[draw, circle] (3) at (-1.5,0) {$3$}; \node[draw, circle] (4) at (-7.5,0) {$4$}; \node[draw, circle] (5) at (-4.5,0) {$5$}; \node[draw, circle] (6) at (-0,0) {$6$}; \path[draw,thick,->,>=latex] (1) edge [bend right=30] (2); \path[draw,thick,->,>=latex] (2) -- (3); \path[draw,thick,->,>=latex] (4) -- (1); \path[draw,thick,->,>=latex] (4) edge [bend left=30] (5); \path[draw,thick,->,>=latex] (5) -- (2); \path[draw,thick,->,>=latex] (5) edge [bend left=30] (3); \path[draw,thick,->,>=latex] (3) -- (6); \end{tikzpicture} \end{center} Topologinen järjestys on olemassa aina silloin, kun verkko on syklitön. Jos taas verkossa on sykli, topologista järjestystä ei voi muodostaa, koska mitään syklissä olevaa solmua ei voi valita ensimmäisenä topologiseen järjestykseen. Seuraavaksi näemme, miten syvyyshaun avulla voi muodostaa topologisen järjestyksen tai todeta, että tämä ei ole mahdollista syklin takia. \subsubsection{Algoritmi} Ideana on käydä läpi verkon solmut ja aloittaa solmusta uusi syvyyshaku aina silloin, kun solmua ei ole vielä käsitelty. Kunkin syvyyshaun aikana solmuilla on kolme mahdollista tilaa: \begin{itemize} \item tila 0: solmua ei ole käsitelty (valkoinen) \item tila 1: solmun käsittely on alkanut (vaaleanharmaa) \item tila 2: solmu on käsitelty (tummanharmaa) \end{itemize} Aluksi jokaisen verkon solmun tila on 0. Kun syvyyshaku saapuu solmuun, sen tilaksi tulee 1. Lopuksi kun syvyyshaku on käsitellyt kaikki solmun naapurit, solmun tilaksi tulee 2. Jos verkossa on sykli, tämä selviää syvyyshaun aikana siitä, että jossain vaiheessa haku saapuu solmuun, jonka tila on 1. Tässä tapauksessa topologista järjestystä ei voi muodostaa. Jos verkossa ei ole sykliä, topologinen järjestys saadaan muodostamalla lista, johon kukin solmu lisätään silloin, kun sen tilaksi tulee 2. Tämä lista käänteisenä on yksi verkon topologinen järjestys. \subsubsection{Esimerkki 1} Esimerkkiverkossa syvyyshaku etenee ensin solmusta 1 solmuun 6 asti: \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle,fill=gray!20] (1) at (1,5) {$1$}; \node[draw, circle,fill=gray!20] (2) at (3,5) {$2$}; \node[draw, circle,fill=gray!20] (3) at (5,5) {$3$}; \node[draw, circle] (4) at (1,3) {$4$}; \node[draw, circle] (5) at (3,3) {$5$}; \node[draw, circle,fill=gray!80] (6) at (5,3) {$6$}; \path[draw,thick,->,>=latex] (4) -- (1); \path[draw,thick,->,>=latex] (4) -- (5); \path[draw,thick,->,>=latex] (5) -- (2); \path[draw,thick,->,>=latex] (5) -- (3); %\path[draw,thick,->,>=latex] (3) -- (6); \path[draw=red,thick,->,line width=2pt] (1) -- (2); \path[draw=red,thick,->,line width=2pt] (2) -- (3); \path[draw=red,thick,->,line width=2pt] (3) -- (6); \end{tikzpicture} \end{center} Tässä vaiheessa solmu 6 on käsitelty, joten se lisätään listalle. Sen jälkeen haku palaa takaisinpäin: \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle,fill=gray!80] (1) at (1,5) {$1$}; \node[draw, circle,fill=gray!80] (2) at (3,5) {$2$}; \node[draw, circle,fill=gray!80] (3) at (5,5) {$3$}; \node[draw, circle] (4) at (1,3) {$4$}; \node[draw, circle] (5) at (3,3) {$5$}; \node[draw, circle,fill=gray!80] (6) at (5,3) {$6$}; \path[draw,thick,->,>=latex] (1) -- (2); \path[draw,thick,->,>=latex] (2) -- (3); \path[draw,thick,->,>=latex] (4) -- (1); \path[draw,thick,->,>=latex] (4) -- (5); \path[draw,thick,->,>=latex] (5) -- (2); \path[draw,thick,->,>=latex] (5) -- (3); \path[draw,thick,->,>=latex] (3) -- (6); \end{tikzpicture} \end{center} Tämän jälkeen listan sisältönä on $[6,3,2,1]$. Sitten seuraava syvyyshaku alkaa solmusta 4: \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle,fill=gray!80] (1) at (1,5) {$1$}; \node[draw, circle,fill=gray!80] (2) at (3,5) {$2$}; \node[draw, circle,fill=gray!80] (3) at (5,5) {$3$}; \node[draw, circle,fill=gray!20] (4) at (1,3) {$4$}; \node[draw, circle,fill=gray!80] (5) at (3,3) {$5$}; \node[draw, circle,fill=gray!80] (6) at (5,3) {$6$}; \path[draw,thick,->,>=latex] (1) -- (2); \path[draw,thick,->,>=latex] (2) -- (3); \path[draw,thick,->,>=latex] (4) -- (1); %\path[draw,thick,->,>=latex] (4) -- (5); \path[draw,thick,->,>=latex] (5) -- (2); \path[draw,thick,->,>=latex] (5) -- (3); \path[draw,thick,->,>=latex] (3) -- (6); \path[draw=red,thick,->,line width=2pt] (4) -- (5); \end{tikzpicture} \end{center} Tämän seurauksena listaksi tulee $[6,3,2,1,5,4]$. Kaikki solmut on käyty läpi, joten topologinen järjestys on valmis. Topologinen järjestys on lista käänteisenä eli $[4,5,1,2,3,6]$: \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle] (1) at (3,0) {$1$}; \node[draw, circle] (2) at (4.5,0) {$2$}; \node[draw, circle] (3) at (6,0) {$3$}; \node[draw, circle] (4) at (0,0) {$4$}; \node[draw, circle] (5) at (1.5,0) {$5$}; \node[draw, circle] (6) at (7.5,0) {$6$}; \path[draw,thick,->,>=latex] (1) -- (2); \path[draw,thick,->,>=latex] (2) -- (3); \path[draw,thick,->,>=latex] (4) edge [bend left=30] (1); \path[draw,thick,->,>=latex] (4) -- (5); \path[draw,thick,->,>=latex] (5) edge [bend right=30] (2); \path[draw,thick,->,>=latex] (5) edge [bend right=40] (3); \path[draw,thick,->,>=latex] (3) -- (6); \end{tikzpicture} \end{center} Huomaa, että topologinen järjestys ei ole yksikäsitteinen, vaan verkolla voi olla useita topologisia järjestyksiä. \subsubsection{Esimerkki 2} Tarkastellaan sitten tilannetta, jossa topologista järjestystä ei voi muodostaa syklin takia. Näin on seuraavassa verkossa: \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle] (1) at (1,5) {$1$}; \node[draw, circle] (2) at (3,5) {$2$}; \node[draw, circle] (3) at (5,5) {$3$}; \node[draw, circle] (4) at (1,3) {$4$}; \node[draw, circle] (5) at (3,3) {$5$}; \node[draw, circle] (6) at (5,3) {$6$}; \path[draw,thick,->,>=latex] (1) -- (2); \path[draw,thick,->,>=latex] (2) -- (3); \path[draw,thick,->,>=latex] (4) -- (1); \path[draw,thick,->,>=latex] (4) -- (5); \path[draw,thick,->,>=latex] (5) -- (2); \path[draw,thick,->,>=latex] (3) -- (5); \path[draw,thick,->,>=latex] (3) -- (6); \end{tikzpicture} \end{center} Nyt syvyyshaun aikana tapahtuu näin: \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle,fill=gray!20] (1) at (1,5) {$1$}; \node[draw, circle,fill=gray!20] (2) at (3,5) {$2$}; \node[draw, circle,fill=gray!20] (3) at (5,5) {$3$}; \node[draw, circle] (4) at (1,3) {$4$}; \node[draw, circle,fill=gray!20] (5) at (3,3) {$5$}; \node[draw, circle] (6) at (5,3) {$6$}; \path[draw,thick,->,>=latex] (4) -- (1); \path[draw,thick,->,>=latex] (4) -- (5); \path[draw,thick,->,>=latex] (3) -- (6); \path[draw=red,thick,->,line width=2pt] (1) -- (2); \path[draw=red,thick,->,line width=2pt] (2) -- (3); \path[draw=red,thick,->,line width=2pt] (3) -- (5); \path[draw=red,thick,->,line width=2pt] (5) -- (2); \end{tikzpicture} \end{center} Syvyyshaku saapuu tilassa 1 olevaan solmuun 2, mikä tarkoittaa, että verkossa on sykli. Tässä tapauksessa sykli on $2 \rightarrow 3 \rightarrow 5 \rightarrow 2$. \section{Dynaaminen ohjelmointi} Jos suunnattu verkko on syklitön, siihen voi soveltaa dynaamista ohjelmointia. Tämän avulla voi ratkaista tehokkaasti ajassa $O(n+m)$ esimerkiksi seuraavat ongelmat koskien verkossa olevia polkuja alkusolmusta loppusolmuun: \begin{itemize} \item montako erilaista polkua on olemassa? \item mikä on lyhin/pisin polku? \item mikä on pienin/suurin määrä kaaria polulla? \item mitkä solmut esiintyvät varmasti polulla? \end{itemize} \subsubsection{Polkujen määrän laskeminen} Lasketaan esimerkkinä polkujen määrä alkusolmusta loppusolmuun suunnatussa, syklittömässä verkossa. Esimerkiksi verkossa \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle] (1) at (1,5) {$1$}; \node[draw, circle] (2) at (3,5) {$2$}; \node[draw, circle] (3) at (5,5) {$3$}; \node[draw, circle] (4) at (1,3) {$4$}; \node[draw, circle] (5) at (3,3) {$5$}; \node[draw, circle] (6) at (5,3) {$6$}; \path[draw,thick,->,>=latex] (1) -- (2); \path[draw,thick,->,>=latex] (2) -- (3); \path[draw,thick,->,>=latex] (4) -- (1); \path[draw,thick,->,>=latex] (4) -- (5); \path[draw,thick,->,>=latex] (5) -- (2); \path[draw,thick,->,>=latex] (5) -- (3); \path[draw,thick,->,>=latex] (3) -- (6); \end{tikzpicture} \end{center} on 3 polkua solmusta 4 solmuun 6: \begin{itemize} \item $4 \rightarrow 1 \rightarrow 2 \rightarrow 3 \rightarrow 6$ \item $4 \rightarrow 5 \rightarrow 2 \rightarrow 3 \rightarrow 6$ \item $4 \rightarrow 5 \rightarrow 3 \rightarrow 6$ \end{itemize} Ideana on käydä läpi verkon solmut topologisessa järjestyksessä ja laskea kunkin solmun kohdalla yhteen eri suunnista tulevien polkujen määrät. Verkon topologinen järjestys on seuraava: \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle] (1) at (3,0) {$1$}; \node[draw, circle] (2) at (4.5,0) {$2$}; \node[draw, circle] (3) at (6,0) {$3$}; \node[draw, circle] (4) at (0,0) {$4$}; \node[draw, circle] (5) at (1.5,0) {$5$}; \node[draw, circle] (6) at (7.5,0) {$6$}; \path[draw,thick,->,>=latex] (1) -- (2); \path[draw,thick,->,>=latex] (2) -- (3); \path[draw,thick,->,>=latex] (4) edge [bend left=30] (1); \path[draw,thick,->,>=latex] (4) -- (5); \path[draw,thick,->,>=latex] (5) edge [bend right=30] (2); \path[draw,thick,->,>=latex] (5) edge [bend right=40] (3); \path[draw,thick,->,>=latex] (3) -- (6); \end{tikzpicture} \end{center} Tuloksena ovat seuraavat lukumäärät: \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle] (1) at (1,5) {$1$}; \node[draw, circle] (2) at (3,5) {$2$}; \node[draw, circle] (3) at (5,5) {$3$}; \node[draw, circle] (4) at (1,3) {$4$}; \node[draw, circle] (5) at (3,3) {$5$}; \node[draw, circle] (6) at (5,3) {$6$}; \path[draw,thick,->,>=latex] (1) -- (2); \path[draw,thick,->,>=latex] (2) -- (3); \path[draw,thick,->,>=latex] (4) -- (1); \path[draw,thick,->,>=latex] (4) -- (5); \path[draw,thick,->,>=latex] (5) -- (2); \path[draw,thick,->,>=latex] (5) -- (3); \path[draw,thick,->,>=latex] (3) -- (6); \node[color=red] at (1,2.3) {$1$}; \node[color=red] at (3,2.3) {$1$}; \node[color=red] at (5,2.3) {$3$}; \node[color=red] at (1,5.7) {$1$}; \node[color=red] at (3,5.7) {$2$}; \node[color=red] at (5,5.7) {$3$}; \end{tikzpicture} \end{center} Esimerkiksi solmuun 2 pääsee solmuista 1 ja 5. Kumpaankin solmuun päättyy yksi polku solmusta 4 alkaen, joten solmuun 2 päättyy kaksi polkua solmusta 4 alkaen. Vastaavasti solmuun 3 pääsee solmuista 2 ja 5, joiden kautta tulee kaksi ja yksi polkua solmusta 4 alkaen. \subsubsection{Dijkstran algoritmin sovellukset} \index{Dijkstran algoritmi@Dijkstran algoritmi} Dijkstran algoritmin sivutuotteena syntyy suunnattu, syklitön verkko, joka kertoo jokaiselle alkuperäisen verkon solmulle, mitä tapoja alkusolmusta on päästä kyseiseen solmuun lyhintä polkua käyttäen. Tähän verkkoon voi soveltaa edelleen dynaamista ohjelmointia. Esimerkiksi verkossa \begin{center} \begin{tikzpicture} \node[draw, circle] (1) at (0,0) {$1$}; \node[draw, circle] (2) at (2,0) {$2$}; \node[draw, circle] (3) at (0,-2) {$3$}; \node[draw, circle] (4) at (2,-2) {$4$}; \node[draw, circle] (5) at (4,-1) {$5$}; \path[draw,thick,-] (1) -- node[font=\small,label=above:3] {} (2); \path[draw,thick,-] (1) -- node[font=\small,label=left:5] {} (3); \path[draw,thick,-] (2) -- node[font=\small,label=right:4] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=above:8] {} (5); \path[draw,thick,-] (3) -- node[font=\small,label=below:2] {} (4); \path[draw,thick,-] (4) -- node[font=\small,label=below:1] {} (5); \path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (3); \end{tikzpicture} \end{center} solmusta 1 lähteviin lyhimpiin polkuihin kuuluvat seuraavat kaaret: \begin{center} \begin{tikzpicture} \node[draw, circle] (1) at (0,0) {$1$}; \node[draw, circle] (2) at (2,0) {$2$}; \node[draw, circle] (3) at (0,-2) {$3$}; \node[draw, circle] (4) at (2,-2) {$4$}; \node[draw, circle] (5) at (4,-1) {$5$}; \path[draw,thick,->] (1) -- node[font=\small,label=above:3] {} (2); \path[draw,thick,->] (1) -- node[font=\small,label=left:5] {} (3); \path[draw,thick,->] (2) -- node[font=\small,label=right:4] {} (4); \path[draw,thick,->] (3) -- node[font=\small,label=below:2] {} (4); \path[draw,thick,->] (4) -- node[font=\small,label=below:1] {} (5); \path[draw,thick,->] (2) -- node[font=\small,label=above:2] {} (3); \end{tikzpicture} \end{center} Koska kyseessä on suunnaton, syklitön verkko, siihen voi soveltaa dynaamista ohjelmointia. Niinpä voi esimerkiksi laskea, montako lyhintä polkua on olemassa solmusta 1 solmuun 5: \begin{center} \begin{tikzpicture} \node[draw, circle] (1) at (0,0) {$1$}; \node[draw, circle] (2) at (2,0) {$2$}; \node[draw, circle] (3) at (0,-2) {$3$}; \node[draw, circle] (4) at (2,-2) {$4$}; \node[draw, circle] (5) at (4,-1) {$5$}; \path[draw,thick,->] (1) -- node[font=\small,label=above:3] {} (2); \path[draw,thick,->] (1) -- node[font=\small,label=left:5] {} (3); \path[draw,thick,->] (2) -- node[font=\small,label=right:4] {} (4); \path[draw,thick,->] (3) -- node[font=\small,label=below:2] {} (4); \path[draw,thick,->] (4) -- node[font=\small,label=below:1] {} (5); \path[draw,thick,->] (2) -- node[font=\small,label=above:2] {} (3); \node[color=red] at (0,0.7) {$1$}; \node[color=red] at (2,0.7) {$1$}; \node[color=red] at (0,-2.7) {$2$}; \node[color=red] at (2,-2.7) {$3$}; \node[color=red] at (4,-1.7) {$3$}; \end{tikzpicture} \end{center} \subsubsection{Ongelman esitys verkkona} Itse asiassa mikä tahansa dynaamisen ohjelmoinnin ongelma voidaan esittää suunnattuna, syklittömänä verkkona. Tällaisessa verkossa solmuja ovat dynaamisen ohjelmoinnin tilat ja kaaret kuvaavat, miten tilat riippuvat toisistaan. Tarkastellaan esimerkkinä tuttua tehtävää, jossa annettuna on rahamäärä $x$ ja tehtävänä on muodostaa se kolikoista $\{c_1,c_2,\ldots,c_k\}$. Tässä tapauksessa voimme muodostaa verkon, jonka solmut vastaavat rahamääriä ja kaaret kuvaavat kolikkojen valintoja. Esimerkiksi jos kolikot ovat $\{1,3,4\}$ ja $x=6$, niin verkosta tulee seuraava: \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle] (0) at (0,0) {$0$}; \node[draw, circle] (1) at (2,0) {$1$}; \node[draw, circle] (2) at (4,0) {$2$}; \node[draw, circle] (3) at (6,0) {$3$}; \node[draw, circle] (4) at (8,0) {$4$}; \node[draw, circle] (5) at (10,0) {$5$}; \node[draw, circle] (6) at (12,0) {$6$}; \path[draw,thick,->] (0) -- (1); \path[draw,thick,->] (1) -- (2); \path[draw,thick,->] (2) -- (3); \path[draw,thick,->] (3) -- (4); \path[draw,thick,->] (4) -- (5); \path[draw,thick,->] (5) -- (6); \path[draw,thick,->] (0) edge [bend right=30] (3); \path[draw,thick,->] (1) edge [bend right=30] (4); \path[draw,thick,->] (2) edge [bend right=30] (5); \path[draw,thick,->] (3) edge [bend right=30] (6); \path[draw,thick,->] (0) edge [bend left=30] (4); \path[draw,thick,->] (1) edge [bend left=30] (5); \path[draw,thick,->] (2) edge [bend left=30] (6); \end{tikzpicture} \end{center} Tätä verkkoesitystä käyttäen lyhin polku solmusta 0 solmuun $x$ vastaa ratkaisua, jossa kolikoita on mahdollisimman vähän, ja polkujen yhteismäärä solmusta 0 solmuun $x$ on sama kuin ratkaisujen yhteismäärä. \section{Tehokas eteneminen} \index{seuraajaverkko@seuraajaverkko} \index{funktionaalinen verkko@funktionaalinen verkko} Seuraavaksi oletamme, että suunnaton verkko on \key{seuraajaverkko}, jolloin jokaisen solmun lähtöaste on 1 eli siitä lähtee tasan yksi kaari ulospäin. Niinpä verkko muodostuu yhdestä tai useammasta komponentista, joista jokaisessa on yksi sykli ja joukko siihen johtavia polkuja. Seuraajaverkosta käytetään joskus nimeä \key{funktionaalinen verkko}. Tämä johtuu siitä, että jokaista seuraajaverkkoa vastaa funktio $f$, joka määrittelee verkon kaaret. Funktion parametrina on verkon solmu ja se palauttaa solmusta lähtevän kaaren kohdesolmun. \begin{samepage} Esimerkiksi funktio \begin{center} \begin{tabular}{r|rrrrrrrrr} $x$ & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 \\ \hline $f(x)$ & 3 & 5 & 7 & 6 & 2 & 2 & 1 & 6 & 3 \\ \end{tabular} \end{center} \end{samepage} määrittelee seuraavan verkon: \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle] (1) at (0,0) {$1$}; \node[draw, circle] (2) at (2,0) {$2$}; \node[draw, circle] (3) at (-2,0) {$3$}; \node[draw, circle] (4) at (1,-3) {$4$}; \node[draw, circle] (5) at (4,0) {$5$}; \node[draw, circle] (6) at (2,-1.5) {$6$}; \node[draw, circle] (7) at (-2,-1.5) {$7$}; \node[draw, circle] (8) at (3,-3) {$8$}; \node[draw, circle] (9) at (-4,0) {$9$}; \path[draw,thick,->] (1) -- (3); \path[draw,thick,->] (2) edge [bend left=40] (5); \path[draw,thick,->] (3) -- (7); \path[draw,thick,->] (4) -- (6); \path[draw,thick,->] (5) edge [bend left=40] (2); \path[draw,thick,->] (6) -- (2); \path[draw,thick,->] (7) -- (1); \path[draw,thick,->] (8) -- (6); \path[draw,thick,->] (9) -- (3); \end{tikzpicture} \end{center} Koska seuraajaverkon jokaisella solmulla on yksikäsitteinen seuraaja, voimme määritellä funktion $f(x,k)$, joka kertoo solmun, johon päätyy solmusta $x$ kulkemalla $k$ askelta. Esimerkiksi yllä olevassa verkossa $f(4,6)=2$, koska solmusta 4 päätyy solmuun 2 kulkemalla 6 askelta: \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle] (1) at (0,0) {$4$}; \node[draw, circle] (2) at (1.5,0) {$6$}; \node[draw, circle] (3) at (3,0) {$2$}; \node[draw, circle] (4) at (4.5,0) {$5$}; \node[draw, circle] (5) at (6,0) {$2$}; \node[draw, circle] (6) at (7.5,0) {$5$}; \node[draw, circle] (7) at (9,0) {$2$}; \path[draw,thick,->] (1) -- (2); \path[draw,thick,->] (2) -- (3); \path[draw,thick,->] (3) -- (4); \path[draw,thick,->] (4) -- (5); \path[draw,thick,->] (5) -- (6); \path[draw,thick,->] (6) -- (7); \end{tikzpicture} \end{center} Suoraviivainen tapa laskea arvo $f(x,k)$ on käydä läpi polku askel askeleelta, mihin kuluu aikaa $O(k)$. Sopivan esikäsittelyn avulla voimme laskea kuitenkin minkä tahansa arvon $f(x,k)$ ajassa $O(\log k)$. Ideana on laskea etukäteen kaikki arvot $f(x,k)$, kun $k$ on 2:n potenssi ja enintään $u$, missä $u$ on suurin mahdollinen määrä askeleita, joista olemme kiinnostuneita. Tämä onnistuu tehokkaasti, koska voimme käyttää rekursiota \begin{equation*} f(x,k) = \begin{cases} f(x) & k = 1\\ f(f(x,k/2),k/2) & k > 1\\ \end{cases} \end{equation*} Arvojen $f(x,k)$ esilaskenta vie aikaa $O(n \log u)$, koska jokaisesta solmusta lasketaan $O(\log u)$ arvoa. Esimerkin tapauksessa taulukko alkaa muodostua seuraavasti: \begin{center} \begin{tabular}{r|rrrrrrrrr} $x$ & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 \\ \hline $f(x,1)$ & 3 & 5 & 7 & 6 & 2 & 2 & 1 & 6 & 3 \\ $f(x,2)$ & 7 & 2 & 1 & 2 & 5 & 5 & 3 & 2 & 7 \\ $f(x,4)$ & 3 & 2 & 7 & 2 & 5 & 5 & 1 & 2 & 3 \\ $f(x,8)$ & 7 & 2 & 1 & 2 & 5 & 5 & 3 & 2 & 7 \\ $\cdots$ \\ \end{tabular} \end{center} Tämän jälkeen funktion $f(x,k)$ arvon saa laskettua esittämällä luvun $k$ summana 2:n potensseja. Esimerkiksi jos haluamme laskea arvon $f(x,11)$, muodostamme ensin esityksen $11=8+2+1$. Tämän ansiosta \[f(x,11)=f(f(f(x,8),2),1).\] Esimerkiksi yllä olevassa verkossa \[f(4,11)=f(f(f(4,8),2),1)=5.\] Tällaisessa esityksessä on aina $O(\log k)$ osaa, joten arvon $f(x,k)$ laskemiseen kuluu aikaa $O(\log k)$. \section{Syklin tunnistaminen} \index{sykli@sykli} \index{syklin tunnistaminen@syklin tunnistaminen} Kiinnostavia kysymyksiä seuraajaverkossa ovat, minkä solmun kohdalla saavutaan sykliin solmusta $x$ lähdettäessä ja montako solmua kyseiseen sykliin kuuluu. Esimerkiksi verkossa \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle] (5) at (0,0) {$5$}; \node[draw, circle] (4) at (-2,0) {$4$}; \node[draw, circle] (6) at (-1,1.5) {$6$}; \node[draw, circle] (3) at (-4,0) {$3$}; \node[draw, circle] (2) at (-6,0) {$2$}; \node[draw, circle] (1) at (-8,0) {$1$}; \path[draw,thick,->] (1) -- (2); \path[draw,thick,->] (2) -- (3); \path[draw,thick,->] (3) -- (4); \path[draw,thick,->] (4) -- (5); \path[draw,thick,->] (5) -- (6); \path[draw,thick,->] (6) -- (4); \end{tikzpicture} \end{center} solmusta 1 lähdettäessä ensimmäinen sykliin kuuluva solmu on solmu 4 ja syklissä on kolme solmua (solmut 4, 5 ja 6). Helppo tapa tunnistaa sykli on alkaa kulkea verkossa solmusta $x$ alkaen ja pitää kirjaa kaikista vastaan tulevista solmuista. Kun jokin solmu tulee vastaan toista kertaa, sykli on löytynyt. Tämän menetelmän aikavaativuus on $O(n)$ ja muistia kuluu myös $O(n)$. Osoittautuu kuitenkin, että syklin tunnistamiseen on olemassa parempia algoritmeja. Niissä aikavaativuus on edelleen $O(n)$, mutta muistia kuluu vain $O(1)$. Tästä on merkittävää hyötyä, jos $n$ on suuri. Tutustumme seuraavaksi Floydin algoritmiin, joka saavuttaa nämä ominaisuudet. \subsubsection{Floydin algoritmi} \index{Floydin algoritmi@Floydin algoritmi} \key{Floydin algoritmi} kulkee verkossa eteenpäin rinnakkain kahdesta kohdasta. Algoritmissa on kaksi osoitinta $a$ ja $b$, jotka molemmat ovat ensin alkusolmussa. Osoitin $a$ liikkuu joka vuorolla askeleen eteenpäin, kun taas osoitin $b$ liikkuu kaksi askelta eteenpäin. Haku jatkuu, kunnes osoittimet kohtaavat: \begin{lstlisting} a = f(x); b = f(f(x)); while (a != b) { a = f(a); b = f(f(b)); } \end{lstlisting} Tässä vaiheessa osoitin $a$ on kulkenut $k$ askelta ja osoitin $b$ on kulkenut $2k$ askelta, missä $k$ on jaollinen syklin pituudella. Niinpä ensimmäinen sykliin kuuluva solmu löytyy siirtämällä osoitin $a$ alkuun ja liikuttamalla osoittimia rinnakkain eteenpäin, kunnes ne kohtaavat: \begin{lstlisting} a = x; while (a != b) { a = f(a); b = f(b); } \end{lstlisting} Nyt $a$ ja $b$ osoittavat ensimmäiseen syklin solmuun, joka tulee vastaan solmusta $x$ lähdettäessä. Lopuksi syklin pituus $c$ voidaan laskea näin: \begin{lstlisting} b = f(a); c = 1; while (a != b) { b = f(b); c++; } \end{lstlisting} % % \subsubsection{Algoritmi 2 (Brent)} % % \index{Brentin algoritmi@Brentin algoritmi} % % Brentin algoritmi % muodostuu peräkkäisistä vaiheista, joissa osoitin $a$ pysyy % paikallaan ja osoitin $b$ liikkuu $k$ askelta % Alussa $k=1$ ja $k$:n arvo kaksinkertaistuu % joka vaiheen alussa. % Lisäksi $a$ siirretään $b$:n kohdalle vaiheen alussa. % Näin jatketaan, kunnes osoittimet kohtaavat. % % \begin{lstlisting} % a = x; % b = f(x); % c = k = 1; % while (a != b) { % if (c == k) { % a = b; % c = 0; % k *= 2; % } % b = f(b); % c++; % } % \end{lstlisting} % % Nyt tiedossa on, että syklin pituus on $c$. % Tämän jälkeen ensimmäinen sykliin kuuluva solmu löytyy % palauttamalla ensin osoittimet alkuun, % siirtämällä sitten osoitinta $b$ eteenpäin $c$ askelta % ja liikuttamalla lopuksi osoittimia rinnakkain, % kunnes ne osoittavat samaan solmuun. % % \begin{lstlisting} % a = b = x; % for (int i = 0; i < c; i++) b = f(b); % while (a != b) { % a = f(a); % b = f(b); % } % \end{lstlisting} % % Nyt $a$ ja $b$ osoittavat ensimmäiseen sykliin kuuluvaan solmuun. % % Brentin algoritmin etuna Floydin algoritmiin verrattuna on, % että se kutsuu funktiota $f$ harvemmin. % Floydin algoritmi kutsuu funktiota $f$ ensimmäisessä silmukassa % kolmesti joka kierroksella, kun taas Brentin algoritmi % kutsuu funktiota $f$ vain kerran kierrosta kohden.