org.jgrapht.alg.connectivity.KosarajuStrongConnectivityInspector Maven / Gradle / Ivy
/*
* (C) Copyright 2005-2023, by Christian Soltenborn and Contributors.
*
* JGraphT : a free Java graph-theory library
*
* See the CONTRIBUTORS.md file distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the
* GNU Lesser General Public License v2.1 or later
* which is available at
* http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
*
* SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
*/
package org.jgrapht.alg.connectivity;
import org.jgrapht.*;
import org.jgrapht.graph.*;
import org.jgrapht.util.*;
import java.util.*;
/**
* Computes strongly connected components of a directed graph. The algorithm is implemented after
* "Cormen et al: Introduction to algorithms", Chapter 22.5. It has a running time of $O(V + E)$.
*
*
* Unlike {@link ConnectivityInspector}, this class does not implement incremental inspection. The
* full algorithm is executed at the first call of
* {@link KosarajuStrongConnectivityInspector#stronglyConnectedSets()} or
* {@link KosarajuStrongConnectivityInspector#isStronglyConnected()}.
*
* @param the graph vertex type
* @param the graph edge type
*
* @author Christian Soltenborn
* @author Christian Hammer
*/
public class KosarajuStrongConnectivityInspector
extends AbstractStrongConnectivityInspector
{
// stores the vertices, ordered by their finishing time in first dfs
private LinkedList> orderedVertices;
// maps vertices to their VertexData object
private Map> vertexToVertexData;
/**
* Constructor
*
* @param graph the input graph
* @throws NullPointerException if the input graph is null
*/
public KosarajuStrongConnectivityInspector(Graph graph)
{
super(graph);
}
@Override
public List> stronglyConnectedSets()
{
if (stronglyConnectedSets == null) {
orderedVertices = new LinkedList<>();
stronglyConnectedSets = new ArrayList<>();
// 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 data : vertexToVertexData.values()) {
if (!data.isDiscovered()) {
dfsVisit(graph, data, null);
}
}
// 'create' inverse graph (i.e. every edge is reversed)
Graph inverseGraph = new EdgeReversedGraph<>(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 data : orderedVertices) {
if (!data.isDiscovered()) {
// new strongly connected set
Set set = new HashSet<>();
stronglyConnectedSets.add(set);
dfsVisit(inverseGraph, data, set);
}
}
// clean up for garbage collection
orderedVertices = null;
vertexToVertexData = null;
}
return stronglyConnectedSets;
}
/*
* Creates a VertexData object for every vertex in the graph and stores them in a HashMap.
*/
private void createVertexData()
{
vertexToVertexData = CollectionUtil.newHashMapWithExpectedSize(graph.vertexSet().size());
for (V vertex : graph.vertexSet()) {
vertexToVertexData.put(vertex, new VertexData2<>(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(Graph visitedGraph, VertexData vertexData, Set vertices)
{
Deque> stack = new ArrayDeque<>();
stack.add(vertexData);
while (!stack.isEmpty()) {
VertexData data = stack.removeLast();
if (!data.isDiscovered()) {
data.setDiscovered(true);
if (vertices != null) {
vertices.add(data.getVertex());
}
stack.add(new VertexData1<>(data, true, true));
// follow all edges
for (E edge : visitedGraph.outgoingEdgesOf(data.getVertex())) {
VertexData targetData =
vertexToVertexData.get(visitedGraph.getEdgeTarget(edge));
if (!targetData.isDiscovered()) {
// the "recursion"
stack.add(targetData);
}
}
} else if (data.isFinished() && vertices == null) {
orderedVertices.addFirst(data.getFinishedData());
}
}
}
/*
* Resets all VertexData objects.
*/
private void resetVertexData()
{
for (VertexData data : vertexToVertexData.values()) {
data.setDiscovered(false);
data.setFinished(false);
}
}
/*
* Lightweight class storing some data for every vertex.
*/
private abstract static class VertexData
{
private byte bitfield;
private VertexData(boolean discovered, boolean finished)
{
this.bitfield = 0;
setDiscovered(discovered);
setFinished(finished);
}
private boolean isDiscovered()
{
return (bitfield & 1) == 1;
}
private boolean isFinished()
{
return (bitfield & 2) == 2;
}
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 getFinishedData();
abstract V getVertex();
}
private static final class VertexData1
extends VertexData
{
private final VertexData finishedData;
private VertexData1(VertexData finishedData, boolean discovered, boolean finished)
{
super(discovered, finished);
this.finishedData = finishedData;
}
@Override
VertexData getFinishedData()
{
return finishedData;
}
@Override
V getVertex()
{
return null;
}
}
private static final class VertexData2
extends VertexData
{
private final V vertex;
private VertexData2(V vertex, boolean discovered, boolean finished)
{
super(discovered, finished);
this.vertex = vertex;
}
@Override
VertexData getFinishedData()
{
return null;
}
@Override
V getVertex()
{
return vertex;
}
}
}