\chapter{Directed graphs} In this chapter, we focus on two classes of directed graphs: \begin{itemize} \item \key{Acyclic graph}: There are no cycles in the graph, so there is no path from any node to itself. \item \key{Successor graph}: The outdegree of each node is 1, so each node has a unique successor. \end{itemize} It turns out that in both cases, we can design efficient algorithms that are based on the special properties of the graphs. \section{Topological sorting} \index{topological sorting} \index{cycle} A \key{topological sort} is a ordering of the nodes of a directed graph where node $a$ is always before node $b$ if there is a path from node $a$ to node $b$. For example, for the graph \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} a possible topological sort is $[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} A topological sort always exists if the graph is acyclic. However, if the graph contains a cycle, it is not possible to find a topological sort because no node in the cycle can appear before other nodes in the cycle in the ordering. It turns out that we can use depth-first search to both construct a topological sort or find out that it is not possible because the graph contains a cycle. \subsubsection{Algorithm} The idea is to go through the nodes in the graph and always begin a depth-first search if the node has not been processed yet. During each search, the nodes have three possible states: \begin{itemize} \item state 0: the node has not been processed (white) \item state 1: the node is under processing (light gray) \item state 2: the node has been processed (dark gray) \end{itemize} Initially, the state of each node is 0. When a search reaches a node for the first time, the state of the node becomes 1. Finally, after all neighbors of a node have been processed, the state of the node becomes 2. If the graph contains a cycle, we will realize this during the search because sooner or later we will arrive at a node whose state is 1. In this case, it is not possible to construct a topological sort. If the graph doesn't contain a cycle, we can construct a topological sort by adding each node to the end of a list when its state becomes 2. This list in reverse order is a topological sort. \subsubsection{Example 1} In the example graph, the search first proceeds from node 1 to node 6: \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} Now node 6 has been processed, so it is added to the list. After this, the search returns back: \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} At this point, the list contains values $[6,3,2,1]$. The next search begins at node 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} Thus, the final list is $[6,3,2,1,5,4]$. We have processed all nodes, so a topological sort has been found. The topological sort is the reverse list $[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} Note that a topological sort is not unique, but there can be several topological sorts for a graph. \subsubsection{Example 2} Let's consider another example where we can't construct a topological sort because there is a cycle in the graph: \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} Now the search proceeds as follows: \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} The search reaches node 2 whose state is 1 which means the graph contains a cycle. In this case, the cycle is $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.