All Topological Sorts of a DAG takes the classic topological-sort question one step further: instead of producing one valid ordering, list every valid ordering. It is a clean showcase of backtracking driven by in-degrees.
Problem. Given a directed acyclic graph (DAG) with n vertices, return all topological orderings.
A topological ordering is a sequence of every vertex such that for each edge u -> v, u appears before
v.
Example: edges A -> C, B -> C, C -> D. The valid orderings are [A, B, C, D] and [B, A, C, D]
(A and B are interchangeable; both must come before C, and C before D).
The slow way first
You could generate every permutation of the vertices and keep the ones that respect all edges. With n vertices that is n! permutations, and each check is O(edges) — wildly slow, and most permutations get thrown away.
The wasted work is the giveaway: we keep building orderings that were doomed from the start. The fix is to only ever extend an ordering with a vertex that is legal right now.
The idea: pick any free vertex, then undo
Track each vertex's in-degree — the count of incoming edges. A vertex is free to be placed next exactly when its in-degree is 0 (everything that must precede it is already placed).
So at each level of a depth-first search, loop over every free vertex. For each one: append it, decrement the in-degrees of its out-neighbors (which may free new vertices), and recurse. When the ordering reaches length n, record a copy. Then backtrack — pop the vertex and restore the in-degrees — so the loop can try the next free vertex.
The branching at the first level (A or B) is exactly what produces the two different orderings.
Walk through it
Step through the animation. A and B start with in-degree 0, so both are choices. We pick A, then B, which frees C, then C frees D — giving [A, B, C, D]. Then we backtrack all the way to the first decision, restoring in-degrees, and pick B before A instead, producing [B, A, C, D].
Pseudocode
compute in-degree of every vertex
order = empty list, result = empty list
dfs():
for each vertex v:
if in-degree[v] == 0 and v not yet placed:
append v to order
for each neighbor w of v: in-degree[w] -= 1
dfs()
if order has all n vertices: save a copy to result
pop v from order # undo
for each neighbor w of v: in-degree[w] += 1 # undo
dfs()
return resultThe Python solution
def all_topo_sorts(n, adj, indeg):
result, order = [], []
def dfs():
progressed = False
for v in range(n):
if indeg[v] == 0 and v not in order:
order.append(v)
for w in adj[v]:
indeg[w] -= 1
dfs()
if len(order) == n:
result.append(order[:])
order.pop()
for w in adj[v]:
indeg[w] += 1
progressed = True
dfs()
return resultindegholds the current in-degree of every vertex;orderis the ordering we are building.- The loop on line 5 scans for a vertex that is free (
indeg[v] == 0) and not already placed (line 6). - Appending
vand decrementing each neighbor (lines 7-9) simulates removingvfrom the graph, which may free new vertices for the recursive call. - When
orderreaches lengthn, we save a copy withorder[:]— copying matters, because we mutateorderin place. - Lines 13-15 are the backtrack: pop
vand add1back to every neighbor, exactly reversing the move so the loop can try the next free vertex.
Complexity
| Case | Time | Notes |
|---|---|---|
| All permutations (brute force) | O(n! * n) (moderate) | generate then validate each |
| Backtracking (this solution) | O(V! * V) (moderate) | but only explores valid prefixes |
O(V) (moderate)The number of topological sorts can itself be as large as n! (an edgeless graph has every permutation), so no algorithm can do better than the output size in the worst case. The win is that backtracking never wastes time on invalid prefixes — each recursion step only extends with a currently-free vertex.
When this pattern shows up
Whenever a problem says "return all valid X" over a graph or a set of choices, think backtracking: make a move, recurse, then undo the move. Pairing it with an in-degree array is the signature trick for ordering problems on a DAG (course schedules, build orders, dependency resolution).
Two easy bugs: forgetting to restore in-degrees on the way back up (later branches then see a corrupted
graph), and saving order directly instead of a copy (every saved result ends up pointing at the same
list, which is empty by the end).
Practice
With edges A -> C, B -> C, C -> D, how many vertices have in-degree 0 at the very start, and which are they?
1. When is a vertex allowed to be placed next in the ordering?
2. Why must we restore the in-degrees during backtracking?
3. Why do we append order[:] rather than order itself to the result?
4. For the graph A -> C, B -> C, C -> D, how many valid topological orderings are there?