org.jgrapht.alg.cycle.JohnsonSimpleCycles Maven / Gradle / Ivy
/* ==========================================
* 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.
*/
/* -------------------------
* JohnsonSimpleCycles.java
* -------------------------
* (C) Copyright 2013, by Nikolay Ognyanov
*
* Original Author: Nikolay Ognyanov.
* Contributor(s) :
*
* $Id$
*
* Changes
* -------
* 06-Sep-2013 : Initial revision (NO);
*/
package org.jgrapht.alg.cycle;
import java.util.*;
import org.jgrapht.*;
import org.jgrapht.graph.*;
/**
* Find all simple cycles of a directed graph using the Johnson's algorithm.
*
* See:
* D.B.Johnson, Finding all the elementary circuits of a directed graph, SIAM J.
* Comput., 4 (1975), pp. 77-84.
*
* @param the vertex type.
* @param the edge type.
*
* @author Nikolay Ognyanov
*/
public class JohnsonSimpleCycles
implements DirectedSimpleCycles
{
// The graph.
private DirectedGraph graph;
// The main state of the algorithm.
private List> cycles = null;
private V [] iToV = null;
private Map vToI = null;
private Set blocked = null;
private Map> bSets = null;
private ArrayDeque stack = null;
// The state of the embedded Tarjan SCC algorithm.
private List> SCCs = null;
private int index = 0;
private Map vIndex = null;
private Map vLowlink = null;
private ArrayDeque path = null;
private Set pathSet = null;
/**
* Create a simple cycle finder with an unspecified graph.
*/
public JohnsonSimpleCycles()
{
}
/**
* Create a simple cycle finder for the specified graph.
*
* @param graph - the DirectedGraph in which to find cycles.
*
* @throws IllegalArgumentException if the graph argument is
* null
.
*/
public JohnsonSimpleCycles(DirectedGraph graph)
{
if (graph == null) {
throw new IllegalArgumentException("Null graph argument.");
}
this.graph = graph;
}
/**
* {@inheritDoc}
*/
@Override public DirectedGraph getGraph()
{
return graph;
}
/**
* {@inheritDoc}
*/
@Override public void setGraph(DirectedGraph graph)
{
if (graph == null) {
throw new IllegalArgumentException("Null graph argument.");
}
this.graph = graph;
}
/**
* {@inheritDoc}
*/
@Override public List> findSimpleCycles()
{
if (graph == null) {
throw new IllegalArgumentException("Null graph.");
}
initState();
int startIndex = 0;
int size = graph.vertexSet().size();
while (startIndex < size) {
Object [] minSCCGResult = findMinSCSG(startIndex);
if (minSCCGResult[0] != null) {
startIndex = (Integer) minSCCGResult[1];
@SuppressWarnings("unchecked")
DirectedGraph scg =
(DirectedGraph) minSCCGResult[0];
V startV = toV(startIndex);
for (E e : scg.outgoingEdgesOf(startV)) {
V v = graph.getEdgeTarget(e);
blocked.remove(v);
getBSet(v).clear();
}
findCyclesInSCG(startIndex, startIndex, scg);
startIndex++;
} else {
break;
}
}
List> result = cycles;
clearState();
return result;
}
private Object [] findMinSCSG(int startIndex)
{
// Per Johnson : "adjacency structure of strong
// component K with least vertex in subgraph of
// G induced by {s, s+ 1, n}".
// Or in contemporary terms: the strongly connected
// component of the subgraph induced by {v1,...,vn}
// which contains the minimum (among those SCCs)
// vertex index. We return that index together with
// the graph.
initMinSCGState();
Object [] result = new Object[2];
List> SCCs = findSCCS(startIndex);
// find the SCC with the minimum index
int minIndexFound = Integer.MAX_VALUE;
Set minSCC = null;
for (Set scc : SCCs) {
for (V v : scc) {
int t = toI(v);
if (t < minIndexFound) {
minIndexFound = t;
minSCC = scc;
}
}
}
if (minSCC == null) {
return result;
}
// build a graph for the SCC found
@SuppressWarnings("unchecked")
DirectedGraph resultGraph =
new DefaultDirectedGraph(
new ClassBasedEdgeFactory(
(Class extends E>) DefaultEdge.class));
for (V v : minSCC) {
resultGraph.addVertex(v);
}
for (V v : minSCC) {
for (V w : minSCC) {
if (graph.containsEdge(v, w)) {
resultGraph.addEdge(v, w);
}
}
}
// It is ugly to return results in an array
// of Object but the idea is to restrict
// dependencies to JgraphT only and there is
// no utility pair container in JgraphT.
result[0] = resultGraph;
result[1] = minIndexFound;
clearMinSCCState();
return result;
}
private List> findSCCS(int startIndex)
{
// Find SCCs in the subgraph induced
// by vertices startIndex and beyond.
// A call to StrongConnectivityInspector
// would be too expensive because of the
// need to materialize the subgraph.
// So - do a local search by the Tarjan's
// algorithm and pretend that vertices
// with an index smaller than startIndex
// do not exist.
for (V v : graph.vertexSet()) {
int vI = toI(v);
if (vI < startIndex) {
continue;
}
if (!vIndex.containsKey(v)) {
getSCCs(startIndex, vI);
}
}
List> result = SCCs;
SCCs = null;
return result;
}
private void getSCCs(int startIndex, int vertexIndex)
{
V vertex = toV(vertexIndex);
vIndex.put(vertex, index);
vLowlink.put(vertex, index);
index++;
path.push(vertex);
pathSet.add(vertex);
Set edges = graph.outgoingEdgesOf(vertex);
for (E e : edges) {
V successor = graph.getEdgeTarget(e);
int successorIndex = toI(successor);
if (successorIndex < startIndex) {
continue;
}
if (!vIndex.containsKey(successor)) {
getSCCs(startIndex, successorIndex);
vLowlink.put(
vertex,
Math.min(vLowlink.get(vertex),
vLowlink.get(successor)));
} else if (pathSet.contains(successor)) {
vLowlink.put(
vertex,
Math.min(vLowlink.get(vertex),
vIndex.get(successor)));
}
}
if (vLowlink.get(vertex).equals(vIndex.get(vertex))) {
Set result = new HashSet();
V temp = null;
do {
temp = path.pop();
pathSet.remove(temp);
result.add(temp);
} while (!vertex.equals(temp));
if (result.size() == 1) {
V v = result.iterator().next();
if (graph.containsEdge(vertex, v)) {
SCCs.add(result);
}
} else {
SCCs.add(result);
}
}
}
private boolean findCyclesInSCG(
int startIndex,
int vertexIndex,
DirectedGraph scg)
{
// Find cycles in a strongly connected graph
// per Johnson.
boolean foundCycle = false;
V vertex = toV(vertexIndex);
stack.push(vertex);
blocked.add(vertex);
for (E e : scg.outgoingEdgesOf(vertex)) {
V successor = scg.getEdgeTarget(e);
int successorIndex = toI(successor);
if (successorIndex == startIndex) {
List cycle = new ArrayList();
cycle.addAll(stack);
cycles.add(cycle);
foundCycle = true;
} else if (!blocked.contains(successor)) {
boolean gotCycle =
findCyclesInSCG(startIndex, successorIndex, scg);
foundCycle = foundCycle || gotCycle;
}
}
if (foundCycle) {
unblock(vertex);
} else {
for (E ew : scg.outgoingEdgesOf(vertex)) {
V w = scg.getEdgeTarget(ew);
Set bSet = getBSet(w);
bSet.add(vertex);
}
}
stack.pop();
return foundCycle;
}
private void unblock(V vertex)
{
blocked.remove(vertex);
Set bSet = getBSet(vertex);
while (bSet.size() > 0) {
V w = bSet.iterator().next();
bSet.remove(w);
if (blocked.contains(w)) {
unblock(w);
}
}
}
@SuppressWarnings("unchecked")
private void initState()
{
cycles = new LinkedList>();
iToV = (V []) graph.vertexSet().toArray();
vToI = new HashMap();
blocked = new HashSet();
bSets = new HashMap>();
stack = new ArrayDeque();
for (int i = 0; i < iToV.length; i++) {
vToI.put(iToV[i], i);
}
}
private void clearState()
{
cycles = null;
iToV = null;
vToI = null;
blocked = null;
bSets = null;
stack = null;
}
private void initMinSCGState()
{
index = 0;
SCCs = new ArrayList>();
vIndex = new HashMap();
vLowlink = new HashMap();
path = new ArrayDeque();
pathSet = new HashSet();
}
private void clearMinSCCState()
{
index = 0;
SCCs = null;
vIndex = null;
vLowlink = null;
path = null;
pathSet = null;
}
private Integer toI(V vertex)
{
return vToI.get(vertex);
}
private V toV(Integer i)
{
return iToV[i];
}
private Set getBSet(V v)
{
// B sets typically not all needed,
// so instantiate lazily.
Set result = bSets.get(v);
if (result == null) {
result = new HashSet();
bSets.put(v, result);
}
return result;
}
}
// End JohnsonSimpleCycles.java