Welcome! In our last lesson, we mastered Kahn's algorithm, a BFS-based method for topological sorting that works by systematically processing nodes with zero in-degree. This provided a concrete way to generate a valid ordering for tasks with dependencies.
Today, we'll explore a powerful and elegant alternative: a topological sort based on Depth-First Search (DFS). While Kahn's algorithm builds the sorted list from the "sources" (nodes with no prerequisites), the DFS approach works from the other end, determining the order based on when a node and all its dependencies have been fully explored. This lesson will teach you the intuition, proof, and implementation of this fundamental technique, giving you another essential tool for problems on Directed Acyclic Graphs (DAGs).
1. The Core Idea: Finishing Times and Post-Order Traversal
The insight behind the DFS-based approach lies in the properties of a post-order traversal. In a standard DFS, a recursive call dfs(u) only finishes after all recursive calls for vertices reachable from u have completed.
Think about what this means in the context of dependencies. If there's an edge , u is a prerequisite for v. In a topological sort, u must appear before v. Now consider the DFS traversal. When we call dfs(u), we will eventually explore v. The call to dfs(v) (and everything reachable from it) must finish before the call to dfs(u) can finish.
This gives us a crucial relationship: If there is an edge , then the finishing time of v in a DFS is always earlier than the finishing time of u.
This suggests a simple algorithm:
- Run a DFS on the entire graph.
- Keep a list of vertices in the order they finish their DFS calls.
- The reverse of this list is a valid topological ordering.
A stack is the perfect data structure for this. When a vertex u finishes its DFS exploration (i.e., after the recursive calls for all its neighbors have returned), we push u onto a stack. After the entire DFS is complete, popping elements from the stack will yield the topological sort.

2. The DFS-Based Algorithm
Let's formalize the step-by-step process. You'll need an adjacency list representation of the graph, a visited array to track explored nodes, and a stack to store the topological order.
-
Initialization: Create an empty stack, say
st, and initialize avisitedarray for all vertices tofalse. -
Main Loop: Iterate through each vertex
ifrom0toV-1. Ifvisited[i]isfalse, start a DFS from that vertex by calling a recursive helper function,dfs_topo(i, visited, st, adj). This loop ensures that we cover all components of the graph, even if it's disconnected. -
Recursive Helper
dfs_topo(u):
a. Mark the current vertexuas visited:visited[u] = true.
b. Iterate through all neighborsvofu.
c. If a neighborvhas not been visited (visited[v] == false), make a recursive call:dfs_topo(v, ...).
d. After the loop over all neighbors is complete, it means we have explored all nodes reachable fromu. Now,uhas "finished". Pushuonto the stack:st.push(u). -
Final Result: Once the main loop is done, the stack
stcontains the vertices in a topologically sorted order. You can pop them one by one into a result vector.
The following animated GIF provides a fantastic visualization of this process. The "closed nodes" (orange) are nodes whose DFS has finished, and they are added to the toposort array, which functions like our stack.

