Topological Sorting
Topological sorting arranges the nodes which belong to a directed acyclic graph (DAG) in a linear order so that for every edge from \(v_i\) to \(v_j\) , \(v_i\) appears before \(v_j\). There’re two well-known algorithms to execute topological sorting: Depth-First Search (DFS) approach, and Kahn’s algorithm.
Topological sorting: DFS approach
In DFS approach, we need to add one step into the plain DFS traversal. This algorithm is composed of the following steps.
- Choose a node.
- Choose an unvisited adjacent node.
- If the current node doesn’t have any unvisited adjacent neighbors, add the node to a stack, and backtrack to the last node that has unvisited adjacent neighbors.
- When we complete traversing, return the stack, which is arranged in a topological ordering.
public class AdjacencyMatrixGraph {
private List<Node> nodes;
private int[][] matrix;
private int size;
}
public class TopologicalSorting {
public static List<Node> sortDfs(AdjacencyMatrixGraph graph) {
Stack<Integer> stack = new Stack<>();
boolean[] visited = new boolean[graph.getSize()];
int src = 0;
sortDfsHelper(graph, src, visited, stack);
List<Node> sorted = new ArrayList<>();
while (!stack.isEmpty()) {
sorted.add(graph.getNodes().get(stack.pop()));
}
return sorted;
}
private static void sortDfsHelper(AdjacencyMatrixGraph graph, int src, boolean[] visited, Stack<Integer> stack) {
if (visited[src]) return;
visited[src] = true;
for (int dst = 0; dst < graph.getMatrix()[src].length; dst++) {
if (graph.getMatrix()[src][dst] == 0) continue;
sortDfsHelper(graph, dst, visited, stack);
}
// additional step to plain DFS
stack.push(src);
}
}
Topological sorting: Kahn’s algorithm
On the other hand, Kahn’s algorithm keeps track of the indegree of each node to execute a topological sorting. The indegree of a node represents the number of incomming edge to that node.
- Calculate the indegrees of all nodes.
- Add zero-indegree nodes to a queue.
- Dequeue a node, append it to a list that we’re going to return, decrement the neighbors’ indegrees, and add zero-indegree nodes to the queue.
- Repeat those operations as long as the queue is not empty.
public class TopologicalSorting {
public static List<Node> sortKahn(AdjacencyMatrixGraph dag) {
Map<Integer, Integer> indegrees = new HashMap<>();
for (int dst = 0; dst < dag.getSize(); dst++) {
int indegree = 0;
for (int src = 0; src < dag.getSize(); src++) {
indegree += dag.getMatrix()[src][dst];
}
indegrees.put(dst, indegree);
}
Queue<Integer> queue = new LinkedList<>();
for (int key : indegrees.keySet()) {
if (indegrees.get(key) != 0) continue;
queue.add(key);
}
List<Node> sorted = new LinkedList<>();
while (!queue.isEmpty()) {
int zeroIndegreeSrc = queue.poll();
sorted.add(dag.getNodes().get(zeroIndegreeSrc));
for (int dst = 0; dst < dag.getSize(); dst++) {
if (dag.getMatrix()[zeroIndegreeSrc][dst] == 0) continue;
indegrees.put(dst, indegrees.get(dst) - 1);
if (indegrees.get(dst) == 0) queue.add(dst);
}
}
return sorted;
}
}
Cycle Detection
Cycle detection: Kahn’s algorithm
Kahn’s algorithm can also be used to detect cycles in a graph. First, if there’re no zero-indegree nodes, there’s a cycle obviously. Additionally, if a graph contains a cycle, Kahn’s topological sorting algorithm won’t encompass all the nodes. That’s because in a cycle such as \(v_1 \rightarrow v_2 \rightarrow v_3 \rightarrow v_1\) , the indegrees of these nodes won’t reach zero. Consequently, they won’t be added to the queue, and therefore won’t appear in the outcome.
Cycle detection: white-gray-black approach
Suppose that a graph is directed. While DFS traversal, if, before exploring every descendant completely, we find a node which is adjacent to its ancestor, it indicates the presence of a cycle. Therefore, to implement this algorithm, we need to keep track of the nodes that we’re currently exploring.
public class CycleDetection {
public static boolean existsCycle(AdjacencyMatrixGraph graph) {
Set<Integer> unvisited = new HashSet<>();
Set<Integer> visiting = new HashSet<>();
Set<Integer> visited = new HashSet<>();
IntStream.range(0, graph.getSize()).forEach(unvisited::add);
for (int src = 0; src < graph.getSize(); src++) {
if (!unvisited.contains(src)) continue;
if (existsCycleHelper(src, graph, unvisited, visiting, visited)) return true;
}
return false;
}
private static boolean existsCycleHelper(int src, AdjacencyMatrixGraph graph, Set<Integer> unvisited, Set<Integer> visiting, Set<Integer> visited) {
unvisited.remove(src);
visiting.add(src);
for (int dst = 0; dst < graph.getSize(); dst++) {
if (graph.getMatrix()[src][dst] == 0) continue;
if (visited.contains(dst)) continue;
if (visiting.contains(dst)) return true;
return existsCycleHelper(dst, graph, unvisited, visiting, visited);
}
visiting.remove(src);
visited.add(src);
return false;
}
}
Reference
- Intuitive explanations on topological sorting:
- (DFS approach) https://www.youtube.com/watch?v=7J3GadLzydI
- (Kahn’s algorithm and DFS approach) https://yuminlee2.medium.com/topological-sort-cf9f8e43af6a
- Intuitive explanations on white-gray-black cycle detection:
- How Kahn’s algorithm works for detecting cycles: