com.salesforce.jgrapht.alg.cycle.JohnsonSimpleCycles Maven / Gradle / Ivy
Show all versions of AptSpringProcessor Show documentation
/*
* (C) Copyright 2013-2018, 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 com.salesforce.jgrapht.alg.cycle;
import com.salesforce.jgrapht.*;
import com.salesforce.jgrapht.alg.util.*;
import com.salesforce.jgrapht.graph.builder.*;
import java.util.*;
/**
* 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 Graph 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 for the specified graph.
*
* @param graph - the DirectedGraph in which to find cycles.
*
* @throws IllegalArgumentException if the graph argument is
* null
.
*/
public JohnsonSimpleCycles(Graph graph)
{
this.graph = GraphTests.requireDirected(graph, "Graph must be directed");
if (GraphTests.hasMultipleEdges(graph)) {
throw new IllegalArgumentException("Graph should not have multiple (parallel) edges");
}
}
/**
* {@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) {
Pair, Integer> minSCCGResult = findMinSCSG(startIndex);
if (minSCCGResult != null) {
startIndex = minSCCGResult.getSecond();
Graph scg = minSCCGResult.getFirst();
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 Pair, Integer> 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 $(v_1, \dotso ,v_n)$ which contains the minimum
* (among those SCCs) vertex index. We return that index together with the graph.
*/
initMinSCGState();
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 null;
}
// build a graph for the SCC found
Graph resultGraph = GraphTypeBuilder
. directed().edgeSupplier(graph.getEdgeSupplier())
.vertexSupplier(graph.getVertexSupplier()).allowingMultipleEdges(false)
.allowingSelfLoops(true).buildGraph();
for (V v : minSCC) {
resultGraph.addVertex(v);
}
for (V v : minSCC) {
for (V w : minSCC) {
E edge = graph.getEdge(v, w);
if (edge != null) {
resultGraph.addEdge(v, w, edge);
}
}
}
Pair, Integer> result = Pair.of(resultGraph, minIndexFound);
clearMinSCCState();
return result;
}
private List> findSCCS(int startIndex)
{
// Find SCCs in the subgraph induced
// by vertices startIndex and beyond.
// A call to StrongConnectivityAlgorithm
// 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;
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, Graph 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<>(stack.size());
stack.descendingIterator().forEachRemaining(cycle::add);
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.
return bSets.computeIfAbsent(v, k -> new HashSet<>());
}
}