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