org.jgrapht.alg.cycle.TarjanSimpleCycles Maven / Gradle / Ivy
/*
* (C) Copyright 2013-2021, by Nikolay Ognyanov 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.cycle;
import org.jgrapht.*;
import java.util.*;
/**
* Find all simple cycles of a directed graph using the Tarjan's algorithm.
*
*
* See:
* R. Tarjan, Enumeration of the elementary circuits of a directed graph, SIAM J. Comput., 2 (1973),
* pp. 211-216.
*
* @param the vertex type.
* @param the edge type.
*
* @author Nikolay Ognyanov
*/
public class TarjanSimpleCycles
implements
DirectedSimpleCycles
{
private Graph graph;
private List> cycles;
private Set marked;
private ArrayDeque markedStack;
private ArrayDeque pointStack;
private Map vToI;
private Map> removed;
/**
* Create a simple cycle finder with an unspecified graph.
*/
public TarjanSimpleCycles()
{
}
/**
* 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 TarjanSimpleCycles(Graph graph)
{
this.graph = GraphTests.requireDirected(graph, "Graph must be directed");
}
/**
* Get the graph
*
* @return graph
*/
public Graph getGraph()
{
return graph;
}
/**
* Set the graph
*
* @param graph graph
*/
public void setGraph(Graph graph)
{
this.graph = GraphTests.requireDirected(graph, "Graph must be directed");
}
/**
* {@inheritDoc}
*/
@Override
public List> findSimpleCycles()
{
if (graph == null) {
throw new IllegalArgumentException("Null graph.");
}
initState();
for (V start : graph.vertexSet()) {
backtrack(start, start);
while (!markedStack.isEmpty()) {
marked.remove(markedStack.pop());
}
}
List> result = cycles;
clearState();
return result;
}
private boolean backtrack(V start, V vertex)
{
boolean foundCycle = false;
pointStack.push(vertex);
marked.add(vertex);
markedStack.push(vertex);
for (E currentEdge : graph.outgoingEdgesOf(vertex)) {
V currentVertex = graph.getEdgeTarget(currentEdge);
if (getRemoved(vertex).contains(currentVertex)) {
continue;
}
int comparison = toI(currentVertex).compareTo(toI(start));
if (comparison < 0) {
getRemoved(vertex).add(currentVertex);
} else if (comparison == 0) {
foundCycle = true;
List cycle = new ArrayList<>();
Iterator it = pointStack.descendingIterator();
V v;
while (it.hasNext()) {
v = it.next();
if (start.equals(v)) {
break;
}
}
cycle.add(start);
while (it.hasNext()) {
cycle.add(it.next());
}
cycles.add(cycle);
} else if (!marked.contains(currentVertex)) {
boolean gotCycle = backtrack(start, currentVertex);
foundCycle = foundCycle || gotCycle;
}
}
if (foundCycle) {
while (!markedStack.peek().equals(vertex)) {
marked.remove(markedStack.pop());
}
marked.remove(markedStack.pop());
}
pointStack.pop();
return foundCycle;
}
private void initState()
{
cycles = new ArrayList<>();
marked = new HashSet<>();
markedStack = new ArrayDeque<>();
pointStack = new ArrayDeque<>();
vToI = new HashMap<>();
removed = new HashMap<>();
int index = 0;
for (V v : graph.vertexSet()) {
vToI.put(v, index++);
}
}
private void clearState()
{
cycles = null;
marked = null;
markedStack = null;
pointStack = null;
vToI = null;
}
private Integer toI(V v)
{
return vToI.get(v);
}
private Set getRemoved(V v)
{
// Removed sets typically not all
// needed, so instantiate lazily.
return removed.computeIfAbsent(v, k -> new HashSet<>());
}
}