org.jgrapht.graph.DirectedAcyclicGraph Maven / Gradle / Ivy
/*
* (C) Copyright 2008-2021, by Peter Giles 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.graph;
import java.io.*;
import java.util.*;
import java.util.function.*;
import org.jgrapht.graph.builder.*;
import org.jgrapht.traverse.*;
import org.jgrapht.util.*;
/**
* A directed acyclic graph (DAG).
*
*
* Implements a DAG that can be modified (vertices & edges added and removed), is guaranteed to
* remain acyclic, and provides fast topological order iteration. An attempt to add an edge which
* would induce a cycle throws an {@link IllegalArgumentException}.
*
*
* This is done using a dynamic topological sort which is based on the algorithm described in "David
* J. Pearce & Paul H. J. Kelly. A dynamic topological sort algorithm for directed acyclic
* graphs. Journal of Experimental Algorithmics, 11, 2007." (see
* paper or
* ACM link for details). The
* implementation differs from the algorithm specified in the above paper in some ways, perhaps most
* notably in that the topological ordering is stored by default using two hash maps, which will
* have some effects on the runtime, but also allow for vertex addition and removal. This storage
* mechanism can be adjusted by subclasses.
*
*
* The complexity of adding a new edge in the graph depends on the number of edges incident to the
* "affected region", and should in general be faster than recomputing the whole topological
* ordering from scratch. For details about the complexity parameters and running times, see the
* previously mentioned paper.
*
*
* This class makes no claims to thread safety, and concurrent usage from multiple threads will
* produce undefined results.
*
* @param the graph vertex type
* @param the graph edge type
*
* @author Peter Giles
*/
public class DirectedAcyclicGraph
extends
AbstractBaseGraph
implements
Iterable
{
private static final long serialVersionUID = 4522128427004938150L;
private final Comparator topoComparator;
private final TopoOrderMap topoOrderMap;
private int maxTopoIndex = 0;
private int minTopoIndex = 0;
// this update count is used to keep internal topological iterators honest
private transient long topoModCount = 0;
/**
* The visited strategy factory to use. Subclasses can change this.
*/
private final VisitedStrategyFactory visitedStrategyFactory;
/**
* Construct a directed acyclic graph.
*
* @param edgeClass the edge class
*/
public DirectedAcyclicGraph(Class extends E> edgeClass)
{
this(null, SupplierUtil.createSupplier(edgeClass), false, false);
}
/**
* Construct a directed acyclic graph.
*
* @param vertexSupplier the vertex supplier
* @param edgeSupplier the edge supplier
* @param weighted if true the graph will be weighted, otherwise not
*/
public DirectedAcyclicGraph(
Supplier vertexSupplier, Supplier edgeSupplier, boolean weighted)
{
this(
vertexSupplier, edgeSupplier, new VisitedBitSetImpl(), new TopoVertexBiMap<>(),
weighted, false);
}
/**
* Construct a directed acyclic graph.
*
* @param vertexSupplier the vertex supplier
* @param edgeSupplier the edge supplier
* @param weighted if true the graph will be weighted, otherwise not
* @param allowMultipleEdges if true the graph will allow multiple edges, otherwise not
*/
public DirectedAcyclicGraph(
Supplier vertexSupplier, Supplier edgeSupplier, boolean weighted,
boolean allowMultipleEdges)
{
this(
vertexSupplier, edgeSupplier, new VisitedBitSetImpl(), new TopoVertexBiMap<>(),
weighted, allowMultipleEdges);
}
/**
* Construct a directed acyclic graph.
*
* @param vertexSupplier the vertex supplier
* @param edgeSupplier the edge supplier
* @param weighted if true the graph will be weighted, otherwise not
* @param allowMultipleEdges if true the graph will allow multiple edges, otherwise not
* @param graphSpecificsStrategy strategy for constructing low-level graph specifics
*/
public DirectedAcyclicGraph(
Supplier vertexSupplier, Supplier edgeSupplier, boolean weighted,
boolean allowMultipleEdges, GraphSpecificsStrategy graphSpecificsStrategy)
{
this(
vertexSupplier, edgeSupplier, new VisitedBitSetImpl(), new TopoVertexBiMap<>(),
weighted, allowMultipleEdges, graphSpecificsStrategy);
}
/**
* Construct a directed acyclic graph.
*
* @param vertexSupplier the vertex supplier
* @param edgeSupplier the edge supplier
* @param visitedStrategyFactory the visited strategy factory. Subclasses can change this
* implementation to adjust the performance tradeoffs.
* @param topoOrderMap the topological order map. For performance reasons, subclasses can change
* the way this class stores the topological order.
* @param weighted if true the graph will be weighted, otherwise not
*/
protected DirectedAcyclicGraph(
Supplier vertexSupplier, Supplier edgeSupplier,
VisitedStrategyFactory visitedStrategyFactory, TopoOrderMap topoOrderMap,
boolean weighted)
{
this(vertexSupplier, edgeSupplier, visitedStrategyFactory, topoOrderMap, weighted, false);
}
/**
* Construct a directed acyclic graph.
*
* @param vertexSupplier the vertex supplier
* @param edgeSupplier the edge supplier
* @param visitedStrategyFactory the visited strategy factory. Subclasses can change this
* implementation to adjust the performance tradeoffs.
* @param topoOrderMap the topological order map. For performance reasons, subclasses can change
* the way this class stores the topological order.
* @param weighted if true the graph will be weighted, otherwise not
* @param allowMultipleEdges if true the graph will allow multiple edges, otherwise not
*/
protected DirectedAcyclicGraph(
Supplier vertexSupplier, Supplier edgeSupplier,
VisitedStrategyFactory visitedStrategyFactory, TopoOrderMap topoOrderMap,
boolean weighted, boolean allowMultipleEdges)
{
this(
vertexSupplier, edgeSupplier, visitedStrategyFactory, topoOrderMap, weighted,
allowMultipleEdges, new FastLookupGraphSpecificsStrategy<>());
}
/**
* Construct a directed acyclic graph.
*
* @param vertexSupplier the vertex supplier
* @param edgeSupplier the edge supplier
* @param visitedStrategyFactory the visited strategy factory. Subclasses can change this
* implementation to adjust the performance tradeoffs.
* @param topoOrderMap the topological order map. For performance reasons, subclasses can change
* the way this class stores the topological order.
* @param weighted if true the graph will be weighted, otherwise not
* @param allowMultipleEdges if true the graph will allow multiple edges, otherwise not
* @param graphSpecificsStrategy strategy for constructing low-level graph specifics
*/
protected DirectedAcyclicGraph(
Supplier vertexSupplier, Supplier edgeSupplier,
VisitedStrategyFactory visitedStrategyFactory, TopoOrderMap topoOrderMap,
boolean weighted, boolean allowMultipleEdges,
GraphSpecificsStrategy graphSpecificsStrategy)
{
super(
vertexSupplier, edgeSupplier,
new DefaultGraphType.Builder()
.directed().allowMultipleEdges(allowMultipleEdges).allowSelfLoops(false)
.weighted(weighted).allowCycles(false).build(),
graphSpecificsStrategy);
this.visitedStrategyFactory =
Objects.requireNonNull(visitedStrategyFactory, "Visited factory cannot be null");
this.topoOrderMap =
Objects.requireNonNull(topoOrderMap, "Topological order map cannot be null");
this.topoComparator = new TopoComparator();
}
/**
* Create a builder for this kind of graph.
*
* @param edgeClass class on which to base factory for edges
* @param the graph vertex type
* @param the graph edge type
* @return a builder for this kind of graph
*/
public static GraphBuilder> createBuilder(
Class extends E> edgeClass)
{
return new GraphBuilder<>(new DirectedAcyclicGraph<>(edgeClass));
}
/**
* Create a builder for this kind of graph.
*
* @param edgeSupplier edge supplier for the edges
* @param the graph vertex type
* @param the graph edge type
* @return a builder for this kind of graph
*/
public static GraphBuilder> createBuilder(
Supplier edgeSupplier)
{
return new GraphBuilder<>(new DirectedAcyclicGraph<>(null, edgeSupplier, false));
}
@Override
public V addVertex()
{
V v = super.addVertex();
if (v != null) {
// add to the topological map
++maxTopoIndex;
topoOrderMap.putVertex(maxTopoIndex, v);
++topoModCount;
}
return v;
}
@Override
public boolean addVertex(V v)
{
boolean added = super.addVertex(v);
if (added) {
// add to the topological map
++maxTopoIndex;
topoOrderMap.putVertex(maxTopoIndex, v);
++topoModCount;
}
return added;
}
@Override
public boolean removeVertex(V v)
{
boolean removed = super.removeVertex(v);
if (removed) {
/*
* Depending on the topoOrderMap implementation, this can leave holes in the topological
* ordering, which can degrade performance for certain operations over time.
*/
Integer topoIndex = topoOrderMap.removeVertex(v);
// if possible contract minTopoIndex
if (topoIndex == minTopoIndex) {
while ((minTopoIndex < 0) && (topoOrderMap.getVertex(minTopoIndex) == null)) {
++minTopoIndex;
}
}
// if possible contract maxTopoIndex
if (topoIndex == maxTopoIndex) {
while ((maxTopoIndex > 0) && (topoOrderMap.getVertex(maxTopoIndex) == null)) {
--maxTopoIndex;
}
}
++topoModCount;
}
return removed;
}
/**
* {@inheritDoc}
*
*
* The complexity of adding a new edge in the graph depends on the number of edges incident to
* the "affected region", and should in general be faster than recomputing the whole topological
* ordering from scratch.
*
* @throws IllegalArgumentException if the vertex is not in the graph
* @throws GraphCycleProhibitedException if the vertex would induce a cycle in the graph
*/
@Override
public E addEdge(V sourceVertex, V targetVertex)
{
assertVertexExist(sourceVertex);
assertVertexExist(targetVertex);
try {
updateDag(sourceVertex, targetVertex);
return super.addEdge(sourceVertex, targetVertex);
} catch (CycleFoundException e) {
throw new GraphCycleProhibitedException();
}
}
/**
* {@inheritDoc}
*
*
* The complexity of adding a new edge in the graph depends on the number of edges incident to
* the "affected region", and should in general be faster than recomputing the whole topological
* ordering from scratch.
*
* @throws IllegalArgumentException if the vertex is not in the graph
* @throws GraphCycleProhibitedException if the vertex would induce a cycle in the graph
*/
@Override
public boolean addEdge(V sourceVertex, V targetVertex, E e)
{
if (e == null) {
throw new NullPointerException();
} else if (containsEdge(e)) {
return false;
}
assertVertexExist(sourceVertex);
assertVertexExist(targetVertex);
try {
updateDag(sourceVertex, targetVertex);
return super.addEdge(sourceVertex, targetVertex, e);
} catch (CycleFoundException ex) {
throw new GraphCycleProhibitedException();
}
}
/**
* Get the ancestors of a vertex.
*
* @param vertex the vertex to get the ancestors of
* @return {@link Set} of ancestors of a vertex
*/
public Set getAncestors(V vertex)
{
EdgeReversedGraph reversedGraph = new EdgeReversedGraph<>(this);
Iterator iterator = new DepthFirstIterator<>(reversedGraph, vertex);
Set ancestors = new HashSet<>();
// Do not add start vertex to result.
if (iterator.hasNext()) {
iterator.next();
}
iterator.forEachRemaining(ancestors::add);
return ancestors;
}
/**
* Get the descendants of a vertex.
*
* @param vertex the vertex to get the descendants of
* @return {@link Set} of descendants of a vertex
*/
public Set getDescendants(V vertex)
{
Iterator iterator = new DepthFirstIterator<>(this, vertex);
Set descendants = new HashSet<>();
// Do not add start vertex to result.
if (iterator.hasNext()) {
iterator.next();
}
iterator.forEachRemaining(descendants::add);
return descendants;
}
/**
* Returns a topological order iterator.
*
* @return a topological order iterator
*/
@Override
public Iterator iterator()
{
return new TopoIterator();
}
/**
* Update as if a new edge is added.
*
* @param sourceVertex the source vertex
* @param targetVertex the target vertex
*/
private void updateDag(V sourceVertex, V targetVertex)
throws CycleFoundException
{
Integer lb = topoOrderMap.getTopologicalIndex(targetVertex);
Integer ub = topoOrderMap.getTopologicalIndex(sourceVertex);
if (lb < ub) {
Set df = new HashSet<>();
Set db = new HashSet<>();
// discovery
Region affectedRegion = new Region(lb, ub);
VisitedStrategy visited = visitedStrategyFactory.getVisitedStrategy(affectedRegion);
// throws CycleFoundException if there is a cycle
dfsF(targetVertex, df, visited, affectedRegion);
dfsB(sourceVertex, db, visited, affectedRegion);
reorder(df, db, visited);
/*
* if we do a reorder, then the topology has been updated
*/
++topoModCount;
}
}
/**
* Depth first search forward, building up the set (df) of forward-connected vertices in the
* Affected Region
*
* @param initialVertex the vertex being visited
* @param df the set we are populating with forward connected vertices in the Affected Region
* @param visited a simple data structure that lets us know if we already visited a node with a
* given topo index
*
* @throws CycleFoundException if a cycle is discovered
*/
private void dfsF(V initialVertex, Set df, VisitedStrategy visited, Region affectedRegion)
throws CycleFoundException
{
Deque vertices = new ArrayDeque<>();
vertices.push(initialVertex);
while (!vertices.isEmpty()) {
V vertex = vertices.pop();
int topoIndex = topoOrderMap.getTopologicalIndex(vertex);
if (visited.getVisited(topoIndex)) {
continue;
}
// Assumption: vertex is in the AR and so it will be in visited
visited.setVisited(topoIndex);
df.add(vertex);
for (E outEdge : outgoingEdgesOf(vertex)) {
V nextVertex = getEdgeTarget(outEdge);
Integer nextVertexTopoIndex = topoOrderMap.getTopologicalIndex(nextVertex);
if (nextVertexTopoIndex == affectedRegion.finish) {
// reset visited
try {
for (V visitedVertex : df) {
visited.clearVisited(topoOrderMap.getTopologicalIndex(visitedVertex));
}
} catch (UnsupportedOperationException e) {
// okay, fine, some implementations (ones that automatically
// reset themselves out) don't work this way
}
throw new CycleFoundException();
}
/*
* Note, order of checks is important as we need to make sure the vertex is in the
* affected region before we check its visited status (otherwise we will be causing
* an ArrayIndexOutOfBoundsException).
*/
if (affectedRegion.isIn(nextVertexTopoIndex)
&& !visited.getVisited(nextVertexTopoIndex))
{
vertices.push(nextVertex); // recurse
}
}
}
}
/**
* Depth first search backward, building up the set (db) of back-connected vertices in the
* Affected Region
*
* @param initialVertex the vertex being visited
* @param db the set we are populating with back-connected vertices in the AR
* @param visited
*/
private void dfsB(V initialVertex, Set db, VisitedStrategy visited, Region affectedRegion)
{
Deque vertices = new ArrayDeque<>();
vertices.push(initialVertex);
while (!vertices.isEmpty()) {
V vertex = vertices.pop();
// Assumption: vertex is in the AR and so we will get a topoIndex from
// the map
int topoIndex = topoOrderMap.getTopologicalIndex(vertex);
if (visited.getVisited(topoIndex)) {
continue;
}
visited.setVisited(topoIndex);
db.add(vertex);
for (E inEdge : incomingEdgesOf(vertex)) {
V previousVertex = getEdgeSource(inEdge);
Integer previousVertexTopoIndex = topoOrderMap.getTopologicalIndex(previousVertex);
/*
* Note, order of checks is important as we need to make sure the vertex is in the
* affected region before we check its visited status (otherwise we will be causing
* an ArrayIndexOutOfBoundsException).
*/
if (affectedRegion.isIn(previousVertexTopoIndex)
&& !visited.getVisited(previousVertexTopoIndex))
{
// if previousVertexTopoIndex != null, the vertex is in the
// Affected Region according to our topoIndexMap
vertices.push(previousVertex);
}
}
}
}
@SuppressWarnings("unchecked")
private void reorder(Set df, Set db, VisitedStrategy visited)
{
List topoDf = new ArrayList<>(df);
List topoDb = new ArrayList<>(db);
topoDf.sort(topoComparator);
topoDb.sort(topoComparator);
// merge these suckers together in topological order
SortedSet availableTopoIndices = new TreeSet<>();
// we have to cast to the generic type, can't do "new V[size]" in java
// 5;
V[] bigL = (V[]) new Object[df.size() + db.size()];
int lIndex = 0; // this index is used for the sole purpose of pushing
// into
// the correct index of bigL
// assume (for now) that we are resetting visited
boolean clearVisited = true;
for (V vertex : topoDb) {
Integer topoIndex = topoOrderMap.getTopologicalIndex(vertex);
// add the available indices to the set
availableTopoIndices.add(topoIndex);
bigL[lIndex++] = vertex;
if (clearVisited) { // reset visited status if supported
try {
visited.clearVisited(topoIndex);
} catch (UnsupportedOperationException e) {
clearVisited = false;
}
}
}
for (V vertex : topoDf) {
Integer topoIndex = topoOrderMap.getTopologicalIndex(vertex);
// add the available indices to the set
availableTopoIndices.add(topoIndex);
bigL[lIndex++] = vertex;
if (clearVisited) { // reset visited status if supported
try {
visited.clearVisited(topoIndex);
} catch (UnsupportedOperationException e) {
clearVisited = false;
}
}
}
lIndex = 0; // reusing lIndex
for (Integer topoIndex : availableTopoIndices) {
// assign the indexes to the elements of bigL in order
V vertex = bigL[lIndex++]; // note the post-increment
topoOrderMap.putVertex(topoIndex, vertex);
}
}
/**
* An interface for storing the topological ordering.
*
* @param the graph vertex type
*
* @author Peter Giles
*/
protected interface TopoOrderMap
extends
Serializable
{
/**
* Add a vertex at the given topological index.
*
* @param index the topological index
* @param vertex the vertex
*/
void putVertex(Integer index, V vertex);
/**
* Get the vertex at the given topological index.
*
* @param index the topological index
* @return vertex the vertex
*/
V getVertex(Integer index);
/**
* Get the topological index of the given vertex.
*
* @param vertex the vertex
* @return the index that the vertex is at, or null if the vertex isn't in the topological
* ordering
*/
Integer getTopologicalIndex(V vertex);
/**
* Remove the given vertex from the topological ordering.
*
* @param vertex the vertex
* @return the index that the vertex was at, or null if the vertex wasn't in the topological
* ordering
*/
Integer removeVertex(V vertex);
/**
* Remove all vertices from the topological ordering.
*/
void removeAllVertices();
}
/**
* A strategy for marking vertices as visited.
*
*
* Vertices are indexed by their topological index, to avoid using the vertex type in the
* interface.
*
* @author Peter Giles
*/
protected interface VisitedStrategy
{
/**
* Mark the given topological index as visited.
*
* @param index the topological index
*/
void setVisited(int index);
/**
* Get if the given topological index has been visited.
*
* @param index the topological index
* @return true if the given topological index has been visited, false otherwise
*/
boolean getVisited(int index);
/**
* Clear the visited state of the given topological index.
*
* @param index the index
* @throws UnsupportedOperationException if the implementation doesn't support (or doesn't
* need) clearance. For example, if the factory creates a new instance every time,
* it is a waste of cycles to reset the state after the search of the Affected
* Region is done, so an UnsupportedOperationException *should* be thrown.
*/
void clearVisited(int index)
throws UnsupportedOperationException;
}
/**
* A visited strategy factory.
*
* @author Peter Giles
*/
protected interface VisitedStrategyFactory
extends
Serializable
{
/**
* Create a new instance of {@link VisitedStrategy}.
*
* @param affectedRegion the affected region
* @return a new instance of {@link VisitedStrategy} for the affected region
*/
VisitedStrategy getVisitedStrategy(Region affectedRegion);
}
/**
* A dual map implementation of the topological order map.
*
* @author Peter Giles
*/
protected static class TopoVertexBiMap
implements
TopoOrderMap
{
private static final long serialVersionUID = 1L;
private final Map topoToVertex = new HashMap<>();
private final Map vertexToTopo = new HashMap<>();
/**
* Constructor
*/
public TopoVertexBiMap()
{
}
@Override
public void putVertex(Integer index, V vertex)
{
topoToVertex.put(index, vertex);
vertexToTopo.put(vertex, index);
}
@Override
public V getVertex(Integer index)
{
return topoToVertex.get(index);
}
@Override
public Integer getTopologicalIndex(V vertex)
{
return vertexToTopo.get(vertex);
}
@Override
public Integer removeVertex(V vertex)
{
Integer topoIndex = vertexToTopo.remove(vertex);
if (topoIndex != null) {
topoToVertex.remove(topoIndex);
}
return topoIndex;
}
@Override
public void removeAllVertices()
{
vertexToTopo.clear();
topoToVertex.clear();
}
}
/**
* An implementation of the topological order map which for performance and flexibility uses an
* ArrayList for topological index to vertex mapping, and a HashMap for vertex to topological
* index mapping.
*
* @author Peter Giles
*/
protected class TopoVertexMap
implements
TopoOrderMap
{
private static final long serialVersionUID = 1L;
private final List topoToVertex = new ArrayList<>();
private final Map vertexToTopo = new HashMap<>();
/**
* Constructor
*/
public TopoVertexMap()
{
}
@Override
public void putVertex(Integer index, V vertex)
{
int translatedIndex = translateIndex(index);
// grow topoToVertex as needed to accommodate elements
while ((translatedIndex + 1) > topoToVertex.size()) {
topoToVertex.add(null);
}
topoToVertex.set(translatedIndex, vertex);
vertexToTopo.put(vertex, index);
}
@Override
public V getVertex(Integer index)
{
return topoToVertex.get(translateIndex(index));
}
@Override
public Integer getTopologicalIndex(V vertex)
{
return vertexToTopo.get(vertex);
}
@Override
public Integer removeVertex(V vertex)
{
Integer topoIndex = vertexToTopo.remove(vertex);
if (topoIndex != null) {
topoToVertex.set(translateIndex(topoIndex), null);
}
return topoIndex;
}
@Override
public void removeAllVertices()
{
vertexToTopo.clear();
topoToVertex.clear();
}
/**
* We translate the topological index to an ArrayList index. We have to do this because
* topological indices can be negative, and we want to do it because we can make better use
* of space by only needing an ArrayList of size |AR|.
*
* @return the ArrayList index
*/
private int translateIndex(int index)
{
if (index >= 0) {
return 2 * index;
}
return -1 * ((index * 2) - 1);
}
}
/**
* An inclusive range of indices: [start, finish].
*
* @author Peter Giles
*/
protected static class Region
implements
Serializable
{
private static final long serialVersionUID = 1L;
private final int start;
private final int finish;
/**
* Construct a new region.
*
* @param start the start of the region
* @param finish the end of the region (inclusive)
*/
public Region(int start, int finish)
{
if (start > finish) {
throw new IllegalArgumentException("(start > finish): invariant broken");
}
this.start = start;
this.finish = finish;
}
/**
* Get the size of the region.
*
* @return the size of the region
*/
public int getSize()
{
return (finish - start) + 1;
}
/**
* Check if index is in the region.
*
* @param index the index to check
* @return true if the index is in the region, false otherwise
*/
public boolean isIn(int index)
{
return (index >= start) && (index <= finish);
}
/**
* Get the start of the region.
*
* @return the start of the region
*/
public int getStart()
{
return start;
}
/**
* Get the end of the region (inclusive).
*
* @return the end of the region (inclusive)
*/
public int getFinish()
{
return finish;
}
}
/**
* A visited strategy which uses a {@link BitSet}.
*
*
* This implementation is close to the performance of {@link VisitedArrayListImpl}, with 1/8 the
* memory usage.
*
* @author John V. Sichi
*/
protected static class VisitedBitSetImpl
implements
VisitedStrategy,
VisitedStrategyFactory
{
private static final long serialVersionUID = 1L;
private final BitSet visited = new BitSet();
private Region affectedRegion;
/**
* Constructor
*/
public VisitedBitSetImpl()
{
}
@Override
public VisitedStrategy getVisitedStrategy(Region affectedRegion)
{
this.affectedRegion = affectedRegion;
return this;
}
@Override
public void setVisited(int index)
{
visited.set(translateIndex(index), true);
}
@Override
public boolean getVisited(int index)
{
return visited.get(translateIndex(index));
}
@Override
public void clearVisited(int index)
throws UnsupportedOperationException
{
visited.clear(translateIndex(index));
}
/**
* We translate the topological index to an ArrayList index. We have to do this because
* topological indices can be negative, and we want to do it because we can make better use
* of space by only needing an ArrayList of size |AR|.
*
* @return the ArrayList index
*/
private int translateIndex(int index)
{
return index - affectedRegion.start;
}
}
/**
* A visited strategy using an {@link ArrayList}.
*
*
* This implementation seems to offer the best performance in most cases. It grows the internal
* ArrayList as needed to be as large as |AR|, so it will be more memory intensive than the
* HashSet implementation, and unlike the Array implementation, it will hold on to that memory
* (it expands, but never contracts).
*
* @author Peter Giles
*/
protected static class VisitedArrayListImpl
implements
VisitedStrategy,
VisitedStrategyFactory
{
private static final long serialVersionUID = 1L;
private final List visited = new ArrayList<>();
private Region affectedRegion;
/**
* Constructor
*/
public VisitedArrayListImpl()
{
}
@Override
public VisitedStrategy getVisitedStrategy(Region affectedRegion)
{
// Make sure visited is big enough
int minSize = (affectedRegion.finish - affectedRegion.start) + 1;
/* plus one because the region range is inclusive of both indices */
while (visited.size() < minSize) {
visited.add(Boolean.FALSE);
}
this.affectedRegion = affectedRegion;
return this;
}
@Override
public void setVisited(int index)
{
visited.set(translateIndex(index), Boolean.TRUE);
}
@Override
public boolean getVisited(int index)
{
return visited.get(translateIndex(index));
}
@Override
public void clearVisited(int index)
throws UnsupportedOperationException
{
visited.set(translateIndex(index), Boolean.FALSE);
}
/**
* We translate the topological index to an ArrayList index. We have to do this because
* topological indices can be negative, and we want to do it because we can make better use
* of space by only needing an ArrayList of size |AR|.
*
* @return the ArrayList index
*/
private int translateIndex(int index)
{
return index - affectedRegion.start;
}
}
/**
* A visited strategy using a {@link HashSet}.
*
*
* This implementation doesn't seem to perform as well, though I can imagine circumstances where
* it should shine (lots and lots of vertices). It also should have the lowest memory footprint
* as it only uses storage for indices that have been visited.
*
* @author Peter Giles
*/
protected static class VisitedHashSetImpl
implements
VisitedStrategy,
VisitedStrategyFactory
{
private static final long serialVersionUID = 1L;
private final Set visited = new HashSet<>();
/**
* Constructor
*/
public VisitedHashSetImpl()
{
}
@Override
public VisitedStrategy getVisitedStrategy(Region affectedRegion)
{
visited.clear();
return this;
}
@Override
public void setVisited(int index)
{
visited.add(index);
}
@Override
public boolean getVisited(int index)
{
return visited.contains(index);
}
@Override
public void clearVisited(int index)
throws UnsupportedOperationException
{
throw new UnsupportedOperationException();
}
}
/**
* A visited strategy using an array.
*
*
* This implementation, somewhat to my surprise, is slower than the ArrayList version, probably
* due to its reallocation of the underlying array for every topology reorder that is required.
*
* @author Peter Giles
*/
protected static class VisitedArrayImpl
implements
VisitedStrategy,
VisitedStrategyFactory
{
private static final long serialVersionUID = 1L;
private final boolean[] visited;
private final Region region;
/**
* Constructs empty instance
*/
public VisitedArrayImpl()
{
this(null);
}
/**
* Construct an empty instance for a region.
*
* @param region the region
*/
public VisitedArrayImpl(Region region)
{
if (region == null) { // make empty instance
this.visited = null;
this.region = null;
} else { // fill in the needed pieces
this.region = region;
// initialized to all false by default
visited = new boolean[region.getSize()];
}
}
@Override
public VisitedStrategy getVisitedStrategy(Region affectedRegion)
{
return new VisitedArrayImpl(affectedRegion);
}
@Override
public void setVisited(int index)
{
visited[index - region.start] = true;
}
@Override
public boolean getVisited(int index)
{
return visited[index - region.start];
}
@Override
public void clearVisited(int index)
throws UnsupportedOperationException
{
throw new UnsupportedOperationException();
}
}
/**
* Exception used in dfsF when a cycle is found
*
* @author Peter Giles
*/
private static class CycleFoundException
extends
Exception
{
private static final long serialVersionUID = 5583471522212552754L;
}
/**
* Comparator for vertices based on their topological ordering
*
* @author Peter Giles
*/
private class TopoComparator
implements
Comparator,
Serializable
{
private static final long serialVersionUID = 8144905376266340066L;
@Override
public int compare(V o1, V o2)
{
return topoOrderMap
.getTopologicalIndex(o1).compareTo(topoOrderMap.getTopologicalIndex(o2));
}
}
/**
* An iterator which follows topological order
*
* @author Peter Giles
*/
private class TopoIterator
implements
Iterator
{
private int currentTopoIndex;
private final long expectedTopoModCount = topoModCount;
private Integer nextIndex = null;
public TopoIterator()
{
currentTopoIndex = minTopoIndex - 1;
}
@Override
public boolean hasNext()
{
if (expectedTopoModCount != topoModCount) {
throw new ConcurrentModificationException();
}
nextIndex = getNextIndex();
return nextIndex != null;
}
@Override
public V next()
{
if (expectedTopoModCount != topoModCount) {
throw new ConcurrentModificationException();
}
if (nextIndex == null) {
// find nextIndex
nextIndex = getNextIndex();
}
if (nextIndex == null) {
throw new NoSuchElementException();
}
currentTopoIndex = nextIndex;
nextIndex = null;
return topoOrderMap.getVertex(currentTopoIndex);
}
@Override
public void remove()
{
if (expectedTopoModCount != topoModCount) {
throw new ConcurrentModificationException();
}
V vertexToRemove;
if ((vertexToRemove = topoOrderMap.getVertex(currentTopoIndex)) != null) {
topoOrderMap.removeVertex(vertexToRemove);
} else {
// should only happen if next() hasn't been called
throw new IllegalStateException();
}
}
private Integer getNextIndex()
{
for (int i = currentTopoIndex + 1; i <= maxTopoIndex; i++) {
if (topoOrderMap.getVertex(i) != null) {
return i;
}
}
return null;
}
}
}