package uk.ac.rhul.cs.graph;
import com.sosnoski.util.array.IntArray;
import com.sosnoski.util.stack.ObjectStack;
import uk.ac.rhul.cs.collections.IntIntHashMap;
import java.util.BitSet;
import java.util.Iterator;
/**
* Iterator that traverses the vertices of a graph in depth first order.
*
* @author tamas
*/
public class DepthFirstSearchIterator implements Iterator<Integer> {
/**
* Graph which the iterator will traverse
*/
protected Graph graph = null;
/**
* When the DFS is restricted to a subgraph, this mapping maps the set of nodes that
* are allowed during the DFS to their indices that are used in auxiliary DFS arrays.
* Otherwise it is null.
*/
protected IntIntHashMap allowedNodeToIndexMapping = null;
/**
* When the DFS is restricted to a subgraph, this array contains the inverse of the
* <code>allowedNodeToIndexMapping</code>. Otherwise it is null.
*/
protected IntArray indexToAllowedNodeMapping = null;
/**
* Queue that holds the nodes that are to be visited, their distances from the seed , their
* parents and the index of the next neighbor to visit.
*/
protected ObjectStack q = new ObjectStack();
/**
* Bitset that holds flags that marks the nodes that have been visited.
*/
protected BitSet visited;
/**
* Distance of the last returned node from the seed
*/
protected int distance = -1;
/**
* Parent of the last returned node
*/
protected int parent = -1;
/**
* The seed node of the depth first search.
*/
protected int seedNode;
/**
* Constructs a new DFS iterator.
*
* @param graph the graph to be traversed
* @param seedNode the index of the seed node
*/
public DepthFirstSearchIterator(Graph graph, int seedNode) {
this(graph, seedNode, null);
}
/**
* Constructs a new DFS iterator restricted to a set of nodes.
*
* @param graph the graph to be traversed
* @param seedNode the index of the seed node
* @param subset an array of node indices which must be traversed.
* Nodes not in this nodeset are assumed to have been
* already visited by the iterator. Can also be null,
* which means that every node can be traversed.
*/
public DepthFirstSearchIterator(Graph graph, int seedNode, int[] subset) {
this.graph = graph;
this.seedNode = seedNode;
if (subset != null) {
restrictToSubgraph(subset);
}
if (allowedNodeToIndexMapping == null) {
visited = new BitSet(graph.getNodeCount());
} else {
visited = new BitSet(indexToAllowedNodeMapping.size());
}
if (allowedNodeToIndexMapping == null ||
allowedNodeToIndexMapping.get(seedNode) != IntIntHashMap.DEFAULT_NOT_FOUND) {
pushNode(seedNode, -1, 0);
}
}
/**
* Hook method that is called when the DFS traversal enters the subtree of a given node.
*
* @param item the item in the DFS queue that holds information about the node that the
* DFS traversal entered
*/
public void enterSubtreeHook(Item item) {
}
/**
* Hook method that is called when the DFS traversal exits the subtree of a given node.
*
* @param item the item in the DFS queue that holds information about the node that the
* DFS traversal exited from
*/
public void exitSubtreeHook(Item item) {
}
/**
* Hook method that is called when the DFS traversal attempts to visit a node that has already
* been visited.
*
* @param item the item representing the node currently being visited
* @param neighbor the neighbor that the traversal attempted to visit
* @param neighborIndex index of the neighbor in the set of nodes that the iterator is restricted
* to; equal to <code>neighbor</code> if the traversal is not restricted.
*/
public void visitedNodeFoundHook(Item item, int neighbor, int neighborIndex) {
}
/**
* Returns the distance of the last returned node from the seed.
*
* @return the distance of the last node from the seed or -1 if the
* traversal has not yet started.
*/
public int getDistance() {
return distance;
}
/**
* Returns the parent of the last returned node from the seed.
*
* @return the parent of the last node in the BFS tree or -1 if the
* traversal has not yet started or the node is the root.
*/
public int getParent() {
return parent;
}
/**
* Returns whether there are more nodes left in the traversal.
*/
public boolean hasNext() {
return !q.isEmpty();
}
/**
* Returns the index of the next visited node
*/
public Integer next() {
Item nextItem = (Item)q.peek();
int result = nextItem.node;
distance = nextItem.distance;
parent = nextItem.parent;
stepIterator();
return result;
}
/**
* Removal is not supported.
*/
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Restricts the BFS traversal to the given subset.
*
* @param subset an array of node indices to which we restrict
* the traversal
*/
public void restrictToSubgraph(int[] subset) {
int i = 0;
// The trick here is that we create a "remapping" between node indices in 'subset' to the range
// of integers from 0 to n-1, where n is the number of unique elements in 'subset'. This allows
// us to work with dense arrays instead of hash maps in the actual DFS iteration to maintain
// state information related to the allowed nodes.
//
// allowedNodeToIndexMapping will map a node index to the index of the corresponding slot in DFS
// arrays, while indexToAllowedNodeMapping will contain the opposite mapping.
allowedNodeToIndexMapping = new IntIntHashMap();
indexToAllowedNodeMapping = new IntArray(subset.length);
for (int node: subset) {
if (allowedNodeToIndexMapping.get(node) == IntIntHashMap.DEFAULT_NOT_FOUND) {
allowedNodeToIndexMapping.add(node, i);
indexToAllowedNodeMapping.add(node);
i++;
}
}
}
/**
* A single item of the DFS queue.
*
* Note that this object implements <code>Iterable<Integer></code>, but we do not expose this
* to disallow external objects holding a reference to an Item to advance the internal state
* without the DFS iterator knowing about it.
*/
protected class Item {
/**
* The node in the DFS queue.
*/
public int node;
/**
* The index of the slot in DFS arrays that store information related to the node.
*/
public int nodeIndex;
/**
* The distance of the node from the seed node of the DFS.
*/
public int distance;
/**
* The parent of the node in the DFS tree or -1 if the node is the root of the
* DFS tree.
*/
public int parent;
/**
* The index of the slot in DFS arrays that store information related to the parent node
* or -1 if the node is the root of the DFS tree.
*/
public int parentIndex;
/**
* Array containing integers that define the neighbors of the node. Each integer must be looked up
* in the <code>indexToAllowedNodeMapping</code> if the iterator is restricted.
*/
int[] neighborIndexes;
/**
* Index pointer into the neighborIndexes array defining the next neighbor of the node to visit.
*/
int neighborIndexesReadPtr;
/**
* The number of neighbors of this node.
*/
int numNeighbors;
/**
* Constructs a new item for the DFS queue with the given distance and the given parent.
*/
public Item(int node, int parent, int distance) {
this.node = node;
this.parent = parent;
this.distance = distance;
if (allowedNodeToIndexMapping == null) {
this.nodeIndex = node;
this.parentIndex = parent;
} else {
this.nodeIndex = allowedNodeToIndexMapping.get(node);
assert this.nodeIndex != IntIntHashMap.DEFAULT_NOT_FOUND;
if (parent >= 0) {
this.parentIndex = allowedNodeToIndexMapping.get(parent);
assert this.parentIndex != IntIntHashMap.DEFAULT_NOT_FOUND;
} else {
this.parentIndex = -1;
}
}
this.neighborIndexes = getAdjacentNodeIndicesArray(node, Directedness.ALL);
this.neighborIndexesReadPtr = 0;
this.numNeighbors = this.neighborIndexes.length;
}
/**
* Returns whether this node has more neighbor nodes to visit
*/
public boolean hasNext() {
return neighborIndexesReadPtr < numNeighbors;
}
/**
* Returns an item representing the <em>array index</em> of the next neighbor to visit and
* advances the state of this item accordingly. You must look this index up in
* <code>indexToAllowedNodeMapping</code> to get the actual node if the iterator
* is restricted.
*/
public int next() {
int nextNeighbor = neighborIndexes[neighborIndexesReadPtr];
neighborIndexesReadPtr++;
return nextNeighbor;
}
/**
* Returns the <em>array indices</em> of the nodes adjacent to the given node.
*/
private int[] getAdjacentNodeIndicesArray(int node, Directedness mode) {
if (allowedNodeToIndexMapping == null) {
// Every node is allowed, so we query the graph directly
return graph.getAdjacentNodeIndicesArray(node, Directedness.ALL);
}
int[] edges = graph.getAdjacentEdgeIndicesArray(node, mode);
IntArray nodes = new IntArray(edges.length);
for (int edge: edges) {
int otherNode = graph.getEdgeEndpoint(edge, node);
otherNode = allowedNodeToIndexMapping.get(otherNode);
if (otherNode != IntIntHashMap.DEFAULT_NOT_FOUND) {
nodes.add(otherNode);
}
}
return nodes.toArray();
}
}
/**
* Pushes the given item into the queue.
*
* @param item the item to push
*/
private void pushItem(Item item) {
visited.set(item.nodeIndex);
q.push(item);
}
/**
* Pushes the given node with the given distance into the queue if we are allowed
* to visit the node.
*
* @param node the node to push to the queue
* @param parent the parent of the node or -1 if the node is the root
* @param distance the distance of the node from the start point
*/
private void pushNode(int node, int parent, int distance) {
pushItem(new Item(node, parent, distance));
}
/**
* Advances the internal state of the iterator to the next node to visit.
*/
private void stepIterator() {
Item item = (Item)q.peek();
int nextNeighbor;
int indexOfNextNeighbor;
while (item != null) {
// Are we entering this item for the first time?
if (item.neighborIndexesReadPtr == 0) {
enterSubtreeHook(item);
}
while (item.hasNext()) {
// Visit the next neighbor of the item
indexOfNextNeighbor = item.next();
if (indexOfNextNeighbor == item.parentIndex)
continue;
if (indexToAllowedNodeMapping != null) {
nextNeighbor = indexToAllowedNodeMapping.get(indexOfNextNeighbor);
} else {
nextNeighbor = indexOfNextNeighbor;
}
if (!visited.get(indexOfNextNeighbor)) {
pushNode(nextNeighbor, item.node, item.distance + 1);
return;
} else {
visitedNodeFoundHook(item, nextNeighbor, indexOfNextNeighbor);
}
}
// Neighbors exhausted; pop the item from the queue instead
exitSubtreeHook((Item)q.pop());
item = q.isEmpty() ? null : (Item)q.peek();
}
}
}