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.
|
|
|
|
|
|
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
|
2021-02-08 23:10:42 +01:00
|
|
|
|
paths from the starting node to all nodes of the graph.
|
|
|
|
|
Dijkstra's algorithm is very efficient and can be used for
|
2017-01-07 19:36:06 +01:00
|
|
|
|
processing large graphs.
|
|
|
|
|
However, the algorithm requires that there
|
|
|
|
|
are no negative weight edges in the graph.
|
|
|
|
|
|
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 Bellman–Ford 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.
|
2017-04-17 14:27:43 +02:00
|
|
|
|
The graph is stored as adjacency lists
|
2021-02-08 23:10:42 +01:00
|
|
|
|
so that \texttt{g[$v$]} contains a pair $(w,\text{cost})$
|
|
|
|
|
always when there is an edge from node $v$ to node $w$
|
|
|
|
|
with weight $\text{cost}$.
|
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
|
2021-02-08 23:10:42 +01:00
|
|
|
|
\texttt{pq} contains pairs of the form $(d,x)$,
|
2017-05-07 20:18:56 +02:00
|
|
|
|
meaning that the current distance to node $x$ is $d$.
|
2021-02-08 23:10:42 +01:00
|
|
|
|
|
2017-05-28 11:07:31 +02:00
|
|
|
|
The array $\texttt{distance}$ contains the distance to
|
2021-02-08 23:10:42 +01:00
|
|
|
|
each node. Initially, the distance is $0$ to $\text{start}$ and $-1$ to all
|
|
|
|
|
other nodes. We use $-1$ as invalid value to denote that the node
|
|
|
|
|
has not been reached yet.
|
2016-12-28 23:54:51 +01:00
|
|
|
|
|
|
|
|
|
\begin{lstlisting}
|
2021-02-08 23:10:42 +01:00
|
|
|
|
vector<int> distance(n, -1);
|
|
|
|
|
priority_queue<pair<int, int>,
|
|
|
|
|
vector<pair<int, int>>,
|
|
|
|
|
greater<pair<int, int>>> pq;
|
|
|
|
|
distance[start] = 0;
|
|
|
|
|
pq.emplace(0, start);
|
|
|
|
|
while (!pq.empty()) {
|
|
|
|
|
auto [d, v] = q.top();
|
|
|
|
|
q.pop();
|
|
|
|
|
if (distance[v] != -1) continue;
|
|
|
|
|
distance[v] = d;
|
|
|
|
|
for (auto [w, cost] : g[v])
|
|
|
|
|
q.emplace(d + cost, w);
|
2016-12-28 23:54:51 +01:00
|
|
|
|
}
|
|
|
|
|
\end{lstlisting}
|
|
|
|
|
|
2021-02-08 23:10:42 +01:00
|
|
|
|
Note that the type of the priority queue is not
|
|
|
|
|
\verb|priority_queue<pair<int, int>| but instead
|
|
|
|
|
\verb|priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>|.
|
|
|
|
|
This is because in C++, a priority queue by default puts the
|
|
|
|
|
\emph{largest} element on top, so we reverse the ordering by changing
|
|
|
|
|
the comparison operator from \verb|less| (the default) to
|
|
|
|
|
\verb|greater| (which does the opposite).
|
|
|
|
|
|
|
|
|
|
In case you forget, you can look up the syntax for the priority queue
|
|
|
|
|
in the C++ cheatsheet linked on the camp page.
|
|
|
|
|
|
2017-04-23 12:24:13 +02:00
|
|
|
|
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-04-17 14:27:43 +02:00
|
|
|
|
|
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.
|