Topological sorting for Directed Acyclic Graph (DAG) is a linear ordering of vertices such that for every directed edge uv, vertex u comes before v in the ordering. Topological Sorting for a graph is not possible if the graph is not a DAG.
Example:
Input: Graph :
Example
Output: 5 4 2 3 1 0
Explanation: The first vertex in topological sorting is always a vertex with an in-degree of 0 (a vertex with no incoming edges). A topological sorting of the following graph is “5 4 2 3 1 0”. There can be more than one topological sorting for a graph. Another topological sorting of the following graph is “4 5 2 3 1 0”.
Topological sorting can be implemented recursively and non-recursively. First, we show the clearer recursive version, then provide the non-recursive version with analysis.
Recursive DFS-Based Topological Sorting
This approach uses Depth-First Search (DFS) and recursion to find the topological order of a Directed Acyclic Graph (DAG). It explores all dependencies first and then adds the node to a stack, ensuring the correct order. The final sequence is obtained by reversing the stack.
Python
fromcollectionsimportdefaultdict#Class to represent a graphclassGraph:def__init__(self,vertices):self.graph=defaultdict(list)#dictionary containing adjacency Listself.V=vertices#No. of vertices# function to add an edge to graphdefaddEdge(self,u,v):self.graph[u].append(v)# A recursive function used by topologicalSortdeftopologicalSortUtil(self,v,visited,stack):# Mark the current node as visited.visited[v]=True# Recur for all the vertices adjacent to this vertexforiinself.graph[v]:ifvisited[i]==False:self.topologicalSortUtil(i,visited,stack)# Push current vertex to stack which stores resultstack.insert(0,v)# The function to do Topological Sort. It uses recursive# topologicalSortUtil()deftopologicalSort(self):# Mark all the vertices as not visitedvisited=[False]*self.Vstack=[]# Call the recursive helper function to store Topological# Sort starting from all vertices one by oneforiinrange(self.V):ifvisited[i]==False:self.topologicalSortUtil(i,visited,stack)# Print contents of stackprint(stack)g=Graph(6)g.addEdge(5,2);g.addEdge(5,0);g.addEdge(4,0);g.addEdge(4,1);g.addEdge(2,3);g.addEdge(3,1);print("Following is a Topological Sort of the given graph")g.topologicalSort()
Output
Following is a Topological Sort of the given graph
[5, 4, 2, 3, 1, 0]
Iterative DFS-Based Topological Sorting
This method eliminates recursion and uses an explicit stack to simulate DFS traversal. It employs generators for efficient neighbor iteration and manually manages backtracking. This approach is useful for handling large graphs without hitting recursion limits.
Algorithm:
The way topological sorting is solved is by processing a node after all of its children are processed. Each time a node is processed, it is pushed onto a stack in order to save the final result. This non-recursive solution builds on the same concept of DFS with a little tweak which can be understood above and in this article.
However, unlike the recursive solution, which saves the order of the nodes in the stack after all the neighboring elements have been pushed to the program stack, this solution replaces the program stack with a working stack. If a node has a neighbor that has not been visited, the current node and the neighbor are pushed to the working stack to be processed until there are no more neighbors available to be visited.
After all the nodes have been visited, what remains is the final result which is found by printing the stack result in reverse.
Python
fromcollectionsimportdefaultdict#Class to represent a graphclassGraph:def__init__(self,vertices):self.graph=defaultdict(list)#dictionary containing adjacency Listself.V=vertices#No. of vertices# function to add an edge to graphdefaddEdge(self,u,v):self.graph[u].append(v)# neighbors generator given keydefneighbor_gen(self,v):forkinself.graph[v]:yieldk# non recursive topological sortdefnonRecursiveTopologicalSortUtil(self,v,visited,stack):# working stack contains key and the corresponding current generatorworking_stack=[(v,self.neighbor_gen(v))]whileworking_stack:# get last element from stackv,gen=working_stack.pop()visited[v]=True# run through neighbor generator until it's emptyfornext_neighboringen:ifnotvisited[next_neighbor]:# not seen before?# remember current workworking_stack.append((v,gen))# restart with new neighborworking_stack.append((next_neighbor,self.neighbor_gen(next_neighbor)))breakelse:# no already-visited neighbor (or no more of them)stack.append(v)# The function to do Topological Sort.defnonRecursiveTopologicalSort(self):# Mark all the vertices as not visitedvisited=[False]*self.V# result stackstack=[]# Call the helper function to store Topological# Sort starting from all vertices one by oneforiinrange(self.V):ifnot(visited[i]):self.nonRecursiveTopologicalSortUtil(i,visited,stack)# Print contents of the stack in reversestack.reverse()print(stack)g=Graph(6)g.addEdge(5,2);g.addEdge(5,0);g.addEdge(4,0);g.addEdge(4,1);g.addEdge(2,3);g.addEdge(3,1);print("The following is a Topological Sort of the given graph")g.nonRecursiveTopologicalSort()
Output
The following is a Topological Sort of the given graph
[5, 4, 2, 3, 1, 0]
Complexity Analysis:
Time Complexity: O(V + E): The above algorithm is simply DFS with a working stack and a result stack. Unlike the recursive solution, recursion depth is not an issue here.
Auxiliary space: O(V): The extra space is needed for the 2 stacks used.