cphb/chapter16.tex

708 lines
24 KiB
TeX
Raw Normal View History

2016-12-28 23:54:51 +01:00
\chapter{Directed graphs}
2017-01-08 15:43:57 +01:00
In this chapter, we focus on two classes of directed graphs:
2016-12-28 23:54:51 +01:00
\begin{itemize}
2017-02-06 01:04:46 +01:00
\item \key{Acyclic graphs}:
2017-01-08 15:43:57 +01:00
There are no cycles in the graph,
2017-02-22 20:36:36 +01:00
so there is no path from any node to itself\footnote{Directed acyclic
graphs are sometimes called DAGs.}.
2017-02-06 01:04:46 +01:00
\item \key{Successor graphs}:
2017-01-08 15:43:57 +01:00
The outdegree of each node is 1,
so each node has a unique successor.
2016-12-28 23:54:51 +01:00
\end{itemize}
2017-01-08 15:43:57 +01:00
It turns out that in both cases,
we can design efficient algorithms that are based
on the special properties of the graphs.
2016-12-28 23:54:51 +01:00
2017-01-08 15:43:57 +01:00
\section{Topological sorting}
2016-12-28 23:54:51 +01:00
2017-01-08 15:43:57 +01:00
\index{topological sorting}
\index{cycle}
2016-12-28 23:54:51 +01:00
2017-05-07 20:18:56 +02:00
A \key{topological sort} is an ordering
2017-01-08 15:43:57 +01:00
of the nodes of a directed graph
2017-02-06 01:04:46 +01:00
such that if there is a path from node $a$ to node $b$,
then node $a$ appears before node $b$ in the ordering.
2017-01-08 15:43:57 +01:00
For example, for the graph
2016-12-28 23:54:51 +01:00
\begin{center}
\begin{tikzpicture}[scale=0.9]
\node[draw, circle] (1) at (1,5) {$1$};
\node[draw, circle] (2) at (3,5) {$2$};
\node[draw, circle] (3) at (5,5) {$3$};
\node[draw, circle] (4) at (1,3) {$4$};
\node[draw, circle] (5) at (3,3) {$5$};
\node[draw, circle] (6) at (5,3) {$6$};
\path[draw,thick,->,>=latex] (1) -- (2);
\path[draw,thick,->,>=latex] (2) -- (3);
\path[draw,thick,->,>=latex] (4) -- (1);
\path[draw,thick,->,>=latex] (4) -- (5);
\path[draw,thick,->,>=latex] (5) -- (2);
\path[draw,thick,->,>=latex] (5) -- (3);
\path[draw,thick,->,>=latex] (3) -- (6);
\end{tikzpicture}
\end{center}
2017-05-29 19:35:23 +02:00
one topological sort is
2016-12-28 23:54:51 +01:00
$[4,1,5,2,3,6]$:
\begin{center}
\begin{tikzpicture}[scale=0.9]
\node[draw, circle] (1) at (-6,0) {$1$};
\node[draw, circle] (2) at (-3,0) {$2$};
\node[draw, circle] (3) at (-1.5,0) {$3$};
\node[draw, circle] (4) at (-7.5,0) {$4$};
\node[draw, circle] (5) at (-4.5,0) {$5$};
\node[draw, circle] (6) at (-0,0) {$6$};
\path[draw,thick,->,>=latex] (1) edge [bend right=30] (2);
\path[draw,thick,->,>=latex] (2) -- (3);
\path[draw,thick,->,>=latex] (4) -- (1);
\path[draw,thick,->,>=latex] (4) edge [bend left=30] (5);
\path[draw,thick,->,>=latex] (5) -- (2);
\path[draw,thick,->,>=latex] (5) edge [bend left=30] (3);
\path[draw,thick,->,>=latex] (3) -- (6);
\end{tikzpicture}
\end{center}
2017-02-06 01:04:46 +01:00
An acyclic graph always has a topological sort.
2017-01-08 15:43:57 +01:00
However, if the graph contains a cycle,
2017-02-06 01:04:46 +01:00
it is not possible to form a topological sort,
2017-05-07 20:18:56 +02:00
because no node of the cycle can appear
before the other nodes of the cycle in the ordering.
2017-02-06 01:04:46 +01:00
It turns out that depth-first search can be used
to both check if a directed graph contains a cycle
and, if it does not contain a cycle, to construct a topological sort.
2016-12-28 23:54:51 +01:00
2017-01-08 15:43:57 +01:00
\subsubsection{Algorithm}
2016-12-28 23:54:51 +01:00
2017-02-17 21:45:59 +01:00
The idea is to go through the nodes of the graph
2017-02-06 01:04:46 +01:00
and always begin a depth-first search at the current node
if it has not been processed yet.
During the searches, the nodes have three possible states:
2016-12-28 23:54:51 +01:00
\begin{itemize}
2017-01-08 15:43:57 +01:00
\item state 0: the node has not been processed (white)
\item state 1: the node is under processing (light gray)
\item state 2: the node has been processed (dark gray)
2016-12-28 23:54:51 +01:00
\end{itemize}
2017-01-08 15:43:57 +01:00
Initially, the state of each node is 0.
When a search reaches a node for the first time,
2017-02-06 01:04:46 +01:00
its state becomes 1.
Finally, after all successors of the node have
2017-02-17 21:45:59 +01:00
been processed, its state becomes 2.
2016-12-28 23:54:51 +01:00
2017-05-07 20:18:56 +02:00
If the graph contains a cycle, we will find this out
2017-02-06 01:04:46 +01:00
during the search, because sooner or later
2017-01-08 15:43:57 +01:00
we will arrive at a node whose state is 1.
In this case, it is not possible to construct a topological sort.
2016-12-28 23:54:51 +01:00
2017-02-06 01:04:46 +01:00
If the graph does not contain a cycle, we can construct
2017-02-17 21:45:59 +01:00
a topological sort by
adding each node to a list when the state of the node becomes 2.
2017-01-08 15:43:57 +01:00
This list in reverse order is a topological sort.
2016-12-28 23:54:51 +01:00
2017-01-08 15:43:57 +01:00
\subsubsection{Example 1}
2016-12-28 23:54:51 +01:00
2017-01-08 15:43:57 +01:00
In the example graph, the search first proceeds
from node 1 to node 6:
2016-12-28 23:54:51 +01:00
\begin{center}
\begin{tikzpicture}[scale=0.9]
\node[draw, circle,fill=gray!20] (1) at (1,5) {$1$};
\node[draw, circle,fill=gray!20] (2) at (3,5) {$2$};
\node[draw, circle,fill=gray!20] (3) at (5,5) {$3$};
\node[draw, circle] (4) at (1,3) {$4$};
\node[draw, circle] (5) at (3,3) {$5$};
\node[draw, circle,fill=gray!80] (6) at (5,3) {$6$};
\path[draw,thick,->,>=latex] (4) -- (1);
\path[draw,thick,->,>=latex] (4) -- (5);
\path[draw,thick,->,>=latex] (5) -- (2);
\path[draw,thick,->,>=latex] (5) -- (3);
%\path[draw,thick,->,>=latex] (3) -- (6);
\path[draw=red,thick,->,line width=2pt] (1) -- (2);
\path[draw=red,thick,->,line width=2pt] (2) -- (3);
\path[draw=red,thick,->,line width=2pt] (3) -- (6);
\end{tikzpicture}
\end{center}
2017-01-08 15:43:57 +01:00
Now node 6 has been processed, so it is added to the list.
2017-02-06 01:04:46 +01:00
After this, also nodes 3, 2 and 1 are added to the list:
2016-12-28 23:54:51 +01:00
\begin{center}
\begin{tikzpicture}[scale=0.9]
\node[draw, circle,fill=gray!80] (1) at (1,5) {$1$};
\node[draw, circle,fill=gray!80] (2) at (3,5) {$2$};
\node[draw, circle,fill=gray!80] (3) at (5,5) {$3$};
\node[draw, circle] (4) at (1,3) {$4$};
\node[draw, circle] (5) at (3,3) {$5$};
\node[draw, circle,fill=gray!80] (6) at (5,3) {$6$};
\path[draw,thick,->,>=latex] (1) -- (2);
\path[draw,thick,->,>=latex] (2) -- (3);
\path[draw,thick,->,>=latex] (4) -- (1);
\path[draw,thick,->,>=latex] (4) -- (5);
\path[draw,thick,->,>=latex] (5) -- (2);
\path[draw,thick,->,>=latex] (5) -- (3);
\path[draw,thick,->,>=latex] (3) -- (6);
\end{tikzpicture}
\end{center}
2017-02-06 01:04:46 +01:00
At this point, the list is $[6,3,2,1]$.
2017-01-08 15:43:57 +01:00
The next search begins at node 4:
2016-12-28 23:54:51 +01:00
\begin{center}
\begin{tikzpicture}[scale=0.9]
\node[draw, circle,fill=gray!80] (1) at (1,5) {$1$};
\node[draw, circle,fill=gray!80] (2) at (3,5) {$2$};
\node[draw, circle,fill=gray!80] (3) at (5,5) {$3$};
\node[draw, circle,fill=gray!20] (4) at (1,3) {$4$};
\node[draw, circle,fill=gray!80] (5) at (3,3) {$5$};
\node[draw, circle,fill=gray!80] (6) at (5,3) {$6$};
\path[draw,thick,->,>=latex] (1) -- (2);
\path[draw,thick,->,>=latex] (2) -- (3);
\path[draw,thick,->,>=latex] (4) -- (1);
%\path[draw,thick,->,>=latex] (4) -- (5);
\path[draw,thick,->,>=latex] (5) -- (2);
\path[draw,thick,->,>=latex] (5) -- (3);
\path[draw,thick,->,>=latex] (3) -- (6);
\path[draw=red,thick,->,line width=2pt] (4) -- (5);
\end{tikzpicture}
\end{center}
2017-01-08 15:43:57 +01:00
Thus, the final list is $[6,3,2,1,5,4]$.
We have processed all nodes, so a topological sort has
been found.
The topological sort is the reverse list
$[4,5,1,2,3,6]$:
2016-12-28 23:54:51 +01:00
\begin{center}
\begin{tikzpicture}[scale=0.9]
\node[draw, circle] (1) at (3,0) {$1$};
\node[draw, circle] (2) at (4.5,0) {$2$};
\node[draw, circle] (3) at (6,0) {$3$};
\node[draw, circle] (4) at (0,0) {$4$};
\node[draw, circle] (5) at (1.5,0) {$5$};
\node[draw, circle] (6) at (7.5,0) {$6$};
\path[draw,thick,->,>=latex] (1) -- (2);
\path[draw,thick,->,>=latex] (2) -- (3);
\path[draw,thick,->,>=latex] (4) edge [bend left=30] (1);
\path[draw,thick,->,>=latex] (4) -- (5);
\path[draw,thick,->,>=latex] (5) edge [bend right=30] (2);
\path[draw,thick,->,>=latex] (5) edge [bend right=40] (3);
\path[draw,thick,->,>=latex] (3) -- (6);
\end{tikzpicture}
\end{center}
2017-01-08 15:43:57 +01:00
Note that a topological sort is not unique,
2017-05-07 20:18:56 +02:00
and there can be several topological sorts for a graph.
2016-12-28 23:54:51 +01:00
2017-01-08 15:43:57 +01:00
\subsubsection{Example 2}
2016-12-28 23:54:51 +01:00
2017-02-06 01:04:46 +01:00
Let us now consider a graph for which we
cannot construct a topological sort,
2017-05-29 19:35:23 +02:00
because the graph contains a cycle:
2016-12-28 23:54:51 +01:00
\begin{center}
\begin{tikzpicture}[scale=0.9]
\node[draw, circle] (1) at (1,5) {$1$};
\node[draw, circle] (2) at (3,5) {$2$};
\node[draw, circle] (3) at (5,5) {$3$};
\node[draw, circle] (4) at (1,3) {$4$};
\node[draw, circle] (5) at (3,3) {$5$};
\node[draw, circle] (6) at (5,3) {$6$};
\path[draw,thick,->,>=latex] (1) -- (2);
\path[draw,thick,->,>=latex] (2) -- (3);
\path[draw,thick,->,>=latex] (4) -- (1);
\path[draw,thick,->,>=latex] (4) -- (5);
\path[draw,thick,->,>=latex] (5) -- (2);
\path[draw,thick,->,>=latex] (3) -- (5);
\path[draw,thick,->,>=latex] (3) -- (6);
\end{tikzpicture}
\end{center}
2017-02-06 01:04:46 +01:00
The search proceeds as follows:
2016-12-28 23:54:51 +01:00
\begin{center}
\begin{tikzpicture}[scale=0.9]
\node[draw, circle,fill=gray!20] (1) at (1,5) {$1$};
\node[draw, circle,fill=gray!20] (2) at (3,5) {$2$};
\node[draw, circle,fill=gray!20] (3) at (5,5) {$3$};
\node[draw, circle] (4) at (1,3) {$4$};
\node[draw, circle,fill=gray!20] (5) at (3,3) {$5$};
\node[draw, circle] (6) at (5,3) {$6$};
\path[draw,thick,->,>=latex] (4) -- (1);
\path[draw,thick,->,>=latex] (4) -- (5);
\path[draw,thick,->,>=latex] (3) -- (6);
\path[draw=red,thick,->,line width=2pt] (1) -- (2);
\path[draw=red,thick,->,line width=2pt] (2) -- (3);
\path[draw=red,thick,->,line width=2pt] (3) -- (5);
\path[draw=red,thick,->,line width=2pt] (5) -- (2);
\end{tikzpicture}
\end{center}
2017-02-06 01:04:46 +01:00
The search reaches node 2 whose state is 1,
2017-05-29 19:35:23 +02:00
which means that the graph contains a cycle.
2017-05-07 20:18:56 +02:00
In this example, there is a cycle
$2 \rightarrow 3 \rightarrow 5 \rightarrow 2$.
2016-12-28 23:54:51 +01:00
2017-01-08 16:07:23 +01:00
\section{Dynamic programming}
If a directed graph is acyclic,
dynamic programming can be applied to it.
2017-02-06 01:04:46 +01:00
For example, we can efficiently solve the following
2017-01-08 16:07:23 +01:00
problems concerning paths from a starting node
2017-02-06 01:04:46 +01:00
to an ending node:
2017-01-08 16:07:23 +01:00
2016-12-28 23:54:51 +01:00
\begin{itemize}
2017-01-08 16:07:23 +01:00
\item how many different paths are there?
\item what is the shortest/longest path?
\item what is the minimum/maximum number of edges in a path?
2017-02-06 01:04:46 +01:00
\item which nodes certainly appear in any path?
2016-12-28 23:54:51 +01:00
\end{itemize}
2017-01-08 16:07:23 +01:00
\subsubsection{Counting the number of paths}
2017-02-06 01:04:46 +01:00
As an example, let us calculate the number of paths
2017-05-07 20:18:56 +02:00
from node 1 to node 6 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,5) {$1$};
\node[draw, circle] (2) at (3,5) {$2$};
\node[draw, circle] (3) at (5,5) {$3$};
\node[draw, circle] (4) at (1,3) {$4$};
\node[draw, circle] (5) at (3,3) {$5$};
\node[draw, circle] (6) at (5,3) {$6$};
\path[draw,thick,->,>=latex] (1) -- (2);
\path[draw,thick,->,>=latex] (2) -- (3);
2017-05-07 20:18:56 +02:00
\path[draw,thick,->,>=latex] (1) -- (4);
2016-12-28 23:54:51 +01:00
\path[draw,thick,->,>=latex] (4) -- (5);
\path[draw,thick,->,>=latex] (5) -- (2);
\path[draw,thick,->,>=latex] (5) -- (3);
\path[draw,thick,->,>=latex] (3) -- (6);
\end{tikzpicture}
\end{center}
2017-02-06 01:04:46 +01:00
There are a total of three such paths:
2016-12-28 23:54:51 +01:00
\begin{itemize}
2017-05-07 20:18:56 +02:00
\item $1 \rightarrow 2 \rightarrow 3 \rightarrow 6$
\item $1 \rightarrow 4 \rightarrow 5 \rightarrow 2 \rightarrow 3 \rightarrow 6$
\item $1 \rightarrow 4 \rightarrow 5 \rightarrow 3 \rightarrow 6$
2016-12-28 23:54:51 +01:00
\end{itemize}
2017-05-29 19:35:23 +02:00
Let $\texttt{paths}(x)$ denote the number of paths from
2017-05-07 20:18:56 +02:00
node 1 to node $x$.
2017-05-29 19:35:23 +02:00
As a base case, $\texttt{paths}(1)=1$.
Then, to calculate other values of $\texttt{paths}(x)$,
2017-05-07 20:18:56 +02:00
we may use the recursion
2017-05-29 19:35:23 +02:00
\[\texttt{paths}(x) = \texttt{paths}(a_1)+\texttt{paths}(a_2)+\cdots+\texttt{paths}(a_k)\]
2017-05-07 20:18:56 +02:00
where $a_1,a_2,\ldots,a_k$ are the nodes from which there
is an edge to $x$.
2017-05-29 19:35:23 +02:00
Since the graph is acyclic, the values of $\texttt{paths}(x)$
2017-05-07 20:18:56 +02:00
can be calculated in the order of a topological sort.
2017-02-17 21:45:59 +01:00
A topological sort for the above graph is as follows:
2016-12-28 23:54:51 +01:00
\begin{center}
\begin{tikzpicture}[scale=0.9]
\node[draw, circle] (1) at (3,0) {$1$};
\node[draw, circle] (2) at (4.5,0) {$2$};
\node[draw, circle] (3) at (6,0) {$3$};
\node[draw, circle] (4) at (0,0) {$4$};
\node[draw, circle] (5) at (1.5,0) {$5$};
\node[draw, circle] (6) at (7.5,0) {$6$};
\path[draw,thick,->,>=latex] (1) -- (2);
\path[draw,thick,->,>=latex] (2) -- (3);
\path[draw,thick,->,>=latex] (4) edge [bend left=30] (1);
\path[draw,thick,->,>=latex] (4) -- (5);
\path[draw,thick,->,>=latex] (5) edge [bend right=30] (2);
\path[draw,thick,->,>=latex] (5) edge [bend right=40] (3);
\path[draw,thick,->,>=latex] (3) -- (6);
\end{tikzpicture}
\end{center}
2017-02-17 21:45:59 +01:00
Hence, the numbers of paths are as follows:
2016-12-28 23:54:51 +01:00
\begin{center}
\begin{tikzpicture}[scale=0.9]
\node[draw, circle] (1) at (1,5) {$1$};
\node[draw, circle] (2) at (3,5) {$2$};
\node[draw, circle] (3) at (5,5) {$3$};
\node[draw, circle] (4) at (1,3) {$4$};
\node[draw, circle] (5) at (3,3) {$5$};
\node[draw, circle] (6) at (5,3) {$6$};
\path[draw,thick,->,>=latex] (1) -- (2);
\path[draw,thick,->,>=latex] (2) -- (3);
2017-05-07 20:18:56 +02:00
\path[draw,thick,->,>=latex] (1) -- (4);
2016-12-28 23:54:51 +01:00
\path[draw,thick,->,>=latex] (4) -- (5);
\path[draw,thick,->,>=latex] (5) -- (2);
\path[draw,thick,->,>=latex] (5) -- (3);
\path[draw,thick,->,>=latex] (3) -- (6);
\node[color=red] at (1,2.3) {$1$};
\node[color=red] at (3,2.3) {$1$};
\node[color=red] at (5,2.3) {$3$};
\node[color=red] at (1,5.7) {$1$};
\node[color=red] at (3,5.7) {$2$};
\node[color=red] at (5,5.7) {$3$};
\end{tikzpicture}
\end{center}
2017-05-29 19:35:23 +02:00
For example, to calculate the value of $\texttt{paths}(3)$,
we can use the formula $\texttt{paths}(2)+\texttt{paths}(5)$,
2017-05-07 20:18:56 +02:00
because there are edges from nodes 2 and 5
to node 3.
2017-05-29 19:35:23 +02:00
Since $\texttt{paths}(2)=2$ and $\texttt{paths}(5)=1$, we conclude that $\texttt{paths}(3)=3$.
2016-12-28 23:54:51 +01:00
2017-01-08 16:07:23 +01:00
\subsubsection{Extending Dijkstra's algorithm}
2016-12-28 23:54:51 +01:00
2017-01-08 16:07:23 +01:00
\index{Dijkstra's algorithm}
2016-12-28 23:54:51 +01:00
2017-01-08 16:07:23 +01:00
A by-product of Dijkstra's algorithm is a directed, acyclic
2017-05-07 20:18:56 +02:00
graph that indicates for each node of the original graph
2017-01-08 16:07:23 +01:00
the possible ways to reach the node using a shortest path
from the starting node.
2017-02-17 21:45:59 +01:00
Dynamic programming can be applied to that graph.
2017-01-08 16:07:23 +01:00
For example, in the graph
2016-12-28 23:54:51 +01:00
\begin{center}
\begin{tikzpicture}
\node[draw, circle] (1) at (0,0) {$1$};
\node[draw, circle] (2) at (2,0) {$2$};
\node[draw, circle] (3) at (0,-2) {$3$};
\node[draw, circle] (4) at (2,-2) {$4$};
\node[draw, circle] (5) at (4,-1) {$5$};
\path[draw,thick,-] (1) -- node[font=\small,label=above:3] {} (2);
\path[draw,thick,-] (1) -- node[font=\small,label=left:5] {} (3);
\path[draw,thick,-] (2) -- node[font=\small,label=right:4] {} (4);
\path[draw,thick,-] (2) -- node[font=\small,label=above:8] {} (5);
\path[draw,thick,-] (3) -- node[font=\small,label=below:2] {} (4);
\path[draw,thick,-] (4) -- node[font=\small,label=below:1] {} (5);
\path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (3);
\end{tikzpicture}
\end{center}
2017-02-17 21:45:59 +01:00
the shortest paths from node 1 may use the following edges:
2016-12-28 23:54:51 +01:00
\begin{center}
\begin{tikzpicture}
\node[draw, circle] (1) at (0,0) {$1$};
\node[draw, circle] (2) at (2,0) {$2$};
\node[draw, circle] (3) at (0,-2) {$3$};
\node[draw, circle] (4) at (2,-2) {$4$};
\node[draw, circle] (5) at (4,-1) {$5$};
\path[draw,thick,->] (1) -- node[font=\small,label=above:3] {} (2);
\path[draw,thick,->] (1) -- node[font=\small,label=left:5] {} (3);
\path[draw,thick,->] (2) -- node[font=\small,label=right:4] {} (4);
\path[draw,thick,->] (3) -- node[font=\small,label=below:2] {} (4);
\path[draw,thick,->] (4) -- node[font=\small,label=below:1] {} (5);
\path[draw,thick,->] (2) -- node[font=\small,label=above:2] {} (3);
\end{tikzpicture}
\end{center}
2017-01-08 16:07:23 +01:00
Now we can, for example, calculate the number of
shortest paths from node 1 to node 5
using dynamic programming:
2016-12-28 23:54:51 +01:00
\begin{center}
\begin{tikzpicture}
\node[draw, circle] (1) at (0,0) {$1$};
\node[draw, circle] (2) at (2,0) {$2$};
\node[draw, circle] (3) at (0,-2) {$3$};
\node[draw, circle] (4) at (2,-2) {$4$};
\node[draw, circle] (5) at (4,-1) {$5$};
\path[draw,thick,->] (1) -- node[font=\small,label=above:3] {} (2);
\path[draw,thick,->] (1) -- node[font=\small,label=left:5] {} (3);
\path[draw,thick,->] (2) -- node[font=\small,label=right:4] {} (4);
\path[draw,thick,->] (3) -- node[font=\small,label=below:2] {} (4);
\path[draw,thick,->] (4) -- node[font=\small,label=below:1] {} (5);
\path[draw,thick,->] (2) -- node[font=\small,label=above:2] {} (3);
\node[color=red] at (0,0.7) {$1$};
\node[color=red] at (2,0.7) {$1$};
\node[color=red] at (0,-2.7) {$2$};
\node[color=red] at (2,-2.7) {$3$};
\node[color=red] at (4,-1.7) {$3$};
\end{tikzpicture}
\end{center}
2017-01-08 16:07:23 +01:00
\subsubsection{Representing problems as graphs}
2016-12-28 23:54:51 +01:00
2017-01-08 16:07:23 +01:00
Actually, any dynamic programming problem
can be represented as a directed, acyclic graph.
2017-02-17 21:45:59 +01:00
In such a graph, each node corresponds to a dynamic programming state
2017-01-08 16:07:23 +01:00
and the edges indicate how the states depend on each other.
2016-12-28 23:54:51 +01:00
2017-01-08 16:07:23 +01:00
As an example, consider the problem
2017-05-18 23:24:08 +02:00
of forming a sum of money $n$
2017-01-08 16:07:23 +01:00
using coins
2016-12-28 23:54:51 +01:00
$\{c_1,c_2,\ldots,c_k\}$.
2017-02-17 21:45:59 +01:00
In this problem, we can construct a graph where
2017-01-08 16:07:23 +01:00
each node corresponds to a sum of money,
2017-02-06 01:04:46 +01:00
and the edges show how the coins can be chosen.
2017-05-18 23:24:08 +02:00
For example, for coins $\{1,3,4\}$ and $n=6$,
2017-01-08 16:07:23 +01:00
the graph is as follows:
2016-12-28 23:54:51 +01:00
\begin{center}
\begin{tikzpicture}[scale=0.9]
\node[draw, circle] (0) at (0,0) {$0$};
\node[draw, circle] (1) at (2,0) {$1$};
\node[draw, circle] (2) at (4,0) {$2$};
\node[draw, circle] (3) at (6,0) {$3$};
\node[draw, circle] (4) at (8,0) {$4$};
\node[draw, circle] (5) at (10,0) {$5$};
\node[draw, circle] (6) at (12,0) {$6$};
\path[draw,thick,->] (0) -- (1);
\path[draw,thick,->] (1) -- (2);
\path[draw,thick,->] (2) -- (3);
\path[draw,thick,->] (3) -- (4);
\path[draw,thick,->] (4) -- (5);
\path[draw,thick,->] (5) -- (6);
\path[draw,thick,->] (0) edge [bend right=30] (3);
\path[draw,thick,->] (1) edge [bend right=30] (4);
\path[draw,thick,->] (2) edge [bend right=30] (5);
\path[draw,thick,->] (3) edge [bend right=30] (6);
\path[draw,thick,->] (0) edge [bend left=30] (4);
\path[draw,thick,->] (1) edge [bend left=30] (5);
\path[draw,thick,->] (2) edge [bend left=30] (6);
\end{tikzpicture}
\end{center}
2017-01-08 16:07:23 +01:00
Using this representation,
2017-05-18 23:24:08 +02:00
the shortest path from node 0 to node $n$
2017-05-07 20:18:56 +02:00
corresponds to a solution with the minimum number of coins,
2017-05-18 23:24:08 +02:00
and the total number of paths from node 0 to node $n$
2017-01-08 16:07:23 +01:00
equals the total number of solutions.
2016-12-28 23:54:51 +01:00
2017-01-08 16:36:46 +01:00
\section{Successor paths}
\index{successor graph}
\index{functional graph}
For the rest of the chapter,
2017-05-07 20:18:56 +02:00
we will focus on \key{successor graphs}.
In those graphs,
the outdegree of each node is 1, i.e.,
2017-02-17 21:45:59 +01:00
exactly one edge starts at each node.
2017-02-06 01:04:46 +01:00
A successor graph consists of one or more
components, each of which contains
2017-01-08 16:36:46 +01:00
one cycle and some paths that lead to it.
Successor graphs are sometimes called
\key{functional graphs}.
The reason for this is that any successor graph
2017-05-29 19:35:23 +02:00
corresponds to a function that defines
2017-05-07 20:18:56 +02:00
the edges of the graph.
The parameter for the function is a node of the graph,
and the function gives the successor of that node.
2016-12-28 23:54:51 +01:00
2017-01-08 16:36:46 +01:00
For example, the function
2016-12-28 23:54:51 +01:00
\begin{center}
\begin{tabular}{r|rrrrrrrrr}
$x$ & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 \\
\hline
2017-05-29 19:35:23 +02:00
$\texttt{succ}(x)$ & 3 & 5 & 7 & 6 & 2 & 2 & 1 & 6 & 3 \\
2016-12-28 23:54:51 +01:00
\end{tabular}
\end{center}
2017-01-08 16:36:46 +01:00
defines 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,0) {$2$};
\node[draw, circle] (3) at (-2,0) {$3$};
\node[draw, circle] (4) at (1,-3) {$4$};
\node[draw, circle] (5) at (4,0) {$5$};
\node[draw, circle] (6) at (2,-1.5) {$6$};
\node[draw, circle] (7) at (-2,-1.5) {$7$};
\node[draw, circle] (8) at (3,-3) {$8$};
\node[draw, circle] (9) at (-4,0) {$9$};
\path[draw,thick,->] (1) -- (3);
\path[draw,thick,->] (2) edge [bend left=40] (5);
\path[draw,thick,->] (3) -- (7);
\path[draw,thick,->] (4) -- (6);
\path[draw,thick,->] (5) edge [bend left=40] (2);
\path[draw,thick,->] (6) -- (2);
\path[draw,thick,->] (7) -- (1);
\path[draw,thick,->] (8) -- (6);
\path[draw,thick,->] (9) -- (3);
\end{tikzpicture}
\end{center}
2017-05-07 20:18:56 +02:00
Since each node of a successor graph has a
2017-05-29 19:35:23 +02:00
unique successor, we can also define a function $\texttt{succ}(x,k)$
2017-04-19 20:08:54 +02:00
that gives the node that we will reach if
2017-01-08 16:36:46 +01:00
we begin at node $x$ and walk $k$ steps forward.
2017-05-29 19:35:23 +02:00
For example, in the above graph $\texttt{succ}(4,6)=2$,
2017-02-06 01:04:46 +01:00
because we will reach node 2 by walking 6 steps from node 4:
2016-12-28 23:54:51 +01:00
\begin{center}
\begin{tikzpicture}[scale=0.9]
\node[draw, circle] (1) at (0,0) {$4$};
\node[draw, circle] (2) at (1.5,0) {$6$};
\node[draw, circle] (3) at (3,0) {$2$};
\node[draw, circle] (4) at (4.5,0) {$5$};
\node[draw, circle] (5) at (6,0) {$2$};
\node[draw, circle] (6) at (7.5,0) {$5$};
\node[draw, circle] (7) at (9,0) {$2$};
\path[draw,thick,->] (1) -- (2);
\path[draw,thick,->] (2) -- (3);
\path[draw,thick,->] (3) -- (4);
\path[draw,thick,->] (4) -- (5);
\path[draw,thick,->] (5) -- (6);
\path[draw,thick,->] (6) -- (7);
\end{tikzpicture}
\end{center}
2017-05-29 19:35:23 +02:00
A straightforward way to calculate a value of $\texttt{succ}(x,k)$
2017-02-06 01:04:46 +01:00
is to start at node $x$ and walk $k$ steps forward, which takes $O(k)$ time.
2017-05-29 19:35:23 +02:00
However, using preprocessing, any value of $\texttt{succ}(x,k)$
2017-02-06 01:04:46 +01:00
can be calculated in only $O(\log k)$ time.
2016-12-28 23:54:51 +01:00
2017-05-29 19:35:23 +02:00
The idea is to precalculate all values of $\texttt{succ}(x,k)$ where
2017-02-06 01:04:46 +01:00
$k$ is a power of two and at most $u$, where $u$ is
2017-01-08 16:36:46 +01:00
the maximum number of steps we will ever walk.
2017-02-06 01:04:46 +01:00
This can be efficiently done, because
2017-01-08 16:36:46 +01:00
we can use the following recursion:
2016-12-28 23:54:51 +01:00
\begin{equation*}
2017-05-29 19:35:23 +02:00
\texttt{succ}(x,k) = \begin{cases}
\texttt{succ}(x) & k = 1\\
\texttt{succ}(\texttt{succ}(x,k/2),k/2) & k > 1\\
2016-12-28 23:54:51 +01:00
\end{cases}
\end{equation*}
2017-05-07 20:18:56 +02:00
Precalculating the values takes $O(n \log u)$ time,
2017-02-06 01:04:46 +01:00
because $O(\log u)$ values are calculated for each node.
2017-01-08 16:36:46 +01:00
In the above graph, the first values are as follows:
2016-12-28 23:54:51 +01:00
\begin{center}
\begin{tabular}{r|rrrrrrrrr}
$x$ & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 \\
\hline
2017-05-29 19:35:23 +02:00
$\texttt{succ}(x,1)$ & 3 & 5 & 7 & 6 & 2 & 2 & 1 & 6 & 3 \\
$\texttt{succ}(x,2)$ & 7 & 2 & 1 & 2 & 5 & 5 & 3 & 2 & 7 \\
$\texttt{succ}(x,4)$ & 3 & 2 & 7 & 2 & 5 & 5 & 1 & 2 & 3 \\
$\texttt{succ}(x,8)$ & 7 & 2 & 1 & 2 & 5 & 5 & 3 & 2 & 7 \\
2016-12-28 23:54:51 +01:00
$\cdots$ \\
\end{tabular}
\end{center}
2017-05-29 19:35:23 +02:00
After this, any value of $\texttt{succ}(x,k)$ can be calculated
2017-02-06 01:04:46 +01:00
by presenting the number of steps $k$ as a sum of powers of two.
2017-05-29 19:35:23 +02:00
For example, if we want to calculate the value of $\texttt{succ}(x,11)$,
2017-01-08 16:36:46 +01:00
we first form the representation $11=8+2+1$.
2017-02-17 21:45:59 +01:00
Using that,
2017-05-29 19:35:23 +02:00
\[\texttt{succ}(x,11)=\texttt{succ}(\texttt{succ}(\texttt{succ}(x,8),2),1).\]
2017-02-17 21:45:59 +01:00
For example, in the previous graph
2017-05-29 19:35:23 +02:00
\[\texttt{succ}(4,11)=\texttt{succ}(\texttt{succ}(\texttt{succ}(4,8),2),1)=5.\]
2016-12-28 23:54:51 +01:00
2017-01-08 16:36:46 +01:00
Such a representation always consists of
2017-05-29 19:35:23 +02:00
$O(\log k)$ parts, so calculating a value of $\texttt{succ}(x,k)$
2017-01-08 16:36:46 +01:00
takes $O(\log k)$ time.
2016-12-28 23:54:51 +01:00
2017-01-08 16:36:46 +01:00
\section{Cycle detection}
2016-12-28 23:54:51 +01:00
2017-01-08 16:36:46 +01:00
\index{cycle}
\index{cycle detection}
2016-12-28 23:54:51 +01:00
2017-02-06 01:04:46 +01:00
Consider a successor graph that only contains
a path that ends in a cycle.
2017-05-07 20:18:56 +02:00
We may ask the following questions:
2017-02-17 21:45:59 +01:00
if we begin our walk at the starting node,
2017-02-06 01:04:46 +01:00
what is the first node in the cycle
and how many nodes does the cycle contain?
2017-01-08 16:36:46 +01:00
For example, in the graph
2016-12-28 23:54:51 +01:00
\begin{center}
\begin{tikzpicture}[scale=0.9]
\node[draw, circle] (5) at (0,0) {$5$};
\node[draw, circle] (4) at (-2,0) {$4$};
\node[draw, circle] (6) at (-1,1.5) {$6$};
\node[draw, circle] (3) at (-4,0) {$3$};
\node[draw, circle] (2) at (-6,0) {$2$};
\node[draw, circle] (1) at (-8,0) {$1$};
\path[draw,thick,->] (1) -- (2);
\path[draw,thick,->] (2) -- (3);
\path[draw,thick,->] (3) -- (4);
\path[draw,thick,->] (4) -- (5);
\path[draw,thick,->] (5) -- (6);
\path[draw,thick,->] (6) -- (4);
\end{tikzpicture}
\end{center}
2017-02-06 01:04:46 +01:00
we begin our walk at node 1,
the first node that belongs to the cycle is node 4, and the cycle consists
2017-01-08 16:36:46 +01:00
of three nodes (4, 5 and 6).
2017-05-29 19:35:23 +02:00
A simple way to detect the cycle is to walk in the
2017-02-06 01:04:46 +01:00
graph and keep track of
all nodes that have been visited. Once a node is visited
for the second time, we can conclude
2017-02-17 21:45:59 +01:00
that the node is the first node in the cycle.
2017-01-08 16:36:46 +01:00
This method works in $O(n)$ time and also uses
$O(n)$ memory.
However, there are better algorithms for cycle detection.
2017-02-17 21:45:59 +01:00
The time complexity of such algorithms is still $O(n)$,
2017-01-08 16:36:46 +01:00
but they only use $O(1)$ memory.
This is an important improvement if $n$ is large.
2017-02-06 01:04:46 +01:00
Next we will discuss Floyd's algorithm that
2017-01-08 16:36:46 +01:00
achieves these properties.
\subsubsection{Floyd's algorithm}
\index{Floyd's algorithm}
2017-02-25 17:21:27 +01:00
\key{Floyd's algorithm}\footnote{The idea of the algorithm is mentioned in \cite{knu982}
2017-02-27 21:13:33 +01:00
and attributed to R. W. Floyd; however, it is not known if Floyd actually
discovered the algorithm.} walks forward
2017-01-08 16:36:46 +01:00
in the graph using two pointers $a$ and $b$.
2017-02-17 21:45:59 +01:00
Both pointers begin at a node $x$ that
is the starting node of the graph.
2017-02-06 01:04:46 +01:00
Then, on each turn, the pointer $a$ walks
one step forward and the pointer $b$
2017-01-08 16:36:46 +01:00
walks two steps forward.
2017-02-06 01:04:46 +01:00
The process continues until
the pointers meet each other:
2016-12-28 23:54:51 +01:00
\begin{lstlisting}
2017-05-29 19:35:23 +02:00
a = succ(x);
b = succ(succ(x));
2016-12-28 23:54:51 +01:00
while (a != b) {
2017-05-29 19:35:23 +02:00
a = succ(a);
b = succ(succ(b));
2016-12-28 23:54:51 +01:00
}
\end{lstlisting}
2017-02-06 01:04:46 +01:00
At this point, the pointer $a$ has walked $k$ steps
and the pointer $b$ has walked $2k$ steps,
so the length of the cycle divides $k$.
2017-01-08 16:36:46 +01:00
Thus, the first node that belongs to the cycle
2017-02-17 21:45:59 +01:00
can be found by moving the pointer $a$ to node $x$
and advancing the pointers
2017-05-29 19:35:23 +02:00
step by step until they meet again.
2016-12-28 23:54:51 +01:00
\begin{lstlisting}
a = x;
while (a != b) {
2017-05-29 19:35:23 +02:00
a = succ(a);
b = succ(b);
2016-12-28 23:54:51 +01:00
}
2017-05-29 19:35:23 +02:00
first = a;
2016-12-28 23:54:51 +01:00
\end{lstlisting}
2017-05-29 19:35:23 +02:00
After this, the length of the cycle
2017-01-08 16:36:46 +01:00
can be calculated as follows:
2016-12-28 23:54:51 +01:00
\begin{lstlisting}
2017-05-29 19:35:23 +02:00
b = succ(a);
length = 1;
2016-12-28 23:54:51 +01:00
while (a != b) {
2017-05-29 19:35:23 +02:00
b = succ(b);
length++;
2016-12-28 23:54:51 +01:00
}
2017-02-18 12:01:56 +01:00
\end{lstlisting}