For a guided walkthrough with a clear explanation tailored to competitive programming, the following video is an excellent resource.
G-21. Topological Sort Algorithm | DFS
This video from the "take U forward" channel clearly explains the logic, provides a step-by-step walkthrough, and presents a C++ implementation of the DFS-based topological sort.
First, watch the walkthrough of the algorithm from the explanation. Pay close attention to how the stack is used to store nodes only after their recursive DFS calls have completed. Next, examine the C++ implementation from the code segment. This will show you how to structure the solution in C++. Finally, listen to the intuition and complexity analysis, which reinforces why this post-order traversal works.
3. Implementation and Correctness
Now, let's put everything together with a formal C++ implementation and a look at the proof of correctness, which you've indicated is important to you.
C++ Implementation
The implementation is a straightforward translation of the algorithm described above. The cp-algorithms website provides a very clean and standard implementation for competitive programming.
This article offers a concise explanation of the DFS-based algorithm and a direct C++ implementation.
First, read the section The Algorithm. This explains the core idea of using DFS finishing times. Then, carefully study the code in the Implementation section. Notice how the ans vector is populated inside the dfs function after the loop, and then reversed at the end. This is equivalent to using a stack.
The core C++ code looks like this:
#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>
void dfs_topo(int u, std::vector<bool>& visited, std::stack<int>& st, const std::vector<std::vector<int>>& adj) {
visited[u] = true;
for (int v : adj[u]) {
if (!visited[v]) {
dfs_topo(v, visited, st, adj);
}
}
// After visiting all reachable nodes, push current node to stack
st.push(u);
}
std::vector<int> topologicalSort(int V, const std::vector<std::vector<int>>& adj) {
std::stack<int> st;
std::vector<bool> visited(V, false);
for (int i = 0; i < V; ++i) {
if (!visited[i]) {
dfs_topo(i, visited, st, adj);
}
}
std::vector<int> topo_order;
while (!st.empty()) {
topo_order.push_back(st.top());
st.pop();
}
return topo_order;
}
Why Does This Work? A Proof of Correctness
Your preference for understanding the "why" is crucial for deep mastery. The correctness of this algorithm hinges on the classification of edges in a DFS traversal of a DAG. As we know, a DAG by definition has no cycles, which means a DFS traversal on it will produce no back edges.
A back edge is an edge (u, v) where v is an ancestor of u in the DFS tree. The absence of back edges is key. Let's consider any arbitrary edge (u, v) in our DAG. When the DFS explores this edge from u, only two scenarios are possible for the state of v:
-
vis unvisited: The DFS will make a recursive calldfs_topo(v). By the nature of recursion, this call must finish (andvbe pushed to the stack) before the call forucan finish. Thus,uis pushed onto the stack afterv, ensuringucomes beforevin the final topological order. -
vhas already been visited: Since there are no back edges,vcannot be an ancestor ofu. This meansdfs_topo(v)has already completed. Therefore,vis already on the stack (or will be added lower down the stack thanu).uwill be pushed on top ofvlater. Again,ucomes beforev.
In all possible cases, for any edge (u, v), vertex u is placed in the final ordering before vertex v. This satisfies the definition of a topological sort.
For a more formal treatment of this proof, the following university lecture slides are excellent.
16.4.2 DFS and cycle detection: Topological sorting using ...
These lecture slides from the University of Illinois provide a rigorous proof based on the properties of DFS post-visit numbering (finishing times).
Please read the slides titled "Decreasing post numbering is valid" on page 55. The proposition states: "If G is a DAG and post(v) > post(u), then (u -> v) is not in G." This is the formal statement we need. It proves that if an edge (u, v) exists, then u must finish after v (post(u) > post(v)), which is exactly what our algorithm relies on.
4. Complexity Analysis
- Time Complexity: . The algorithm is a standard DFS traversal. Each vertex is visited once, and every edge is considered once.
- Space Complexity: . This is required for the
visitedarray, the recursion stack (which can goVdeep in the worst case of a path graph), and the stack used to store the topological order.
Conclusion
You now have a second, equally efficient method for generating a topological sort. The DFS-based approach is often considered more concise to implement recursively and provides a different but powerful perspective based on post-order traversal and finishing times.
Key Takeaways:
- The DFS-based topological sort orders vertices by their reverse finishing times.
- The logic relies on the fact that for any edge in a DAG, the DFS call for
vwill always finish before the call foru. - A stack is the natural data structure to collect vertices as they finish, which automatically reverses the finishing order.
- Like Kahn's algorithm, this method runs in linear time, , and has linear space complexity, .
- This algorithm implicitly detects cycles. If a back edge is found during DFS (which we covered in Module 2), the graph is not a DAG, and no topological sort exists.
In our next lesson, we will start applying these sorting algorithms. You'll learn how to use a topological sort to solve Dynamic Programming problems on DAGs, a very common and powerful pattern for finding shortest paths, longest paths, and counting paths in directed acyclic graphs.