\chapter{Shortest paths} \index{shortest path} Finding the shortest path between two nodes is an important graph problem that has many applications in practice. For example, a natural problem in a road network is to calculate the length of the shorthest route between two cities, given the lengths of the roads. In an unweighted graph, the length of a path equals the number of edges in the path and we can simply use breadth-first search for finding the shortest path. However, in this chapter we concentrate on weighted graphs. In this case we need more sophisticated algorithms for finding shortest paths. \section{Bellman–Ford algorithm} \index{Bellman–Ford algorithm} The \key{Bellman–Fordin algoritmi} finds the shortest path from a starting node to all other nodes in the graph. The algorithm works in all kinds of graphs, provided that the graph doesn't contain a cycle with negative length. If the graph contains a negative cycle, the algorithm can detect this. The algorithm keeps track of estimated distances from the starting node to other nodes. Initially, the estimated distance is 0 to the starting node and infinite to all other nodes. The algorithm improves the estimates by finding edges that shorten the paths until it is not possible to improve any estimate. \subsubsection{Example} Let's consider how the Bellman–Ford algorithm works in the following graph: \begin{center} \begin{tikzpicture} \node[draw, circle] (1) at (1,3) {1}; \node[draw, circle] (2) at (4,3) {2}; \node[draw, circle] (3) at (1,1) {3}; \node[draw, circle] (4) at (4,1) {4}; \node[draw, circle] (5) at (6,2) {5}; \node[color=red] at (1,3+0.55) {$0$}; \node[color=red] at (4,3+0.55) {$\infty$}; \node[color=red] at (1,1-0.55) {$\infty$}; \node[color=red] at (4,1-0.55) {$\infty$}; \node[color=red] at (6,2-0.55) {$\infty$}; \path[draw,thick,-] (1) -- node[font=\small,label=above:2] {} (2); \path[draw,thick,-] (1) -- node[font=\small,label=left:3] {} (3); \path[draw,thick,-] (3) -- node[font=\small,label=below:$-2$] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=left:3] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (5); \path[draw,thick,-] (4) -- node[font=\small,label=below:2] {} (5); \path[draw,thick,-] (1) -- node[font=\small,label=above:7] {} (4); \end{tikzpicture} \end{center} Each node in the graph is assigned an estimated distance. Initially, the distance is 0 to the starting node and infinite to all other nodes. The algorithm searches for edges that improve the estimated distances. First, all edges from node 1 improve the estimates: \begin{center} \begin{tikzpicture} \node[draw, circle] (1) at (1,3) {1}; \node[draw, circle] (2) at (4,3) {2}; \node[draw, circle] (3) at (1,1) {3}; \node[draw, circle] (4) at (4,1) {4}; \node[draw, circle] (5) at (6,2) {5}; \node[color=red] at (1,3+0.55) {$0$}; \node[color=red] at (4,3+0.55) {$2$}; \node[color=red] at (1,1-0.55) {$3$}; \node[color=red] at (4,1-0.55) {$7$}; \node[color=red] at (6,2-0.55) {$\infty$}; \path[draw,thick,-] (1) -- node[font=\small,label=above:2] {} (2); \path[draw,thick,-] (1) -- node[font=\small,label=left:3] {} (3); \path[draw,thick,-] (3) -- node[font=\small,label=below:$-2$] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=left:3] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (5); \path[draw,thick,-] (4) -- node[font=\small,label=below:2] {} (5); \path[draw,thick,-] (1) -- node[font=\small,label=above:7] {} (4); \path[draw=red,thick,->,line width=2pt] (1) -- (2); \path[draw=red,thick,->,line width=2pt] (1) -- (3); \path[draw=red,thick,->,line width=2pt] (1) -- (4); \end{tikzpicture} \end{center} After this, edges $2 \rightarrow 5$ and $3 \rightarrow 4$ improve the estimates: \begin{center} \begin{tikzpicture} \node[draw, circle] (1) at (1,3) {1}; \node[draw, circle] (2) at (4,3) {2}; \node[draw, circle] (3) at (1,1) {3}; \node[draw, circle] (4) at (4,1) {4}; \node[draw, circle] (5) at (6,2) {5}; \node[color=red] at (1,3+0.55) {$0$}; \node[color=red] at (4,3+0.55) {$2$}; \node[color=red] at (1,1-0.55) {$3$}; \node[color=red] at (4,1-0.55) {$1$}; \node[color=red] at (6,2-0.55) {$7$}; \path[draw,thick,-] (1) -- node[font=\small,label=above:2] {} (2); \path[draw,thick,-] (1) -- node[font=\small,label=left:3] {} (3); \path[draw,thick,-] (3) -- node[font=\small,label=below:$-2$] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=left:3] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (5); \path[draw,thick,-] (4) -- node[font=\small,label=below:2] {} (5); \path[draw,thick,-] (1) -- node[font=\small,label=above:7] {} (4); \path[draw=red,thick,->,line width=2pt] (2) -- (5); \path[draw=red,thick,->,line width=2pt] (3) -- (4); \end{tikzpicture} \end{center} Finally, there is one more improvment: \begin{center} \begin{tikzpicture} \node[draw, circle] (1) at (1,3) {1}; \node[draw, circle] (2) at (4,3) {2}; \node[draw, circle] (3) at (1,1) {3}; \node[draw, circle] (4) at (4,1) {4}; \node[draw, circle] (5) at (6,2) {5}; \node[color=red] at (1,3+0.55) {$0$}; \node[color=red] at (4,3+0.55) {$2$}; \node[color=red] at (1,1-0.55) {$3$}; \node[color=red] at (4,1-0.55) {$1$}; \node[color=red] at (6,2-0.55) {$3$}; \path[draw,thick,-] (1) -- node[font=\small,label=above:2] {} (2); \path[draw,thick,-] (1) -- node[font=\small,label=left:3] {} (3); \path[draw,thick,-] (3) -- node[font=\small,label=below:$-2$] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=left:3] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (5); \path[draw,thick,-] (4) -- node[font=\small,label=below:2] {} (5); \path[draw,thick,-] (1) -- node[font=\small,label=above:7] {} (4); \path[draw=red,thick,->,line width=2pt] (4) -- (5); \end{tikzpicture} \end{center} After this, no edge improves the estimates. This means that the distances are final and we have successfully calculated the shortest distance from the starting node to all other nodes. For example, the smallest distance 3 from node 1 to node 5 corresponds to the following path: \begin{center} \begin{tikzpicture} \node[draw, circle] (1) at (1,3) {1}; \node[draw, circle] (2) at (4,3) {2}; \node[draw, circle] (3) at (1,1) {3}; \node[draw, circle] (4) at (4,1) {4}; \node[draw, circle] (5) at (6,2) {5}; \node[color=red] at (1,3+0.55) {$0$}; \node[color=red] at (4,3+0.55) {$2$}; \node[color=red] at (1,1-0.55) {$3$}; \node[color=red] at (4,1-0.55) {$1$}; \node[color=red] at (6,2-0.55) {$3$}; \path[draw,thick,-] (1) -- node[font=\small,label=above:2] {} (2); \path[draw,thick,-] (1) -- node[font=\small,label=left:3] {} (3); \path[draw,thick,-] (3) -- node[font=\small,label=below:$-2$] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=left:3] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (5); \path[draw,thick,-] (4) -- node[font=\small,label=below:2] {} (5); \path[draw,thick,-] (1) -- node[font=\small,label=above:7] {} (4); \path[draw=red,thick,->,line width=2pt] (1) -- (3); \path[draw=red,thick,->,line width=2pt] (3) -- (4); \path[draw=red,thick,->,line width=2pt] (4) -- (5); \end{tikzpicture} \end{center} \subsubsection{Implementation} The following implementation of the Bellman–Ford algorithm finds the shortest paths from a node $x$ to all other nodes in the graph. The code assumes that the graph is stored as adjacency lists in array \begin{lstlisting} vector> v[N]; \end{lstlisting} so that each pair contains the target node and the edge weight. The algorithm consists of $n-1$ rounds, and on each round the algorithm goes through all nodes in the graph and tries to improve the estimated distances. The algorithm builds an array \texttt{e} that will contain the distance from $x$ to all nodes in the graph. The initial value $10^9$ means infinity. \begin{lstlisting} for (int i = 1; i <= n; i++) e[i] = 1e9; e[x] = 0; for (int i = 1; i <= n-1; i++) { for (int a = 1; a <= n; a++) { for (auto b : v[a]) { e[b.first] = min(e[b.first],e[a]+b.second); } } } \end{lstlisting} The time complexity of the algorithm is $O(nm)$ because it consists of $n-1$ rounds and iterates through all $m$ nodes during a round. If there are no negative cycles in the graph, all distances are final after $n-1$ rounds because each shortest path can contain at most $n-1$ edges. In practice, the final distances can usually be found much faster than in $n-1$ rounds. Thus, a possible way to make the algorithm more efficient is to stop the algorithm if we can't improve any distance during a round. \subsubsection{Negative cycle} \index{negative cycle} Using the Bellman–Ford algorithm we can also check if the graph contains a cycle with negative length. For example, the graph \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle] (1) at (0,0) {$1$}; \node[draw, circle] (2) at (2,1) {$2$}; \node[draw, circle] (3) at (2,-1) {$3$}; \node[draw, circle] (4) at (4,0) {$4$}; \path[draw,thick,-] (1) -- node[font=\small,label=above:$3$] {} (2); \path[draw,thick,-] (2) -- node[font=\small,label=above:$1$] {} (4); \path[draw,thick,-] (1) -- node[font=\small,label=below:$5$] {} (3); \path[draw,thick,-] (3) -- node[font=\small,label=below:$-7$] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=right:$2$] {} (3); \end{tikzpicture} \end{center} \noindent contains a negative cycle $2 \rightarrow 3 \rightarrow 4 \rightarrow 2$ with length $-4$. If the graph contains a negative cycle, we can shorten a path that contains the cycle infinitely many times by repeating the cycle again and again. Thus, the concept of a shortest path is not meaningful here. A negative cycle can be detected using the Bellman–Ford algorithm by running the algorithm for $n$ rounds. If the last round improves any distance, the graph contains a negative cycle. Note that this algorithm searches for a negative cycle in the whole graph regardless of the starting node. \subsubsection{SPFA algorithm} \index{SPFA algorithm} The \key{SPFA algoritmi} (''Shortest Path Faster Algorithm'') is a variation for the Bellman–Ford algorithm, that is often more efficient than the original algorithm. It doesn't go through all the edges on each round, but instead, it chooses the edges to be examined in a more intelligent way. The algorithm maintains a queue of nodes that might be used for improving the distances. First, the algorithm adds the starting node $x$ to the queue. Then, the algorithm always processes the first node in the queue, and when an edge $a \rightarrow b$ improves a distance, node $b$ is added to the end of the queue. The following implementation uses a \texttt{queue} structure \texttt{q}. In addition, array \texttt{z} indicates if a node is already in the queue, in which case the algorithm doesn't add the node to the queue again. \begin{lstlisting} for (int i = 1; i <= n; i++) e[i] = 1e9; e[x] = 0; q.push(x); while (!q.empty()) { int a = q.front(); q.pop(); z[a] = 0; for (auto b : v[a]) { if (e[a]+b.second < e[b.first]) { e[b.first] = e[a]+b.second; if (!z[b]) {q.push(b); z[b] = 1;} } } } \end{lstlisting} The efficiency of the SPFA algorithm depends on the structure of the graph: the algorithm is usually very efficient, but its worst case time complexity is still $O(nm)$ and it is possible to create inputs that make the algorithm as slow as the standard Bellman–Ford algorithm. \section{Dijkstran algoritmi} \index{Dijkstran algoritmi@Dijkstran algoritmi} \key{Dijkstran algoritmi} etsii Bellman–Fordin algoritmin tavoin lyhimmät polut alkusolmusta kaikkiin muihin solmuihin. Dijkstran algoritmi on tehokkaampi kuin Bellman–Fordin algoritmi, minkä ansiosta se soveltuu suurten verkkojen käsittelyyn. Algoritmi vaatii kuitenkin, ettei verkossa ole negatiivisia kaaria. Dijkstran algoritmi vastaa Bellman–Fordin algoritmia siinä, että se pitää yllä etäisyysarvioita solmuihin ja parantaa niitä algoritmin aikana. Algoritmin tehokkuus perustuu siihen, että sen riittää käydä läpi verkon kaaret vain kerran hyödyntäen tietoa, ettei verkossa ole negatiivisia kaaria. \subsubsection{Esimerkki} Tarkastellaan Dijkstran algoritmin toimintaa seuraavassa verkossa, kun alkusolmuna on solmu 1: \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle] (1) at (1,3) {3}; \node[draw, circle] (2) at (4,3) {4}; \node[draw, circle] (3) at (1,1) {2}; \node[draw, circle] (4) at (4,1) {1}; \node[draw, circle] (5) at (6,2) {5}; \node[color=red] at (1,3+0.6) {$\infty$}; \node[color=red] at (4,3+0.6) {$\infty$}; \node[color=red] at (1,1-0.6) {$\infty$}; \node[color=red] at (4,1-0.6) {$0$}; \node[color=red] at (6,2-0.6) {$\infty$}; \path[draw,thick,-] (1) -- node[font=\small,label=above:6] {} (2); \path[draw,thick,-] (1) -- node[font=\small,label=left:2] {} (3); \path[draw,thick,-] (3) -- node[font=\small,label=below:5] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=left:9] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (5); \path[draw,thick,-] (4) -- node[font=\small,label=below:1] {} (5); \end{tikzpicture} \end{center} Bellman–Fordin algoritmin tavoin alkusolmun etäisyysarvio on 0 ja kaikissa muissa solmuissa etäisyysarvio on aluksi ääretön. Dijkstran algoritmi ottaa joka askeleella käsittelyyn sellaisen solmun, jota ei ole vielä käsitelty ja jonka etäisyysarvio on mahdollisimman pieni. Alussa tällainen solmu on solmu 1, jonka etäisyysarvio on 0. Kun solmu tulee käsittelyyn, algoritmi käy läpi kaikki siitä lähtevät kaaret ja parantaa etäisyysarvioita niiden avulla: \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle] (1) at (1,3) {3}; \node[draw, circle] (2) at (4,3) {4}; \node[draw, circle] (3) at (1,1) {2}; \node[draw, circle, fill=lightgray] (4) at (4,1) {1}; \node[draw, circle] (5) at (6,2) {5}; \node[color=red] at (1,3+0.6) {$\infty$}; \node[color=red] at (4,3+0.6) {$9$}; \node[color=red] at (1,1-0.6) {$5$}; \node[color=red] at (4,1-0.6) {$0$}; \node[color=red] at (6,2-0.6) {$1$}; \path[draw,thick,-] (1) -- node[font=\small,label=above:6] {} (2); \path[draw,thick,-] (1) -- node[font=\small,label=left:2] {} (3); \path[draw,thick,-] (3) -- node[font=\small,label=below:5] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=left:9] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (5); \path[draw,thick,-] (4) -- node[font=\small,label=below:1] {} (5); \path[draw=red,thick,->,line width=2pt] (4) -- (2); \path[draw=red,thick,->,line width=2pt] (4) -- (3); \path[draw=red,thick,->,line width=2pt] (4) -- (5); \end{tikzpicture} \end{center} Solmun 1 käsittely paransi etäisyysarvioita solmuihin 2, 4 ja 5, joiden uudet etäisyydet ovat nyt 5, 9 ja 1. Seuraavaksi käsittelyyn tulee solmu 5, jonka etäisyys on 1: \begin{center} \begin{tikzpicture} \node[draw, circle] (1) at (1,3) {3}; \node[draw, circle] (2) at (4,3) {4}; \node[draw, circle] (3) at (1,1) {2}; \node[draw, circle, fill=lightgray] (4) at (4,1) {1}; \node[draw, circle, fill=lightgray] (5) at (6,2) {5}; \node[color=red] at (1,3+0.6) {$\infty$}; \node[color=red] at (4,3+0.6) {$3$}; \node[color=red] at (1,1-0.6) {$5$}; \node[color=red] at (4,1-0.6) {$0$}; \node[color=red] at (6,2-0.6) {$1$}; \path[draw,thick,-] (1) -- node[font=\small,label=above:6] {} (2); \path[draw,thick,-] (1) -- node[font=\small,label=left:2] {} (3); \path[draw,thick,-] (3) -- node[font=\small,label=below:5] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=left:9] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (5); \path[draw,thick,-] (4) -- node[font=\small,label=below:1] {} (5); \path[draw=red,thick,->,line width=2pt] (5) -- (2); \end{tikzpicture} \end{center} Tämän jälkeen vuorossa on solmu 4: \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle] (1) at (1,3) {3}; \node[draw, circle, fill=lightgray] (2) at (4,3) {4}; \node[draw, circle] (3) at (1,1) {2}; \node[draw, circle, fill=lightgray] (4) at (4,1) {1}; \node[draw, circle, fill=lightgray] (5) at (6,2) {5}; \node[color=red] at (1,3+0.6) {$9$}; \node[color=red] at (4,3+0.6) {$3$}; \node[color=red] at (1,1-0.6) {$5$}; \node[color=red] at (4,1-0.6) {$0$}; \node[color=red] at (6,2-0.6) {$1$}; \path[draw,thick,-] (1) -- node[font=\small,label=above:6] {} (2); \path[draw,thick,-] (1) -- node[font=\small,label=left:2] {} (3); \path[draw,thick,-] (3) -- node[font=\small,label=below:5] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=left:9] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (5); \path[draw,thick,-] (4) -- node[font=\small,label=below:1] {} (5); \path[draw=red,thick,->,line width=2pt] (2) -- (1); \end{tikzpicture} \end{center} Dijkstran algoritmissa on hienoutena, että aina kun solmu tulee käsittelyyn, sen etäisyysarvio on siitä lähtien lopullinen. Esimerkiksi tässä vaiheessa etäisyydet 0, 1 ja 3 ovat lopulliset etäisyydet solmuihin 1, 5 ja 4. Algoritmi käsittelee vastaavasti vielä kaksi viimeistä solmua, minkä jälkeen algoritmin päätteeksi etäisyydet ovat: \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle, fill=lightgray] (1) at (1,3) {3}; \node[draw, circle, fill=lightgray] (2) at (4,3) {4}; \node[draw, circle, fill=lightgray] (3) at (1,1) {2}; \node[draw, circle, fill=lightgray] (4) at (4,1) {1}; \node[draw, circle, fill=lightgray] (5) at (6,2) {5}; \node[color=red] at (1,3+0.6) {$7$}; \node[color=red] at (4,3+0.6) {$3$}; \node[color=red] at (1,1-0.6) {$5$}; \node[color=red] at (4,1-0.6) {$0$}; \node[color=red] at (6,2-0.6) {$1$}; \path[draw,thick,-] (1) -- node[font=\small,label=above:6] {} (2); \path[draw,thick,-] (1) -- node[font=\small,label=left:2] {} (3); \path[draw,thick,-] (3) -- node[font=\small,label=below:5] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=left:9] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (5); \path[draw,thick,-] (4) -- node[font=\small,label=below:1] {} (5); \end{tikzpicture} \end{center} \subsubsection{Negatiiviset kaaret} Dijkstran algoritmin tehokkuus perustuu siihen, että verkossa ei ole negatiivisia kaaria. Jos verkossa on negatiivinen kaari, algoritmi ei välttämättä toimi oikein. Tarkastellaan esimerkkinä seuraavaa verkkoa: \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle] (1) at (0,0) {$1$}; \node[draw, circle] (2) at (2,1) {$2$}; \node[draw, circle] (3) at (2,-1) {$3$}; \node[draw, circle] (4) at (4,0) {$4$}; \path[draw,thick,-] (1) -- node[font=\small,label=above:2] {} (2); \path[draw,thick,-] (2) -- node[font=\small,label=above:3] {} (4); \path[draw,thick,-] (1) -- node[font=\small,label=below:6] {} (3); \path[draw,thick,-] (3) -- node[font=\small,label=below:$-5$] {} (4); \end{tikzpicture} \end{center} \noindent Lyhin polku solmusta 1 solmuun 4 on $1 \rightarrow 3 \rightarrow 4$, ja sen pituus on 1. Dijkstran algoritmi löytää kuitenkin keveimpiä kaaria seuraten polun $1 \rightarrow 2 \rightarrow 4$. Algoritmi ei pysty ottamaan huomioon, että alemmalla polulla kaaren paino $-5$ kumoaa aiemman suuren kaaren painon $6$. \subsubsection{Toteutus} Seuraava Dijkstran algoritmin toteutus laskee pienimmän etäisyyden solmusta $x$ kaikkiin muihin solmuihin. Verkko on tallennettu taulukkoon \texttt{v} vieruslistoina, joissa on pareina kohdesolmu ja kaaren pituus. Dijkstran algoritmin tehokas toteutus vaatii, että verkosta pystyy löytämään nopeasti vielä käsittelemättömän solmun, jonka etäisyysarvio on pienin. Sopiva tietorakenne tähän on prioriteettijono, jossa solmut ovat järjestyksessä etäisyys\-arvioiden mukaan. Prioriteettijonon avulla seuraavaksi käsiteltävän solmun saa selville logaritmisessa ajassa. Seuraavassa toteutuksessa prioriteettijono sisältää pareja, joiden ensimmäinen kenttä on etäisyysarvio ja toinen kenttä on solmun tunniste: \begin{lstlisting} priority_queue> q; \end{lstlisting} Pieni hankaluus on, että Dijkstran algoritmissa täytyy saada selville \emph{pienimmän} etäisyysarvion solmu, kun taas C++:n prioriteettijono antaa oletuksena \emph{suurimman} alkion. Helppo ratkaisu on tallentaa etäisyysarviot \emph{negatiivisina}, jolloin C++:n prioriteettijonoa voi käyttää suoraan. Koodi merkitsee taulukkoon \texttt{z}, onko solmu käsitelty, ja pitää yllä etäisyysarvioita taulukossa \texttt{e}. Alussa alkusolmun etäisyysarvio on 0 ja jokaisen muun solmun etäisyysarviona on ääretöntä vastaava $10^9$. \begin{lstlisting} for (int i = 1; i <= n; i++) e[i] = 1e9; e[x] = 0; q.push({0,x}); while (!q.empty()) { int a = q.top().second; q.pop(); if (z[a]) continue; z[a] = 1; for (auto b : v[a]) { if (e[a]+b.second < e[b]) { e[b] = e[a]+b.second; q.push({-e[b],b}); } } } \end{lstlisting} Yllä olevan toteutuksen aikavaativuus on $O(n+m \log m)$, koska algoritmi käy läpi kaikki verkon solmut ja lisää jokaista kaarta kohden korkeintaan yhden etäisyysarvion prioriteettijonoon. \section{Floyd–Warshallin algoritmi} \index{Floyd–Warshallin algoritmi} \key{Floyd–Warshallin algoritmi} on toisenlainen lähestymistapa lyhimpien polkujen etsintään. Toisin kuin muut tämän luvun algoritmit, se etsii yhdellä kertaa lyhimmät polut kaikkien verkon solmujen välillä. Algoritmi ylläpitää kaksiulotteista taulukkoa etäisyyksistä solmujen välillä. Ensin taulukkoon on merkitty etäisyydet käyttäen vain solmujen välisiä kaaria. Tämän jälkeen algoritmi päivittää etäisyyksiä, kun verkon solmut saavat yksi kerrallaan toimia välisolmuina poluilla. \subsubsection{Esimerkki} Tarkastellaan Floyd–Warshallin algoritmin toimintaa seuraavassa verkossa: \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle] (1) at (1,3) {$3$}; \node[draw, circle] (2) at (4,3) {$4$}; \node[draw, circle] (3) at (1,1) {$2$}; \node[draw, circle] (4) at (4,1) {$1$}; \node[draw, circle] (5) at (6,2) {$5$}; \path[draw,thick,-] (1) -- node[font=\small,label=above:7] {} (2); \path[draw,thick,-] (1) -- node[font=\small,label=left:2] {} (3); \path[draw,thick,-] (3) -- node[font=\small,label=below:5] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=left:9] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (5); \path[draw,thick,-] (4) -- node[font=\small,label=below:1] {} (5); \end{tikzpicture} \end{center} Algoritmi merkitsee aluksi taulukkoon etäisyyden 0 jokaisesta solmusta itseensä sekä etäisyyden $x$, jos solmuparin välillä on kaari, jonka pituus on $x$. Muiden solmuparien etäisyys on aluksi ääretön. Tässä verkossa taulukosta tulee: \begin{center} \begin{tabular}{r|rrrrr} & 1 & 2 & 3 & 4 & 5 \\ \hline 1 & 0 & 5 & $\infty$ & 9 & 1 \\ 2 & 5 & 0 & 2 & $\infty$ & $\infty$ \\ 3 & $\infty$ & 2 & 0 & 7 & $\infty$ \\ 4 & 9 & $\infty$ & 7 & 0 & 2 \\ 5 & 1 & $\infty$ & $\infty$ & 2 & 0 \\ \end{tabular} \end{center} \vspace{10pt} Algoritmin toiminta muodostuu peräkkäisistä kierroksista. Jokaisella kierroksella valitaan yksi uusi solmu, joka saa toimia välisolmuna poluilla, ja algoritmi parantaa taulukon etäisyyksiä muodostaen polkuja tämän solmun avulla. Ensimmäisellä kierroksella solmu 1 on välisolmu. Tämän ansiosta solmujen 2 ja 4 välille muodostuu polku, jonka pituus on 14, koska solmu 1 yhdistää ne toisiinsa. Vastaavasti solmut 2 ja 5 yhdistyvät polulla, jonka pituus on 6. \begin{center} \begin{tabular}{r|rrrrr} & 1 & 2 & 3 & 4 & 5 \\ \hline 1 & 0 & 5 & $\infty$ & 9 & 1 \\ 2 & 5 & 0 & 2 & \textbf{14} & \textbf{6} \\ 3 & $\infty$ & 2 & 0 & 7 & $\infty$ \\ 4 & 9 & \textbf{14} & 7 & 0 & 2 \\ 5 & 1 & \textbf{6} & $\infty$ & 2 & 0 \\ \end{tabular} \end{center} \vspace{10pt} Toisella kierroksella solmu 2 saa toimia välisolmuna. Tämä mahdollistaa uudet polut solmuparien 1 ja 3 sekä 3 ja 5 välille: \begin{center} \begin{tabular}{r|rrrrr} & 1 & 2 & 3 & 4 & 5 \\ \hline 1 & 0 & 5 & \textbf{7} & 9 & 1 \\ 2 & 5 & 0 & 2 & 14 & 6 \\ 3 & \textbf{7} & 2 & 0 & 7 & \textbf{8} \\ 4 & 9 & 14 & 7 & 0 & 2 \\ 5 & 1 & 6 & \textbf{8} & 2 & 0 \\ \end{tabular} \end{center} \vspace{10pt} Kolmannella kierroksella solmu 3 saa toimia välisolmuna, jolloin syntyy uusi polku solmuparin 2 ja 4 välille: \begin{center} \begin{tabular}{r|rrrrr} & 1 & 2 & 3 & 4 & 5 \\ \hline 1 & 0 & 5 & 7 & 9 & 1 \\ 2 & 5 & 0 & 2 & \textbf{9} & 6 \\ 3 & 7 & 2 & 0 & 7 & 8 \\ 4 & 9 & \textbf{9} & 7 & 0 & 2 \\ 5 & 1 & 6 & 8 & 2 & 0 \\ \end{tabular} \end{center} \vspace{10pt} Algoritmin toiminta jatkuu samalla tavalla niin, että kukin solmu tulee vuorollaan välisolmuksi. Algoritmin päätteeksi taulukko sisältää lyhimmän etäisyyden minkä tahansa solmuparin välillä: \begin{center} \begin{tabular}{r|rrrrr} & 1 & 2 & 3 & 4 & 5 \\ \hline 1 & 0 & 5 & 7 & 3 & 1 \\ 2 & 5 & 0 & 2 & 9 & 6 \\ 3 & 7 & 2 & 0 & 7 & 8 \\ 4 & 3 & 9 & 7 & 0 & 2 \\ 5 & 1 & 6 & 8 & 2 & 0 \\ \end{tabular} \end{center} Esimerkiksi taulukosta selviää, että lyhin polku solmusta 2 solmuun 4 on pituudeltaan 8. Tämä vastaa seuraavaa polkua: \begin{center} \begin{tikzpicture}[scale=0.9] \node[draw, circle] (1) at (1,3) {$3$}; \node[draw, circle] (2) at (4,3) {$4$}; \node[draw, circle] (3) at (1,1) {$2$}; \node[draw, circle] (4) at (4,1) {$1$}; \node[draw, circle] (5) at (6,2) {$5$}; \path[draw,thick,-] (1) -- node[font=\small,label=above:7] {} (2); \path[draw,thick,-] (1) -- node[font=\small,label=left:2] {} (3); \path[draw,thick,-] (3) -- node[font=\small,label=below:5] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=left:9] {} (4); \path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (5); \path[draw,thick,-] (4) -- node[font=\small,label=below:1] {} (5); \path[draw=red,thick,->,line width=2pt] (3) -- (4); \path[draw=red,thick,->,line width=2pt] (4) -- (5); \path[draw=red,thick,->,line width=2pt] (5) -- (2); \end{tikzpicture} \end{center} \subsubsection{Toteutus} Floyd–Warshallin algoritmin etuna on, että se on helppoa toteuttaa. Seuraava toteutus muodostaa etäisyysmatriisin \texttt{d}, jossa $\texttt{d}[a][b]$ on pienin etäisyys polulla solmusta $a$ solmuun $b$. Aluksi algoritmi alustaa matriisin \texttt{d} verkon vierusmatriisin \texttt{v} perusteella (arvo $10^9$ kuvastaa ääretöntä): \begin{lstlisting} for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (i == j) d[i][j] = 0; else if (v[i][j]) d[i][j] = v[i][j]; else d[i][j] = 1e9; } } \end{lstlisting} Tämän jälkeen lyhimmät polut löytyvät seuraavasti: \begin{lstlisting} for (int k = 1; k <= n; k++) { for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { d[i][j] = min(d[i][j], d[i][k]+d[k][j]); } } } \end{lstlisting} Algoritmin aikavaativuus on $O(n^3)$, koska siinä on kolme sisäkkäistä silmukkaa, jotka käyvät läpi verkon solmut. Koska Floyd–Warshallin algoritmin toteutus on yksinkertainen, algoritmi voi olla hyvä valinta jopa silloin, kun haettavana on yksittäinen lyhin polku verkossa. Tämä on kuitenkin mahdollista vain silloin, kun verkko on niin pieni, että kuutiollinen aikavaativuus on riittävä.