TopologicalSort.java 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. package be.uantwerpen.ansymo.semanticadaptation.cg.canonical.graph;
  2. /******************************************************************************
  3. * File: TopologicalSort.java
  4. * Author: Keith Schwarz (htiek@cs.stanford.edu)
  5. *
  6. * A linear-time algorithm for computing a topological sort of a directed
  7. * acyclic graph. A topological sort is an ordering of the nodes in a graph
  8. * such that for each node v, all of the ancestors of v appear in the ordering
  9. * before v itself. Topological sorting is useful, for example, when computing
  10. * some function on a DAG where each node's value depends on its ancestors.
  11. * Running a topological sort and then visiting the nodes in the order
  12. * specified by this sorted order ensures that the necessary values for each
  13. * node are available before the node is visited.
  14. *
  15. * There are several algorithms for computing topological sorts. The one used
  16. * here was first described in "Edge-Disjoint Spanning Trees and Depth-First
  17. * Search" by Robert Tarjan. The algorithm is reminiscent of Kosaraju's SCC
  18. * algorithm. We begin by constructing the reverse graph G^{rev} from the
  19. * source graph, then running a depth-first search from each node in the graph.
  20. * Whenever we finish expanding a node, we add it to a list of visited nodes.
  21. * The intution behind this algorithm is that a DFS in the reverse graph will
  22. * visit every node that is an ancestor of the given node before it finishes
  23. * expanding out any node. Since those nodes will be added to the sorted order
  24. * before the expanded node, we have the desired property of the topological
  25. * sort.
  26. *
  27. * This process can be augmented to detect a cycle in the original graph. As
  28. * we do the search, we'll maintain a set of nodes that we have visited and a
  29. * set of nodes that we have expanded. If when doing the DFS we find a node
  30. * that has been visited but not expanded, it means that we have encountered a
  31. * cycle in the graph. Moreover, if a cycle exists, we know that this will
  32. * occur, since the first time any node in the cycle is visited the DFS will
  33. * expand out the cycle.
  34. */
  35. import java.util.*; // For List, Map.
  36. public final class TopologicalSort {
  37. /**
  38. * Given a directed acyclic graph, returns a topological sorting of the
  39. * nodes in the graph. If the input graph is not a DAG, throws an
  40. * IllegalArgumentException.
  41. *
  42. * @param g A directed acyclic graph.
  43. * @return A topological sort of that graph.
  44. * @throws IllegalArgumentException If the graph is not a DAG.
  45. */
  46. public static <T> List<T> sort(DirectedGraph<T> g) {
  47. /* Construct the reverse graph from the input graph. */
  48. DirectedGraph<T> gRev = reverseGraph(g);
  49. /* Maintain two structures - a set of visited nodes (so that once we've
  50. * added a node to the list, we don't label it again), and a list of
  51. * nodes that actually holds the topological ordering.
  52. */
  53. List<T> result = new ArrayList<T>();
  54. Set<T> visited = new HashSet<T>();
  55. /* We'll also maintain a third set consisting of all nodes that have
  56. * been fully expanded. If the graph contains a cycle, then we can
  57. * detect this by noting that a node has been explored but not fully
  58. * expanded.
  59. */
  60. Set<T> expanded = new HashSet<T>();
  61. /* Fire off a DFS from each node in the graph. */
  62. for (T node: gRev)
  63. explore(node, gRev, result, visited, expanded);
  64. /* Hand back the resulting ordering. */
  65. return result;
  66. }
  67. /**
  68. * Recursively performs a DFS from the specified node, marking all nodes
  69. * encountered by the search.
  70. *
  71. * @param node The node to begin the search from.
  72. * @param g The graph in which to perform the search.
  73. * @param ordering A list holding the topological sort of the graph.
  74. * @param visited A set of nodes that have already been visited.
  75. * @param expanded A set of nodes that have been fully expanded.
  76. */
  77. private static <T> void explore(T node, DirectedGraph<T> g,
  78. List<T> ordering, Set<T> visited,
  79. Set<T> expanded) {
  80. /* Check whether we've been here before. If so, we should stop the
  81. * search.
  82. */
  83. if (visited.contains(node)) {
  84. /* There are two cases to consider. First, if this node has
  85. * already been expanded, then it's already been assigned a
  86. * position in the final topological sort and we don't need to
  87. * explore it again. However, if it hasn't been expanded, it means
  88. * that we've just found a node that is currently being explored,
  89. * and therefore is part of a cycle. In that case, we should
  90. * report an error.
  91. */
  92. if (expanded.contains(node)) return;
  93. throw new IllegalArgumentException("Graph contains a cycle.");
  94. }
  95. /* Mark that we've been here */
  96. visited.add(node);
  97. /* Recursively explore all of the node's predecessors. */
  98. for (T predecessor: g.edgesFrom(node))
  99. explore(predecessor, g, ordering, visited, expanded);
  100. /* Having explored all of the node's predecessors, we can now add this
  101. * node to the sorted ordering.
  102. */
  103. ordering.add(node);
  104. /* Similarly, mark that this node is done being expanded. */
  105. expanded.add(node);
  106. }
  107. /**
  108. * Returns the reverse of the input graph.
  109. *
  110. * @param g A graph to reverse.
  111. * @return The reverse of that graph.
  112. */
  113. private static <T> DirectedGraph<T> reverseGraph(DirectedGraph<T> g) {
  114. DirectedGraph<T> result = new DirectedGraph<T>();
  115. /* Add all the nodes from the original graph. */
  116. for (T node: g)
  117. result.addNode(node);
  118. /* Scan over all the edges in the graph, adding their reverse to the
  119. * reverse graph.
  120. */
  121. for (T node: g)
  122. for (T endpoint: g.edgesFrom(node))
  123. result.addEdge(endpoint, node);
  124. return result;
  125. }
  126. }