A node can only be finalized after everything it points to is done. So push each node onto a stack the moment DFS finishes it, then reverse. Last finished ends up first.
Run DFS. When a node has no more unexplored out-edges, it's finished, push it onto a stack. Because you push children before parents, reversing the stack yields a valid topological order.
void dfs(int u, vector<vector<int>>& adj,
vector<bool>& vis, stack<int>& st) {
vis[u] = true;
for (int v : adj[u])
if (!vis[v]) dfs(v, adj, vis, st);
st.push(u); // push on finish (post-order)
}
vector<int> topoSort(int n, vector<vector<int>>& adj) {
vector<bool> vis(n, false);
stack<int> st;
for (int i = 0; i < n; i++)
if (!vis[i]) dfs(i, adj, vis, st);
vector<int> order;
while (!st.empty()) { // pop = reverse finish order
order.push_back(st.top());
st.pop();
}
return order;
}
One DFS traversal; each vertex and edge visited once.
Visited array, the finish stack, and recursion depth.
Pushing on exit guarantees dependencies land deeper in the stack.