Card 04/ 08
Kahn's Algorithm in Python, Java and TypeScript
In-degrees, a queue of ready vertices and an output list, with a cycle reported when the list comes up short.
Kahn's algorithm is a count of in-degrees, a queue, and a check at the end that every vertex was output. The listings below return the order, or nothing when the graph has a cycle, and run on the topic's graph and on the same graph with F->B added.
Kahn's algorithm with a cycle check, in each language
from collections import deque
def topological_order(vertices: list[str], edges: list[tuple[str, str]]) -> list[str] | None:
graph: dict[str, list[str]] = {v: [] for v in vertices}
indegree = {v: 0 for v in vertices}
for u, v in edges:
graph[u].append(v)
indegree[v] += 1
ready = deque(v for v in vertices if indegree[v] == 0)
order = []
while ready:
vertex = ready.popleft()
order.append(vertex)
for neighbour in graph[vertex]:
indegree[neighbour] -= 1
if indegree[neighbour] == 0:
ready.append(neighbour)
return order if len(order) == len(vertices) else None
vertices = ["A", "B", "C", "D", "E", "F"]
edges = [("D", "A"), ("D", "B"), ("C", "B"), ("A", "F"), ("B", "E"), ("B", "F")]
for name, graph_edges in (("topic graph", edges), ("with F->B", edges + [("F", "B")])):
order = topological_order(vertices, graph_edges)
print(f"{name}: {' '.join(order) if order else 'cycle, no topological order'}")topic graph: C D A B E F
with F->B: cycle, no topological orderimport java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class KahnCode {
static Optional<List<String>> topologicalOrder(List<String> vertices, List<String[]> edges) {
Map<String, List<String>> graph = new HashMap<>();
Map<String, Integer> indegree = new HashMap<>();
for (String v : vertices) {
graph.put(v, new ArrayList<>());
indegree.put(v, 0);
}
for (String[] edge : edges) {
graph.get(edge[0]).add(edge[1]);
indegree.merge(edge[1], 1, Integer::sum);
}
ArrayDeque<String> ready = new ArrayDeque<>();
for (String v : vertices) {
if (indegree.get(v) == 0) ready.add(v);
}
List<String> order = new ArrayList<>();
while (!ready.isEmpty()) {
String vertex = ready.poll();
order.add(vertex);
for (String neighbour : graph.get(vertex)) {
if (indegree.merge(neighbour, -1, Integer::sum) == 0) ready.add(neighbour);
}
}
return order.size() == vertices.size() ? Optional.of(order) : Optional.empty();
}
public static void main(String[] args) {
List<String> vertices = List.of("A", "B", "C", "D", "E", "F");
List<String[]> edges = new ArrayList<>(List.of(
new String[] {"D", "A"}, new String[] {"D", "B"}, new String[] {"C", "B"},
new String[] {"A", "F"}, new String[] {"B", "E"}, new String[] {"B", "F"}));
List<String[]> withCycle = new ArrayList<>(edges);
withCycle.add(new String[] {"F", "B"});
print("topic graph", topologicalOrder(vertices, edges));
print("with F->B", topologicalOrder(vertices, withCycle));
}
static void print(String name, Optional<List<String>> order) {
System.out.println(name + ": " + order.map(o -> String.join(" ", o)).orElse("cycle, no topological order"));
}
}topic graph: C D A B E F
with F->B: cycle, no topological orderfunction topologicalOrder(vertices: string[], edges: [string, string][]): string[] | null {
const graph = new Map<string, string[]>(vertices.map((v) => [v, []]));
const indegree = new Map<string, number>(vertices.map((v) => [v, 0]));
for (const [u, v] of edges) {
graph.get(u)!.push(v);
indegree.set(v, indegree.get(v)! + 1);
}
const ready = vertices.filter((v) => indegree.get(v) === 0);
let head = 0;
while (head < ready.length) {
const vertex = ready[head++];
for (const neighbour of graph.get(vertex)!) {
indegree.set(neighbour, indegree.get(neighbour)! - 1);
if (indegree.get(neighbour) === 0) ready.push(neighbour);
}
}
return ready.length === vertices.length ? ready : null;
}
const vertices = ["A", "B", "C", "D", "E", "F"];
const edges: [string, string][] = [["D", "A"], ["D", "B"], ["C", "B"], ["A", "F"], ["B", "E"], ["B", "F"]];
const graphs: [string, [string, string][]][] = [["topic graph", edges], ["with F->B", [...edges, ["F", "B"]]]];
for (const [name, graphEdges] of graphs) {
const order = topologicalOrder(vertices, graphEdges);
console.log(`${name}: ${order ? order.join(" ") : "cycle, no topological order"}`);
}topic graph: C D A B E F
with F->B: cycle, no topological orderAll three printed the same two lines. The highlighted lines lower a neighbour's count, add it to the queue when the count reaches 0, and compare the length of the output with the number of vertices. That last comparison is the cycle test: Detecting a Cycle by Counting the Output explains why a short list means a cycle.
The return types say the same thing in each language's terms: list[str] | None in Python, Optional<List<String>> in Java, and string[] | null in TypeScript. A caller cannot use the order without first handling the case where there is none.
Java's Map.merge with -1 lowers the count and returns the new value, so the test fits on the same line. TypeScript again uses an array with a head index as its queue, and since every vertex that is output joined that array once, the array is the order and its length is the count.
← → move through sections and cards