package uk.ac.rhul.cs.graph;
import uk.ac.rhul.cs.utils.IteratorUtils;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* Finds all the cut vertices (articulation points) in a graph or one of its subgraphs
* using Tarjan's algorithm.
*
* @author ntamas
*/
public class TarjanCutVertexFinder extends GraphAlgorithm {
/**
* Subgraph on which we search for cut vertices or null if we run it on the whole graph
*/
protected int[] subgraph = null;
/**
* Finds all the cut vertices of the associated graph or subgraph and returns them
* in an array.
*
* @return the set of cut vertices of the graph
*/
public Set<Integer> findCutVertices() {
Set<Integer> result = new HashSet<Integer>();
if (graph != null && graph.getNodeCount() > 0) {
DepthFirstSearchIteratorWithLowpoints iterator;
int seedNode = (subgraph != null) ? subgraph[0] : 0;
iterator = new DepthFirstSearchIteratorWithLowpoints(graph, seedNode, subgraph, result);
IteratorUtils.exhaust(iterator);
}
return result;
}
/**
* Restricts the traversal to the given subgraph if supported.
*
* @throws java.lang.UnsupportedOperationException if the traversal does not support
* restrictions to subgraphs.
*/
public void restrictToSubgraph(int[] subgraph) {
this.subgraph = subgraph.clone();
Arrays.sort(this.subgraph);
}
/**
* Subclass of a depth first search iterator that maintains the lowpoints of each DFS
* subtree.
*/
class DepthFirstSearchIteratorWithLowpoints extends DepthFirstSearchIterator {
private int[] depths;
private int[] lowPoints;
private int rootChildCount = 0;
private Set<Integer> cutVertices;
public DepthFirstSearchIteratorWithLowpoints(Graph graph, int seedNode, int[] subset,
Set<Integer> cutVertices) {
super(graph, seedNode, subset);
this.cutVertices = cutVertices;
int numNodes = (subset != null) ? subset.length : graph.getNodeCount();
depths = new int[numNodes];
lowPoints = new int[numNodes];
}
@Override
public void enterSubtreeHook(Item item) {
depths[item.nodeIndex] = item.distance;
lowPoints[item.nodeIndex] = item.distance;
}
@Override
public void exitSubtreeHook(Item item) {
// TODO: this can be made faster by using an array if subset == null
int child = item.node;
int childIndex = item.nodeIndex;
int parent = item.parent;
int parentIndex = item.parentIndex;
if (parent == -1) {
// This is the seed vertex. We must check whether there were at least two
// children to decide whether the seed is a cut vertex.
if (rootChildCount > 1) {
cutVertices.add(child);
}
} else {
// Parent may become a cut vertex if it is not the root and
// lowPoints[child] >= lowPoints[parent]
int childLowPoint = lowPoints[childIndex];
int parentLowPoint = lowPoints[parentIndex];
if ((parent != seedNode) && (childLowPoint >= depths[parentIndex])) {
cutVertices.add(parent);
}
if (childLowPoint < parentLowPoint) {
lowPoints[parentIndex] = childLowPoint;
}
if (parent == seedNode) {
rootChildCount++;
}
}
}
@Override
public void visitedNodeFoundHook(Item item, int neighbor, int neighborIndex) {
// Update the lowpoint of the current node with the depth of the neighbor if needed
int ownLowpoint = lowPoints[item.nodeIndex];
int neighborDepth = depths[neighborIndex];
if (neighborDepth < ownLowpoint) {
lowPoints[item.nodeIndex] = neighborDepth;
}
}
}
}