Here goes a implementation I did some time ago:
/**
*
* Sorts a directed graph, obtaining a visiting sequence ("sorted" list)
* that respects the "Predecessors" (as in a job/task requirements list).
* (when there is freedom, the original ordering is preferred)
*
* The behaviour in case of loops (cycles) depends on the "mode":
* permitLoops == false : loops are detected, but result is UNDEFINED (simpler)
* permitLoops == true : loops are detected, result a "best effort" try, original ordering is privileged
*
* http://en.wikipedia.org/wiki/Topological_sort
*/
public class TopologicalSorter {
private final boolean permitLoops;
private final Collection graph; // original graph. this is not touched.
private final List sorted = new ArrayList(); // result
private final Set visited = new HashSet(); // auxiliar list
private final Set withLoops = new HashSet();
// auxiliar: all succesors (also remote) of each node; this is only used if permitLoops==true
private HashMap> succesors = null;
public TopologicalSorter(Collection graph, boolean permitLoops) {
this.graph = graph;
this.permitLoops = permitLoops;
}
public void sort() {
init();
for( T n : graph ) {
if( permitLoops ) visitLoopsPermitted(n);
else visitLoopsNoPermitted(n, new HashSet());
}
}
private void init() {
sorted.clear();
visited.clear();
withLoops.clear();
// build succesors map: only it permitLoops == true
if( permitLoops ) {
succesors = new HashMap>();
HashMap> addTo = new HashMap();
for( T n : graph ) {
succesors.put(n, new HashSet());
addTo.put(n, new HashSet());
}
for( T n2 : graph ) {
for( DirectedGraphNode n1 : n2.getPredecessors() ) {
succesors.get(n1).add(n2);
}
}
boolean change = false;
do {
change = false;
for( T n : graph ) {
addTo.get(n).clear();
for( T ns : succesors.get(n) ) {
for( T ns2 : succesors.get(ns) ) {
if( !succesors.get(n).contains(ns2) ) {
change = true;
addTo.get(n).add(ns2);
}
}
}
}
for( DirectedGraphNode n : graph ) {
succesors.get(n).addAll(addTo.get(n));
}
} while(change);
}
}
private void visitLoopsNoPermitted(T n, Set visitedInThisCallStack) { // this is simpler than visitLoopsPermitted
if( visited.contains(n) ) {
if( visitedInThisCallStack.contains(n) ) {
withLoops.add(n); // loop!
}
return;
}
//System.out.println("visiting " + n.toString());
visited.add(n);
visitedInThisCallStack.add(n);
for( DirectedGraphNode n1 : n.getPredecessors() ) {
visitLoopsNoPermitted((T) n1, visitedInThisCallStack);
}
sorted.add(n);
}
private void visitLoopsPermitted(T n) {
if( visited.contains(n) ) return;
//System.out.println("visiting " + n.toString());
visited.add(n);
for( DirectedGraphNode n1 : n.getPredecessors() ) {
if( succesors.get(n).contains(n1) ) {
withLoops.add(n);
withLoops.add((T) n1);
continue;
} // loop!
visitLoopsPermitted((T) n1);
}
sorted.add(n);
}
public boolean hadLoops() {
return withLoops.size() > 0;
}
public List getSorted() {
return sorted;
}
public Set getWithLoops() {
return withLoops;
}
public void showResult() { // for debugging
for( DirectedGraphNode node : sorted ) {
System.out.println(node.toString());
}
if( hadLoops() ) {
System.out.println("LOOPS!:");
for( DirectedGraphNode node : withLoops ) {
System.out.println(" " + node.toString());
}
}
}
}
/**
* Node that conform a DirectedGraph
* It is used by TopologicalSorter
*/
public interface DirectedGraphNode {
/**
* empty collection if no predecessors
* @return
*/
public Collection getPredecessors();
}
And here one example of use:
public class TopologicalSorterExample {
public static class Node implements DirectedGraphNode {
public final String x;
public ArrayList antec = new ArrayList(); // immediate antecesors
public Node(String x) {this.x= x;}
public Collection getPredecessors() {
return antec;
}
public String toString() {
return x;
}
}
public static void main(String[] args) {
List graph = new ArrayList();
Node na = new Node("A");
Node nb = new Node("B");
Node nc = new Node("C");
Node nd = new Node("D");
Node ne = new Node("E");
nc.antec.add(na);
nc.antec.add(nb);
nd.antec.add(ne);
ne.antec.add(na);
na.antec.add(nd);
graph.add(nc);
graph.add(na);
graph.add(nb);
graph.add(ne);
graph.add(nd);
TopologicalSorter ts = new TopologicalSorter(graph, false);
ts.sort();
ts.showResult();
}
}
Two additional features (or complications) in my code:
I needed to support loops (cycles) in my case, so that if the graph has loops it makes some "best effort" ordering. This behaviour is controlled by a flag passed to the constructor. In any case, you can (should) call hadLoops()
to ask if there were cycles detected.
Besides, I wanted the sorting algorithm to prefer the original ordering in case of freedom.