File: biconnected_components.w

package info (click to toggle)
boost1.35 1.35.0-5
  • links: PTS
  • area: main
  • in suites: lenny
  • size: 203,856 kB
  • ctags: 337,867
  • sloc: cpp: 938,683; xml: 56,847; ansic: 41,589; python: 18,999; sh: 11,566; makefile: 664; perl: 494; yacc: 456; asm: 353; csh: 6
file content (386 lines) | stat: -rw-r--r-- 14,282 bytes parent folder | download | duplicates (5)
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