1 /* 2 * Copyright (c) 2003, the JUNG Project and the Regents of the University 3 * of California 4 * All rights reserved. 5 * 6 * This software is open-source under the BSD license; see either 7 * "license.txt" or 8 * http://jung.sourceforge.net/license.txt for a description. 9 */ 10 package edu.uci.ics.jung.algorithms.cluster; 11 12 import java.util.HashMap; 13 import java.util.HashSet; 14 import java.util.LinkedHashSet; 15 import java.util.Map; 16 import java.util.Set; 17 import java.util.Stack; 18 19 import org.apache.commons.collections15.Transformer; 20 21 import edu.uci.ics.jung.graph.UndirectedGraph; 22 23 /** 24 * Finds all biconnected components (bicomponents) of an undirected graph. 25 * A graph is a biconnected component if 26 * at least 2 vertices must be removed in order to disconnect the graph. (Graphs 27 * consisting of one vertex, or of two connected vertices, are also biconnected.) Biconnected 28 * components of three or more vertices have the property that every pair of vertices in the component 29 * are connected by two or more vertex-disjoint paths. 30 * <p> 31 * Running time: O(|V| + |E|) where |V| is the number of vertices and |E| is the number of edges 32 * @see "Depth first search and linear graph algorithms by R. E. Tarjan (1972), SIAM J. Comp." 33 * 34 * @author Joshua O'Madadhain 35 */ 36 public class BicomponentClusterer<V,E> implements Transformer<UndirectedGraph<V,E>, Set<Set<V>>> 37 { 38 protected Map<V,Number> dfs_num; 39 protected Map<V,Number> high; 40 protected Map<V,V> parents; 41 protected Stack<E> stack; 42 protected int converse_depth; 43 44 /** 45 * Constructs a new bicomponent finder 46 */ 47 public BicomponentClusterer() { 48 } 49 50 /** 51 * Extracts the bicomponents from the graph. 52 * @param theGraph the graph whose bicomponents are to be extracted 53 * @return the <code>ClusterSet</code> of bicomponents 54 */ 55 public Set<Set<V>> transform(UndirectedGraph<V,E> theGraph) 56 { 57 Set<Set<V>> bicomponents = new LinkedHashSet<Set<V>>(); 58 59 if (theGraph.getVertices().isEmpty()) 60 return bicomponents; 61 62 // initialize DFS number for each vertex to 0 63 dfs_num = new HashMap<V,Number>(); 64 for (V v : theGraph.getVertices()) 65 { 66 dfs_num.put(v, 0); 67 } 68 69 for (V v : theGraph.getVertices()) 70 { 71 if (dfs_num.get(v).intValue() == 0) // if we haven't hit this vertex yet... 72 { 73 high = new HashMap<V,Number>(); 74 stack = new Stack<E>(); 75 parents = new HashMap<V,V>(); 76 converse_depth = theGraph.getVertexCount(); 77 // find the biconnected components for this subgraph, starting from v 78 findBiconnectedComponents(theGraph, v, bicomponents); 79 80 // if we only visited one vertex, this method won't have 81 // ID'd it as a biconnected component, so mark it as one 82 if (theGraph.getVertexCount() - converse_depth == 1) 83 { 84 Set<V> s = new HashSet<V>(); 85 s.add(v); 86 bicomponents.add(s); 87 } 88 } 89 } 90 91 return bicomponents; 92 } 93 94 /** 95 * <p>Stores, in <code>bicomponents</code>, all the biconnected 96 * components that are reachable from <code>v</code>.</p> 97 * 98 * <p>The algorithm basically proceeds as follows: do a depth-first 99 * traversal starting from <code>v</code>, marking each vertex with 100 * a value that indicates the order in which it was encountered (dfs_num), 101 * and with 102 * a value that indicates the highest point in the DFS tree that is known 103 * to be reachable from this vertex using non-DFS edges (high). (Since it 104 * is measured on non-DFS edges, "high" tells you how far back in the DFS 105 * tree you can reach by two distinct paths, hence biconnectivity.) 106 * Each time a new vertex w is encountered, push the edge just traversed 107 * on a stack, and call this method recursively. If w.high is no greater than 108 * v.dfs_num, then the contents of the stack down to (v,w) is a 109 * biconnected component (and v is an articulation point, that is, a 110 * component boundary). In either case, set v.high to max(v.high, w.high), 111 * and continue. If w has already been encountered but is 112 * not v's parent, set v.high max(v.high, w.dfs_num) and continue. 113 * 114 * <p>(In case anyone cares, the version of this algorithm on p. 224 of 115 * Udi Manber's "Introduction to Algorithms: A Creative Approach" seems to be 116 * wrong: the stack should be initialized outside this method, 117 * (v,w) should only be put on the stack if w hasn't been seen already, 118 * and there's no real benefit to putting v on the stack separately: just 119 * check for (v,w) on the stack rather than v. Had I known this, I could 120 * have saved myself a few days. JRTOM)</p> 121 * 122 */ 123 protected void findBiconnectedComponents(UndirectedGraph<V,E> g, V v, Set<Set<V>> bicomponents) 124 { 125 int v_dfs_num = converse_depth; 126 dfs_num.put(v, v_dfs_num); 127 converse_depth--; 128 high.put(v, v_dfs_num); 129 130 for (V w : g.getNeighbors(v)) 131 { 132 int w_dfs_num = dfs_num.get(w).intValue();//get(w, dfs_num); 133 E vw = g.findEdge(v,w); 134 if (w_dfs_num == 0) // w hasn't yet been visited 135 { 136 parents.put(w, v); // v is w's parent in the DFS tree 137 stack.push(vw); 138 findBiconnectedComponents(g, w, bicomponents); 139 int w_high = high.get(w).intValue();//get(w, high); 140 if (w_high <= v_dfs_num) 141 { 142 // v disconnects w from the rest of the graph, 143 // i.e., v is an articulation point 144 // thus, everything between the top of the stack and 145 // v is part of a single biconnected component 146 Set<V> bicomponent = new HashSet<V>(); 147 E e; 148 do 149 { 150 e = stack.pop(); 151 bicomponent.addAll(g.getIncidentVertices(e)); 152 } 153 while (e != vw); 154 bicomponents.add(bicomponent); 155 } 156 high.put(v, Math.max(w_high, high.get(v).intValue())); 157 } 158 else if (w != parents.get(v)) // (v,w) is a back or a forward edge 159 high.put(v, Math.max(w_dfs_num, high.get(v).intValue())); 160 } 161 } 162 }