/* * Copyright (c) 2003, the JUNG Project and the Regents of the University * of California * All rights reserved. * * This software is open-source under the BSD license; see either * "license.txt" or * http://jung.sourceforge.net/license.txt for a description. */ package edu.uci.ics.jung.algorithms.cluster; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.Stack; import org.apache.commons.collections15.Transformer; import edu.uci.ics.jung.graph.UndirectedGraph; /** * Finds all biconnected components (bicomponents) of an undirected graph. * A graph is a biconnected component if * at least 2 vertices must be removed in order to disconnect the graph. (Graphs * consisting of one vertex, or of two connected vertices, are also biconnected.) Biconnected * components of three or more vertices have the property that every pair of vertices in the component * are connected by two or more vertex-disjoint paths. *

* Running time: O(|V| + |E|) where |V| is the number of vertices and |E| is the number of edges * @see "Depth first search and linear graph algorithms by R. E. Tarjan (1972), SIAM J. Comp." * * @author Joshua O'Madadhain */ public class BicomponentClusterer implements Transformer, Set>> { protected Map dfs_num; protected Map high; protected Map parents; protected Stack stack; protected int converse_depth; /** * Constructs a new bicomponent finder */ public BicomponentClusterer() { } /** * Extracts the bicomponents from the graph. * @param theGraph the graph whose bicomponents are to be extracted * @return the ClusterSet of bicomponents */ public Set> transform(UndirectedGraph theGraph) { Set> bicomponents = new LinkedHashSet>(); if (theGraph.getVertices().isEmpty()) return bicomponents; // initialize DFS number for each vertex to 0 dfs_num = new HashMap(); for (V v : theGraph.getVertices()) { dfs_num.put(v, 0); } for (V v : theGraph.getVertices()) { if (dfs_num.get(v).intValue() == 0) // if we haven't hit this vertex yet... { high = new HashMap(); stack = new Stack(); parents = new HashMap(); converse_depth = theGraph.getVertexCount(); // find the biconnected components for this subgraph, starting from v findBiconnectedComponents(theGraph, v, bicomponents); // if we only visited one vertex, this method won't have // ID'd it as a biconnected component, so mark it as one if (theGraph.getVertexCount() - converse_depth == 1) { Set s = new HashSet(); s.add(v); bicomponents.add(s); } } } return bicomponents; } /** *

Stores, in bicomponents, all the biconnected * components that are reachable from v.

* *

The algorithm basically proceeds as follows: do a depth-first * traversal starting from v, marking each vertex with * a value that indicates the order in which it was encountered (dfs_num), * and with * a value that indicates the highest point in the DFS tree that is known * to be reachable from this vertex using non-DFS edges (high). (Since it * is measured on non-DFS edges, "high" tells you how far back in the DFS * tree you can reach by two distinct paths, hence biconnectivity.) * Each time a new vertex w is encountered, push the edge just traversed * on a stack, and call this method recursively. If w.high is no greater than * v.dfs_num, then the contents of the stack down to (v,w) is a * biconnected component (and v is an articulation point, that is, a * component boundary). In either case, set v.high to max(v.high, w.high), * and continue. If w has already been encountered but is * not v's parent, set v.high max(v.high, w.dfs_num) and continue. * *

(In case anyone cares, the version of this algorithm on p. 224 of * Udi Manber's "Introduction to Algorithms: A Creative Approach" seems to be * wrong: the stack should be initialized outside this method, * (v,w) should only be put on the stack if w hasn't been seen already, * and there's no real benefit to putting v on the stack separately: just * check for (v,w) on the stack rather than v. Had I known this, I could * have saved myself a few days. JRTOM)

* */ protected void findBiconnectedComponents(UndirectedGraph g, V v, Set> bicomponents) { int v_dfs_num = converse_depth; dfs_num.put(v, v_dfs_num); converse_depth--; high.put(v, v_dfs_num); for (V w : g.getNeighbors(v)) { int w_dfs_num = dfs_num.get(w).intValue();//get(w, dfs_num); E vw = g.findEdge(v,w); if (w_dfs_num == 0) // w hasn't yet been visited { parents.put(w, v); // v is w's parent in the DFS tree stack.push(vw); findBiconnectedComponents(g, w, bicomponents); int w_high = high.get(w).intValue();//get(w, high); if (w_high <= v_dfs_num) { // v disconnects w from the rest of the graph, // i.e., v is an articulation point // thus, everything between the top of the stack and // v is part of a single biconnected component Set bicomponent = new HashSet(); E e; do { e = stack.pop(); bicomponent.addAll(g.getIncidentVertices(e)); } while (e != vw); bicomponents.add(bicomponent); } high.put(v, Math.max(w_high, high.get(v).intValue())); } else if (w != parents.get(v)) // (v,w) is a back or a forward edge high.put(v, Math.max(w_dfs_num, high.get(v).intValue())); } } }