/* ========================================== * JGraphT : a free Java graph-theory library * ========================================== * * Project Info: http://jgrapht.sourceforge.net/ * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) * * (C) Copyright 2003-2008, by Barak Naveh and Contributors. * * This program and the accompanying materials are dual-licensed under * either * * (a) the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation, or (at your option) any * later version. * * or (per the licensee's choosing) * * (b) the terms of the Eclipse Public License v1.0 as published by * the Eclipse Foundation. */ /* -------------------------- * StrongConnectivityInspector.java * -------------------------- * (C) Copyright 2005-2008, by Christian Soltenborn and Contributors. * * Original Author: Christian Soltenborn * * $Id$ * * Changes * ------- * 2-Feb-2005 : Initial revision (CS); * 5-Feb-2007 : fixed NullPointerException (CS); * 1-Apr-2008 : Reduced memory consumption (CS); * */ package org.jgrapht.alg; import java.util.*; import org.jgrapht.*; import org.jgrapht.graph.*; /** * <p>Complements the {@link org.jgrapht.alg.ConnectivityInspector} class with * the capability to compute the strongly connected components of a directed * graph. The algorithm is implemented after "Cormen et al: Introduction to * agorithms", Chapter 22.5. It has a running time of O(V + E).</p> * * <p>Unlike {@link org.jgrapht.alg.ConnectivityInspector}, this class does not * implement incremental inspection. The full algorithm is executed at the first * call of {@link StrongConnectivityInspector#stronglyConnectedSets()} or {@link * StrongConnectivityInspector#isStronglyConnected()}.</p> * * @author Christian Soltenborn * @author Christian Hammer * @since Feb 2, 2005 */ public class StrongConnectivityInspector<V, E> { // the graph to compute the strongly connected sets for private final DirectedGraph<V, E> graph; // stores the vertices, ordered by their finishing time in first dfs private LinkedList<VertexData<V>> orderedVertices; // the result of the computation, cached for future calls private List<Set<V>> stronglyConnectedSets; // the result of the computation, cached for future calls private List<DirectedSubgraph<V, E>> stronglyConnectedSubgraphs; // maps vertices to their VertexData object private Map<V, VertexData<V>> vertexToVertexData; /** * The constructor of the StrongConnectivityInspector class. * * @param directedGraph the graph to inspect * * @throws IllegalArgumentException */ public StrongConnectivityInspector(DirectedGraph<V, E> directedGraph) { if (directedGraph == null) { throw new IllegalArgumentException("null not allowed for graph!"); } graph = directedGraph; vertexToVertexData = null; orderedVertices = null; stronglyConnectedSets = null; stronglyConnectedSubgraphs = null; } /** * Returns the graph inspected by the StrongConnectivityInspector. * * @return the graph inspected by this StrongConnectivityInspector */ public DirectedGraph<V, E> getGraph() { return graph; } /** * Returns true if the graph of this <code> * StronglyConnectivityInspector</code> instance is strongly connected. * * @return true if the graph is strongly connected, false otherwise */ public boolean isStronglyConnected() { return stronglyConnectedSets().size() == 1; } /** * Computes a {@link List} of {@link Set}s, where each set contains vertices * which together form a strongly connected component within the given * graph. * * @return <code>List</code> of <code>Set</code> s containing the strongly * connected components */ public List<Set<V>> stronglyConnectedSets() { if (stronglyConnectedSets == null) { orderedVertices = new LinkedList<VertexData<V>>(); stronglyConnectedSets = new Vector<Set<V>>(); // create VertexData objects for all vertices, store them createVertexData(); // perform the first round of DFS, result is an ordering // of the vertices by decreasing finishing time for (VertexData<V> data : vertexToVertexData.values()) { if (!data.isDiscovered()) { dfsVisit(graph, data, null); } } // 'create' inverse graph (i.e. every edge is reversed) DirectedGraph<V, E> inverseGraph = new EdgeReversedGraph<V, E>(graph); // get ready for next dfs round resetVertexData(); // second dfs round: vertices are considered in decreasing // finishing time order; every tree found is a strongly // connected set for (VertexData<V> data : orderedVertices) { if (!data.isDiscovered()) { // new strongly connected set Set<V> set = new HashSet<V>(); stronglyConnectedSets.add(set); dfsVisit(inverseGraph, data, set); } } // clean up for garbage collection orderedVertices = null; vertexToVertexData = null; } return stronglyConnectedSets; } /** * <p>Computes a list of {@link DirectedSubgraph}s of the given graph. Each * subgraph will represent a strongly connected component and will contain * all vertices of that component. The subgraph will have an edge (u,v) iff * u and v are contained in the strongly connected component.</p> * * <p>NOTE: Calling this method will first execute {@link * StrongConnectivityInspector#stronglyConnectedSets()}. If you don't need * subgraphs, use that method.</p> * * @return a list of subgraphs representing the strongly connected * components */ public List<DirectedSubgraph<V, E>> stronglyConnectedSubgraphs() { if (stronglyConnectedSubgraphs == null) { List<Set<V>> sets = stronglyConnectedSets(); stronglyConnectedSubgraphs = new Vector<DirectedSubgraph<V, E>>(sets.size()); for (Set<V> set : sets) { stronglyConnectedSubgraphs.add( new DirectedSubgraph<V, E>( graph, set, null)); } } return stronglyConnectedSubgraphs; } /* * Creates a VertexData object for every vertex in the graph and stores * them * in a HashMap. */ private void createVertexData() { vertexToVertexData = new HashMap<V, VertexData<V>>(graph.vertexSet().size()); for (V vertex : graph.vertexSet()) { vertexToVertexData.put( vertex, new VertexData2<V>(vertex, false, false)); } } /* * The subroutine of DFS. NOTE: the set is used to distinguish between 1st * and 2nd round of DFS. set == null: finished vertices are stored (1st * round). set != null: all vertices found will be saved in the set (2nd * round) */ private void dfsVisit( DirectedGraph<V, E> visitedGraph, VertexData<V> vertexData, Set<V> vertices) { Deque<VertexData<V>> stack = new ArrayDeque<VertexData<V>>(); stack.add(vertexData); while (!stack.isEmpty()) { VertexData<V> data = stack.removeLast(); if (!data.isDiscovered()) { data.setDiscovered(true); if (vertices != null) { vertices.add(data.getVertex()); } stack.add(new VertexData1<V>(data, true, true)); // follow all edges for (E edge : visitedGraph.outgoingEdgesOf(data.getVertex())) { VertexData<V> targetData = vertexToVertexData.get( visitedGraph.getEdgeTarget(edge)); if (!targetData.isDiscovered()) { // the "recursion" stack.add(targetData); } } } else if (data.isFinished()) { if (vertices == null) { orderedVertices.addFirst(data.getFinishedData()); } } } } /* * Resets all VertexData objects. */ private void resetVertexData() { for (VertexData<V> data : vertexToVertexData.values()) { data.setDiscovered(false); data.setFinished(false); } } /* * Lightweight class storing some data for every vertex. */ private static abstract class VertexData<V> { private byte bitfield; private VertexData( boolean discovered, boolean finished) { this.bitfield = 0; setDiscovered(discovered); setFinished(finished); } private boolean isDiscovered() { if ((bitfield & 1) == 1) { return true; } return false; } private boolean isFinished() { if ((bitfield & 2) == 2) { return true; } return false; } private void setDiscovered(boolean discovered) { if (discovered) { bitfield |= 1; } else { bitfield &= ~1; } } private void setFinished(boolean finished) { if (finished) { bitfield |= 2; } else { bitfield &= ~2; } } abstract VertexData<V> getFinishedData(); abstract V getVertex(); } private static final class VertexData1<V> extends VertexData<V> { private final VertexData<V> finishedData; private VertexData1( VertexData<V> finishedData, boolean discovered, boolean finished) { super(discovered, finished); this.finishedData = finishedData; } VertexData<V> getFinishedData() { return finishedData; } V getVertex() { return null; } } private static final class VertexData2<V> extends VertexData<V> { private final V vertex; private VertexData2( V vertex, boolean discovered, boolean finished) { super(discovered, finished); this.vertex = vertex; } VertexData<V> getFinishedData() { return null; } V getVertex() { return vertex; } } } // End StrongConnectivityInspector.java