Biconnectivity of a graph This article is a review of the standard algorithm of testing the biconnectivity of a graph. A connected graph is biconnected if and only if no vertex in the graph is an articulation point, whose removal along with the incident edges disconnects the graph. Our aim is thus to detect the existence of any articulation points in a given graph. Since the algorithm of testing biconnectivity is an extension of the algorithm of testing connectivity, we review the latter in Sec. I, then proceed to the former in Sec. II. I. Testing connectivity Let us first recall how to check if a graph is connected. Algorithm Connected: 1. Prepare an empty list Q of vertices. Put an arbitrary vertex v0 into Q. Mark v0 as visited. Repeat the following steps 2 and 3 until the list Q is empty. 2. Pick a vertex v from Q, and remove v from Q. 3. Add all unvisited vertices adjacent to v into Q, and mark them as visited. 4. The graph is connected if and only if every vertex is visited at the end of the above loop. The above algorithm grows a spanning tree, which is a noncyclic connected skeleton of a graph (see Figure 1), from the root vertex v0. If the graph is connected, the spanning tree is able to reach all vertices, otherwise it only covers a connected subgraph, or a connected component. The above strategy of building the spanning tree is a breadth-first search (BFS). It means that vertices adjacent to the root are added first into the list Q, while those of a 1 few edges away are added later. The type of list is called a queue, and vertices entering the queue first also leave the queue first. Figure 1. Breadth-first search vs. depth-first search. The black thinner lines represent the edges in the original graph. The red thicker lines represent the spanning trees from the two searching strategies. The darkness and numbers of the vertices represent the processing orders during the search. We can alternatively construct the spanning tree by a depth-first search (DFS). The difference between the breadth-first and depth-first searches is illustrated in Figure 1. The algorithm of finding the connected component by a depth-first search can be written succinctly as a recursion. Algorithm CDFS(v): 1. Mark v as used. 2. While there is any unused vertex u that is adjacent to v, call CDFS(u). In the end, the set of all used vertices gives a connected component of the graph. The above recursion will not last indefinitely because step 1 reduces the number of unused vertices by one until no vertex can be used in step 2. The depth-first search may yield a different spanning tree from the breadth-first search, but the resulting connected components are the same. 2 II. Testing biconnectivity We now discuss the standard algorithm of testing biconnectivity based on a modification of the above depth-first search. We will use the ordering of vertices defined by the depth-first search to locate articulation points in the graph. We will assume the graph is connected below in the following development, although the final algorithm does not rely on this assumption. Let us now consider the task of detecting articulation points. Suppose the graph contains an articulation point a. Then, by definition, removing vertex a breaks the graph into at least two connected components. We will distinguish two cases. First, if a is the root of the spanning tree, then a is an articulation point if and only if a has two or more children. This is because the second child u of the root vertex a would be disconnected from the first child w of a, or any descendant of w, unless through the root a. So a is an articulation point. Second, consider the case that a is not the root. Denote the children of a as b1, b2, …, bk. For each bi, denote the vertex set of bi itself and its descendants by Bi. The set of vertices discovered before a is denoted as B0. See Figure 2 for an example. Obviously each Bi ( i 0...k ) is a connected subgraph. We say that two subgraphs Bi and Bj are connected if there are two vertices, vi Bi and v j B j , such that vi and v j are adjacent in the original graph. Note that any subgraphs Bi and Bj (with i > j > 0) are disconnected, for otherwise Bi would be merged into Bj in the depth-first search (this is analogous to the previous case of a being the root vertex). So. 3 LEMMA 1. A non-root vertex a is an articulation point if and only if there is at least one child branch Bi (i > 0) that is not connected to B0. Figure 2. Edges on the spanning tree from the depth-first search are shown as solid black lines, other edges (back edges) are shown as dashed curves. The graph is biconnected. However, without either the green edge, which connects B1 and B0, or the blue edge, which connects B2 and B0, vertex 4 would be an articulation point. Note also that B1 and B2 must be disconnected, otherwise vertex 7, hence B2, would be merged into B1. The low-point values are listed and explained on the right side. Let us define a back edge as an edge excluded from the DFS spanning tree, and restate the LEMMA 1 as follows. THEOREM 2. A non-root vertex a is an articulation point if and only if there is a child b of a such that neither b itself nor any descendant of b leads a back edge to an ancestor of a. For example, in Figure 2, vertex 4 (a) would be an articulation point without the green back edge between vertices 1 and 6, because neither vertex 5 (which is a child of 4 vertex 4) nor the descendant of vertex 5 would be adjacent to any vertex in B0. This is equivalent of saying that the components B0 and B1 are disconnected without the green back edge. To use THEOREM 2 in implementation, we need to search all back edges from all descendants of a vertex. This seemingly challenging task can be, however, efficiently embedded into the recursion of the depth-first search (CDFS above). First, we denote by dfn(v) the order of discovery of v in the depth-first search, and dfn(v0) = 1 for the root vertex v0. Then we give each vertex v a low-point value low(v), which is defined as the order of discovery of the oldest vertex in the spanning tree reachable from v by a path u0u1-…-uk(-uk+1) such that i) u0 = v. ii) For i < k, ui-ui+1 is an edge of spanning tree with dfn(ui) < dfn(ui+1). iii) The (optional) last edge uk-uk+1 is a back edge with dfn(uk) > dfn(uk+1). Thus we can compute the low-point value from the following formula: low (v) min dfn( v), min v u is a back edge dfn( u ), min w is a child of v low( w) , (1) where the three terms in the braces correspond to a path of one vertex u0 = v, a path of a single back edge, and a path of multiple edges ended with a back edge, respectively. The last contribution can be computed once we have enumerated over all children of vertex v in the depth-first search (i.e., after step 2 of algorithm CDFS). If the low-point of a child vertex bi of vertex a is less than the order of discovery dfn(a), then either b itself or a descendant of b leads a back edge to an ancestor of a. Thus, we can restate THEOREM 2 as 5 THEOREM 3. A non-root vertex a is an articulation point if and only if there is a child b of a such that low(b) ≥ dfn(a). We now state the algorithm below. The parent vertex of v in the spanning tree is denoted as parent (v) . Algorithm Biconnected: (Preparation.) Pick an arbitrary vertex v as the root of the spanning tree. Set the order of discovery (a global variable) n to 1. Call BCDFS(v) below to construct the spanning tree. BCDFS(v): 1. Mark v as visited. Set low(v) = dfn(v) = n. Increase n by 1. 2. While v has an unvisited child u, call BCDFS(u). 3. If v or parent(v) is the root, quit Biconnected, the graph is biconnected if and only if all vertices have been visited. 4. For each w that is adjacent to v but not the parent of v, update the low-point as low(v) = min{low(v), low(w)}. 5. If low(v) ≥ dfn(parent(v)), quit Biconnected, return with false. Several comments are in order. The first two steps are similar to those in CDFS. Step 3 checks if the root vertex is an articulation point and the graph is connected. When the parent of vertex v is the root, we have finished searching the first branch of the root. If there is any unused vertex, it belongs to another branch or connected component. Thus, the graph is not biconnected. Step 4 updates the low-point value according to Eq. (1). Here, vertex w can be either an ancestor or a descendant of vertex v. In the former case, we apply the second term in braces of Eq. (1), and note that low(u) = dfn(u) when the update occurs because of the order of the depth-first search. In the latter cases, we apply 6 the last term of the braces of Eq. (1). Step 5 checks if a non-root vertex is an articulation point by THEOREM 3. References 1. T. H. Cormen, Introduction to Algorithms, 3rd ed. (MIT Press, Cambridge, Mass., 2009). 2. http://www.ics.uci.edu/~dan/class/161/notes/8/Bicomps.html 3. http://www.csie.ntu.edu.tw/~wcchen/algorithm/biconnectedGraph/algorithm.htm 7