cphb/luku16.tex

754 lines
24 KiB
TeX
Raw Normal View History

2016-12-28 23:54:51 +01:00
\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.