cphb/chapter13.tex

293 lines
10 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}
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 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
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.
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.