cphb/luku13.tex

807 lines
26 KiB
TeX
Raw Normal View History

2016-12-28 23:54:51 +01:00
\chapter{Shortest paths}
2017-01-07 19:08:47 +01:00
\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{BellmanFord algorithm}
\index{BellmanFord algorithm}
The \key{BellmanFordin 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 BellmanFord algorithm
works in the following graph:
2016-12-28 23:54:51 +01:00
\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}
2017-01-07 19:08:47 +01:00
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:
2016-12-28 23:54:51 +01:00
\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}
2017-01-07 19:08:47 +01:00
After this, edges
$2 \rightarrow 5$ and $3 \rightarrow 4$
improve the estimates:
2016-12-28 23:54:51 +01:00
\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}
2017-01-07 19:08:47 +01:00
Finally, there is one more improvment:
2016-12-28 23:54:51 +01:00
\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}
2017-01-07 19:08:47 +01:00
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.
2016-12-28 23:54:51 +01:00
2017-01-07 19:08:47 +01:00
For example, the smallest distance 3
from node 1 to node 5 corresponds to
the following path:
2016-12-28 23:54:51 +01:00
\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}
2017-01-07 19:08:47 +01:00
\subsubsection{Implementation}
2016-12-28 23:54:51 +01:00
2017-01-07 19:08:47 +01:00
The following implementation of the
BellmanFord 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
2016-12-28 23:54:51 +01:00
\begin{lstlisting}
vector<pair<int,int>> v[N];
\end{lstlisting}
2017-01-07 19:08:47 +01:00
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.
2016-12-28 23:54:51 +01:00
\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}
2017-01-07 19:08:47 +01:00
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.
2016-12-28 23:54:51 +01:00
2017-01-07 19:08:47 +01:00
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.
2016-12-28 23:54:51 +01:00
2017-01-07 19:08:47 +01:00
\subsubsection{Negative cycle}
2016-12-28 23:54:51 +01:00
2017-01-07 19:08:47 +01:00
\index{negative cycle}
2016-12-28 23:54:51 +01:00
2017-01-07 19:08:47 +01:00
Using the BellmanFord algorithm we can also
check if the graph contains a cycle with negative length.
For example, the graph
2016-12-28 23:54:51 +01:00
\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
2017-01-07 19:08:47 +01:00
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 BellmanFord 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 BellmanFord 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.
2016-12-28 23:54:51 +01:00
\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}
2017-01-07 19:08:47 +01:00
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 BellmanFord algorithm.
2016-12-28 23:54:51 +01:00
2017-01-07 19:36:06 +01:00
\section{Dijkstra's algorithm}
\index{Dijkstra's algorithm}
\key{Dijkstra's algorithm} finds the shortest
paths from the starting node to all other nodes,
like the BellmanFord algorithm.
The benefit in Dijsktra's algorithm is that
it is more efficient and can be used for
processing large graphs.
However, the algorithm requires that there
are no negative weight edges in the graph.
Like the BellmanFord algorithm,
Dijkstra's algorithm maintains estimated distances
for the nodes and improves them during the algorithm.
Dijkstra's algorithm is efficient because
it only processes
each edge in the graph once, using the fact
that there are no negative edges.
2016-12-28 23:54:51 +01:00
2017-01-07 19:36:06 +01:00
\subsubsection{Example}
2016-12-28 23:54:51 +01:00
2017-01-07 19:36:06 +01:00
Let's consider how Dijkstra's algorithm
works in the following graph when the
starting node is node 1:
2016-12-28 23:54:51 +01:00
\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}
2017-01-07 19:36:06 +01:00
Like in the BellmanFord algorithm,
the estimated distance is 0 to the starting node
and infinite to all other nodes.
At each step, Dijkstra's algorithm selects a node
that has not been processed yet and whose estimated distance
is as small as possible.
The first such node is node 1 with distance 0.
When a node is selected, the algorithm
goes through all edges that begin from the node
and improves the distances using them:
2016-12-28 23:54:51 +01:00
\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}
2017-01-07 19:36:06 +01:00
The edges from node 1 improved distances to
nodes 2, 4 and 5 whose now distances are now 5, 9 and 1.
2016-12-28 23:54:51 +01:00
2017-01-07 19:36:06 +01:00
The next node to be processed is node 5 with distance 1:
2016-12-28 23:54:51 +01:00
\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}
2017-01-07 19:36:06 +01:00
After this, the next node is node 4:
2016-12-28 23:54:51 +01:00
\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}
2017-01-07 19:36:06 +01:00
A nice property in Dijkstra's algorithm is that
whenever a node is selected, its distance is final.
For example, at this point of the algorithm,
the distances 0, 1 and 3 are the final distances
to nodes 1, 5 and 4.
2016-12-28 23:54:51 +01:00
2017-01-07 19:36:06 +01:00
After this, the algorithm processes the two
remaining nodes, and the final distances are as follows:
2016-12-28 23:54:51 +01:00
\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}
2017-01-07 19:36:06 +01:00
\subsubsection{Negative edges}
2016-12-28 23:54:51 +01:00
2017-01-07 19:36:06 +01:00
The efficiency of Dijkstra's algorithm is
based on the fact that the graph doesn't
contain negative edges.
If there is a negative edge,
the algorithm may give incorrect results.
As an example, consider the following graph:
2016-12-28 23:54:51 +01:00
\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
2017-01-07 19:36:06 +01:00
The shortest path from node 1 to node 4 is
2016-12-28 23:54:51 +01:00
$1 \rightarrow 3 \rightarrow 4$,
2017-01-07 19:36:06 +01:00
and its length is 1.
However, Dijkstra's algorithm
finds the path $1 \rightarrow 2 \rightarrow 4$
by following the lightest edges.
The algorithm cannot recognize that
in the lower path, the weight $-5$
compensates the previous large weight $6$.
\subsubsection{Implementation}
The following implementation of Dijkstra's algorithm
calculates the minimum distance from a node $x$
to all other nodes.
The graph is stored in an array \texttt{v}
as adjacency lists that contain target nodes
and weights for each edge.
An efficient implementation of Dijkstra's algorithm
requires that it is possible to quickly find the
smallest node that has not been processed.
A suitable data structure for this is a priority queue
that contains the nodes ordered by the estimated distances.
Using a priority queue, the next node to be processed
can be retrieved in logarithmic time.
In the following implementation,
the priority queue contains pairs whose first
element is the estimated distance and second
element is the identifier of the corresponding node.
2016-12-28 23:54:51 +01:00
\begin{lstlisting}
priority_queue<pair<int,int>> q;
\end{lstlisting}
2017-01-07 19:36:06 +01:00
A small difficulty is that in Dijkstra's algorithm,
we should find the node with \emph{minimum} distance,
while the C++ priority queue finds the \emph{maximum}
element as default.
An easy solution is to use \emph{negative} distances,
so we can directly use the C++ priority queue.
The code keeps track of processed nodes
in array \texttt{z},
and maintains estimated distances in array \texttt{e}.
Initially, the distance to the starting node is 0,
and the distance to all other nodes is $10^9$
that corresponds to infinity.
2016-12-28 23:54:51 +01:00
\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}
2017-01-07 19:36:06 +01:00
The time complexity of the above implementation is
$O(n+m \log m)$ because the algorithm goes through
all nodes in the graph, and adds for each edge
at most one estimated distance to the priority queue.
2016-12-28 23:54:51 +01:00
\section{FloydWarshallin algoritmi}
\index{FloydWarshallin algoritmi}
\key{FloydWarshallin 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 FloydWarshallin
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}
FloydWarshallin 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 FloydWarshallin
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ä.