cphb/chapter13.tex

803 lines
27 KiB
TeX
Raw Permalink 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}
2017-05-07 20:18:56 +02:00
Finding a shortest path between two nodes
2017-02-05 10:51:38 +01:00
of a graph
is an important problem that has many
2017-05-07 20:18:56 +02:00
practical applications.
For example, a natural problem related to a road network
is to calculate the shortest possible length of a route
2017-01-07 19:08:47 +01:00
between two cities, given the lengths of the roads.
In an unweighted graph, the length of a path equals
2017-05-07 20:18:56 +02:00
the number of its edges, and we can
2017-02-05 10:51:38 +01:00
simply use breadth-first search to find
2017-05-07 20:18:56 +02:00
a shortest path.
However, in this chapter we focus on
2017-02-17 21:13:30 +01:00
weighted graphs
where more sophisticated algorithms
2017-02-05 10:51:38 +01:00
are needed
2017-01-07 19:08:47 +01:00
for finding shortest paths.
\section{BellmanFord algorithm}
\index{BellmanFord algorithm}
2017-02-26 12:51:38 +01:00
The \key{BellmanFord algorithm}\footnote{The algorithm is named after
R. E. Bellman and L. R. Ford who published it independently
2017-05-07 20:18:56 +02:00
in 1958 and 1956, respectively \cite{bel58,for56a}.} finds
2017-02-05 10:51:38 +01:00
shortest paths from a starting node to all
2017-05-07 20:18:56 +02:00
nodes of the graph.
2017-02-05 10:51:38 +01:00
The algorithm can process all kinds of graphs,
provided that the graph does not contain a
2017-01-07 19:08:47 +01:00
cycle with negative length.
If the graph contains a negative cycle,
the algorithm can detect this.
2017-02-05 10:51:38 +01:00
The algorithm keeps track of distances
2017-05-07 20:18:56 +02:00
from the starting node to all nodes of the graph.
2017-02-05 10:51:38 +01:00
Initially, the distance to the starting node is 0
and the distance to all other nodes in infinite.
The algorithm reduces the distances by finding
2017-01-07 19:08:47 +01:00
edges that shorten the paths until it is not
2017-02-05 10:51:38 +01:00
possible to reduce any distance.
2017-01-07 19:08:47 +01:00
\subsubsection{Example}
2017-02-05 10:51:38 +01:00
Let us consider how the BellmanFord algorithm
2017-01-07 19:08:47 +01:00
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};
2018-07-03 13:48:22 +02:00
\node[draw, circle] (5) at (6,2) {6};
2016-12-28 23:54:51 +01:00
\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$};
2018-07-03 13:48:22 +02:00
\path[draw,thick,-] (1) -- node[font=\small,label=above:5] {} (2);
2016-12-28 23:54:51 +01:00
\path[draw,thick,-] (1) -- node[font=\small,label=left:3] {} (3);
2018-07-03 13:48:22 +02:00
\path[draw,thick,-] (3) -- node[font=\small,label=below:1] {} (4);
2016-12-28 23:54:51 +01:00
\path[draw,thick,-] (2) -- node[font=\small,label=left:3] {} (4);
2018-07-03 13:48:22 +02:00
\path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (5);
2016-12-28 23:54:51 +01:00
\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-05-07 20:18:56 +02:00
Each node of the graph is assigned a distance.
2017-02-05 10:51:38 +01:00
Initially, the distance to the starting node is 0,
and the distance to all other nodes is infinite.
2017-01-07 19:08:47 +01:00
2017-02-17 21:13:30 +01:00
The algorithm searches for edges that reduce distances.
First, all edges from node 1 reduce distances:
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$};
2018-07-03 13:48:22 +02:00
\node[color=red] at (4,3+0.55) {$5$};
2016-12-28 23:54:51 +01:00
\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$};
2018-07-03 13:48:22 +02:00
\path[draw,thick,-] (1) -- node[font=\small,label=above:5] {} (2);
2016-12-28 23:54:51 +01:00
\path[draw,thick,-] (1) -- node[font=\small,label=left:3] {} (3);
2018-07-03 13:48:22 +02:00
\path[draw,thick,-] (3) -- node[font=\small,label=below:1] {} (4);
2016-12-28 23:54:51 +01:00
\path[draw,thick,-] (2) -- node[font=\small,label=left:3] {} (4);
2018-07-03 13:48:22 +02:00
\path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (5);
2016-12-28 23:54:51 +01:00
\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$
2017-02-17 21:13:30 +01:00
reduce distances:
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$};
2018-07-03 13:48:22 +02:00
\node[color=red] at (4,3+0.55) {$5$};
2016-12-28 23:54:51 +01:00
\node[color=red] at (1,1-0.55) {$3$};
2018-07-03 13:48:22 +02:00
\node[color=red] at (4,1-0.55) {$4$};
2016-12-28 23:54:51 +01:00
\node[color=red] at (6,2-0.55) {$7$};
2018-07-03 13:48:22 +02:00
\path[draw,thick,-] (1) -- node[font=\small,label=above:5] {} (2);
2016-12-28 23:54:51 +01:00
\path[draw,thick,-] (1) -- node[font=\small,label=left:3] {} (3);
2018-07-03 13:48:22 +02:00
\path[draw,thick,-] (3) -- node[font=\small,label=below:1] {} (4);
2016-12-28 23:54:51 +01:00
\path[draw,thick,-] (2) -- node[font=\small,label=left:3] {} (4);
2018-07-03 13:48:22 +02:00
\path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (5);
2016-12-28 23:54:51 +01:00
\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-02-05 10:51:38 +01:00
Finally, there is one more change:
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$};
2018-07-03 13:48:22 +02:00
\node[color=red] at (4,3+0.55) {$5$};
2016-12-28 23:54:51 +01:00
\node[color=red] at (1,1-0.55) {$3$};
2018-07-03 13:48:22 +02:00
\node[color=red] at (4,1-0.55) {$4$};
\node[color=red] at (6,2-0.55) {$6$};
\path[draw,thick,-] (1) -- node[font=\small,label=above:5] {} (2);
2016-12-28 23:54:51 +01:00
\path[draw,thick,-] (1) -- node[font=\small,label=left:3] {} (3);
2018-07-03 13:48:22 +02:00
\path[draw,thick,-] (3) -- node[font=\small,label=below:1] {} (4);
2016-12-28 23:54:51 +01:00
\path[draw,thick,-] (2) -- node[font=\small,label=left:3] {} (4);
2018-07-03 13:48:22 +02:00
\path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (5);
2016-12-28 23:54:51 +01:00
\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-02-05 10:51:38 +01:00
After this, no edge can reduce any distance.
2017-05-07 20:18:56 +02:00
This means that the distances are final,
2017-01-07 19:08:47 +01:00
and we have successfully
2017-05-07 20:18:56 +02:00
calculated the shortest distances
from the starting node to all nodes of the graph.
2016-12-28 23:54:51 +01:00
2017-02-17 21:13:30 +01:00
For example, the shortest distance 3
2017-01-07 19:08:47 +01:00
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$};
2018-07-03 13:48:22 +02:00
\node[color=red] at (4,3+0.55) {$5$};
2016-12-28 23:54:51 +01:00
\node[color=red] at (1,1-0.55) {$3$};
2018-07-03 13:48:22 +02:00
\node[color=red] at (4,1-0.55) {$4$};
\node[color=red] at (6,2-0.55) {$6$};
\path[draw,thick,-] (1) -- node[font=\small,label=above:5] {} (2);
2016-12-28 23:54:51 +01:00
\path[draw,thick,-] (1) -- node[font=\small,label=left:3] {} (3);
2018-07-03 13:48:22 +02:00
\path[draw,thick,-] (3) -- node[font=\small,label=below:1] {} (4);
2016-12-28 23:54:51 +01:00
\path[draw,thick,-] (2) -- node[font=\small,label=left:3] {} (4);
2018-07-03 13:48:22 +02:00
\path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (5);
2016-12-28 23:54:51 +01:00
\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
2017-05-07 20:18:56 +02:00
BellmanFord algorithm determines the shortest distances
from a node $x$ to all nodes of the graph.
2017-01-07 19:08:47 +01:00
The code assumes that the graph is stored
as an edge list \texttt{edges}
that consists of tuples of the form $(a,b,w)$,
meaning that there is an edge from node $a$ to node $b$
with weight $w$.
2017-01-07 19:08:47 +01:00
The algorithm consists of $n-1$ rounds,
and on each round the algorithm goes through
2017-02-17 21:13:30 +01:00
all edges of the graph and tries to
2017-02-05 10:51:38 +01:00
reduce the distances.
2017-05-28 11:07:31 +02:00
The algorithm constructs an array \texttt{distance}
2017-05-07 20:18:56 +02:00
that will contain the distances from $x$
to all nodes of the graph.
2017-04-17 13:38:33 +02:00
The constant \texttt{INF} denotes an infinite distance.
2016-12-28 23:54:51 +01:00
\begin{lstlisting}
2017-05-28 11:07:31 +02:00
for (int i = 1; i <= n; i++) distance[i] = INF;
distance[x] = 0;
2016-12-28 23:54:51 +01:00
for (int i = 1; i <= n-1; i++) {
for (auto e : edges) {
int a, b, w;
tie(a, b, w) = e;
2017-05-28 11:07:31 +02:00
distance[b] = min(distance[b], distance[a]+w);
2016-12-28 23:54:51 +01:00
}
}
\end{lstlisting}
2017-02-05 10:51:38 +01:00
The time complexity of the algorithm is $O(nm)$,
because the algorithm consists of $n-1$ rounds and
iterates through all $m$ edges during a round.
2017-01-07 19:08:47 +01:00
If there are no negative cycles in the graph,
2017-02-05 10:51:38 +01:00
all distances are final after $n-1$ rounds,
2017-01-07 19:08:47 +01:00
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
2017-02-17 21:13:30 +01:00
be found faster than in $n-1$ rounds.
2017-01-07 19:08:47 +01:00
Thus, a possible way to make the algorithm more efficient
2017-02-05 10:51:38 +01:00
is to stop the algorithm if no distance
can be reduced during a round.
2016-12-28 23:54:51 +01:00
2017-04-17 12:58:04 +02:00
\subsubsection{Negative cycles}
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-05-07 20:18:56 +02:00
The BellmanFord algorithm can also be used to
2017-01-07 19:08:47 +01:00
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,
2017-05-07 20:18:56 +02:00
we can shorten infinitely many times
any path that contains the cycle by repeating the cycle
2017-01-07 19:08:47 +01:00
again and again.
Thus, the concept of a shortest path
2017-02-05 10:51:38 +01:00
is not meaningful in this situation.
2017-01-07 19:08:47 +01:00
A negative cycle can be detected
using the BellmanFord algorithm by
running the algorithm for $n$ rounds.
2017-02-05 10:51:38 +01:00
If the last round reduces any distance,
2017-01-07 19:08:47 +01:00
the graph contains a negative cycle.
2017-02-17 21:13:30 +01:00
Note that this algorithm can be used to
search for
2017-01-07 19:08:47 +01:00
a negative cycle in the whole graph
regardless of the starting node.
\subsubsection{SPFA algorithm}
\index{SPFA algorithm}
2017-02-21 00:17:36 +01:00
The \key{SPFA algorithm} (''Shortest Path Faster Algorithm'') \cite{fan94}
2017-02-05 10:51:38 +01:00
is a variant of the BellmanFord algorithm,
2017-01-07 19:08:47 +01:00
that is often more efficient than the original algorithm.
2017-02-17 21:13:30 +01:00
The SPFA algorithm does not go through all the edges on each round,
2017-01-07 19:08:47 +01:00
but instead, it chooses the edges to be examined
in a more intelligent way.
The algorithm maintains a queue of nodes that might
2017-02-05 10:51:38 +01:00
be used for reducing the distances.
2017-01-07 19:08:47 +01:00
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
2017-02-05 10:51:38 +01:00
$a \rightarrow b$ reduces a distance,
2017-02-17 21:13:30 +01:00
node $b$ is added to the queue.
%
% The following implementation uses a
% \texttt{queue} \texttt{q}.
% In addition, an array \texttt{inqueue} indicates
% if a node is already in the queue,
% in which case the algorithm does not add
% the node to the queue again.
%
% \begin{lstlisting}
% for (int i = 1; i <= n; i++) distance[i] = INF;
% distance[x] = 0;
% q.push(x);
% while (!q.empty()) {
% int a = q.front(); q.pop();
% inqueue[a] = false;
% for (auto b : v[a]) {
% if (distance[a]+b.second < distance[b.first]) {
% distance[b.first] = distance[a]+b.second;
% if (!inqueue[b]) {q.push(b); inqueue[b] = true;}
% }
% }
% }
% \end{lstlisting}
2016-12-28 23:54:51 +01:00
2017-01-07 19:08:47 +01:00
The efficiency of the SPFA algorithm depends
on the structure of the graph:
2017-02-17 21:13:30 +01:00
the algorithm is often efficient,
2017-01-07 19:08:47 +01:00
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
2017-02-17 21:13:30 +01:00
original 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}
2017-02-26 12:51:38 +01:00
\key{Dijkstra's algorithm}\footnote{E. W. Dijkstra published the algorithm in 1959 \cite{dij59};
however, his original paper does not mention how to implement the algorithm efficiently.}
2017-05-07 20:18:56 +02:00
finds shortest
paths from the starting node to all nodes of the graph,
2017-01-07 19:36:06 +01:00
like the BellmanFord algorithm.
2017-05-07 20:18:56 +02:00
The benefit of Dijsktra's algorithm is that
2017-01-07 19:36:06 +01:00
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,
2017-02-05 10:51:38 +01:00
Dijkstra's algorithm maintains distances
2017-02-17 21:13:30 +01:00
to the nodes and reduces them during the search.
2017-02-05 10:51:38 +01:00
Dijkstra's algorithm is efficient, because
2017-01-07 19:36:06 +01:00
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-02-05 10:51:38 +01:00
Let us consider how Dijkstra's algorithm
2017-01-07 19:36:06 +01:00
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,
2017-02-17 21:13:30 +01:00
initially the distance to the starting node is 0
2017-02-05 10:51:38 +01:00
and the distance to all other nodes is infinite.
2017-01-07 19:36:06 +01:00
At each step, Dijkstra's algorithm selects a node
2017-02-05 10:51:38 +01:00
that has not been processed yet and whose distance
2017-01-07 19:36:06 +01:00
is as small as possible.
The first such node is node 1 with distance 0.
When a node is selected, the algorithm
2017-02-05 10:51:38 +01:00
goes through all edges that start at the node
and reduces 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-05-07 20:18:56 +02:00
In this case,
the edges from node 1 reduced the distances of
2017-02-05 10:51:38 +01:00
nodes 2, 4 and 5, whose distances are now 5, 9 and 1.
2016-12-28 23:54:51 +01:00
2017-05-28 11:07:31 +02:00
The next node to be processed is node 5 with distance 1.
This reduces the distance to node 4 from 9 to 3:
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-05-28 11:07:31 +02:00
After this, the next node is node 4, which reduces
the distance to node 3 to 9:
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-02-05 10:51:38 +01:00
A remarkable property in Dijkstra's algorithm is that
2017-01-07 19:36:06 +01:00
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
2017-02-05 10:51:38 +01:00
based on the fact that the graph does not
2017-01-07 19:36:06 +01:00
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
2017-02-05 10:51:38 +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$
2017-02-05 10:51:38 +01:00
by following the minimum weight edges.
The algorithm does not take into account that
2017-02-17 21:13:30 +01:00
on the other path, the weight $-5$
2017-01-07 19:36:06 +01:00
compensates the previous large weight $6$.
\subsubsection{Implementation}
The following implementation of Dijkstra's algorithm
2017-02-05 10:51:38 +01:00
calculates the minimum distances from a node $x$
2017-05-07 20:18:56 +02:00
to other nodes of the graph.
The graph is stored as adjacency lists
so that \texttt{adj[$a$]} contains a pair $(b,w)$
always when there is an edge from node $a$ to node $b$
with weight $w$.
2017-01-07 19:36:06 +01:00
An efficient implementation of Dijkstra's algorithm
2017-02-05 10:51:38 +01:00
requires that it is possible to efficiently find the
minimum distance node that has not been processed.
An appropriate data structure for this is a priority queue
that contains the nodes ordered by their distances.
2017-01-07 19:36:06 +01:00
Using a priority queue, the next node to be processed
can be retrieved in logarithmic time.
2017-05-28 11:07:31 +02:00
In the following code, the priority queue
2017-05-07 20:18:56 +02:00
\texttt{q} contains pairs of the form $(-d,x)$,
meaning that the current distance to node $x$ is $d$.
2017-05-28 11:07:31 +02:00
The array $\texttt{distance}$ contains the distance to
each node, and the array $\texttt{processed}$ indicates
whether a node has been processed.
2017-05-07 20:18:56 +02:00
Initially the distance is $0$ to $x$ and $\infty$ to all other nodes.
2016-12-28 23:54:51 +01:00
\begin{lstlisting}
2017-05-28 11:07:31 +02:00
for (int i = 1; i <= n; i++) distance[i] = INF;
distance[x] = 0;
2016-12-28 23:54:51 +01:00
q.push({0,x});
while (!q.empty()) {
int a = q.top().second; q.pop();
2017-05-28 11:07:31 +02:00
if (processed[a]) continue;
processed[a] = true;
for (auto u : adj[a]) {
int b = u.first, w = u.second;
2017-05-28 11:07:31 +02:00
if (distance[a]+w < distance[b]) {
distance[b] = distance[a]+w;
q.push({-distance[b],b});
2016-12-28 23:54:51 +01:00
}
}
}
\end{lstlisting}
Note that the priority queue contains \emph{negative}
distances to nodes.
2017-05-07 20:18:56 +02:00
The reason for this is that the
default version of the C++ priority queue finds maximum
elements, while we want to find minimum elements.
By using negative distances,
2017-05-28 11:07:31 +02:00
we can directly use the default priority queue\footnote{Of
course, we could also declare the priority queue as in Chapter 4.5
and use positive distances, but the implementation would be a bit longer.}.
Also note that there may be several instances of the same
node in the priority queue; however, only the instance with the
minimum distance will be processed.
2017-01-07 19:36:06 +01:00
The time complexity of the above implementation is
2017-05-07 20:18:56 +02:00
$O(n+m \log m)$, because the algorithm goes through
all nodes of the graph and adds for each edge
2017-02-17 21:13:30 +01:00
at most one distance to the priority queue.
2016-12-28 23:54:51 +01:00
2017-01-07 19:50:41 +01:00
\section{FloydWarshall algorithm}
2016-12-28 23:54:51 +01:00
2017-01-07 19:50:41 +01:00
\index{FloydWarshall algorithm}
2016-12-28 23:54:51 +01:00
2017-02-26 12:51:38 +01:00
The \key{FloydWarshall algorithm}\footnote{The algorithm
is named after R. W. Floyd and S. Warshall
who published it independently in 1962 \cite{flo62,war62}.}
2017-05-07 20:18:56 +02:00
provides an alternative way to approach the problem
2017-01-07 19:50:41 +01:00
of finding shortest paths.
2017-05-07 20:18:56 +02:00
Unlike the other algorithms of this chapter,
2017-01-07 19:50:41 +01:00
it finds all shortest paths between the nodes
in a single run.
2016-12-28 23:54:51 +01:00
2017-01-07 19:50:41 +01:00
The algorithm maintains a two-dimensional array
that contains distances between the nodes.
2017-05-28 11:07:31 +02:00
First, distances are calculated only using
direct edges between the nodes,
and after this, the algorithm reduces distances
by using intermediate nodes in paths.
2016-12-28 23:54:51 +01:00
2017-01-07 19:50:41 +01:00
\subsubsection{Example}
2016-12-28 23:54:51 +01:00
2017-02-05 10:51:38 +01:00
Let us consider how the FloydWarshall algorithm
2017-01-07 19:50:41 +01:00
works in the following graph:
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$};
\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}
2017-01-07 19:50:41 +01:00
Initially, the distance from each node to itself is $0$,
and the distance between nodes $a$ and $b$ is $x$
if there is an edge between nodes $a$ and $b$ with weight $x$.
All other distances are infinite.
2016-12-28 23:54:51 +01:00
2017-01-07 19:50:41 +01:00
In this graph, the initial array is as follows:
2016-12-28 23:54:51 +01:00
\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}
2017-02-05 10:51:38 +01:00
The algorithm consists of consecutive rounds.
On each round, the algorithm selects a new node
that can act as an intermediate node in paths from now on,
2017-05-28 11:07:31 +02:00
and distances are reduced using this node.
2017-01-07 19:50:41 +01:00
2017-02-17 21:13:30 +01:00
On the first round, node 1 is the new intermediate node.
2017-02-05 10:51:38 +01:00
There is a new path between nodes 2 and 4
with length 14, because node 1 connects them.
There is also a new path
2017-01-07 19:50:41 +01:00
between nodes 2 and 5 with length 6.
2016-12-28 23:54:51 +01:00
\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}
2017-02-17 21:13:30 +01:00
On the second round, node 2 is the new intermediate node.
2017-02-05 10:51:38 +01:00
This creates new paths between nodes 1 and 3
2017-01-07 19:50:41 +01:00
and between nodes 3 and 5:
2016-12-28 23:54:51 +01:00
\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}
2017-02-17 21:13:30 +01:00
On the third round, node 3 is the new intermediate round.
2017-01-07 19:50:41 +01:00
There is a new path between nodes 2 and 4:
2016-12-28 23:54:51 +01:00
\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}
2017-01-07 19:50:41 +01:00
The algorithm continues like this,
2017-02-17 21:13:30 +01:00
until all nodes have been appointed intermediate nodes.
2017-01-07 19:50:41 +01:00
After the algorithm has finished, the array contains
2017-02-05 10:51:38 +01:00
the minimum distances between any two nodes:
2016-12-28 23:54:51 +01:00
\begin{center}
\begin{tabular}{r|rrrrr}
& 1 & 2 & 3 & 4 & 5 \\
\hline
1 & 0 & 5 & 7 & 3 & 1 \\
2017-04-24 20:25:58 +02:00
2 & 5 & 0 & 2 & 8 & 6 \\
2016-12-28 23:54:51 +01:00
3 & 7 & 2 & 0 & 7 & 8 \\
2017-04-24 20:25:58 +02:00
4 & 3 & 8 & 7 & 0 & 2 \\
2016-12-28 23:54:51 +01:00
5 & 1 & 6 & 8 & 2 & 0 \\
\end{tabular}
\end{center}
2017-02-17 21:13:30 +01:00
For example, the array tells us that the
shortest distance between nodes 2 and 4 is 8.
2017-01-07 19:50:41 +01:00
This corresponds to the following path:
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$};
\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}
2017-01-07 19:50:41 +01:00
\subsubsection{Implementation}
2016-12-28 23:54:51 +01:00
2017-02-05 10:51:38 +01:00
The advantage of the
2017-01-07 19:50:41 +01:00
FloydWarshall algorithm that it is
easy to implement.
The following code constructs a
2017-05-28 11:07:31 +02:00
distance matrix where $\texttt{distance}[a][b]$
2017-02-17 21:13:30 +01:00
is the shortest distance between nodes $a$ and $b$.
2017-05-28 11:07:31 +02:00
First, the algorithm initializes \texttt{distance}
using the adjacency matrix \texttt{adj} of the graph:
2016-12-28 23:54:51 +01:00
\begin{lstlisting}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
2017-05-28 11:07:31 +02:00
if (i == j) distance[i][j] = 0;
else if (adj[i][j]) distance[i][j] = adj[i][j];
else distance[i][j] = INF;
2016-12-28 23:54:51 +01:00
}
}
\end{lstlisting}
2017-02-17 21:13:30 +01:00
After this, the shortest distances can be found as follows:
2016-12-28 23:54:51 +01:00
\begin{lstlisting}
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
2017-05-28 11:07:31 +02:00
distance[i][j] = min(distance[i][j],
distance[i][k]+distance[k][j]);
2016-12-28 23:54:51 +01:00
}
}
}
\end{lstlisting}
2017-02-05 10:51:38 +01:00
The time complexity of the algorithm is $O(n^3)$,
2017-01-07 19:50:41 +01:00
because it contains three nested loops
that go through the nodes of the graph.
2017-01-07 19:50:41 +01:00
Since the implementation of the FloydWarshall
algorithm is simple, the algorithm can be
2017-02-05 10:51:38 +01:00
a good choice even if it is only needed to find a
2017-01-07 19:50:41 +01:00
single shortest path in the graph.
2017-02-05 10:51:38 +01:00
However, the algorithm can only be used when the graph
2017-03-06 23:57:32 +01:00
is so small that a cubic time complexity is fast enough.