807 lines
26 KiB
TeX
807 lines
26 KiB
TeX
\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<pair<int,int>> 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{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 Bellman–Ford 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 Bellman–Ford 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.
|
||
|
||
\subsubsection{Example}
|
||
|
||
Let's consider how Dijkstra's algorithm
|
||
works in the following graph when the
|
||
starting node is node 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}
|
||
Like in the Bellman–Ford 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:
|
||
\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}
|
||
The edges from node 1 improved distances to
|
||
nodes 2, 4 and 5 whose now distances are now 5, 9 and 1.
|
||
|
||
The next node to be processed is node 5 with distance 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}
|
||
After this, the next node is node 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}
|
||
|
||
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.
|
||
|
||
After this, the algorithm processes the two
|
||
remaining nodes, and the final distances are as follows:
|
||
|
||
\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{Negative edges}
|
||
|
||
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:
|
||
|
||
\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
|
||
The shortest path from node 1 to node 4 is
|
||
$1 \rightarrow 3 \rightarrow 4$,
|
||
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.
|
||
\begin{lstlisting}
|
||
priority_queue<pair<int,int>> q;
|
||
\end{lstlisting}
|
||
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.
|
||
|
||
\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}
|
||
|
||
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.
|
||
|
||
\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ä.
|