1 /*
2 * Copyright (c) 2003, The JUNG Authors
3 *
4 * All rights reserved.
5 *
6 * This software is open-source under the BSD license; see either
7 * "license.txt" or
8 * https://github.com/jrtom/jung/blob/master/LICENSE 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 com.google.common.base.Function;
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 Function<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>> apply(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>.
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)
121 *
122 * @param g the graph to check for biconnected components
123 * @param v the starting place for searching for biconnected components
124 * @param bicomponents storage for the biconnected components found by this algorithm
125 */
126 protected void findBiconnectedComponents(UndirectedGraph<V,E> g, V v, Set<Set<V>> bicomponents)
127 {
128 int v_dfs_num = converse_depth;
129 dfs_num.put(v, v_dfs_num);
130 converse_depth--;
131 high.put(v, v_dfs_num);
132
133 for (V w : g.getNeighbors(v))
134 {
135 int w_dfs_num = dfs_num.get(w).intValue();//get(w, dfs_num);
136 E vw = g.findEdge(v,w);
137 if (w_dfs_num == 0) // w hasn't yet been visited
138 {
139 parents.put(w, v); // v is w's parent in the DFS tree
140 stack.push(vw);
141 findBiconnectedComponents(g, w, bicomponents);
142 int w_high = high.get(w).intValue();//get(w, high);
143 if (w_high <= v_dfs_num)
144 {
145 // v disconnects w from the rest of the graph,
146 // i.e., v is an articulation point
147 // thus, everything between the top of the stack and
148 // v is part of a single biconnected component
149 Set<V> bicomponent = new HashSet<V>();
150 E e;
151 do
152 {
153 e = stack.pop();
154 bicomponent.addAll(g.getIncidentVertices(e));
155 }
156 while (e != vw);
157 bicomponents.add(bicomponent);
158 }
159 high.put(v, Math.max(w_high, high.get(v).intValue()));
160 }
161 else if (w != parents.get(v)) // (v,w) is a back or a forward edge
162 high.put(v, Math.max(w_dfs_num, high.get(v).intValue()));
163 }
164 }
165 }