1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
|
\documentclass[11pt]{report}
\usepackage[leqno]{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}
\usepackage{amsthm}
\usepackage{latexsym}
\usepackage{jweb}
\usepackage{times}
\usepackage{graphicx}
\usepackage[nolineno]{lgrind}
\newif\ifpdf
\ifx\pdfoutput\undefined
\pdffalse
\else
\pdfoutput=1
\pdftrue
\fi
\ifpdf
\usepackage[
pdftex,
colorlinks=true, % change to true for the electronic version
linkcolor=blue,filecolor=blue,pagecolor=blue,urlcolor=blue
]{hyperref}
\newcommand{\myhyperref}[2]{\hyperref[#1]{#2}}
\fi
\newcommand{\mtlfig}[2]{\centerline{\includegraphics*[#2]{#1.pdf}}}
\newcommand{\Path}{\rightsquigarrow}
\newcommand{\ancestor}{\overset{T}{\rightsquigarrow}}
\newcommand{\descendant}{\ancestor^{-1}}
\newcommand{\backedge}{\overset{B}{\rightarrow}}
\newcommand{\edge}{\rightarrow}
\DeclareMathOperator{\suchthat}{s.t.}
\newcommand{\code}[1]{{\small{\em \textbf{#1}}}}
\newcommand{\concept}[1]{\textsf{#1}}
\begin{document}
\title{An Implementation of Biconnected Components}
\author{Jeremy Siek}
\maketitle
\section{Introduction}
This paper documents the implementation of the
\code{biconnected\_components()} function of the Boost Graph
Library. The function was implemented by Jeremy Siek.
The algorithm used to implement the \code{biconnected\_components()}
function is the one based on depth-first search described
by Tarjan~\cite{tarjan72:dfs_and_linear_algo}.
An undirected graph $G = (V,E)$ is \emph{biconnected} if for each
triple of distinct vertices $v, w, a \in V$ there is a path $p : v
\Path w$ such that $a$ is not on the path $p$. An \emph{articulation
point} of $G = (V,E)$ is a vertex $a \in V$ where there are two other
distinct vertices $v,w \in V$ such that $a$ is on any path $p:v \Path
w$ and there is at least one such path. If $a$ were to be removed from
$G$, then $v$ and $w$ would no longer be reachable from each other.
So articulation points act as bridges between biconnected components;
the only path from one biconnected component to another is through an
articulation point.
The algorithm finds articulation points based on information provided
by depth-first search. During a DFS, we label each vertex $v \in G$
with its discover time, denoted $d[v]$. During the DFS we also
compute the $lowpt(v)$, which is the smallest (in terms of discover
time) vertex reachable from $v$ by traversing zero or more DFS-tree
edges followed by at most one back edge. Tree edges and back edges can
be identified based on discover time because for tree edge $(u,v)$ we
have $d[u] < d[v]$ and for back edge $(u,v)$ we have $d[u] >
d[v]$. The $lowpt(v)$ is computed for $v$ by taking the minimum
$lowpt$ of the vertices to which $v$ is adjacent. The $lowpt(v)$ is
computed after the recursive DFS call so the $lowpt$ has already been
computed for the adjacent vertices by the time $lowpt(v)$ is computed.
Now it turns out that $lowpt$ can be used to identify articulation
points. Suppose $a,v,w$ are distinct vertices in $G$ such that $(a,v)$
is a tree edge and $w$ is not a descendant of $v$. If $d[lowpt(v)]
\geq d[a]$, then $a$ is an articulation point and removal of $a$
disconnects $v$ and $w$. The reason this works is that if $d[lowpt(v)]
\geq d[a]$, then we know all paths starting from $v$ stay within the
sub-tree $T_v$ rooted at $v$. If a path were to escape from the
sub-tree, then consider the first vertex $w$ in that path outside of
$T_v$. $v \Path w$ must be considered for $lowpt(v)$, so $d[lowpt(v)]
< d[w]$. Now $d[w] < d[a]$ due the structure of the DFS, so
transitively $d[lowpt(v)] < d[a]$.
\section{The Implementation}
The implementation consists of a recursive DFS-like function named
\code{biconnect()} and the public interface function
\code{biconnected\-\_components()}. The \code{Graph} type must be a
model of \concept{VertexListGraph} and of \concept{IncidenceGraph}.
The result of the algorithm is recorded in the \code{ComponentMap},
which maps edges to the biconnected component that they belong to
(components are labeled with integers from zero on up). The
\code{ComponentMap} type must be a model of
\concept{WritablePropertyMap}, which the graph's
\code{edge\-\_descriptor} type as the key type and an unsigned integer
type as the value type. We do not record which component each vertex
belongs to because vertices that are articulation points belong to
multiple biconnected components. The number of biconnected components
is returned in the \code{num\_components} parameter. The
\code{discover\_time} parameter is used internally to keep track of
the DFS discover time for each vertex. It must be a
\concept{ReadWritePropertyMap} with the graph's
\code{vertex\_\-descriptor} type as the key type and an unsigned
integer as the value type. The \code{lowpt} parameter is used
internally to keep track of the $d[lowpt(v)]$ for each vertex $v$. It
must be a \concept{ReadWritePropertyMap} with the graph's
\code{vertex\_\-descriptor} type is the key type and the value type is
the same unsigned integer type as the value type in the
\code{discover\-\_time} map.
@d Biconnected Components Algorithm
@{
namespace detail {
@<Recursive Biconnect Function@>
}
template <typename Graph, typename ComponentMap,
typename DiscoverTimeMap, typename LowPointMap>
void biconnected_components
(typename graph_traits<Graph>::vertex_descriptor v,
typename graph_traits<Graph>::vertex_descriptor u,
const Graph& g,
ComponentMap comp,
std::size_t& num_components,
DiscoverTimeMap discover_time,
LowPointMap lowpt)
{
typedef graph_traits<Graph>::vertex_descriptor vertex_t;
typedef graph_traits<Graph>::edge_descriptor edge_t;
@<Concept checking of type parameters@>
typedef typename property_traits<DiscoverTimeMap>::value_type D;
num_components = 0;
std::size_t dfs_time = 0;
std::stack<edge_t> S;
@<Initialize discover times to infinity@>
@<Process each connected component@>
}
@}
\noindent In the following code we use the Boost Concept Checking
Library to provide better error messages in the event that the user
makes a mistake in the kind of parameter used in the function
template.
@d Concept checking of type parameters
@{
function_requires< VertexListGraphConcept<Graph> >();
function_requires< IncidenceGraphConcept<Graph> >();
function_requires< WritablePropertyMapConcept<ComponentMap, edge_t> >();
function_requires< ReadWritePropertyMapConcept<DiscoverTimeMap, vertex_t> >();
function_requires< ReadWritePropertyMapConcept<LowPointMap, vertex_t> >();
@}
The first step of the algorithm is to initialize the discover times of
all the vertices to infinity. This marks the vertices as undiscovered.
@d Initialize discover times to infinity
@{
typename graph_traits<Graph>::vertex_iterator wi, wi_end;
std::size_t infinity = std::numeric_limits<std::size_t>::max();
for (tie(wi, wi_end) = vertices(g); wi != wi_end; ++wi)
put(discover_time, *wi, infinity);
@}
\noindent Next we invoke \code{biconnect()} on every vertex in the
graph, making sure that all connected components within the graph are
searched (\code{biconnect()} only processes a single connected
component).
@d Process each connected component
@{
for (tie(wi, wi_end) = vertices(g); wi != wi_end; ++wi)
if (get(discover_time, *wi) == std::numeric_limits<D>::max())
detail::biconnect(*wi, *wi, true,
g, comp, num_components,
discover_time, dfs_time, lowpt, S);
@}
The recursive \code{biconnect()} function is shown below. The
\code{v} parameter is where the DFS is started. The \code{u}
parameter is the parent of \code{v} in the DFS-tree if \code{at\_top
== false} or if \code{at\_top == true} the \code{u} is not used.
\code{S} is a stack of edges that is used to collect all edges in a
biconnected component. The way this works is that on ``the way down''
edges are pushed into the stack. ``On the way back up'', when an
articulation point $v$ is found (identified because $d[lowpt(w)] \geq
d[v]$) we know that a contiguous portion of the stack (starting at the
top) contains the edges in the sub-tree $T_v$ which is the biconnected
component. We therefore pop these edges off of the stack (until we
find an edge $e$ where $d[lowpt(source(e))] < d[w]$) and mark them as
belonging to the same component. The code below also includes the
bookkeeping details such as recording the discover times and computing
$lowpt$. When a back edge $(v,w)$ is encountered, we do not use
$lowpt(w)$ in calculating $lowpt(v)$ since $lowpt(w)$ has not yet been
computed. Also, we ignore the edge $(v,w)$ if $w$ is the parent of $v$
in the DFS-tree, meaning that $(w,v)$ has already been examined and
categorized as a tree edge (not a back edge).
@d Recursive Biconnect Function
@{
template <typename Graph, typename ComponentMap,
typename DiscoverTimeMap, typename LowPointMap, typename Stack>
void biconnect(typename graph_traits<Graph>::vertex_descriptor v,
typename graph_traits<Graph>::vertex_descriptor u,
bool at_top,
const Graph& g,
ComponentMap comp,
std::size_t& c,
DiscoverTimeMap d,
std::size_t& dfs_time,
LowPointMap lowpt,
Stack& S)
{
typedef typename graph_traits<Graph>::vertex_descriptor vertex_t;
typedef typename property_traits<DiscoverTimeMap>::value_type D;
D infinity = std::numeric_limits<D>::max();
put(d, v, ++dfs_time);
put(lowpt, v, get(d, v));
typename graph_traits<Graph>::out_edge_iterator ei, ei_end;
for (tie(ei, ei_end) = out_edges(v, g); ei != ei_end; ++ei) {
vertex_t w = target(*ei, g);
if (get(d, w) == infinity) {
S.push(*ei);
biconnect(w, v, false, g, comp, c, d, dfs_time, lowpt, S);
put(lowpt, v, std::min(get(lowpt, v), get(lowpt, w)));
if (get(lowpt, w) >= get(d, v)) {
@<Record the biconnected component@>
}
} else if (get(d, w) < get(d, v) && (!at_top && w != u)) {
S.push(*ei);
put(lowpt, v, std::min(get(lowpt, v), get(d, w)));
}
}
}
@}
\noindent The following is the code for popping the edges of sub-tree
$T_v$ off of the stack and recording them as being in the same
biconnected component.
@d Record the biconnected component
@{
while (d[source(S.top(), g)] >= d[w]) {
put(comp, S.top(), c);
S.pop();
}
put(comp, S.top(), c);
S.pop();
++c;
@}
\section{Appendix}
@o biconnected-components.hpp
@{
// Copyright (c) Jeremy Siek 2001
//
// Permission to use, copy, modify, distribute and sell this software
// and its documentation for any purpose is hereby granted without fee,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and this permission notice appear
// in supporting documentation. Silicon Graphics makes no
// representations about the suitability of this software for any
// purpose. It is provided "as is" without express or implied warranty.
// NOTE: this final is generated by libs/graph/doc/biconnected_components.w
#ifndef BOOST_GRAPH_BICONNECTED_COMPONENTS_HPP
#define BOOST_GRAPH_BICONNECTED_COMPONENTS_HPP
#include <stack>
#include <boost/limits.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/graph_concepts.hpp>
#include <boost/property_map.hpp>
namespace boost {
@<Biconnected Components Algorithm@>
} // namespace boost
#endif BOOST_GRAPH_BICONNECTED_COMPONENTS_HPP
@}
Figure~\ref{fig:bcc} shows the graph used in the following example and
the edges are labeled by biconnected component number, as computed by
the algorithm.
\begin{figure}[htbp]
\mtlfig{bcc}{width=3.0in}
\caption{The biconnected components.}
\label{fig:bcc}
\end{figure}
@o biconnected-components.cpp
@{
#include <vector>
#include <list>
#include <boost/graph/biconnected_components.hpp>
#include <boost/graph/adjacency_list.hpp>
namespace boost {
struct edge_component_t {
enum { num = 555 };
typedef edge_property_tag kind;
} edge_component;
}
int main()
{
using namespace boost;
typedef adjacency_list<vecS, vecS, undirectedS,
no_property, property<edge_component_t, std::size_t> > graph_t;
typedef graph_traits<graph_t>::vertex_descriptor vertex_t;
graph_t g(9);
add_edge(0, 5, g); add_edge(0, 1, g); add_edge(0, 6, g);
add_edge(1, 2, g); add_edge(1, 3, g); add_edge(1, 4, g);
add_edge(2, 3, g);
add_edge(4, 5, g);
add_edge(6, 8, g); add_edge(6, 7, g);
add_edge(7, 8, g);
std::size_t c = 0;
std::vector<std::size_t> discover_time(num_vertices(g));
std::vector<vertex_t> lowpt(num_vertices(g));
property_map<graph_t, edge_component_t>::type
component = get(edge_component, g);
biconnected_components(0, 8, g, component,
c, &discover_time[0], &lowpt[0]);
std::cout << "graph A {\n"
<< " node[shape=\"circle\"]\n";
graph_traits<graph_t>::edge_iterator ei, ei_end;
for (tie(ei, ei_end) = edges(g); ei != ei_end; ++ei)
std::cout << source(*ei, g) << " -- " << target(*ei, g)
<< "[label=\"" << component[*ei] << "\"]\n";
std::cout << "}\n";
return 0;
}
@}
% \paragraph{Definition.} A \emph{palm tree} $P$ is a directed graph that
% consists of two disjoint sets of edges, denoted by $v \rightarrow w$
% and $v \backedge w$ respectively, with the following properties:
% \begin{enumerate}
% \item The subgraph $T$ containing the edges $v \rightarrow w$ is a
% spanning tree of $P$.
% \item $\backedge \; \subseteq \descendant$. That is, each edge of $P$
% that is not in $T$ connects a vertex to one of its ancestors in $T$.
% \end{enumerate}
\bibliographystyle{abbrv}
\bibliography{jtran,ggcl,optimization,generic-programming,cad}
\end{document}
% LocalWords: Biconnected Siek biconnected Tarjan undirected DFS lowpt num dfs
% LocalWords: biconnect VertexListGraph IncidenceGraph ComponentMap namespace
% LocalWords: WritablePropertyMap ReadWritePropertyMap typename LowPointMap wi
% LocalWords: DiscoverTimeMap const comp typedef VertexListGraphConcept max ei
% LocalWords: IncidenceGraphConcept WritablePropertyMapConcept iterator bool
% LocalWords: ReadWritePropertyMapConcept hpp ifndef endif bcc cpp struct enum
% LocalWords: adjacency vecS undirectedS jtran ggcl
|