Topological sorting is a linear ordering of the vertices in a directed acyclic graph (DAG) such that for every directed edge (u, v), vertex u comes before vertex v in the ordering. In simpler terms, it helps organize tasks or elements in a way that respects the dependencies between them.
Applications
Topological sorting has several practical applications:
Task Scheduling: In project management, tasks often have dependencies on each other. Topological sorting helps in determining a valid order of execution.
Compiler Design: Compilers use topological sorting during code generation to ensure that all dependencies (like variable declarations and definitions) are resolved correctly.
Package Management: Package managers like npm, pip, and others use topological sorting to install packages in the correct order, ensuring that dependencies are met.
Course Scheduling: In academic institutions, where courses have prerequisites, topological sorting helps in scheduling courses such that students can take them in the correct order.
Limitations
Topological sorting is applicable only to directed acyclic graphs (DAGs).A DAG is a graph in which there are no cycles, meaning you can’t start at a vertex and follow a sequence of directed edges to return to that same vertex.
It cannot be applied to graphs with cycles because there’s no way to order the vertices such that all edges point in the correct direction.
Examples
Chain
graph TD;
A --> B;
B --> C;
C --> D;
D --> E;
Topological Sorting Result: A, B, C, D, E
Fork
graph TD;
A --> B;
A --> C;
B --> D;
C --> D;
Topological Sorting Result: A, B, C, D
Diamond
graph TD;
A --> B;
A --> C;
B --> D;
C --> D;
Topological Sorting Result: A, B, C, D
Complex DAG
graph TD;
A --> B;
A --> C;
B --> D;
B --> E;
C --> E;
D --> F;
E --> F;
F --> G;
Topological Sorting Result: A, C, B, E, D, F, G
Disconnected Components
graph TD;
A --> B;
C --> D;
Topological Sorting Result: A, C, B, D
Cyclic Graph
graph TD;
A --> B;
B --> C;
C --> D;
D --> A;
This graph contains a cycle, so topological sorting is not possible.
Implementation
Kahn’s Algorithm
importjava.util.*;publicclassTopologicalSort{publicstaticList<Character>topologicalSort(Map<Character,List<Character>>graph){// Step 1: Initialize in-degree and queueMap<Character,Integer>inDegree=newHashMap<>();Queue<Character>queue=newLinkedList<>();// Initialize in-degrees for each nodefor(charnode:graph.keySet()){inDegree.put(node,0);}// Calculate in-degreesfor(List<Character>neighbors:graph.values()){for(charneighbor:neighbors){inDegree.put(neighbor,inDegree.get(neighbor)+1);}}// Find nodes with no incoming edges (in-degree == 0) and add them to the queuefor(charnode:inDegree.keySet()){if(inDegree.get(node)==0){queue.offer(node);}}// Step 2: Perform topological sortingList<Character>result=newArrayList<>();while(!queue.isEmpty()){charnode=queue.poll();result.add(node);// Update in-degrees and add newly isolated nodes to the queuefor(charneighbor:graph.get(node)){inDegree.put(neighbor,inDegree.get(neighbor)-1);if(inDegree.get(neighbor)==0){queue.offer(neighbor);}}}// Step 3: Check if topological sorting is possibleif(result.size()==graph.size()){returnresult;}else{returnnull;// Graph has a cycle, topological sorting is not possible}}publicstaticvoidmain(String[]args){// Example UsageMap<Character,List<Character>>graph=newHashMap<>();graph.put('A',Arrays.asList('B','C'));graph.put('B',Collections.singletonList('D'));graph.put('C',Collections.singletonList('D'));List<Character>sortedOrder=topologicalSort(graph);if(sortedOrder!=null){System.out.println("Topological Sorting Result: "+sortedOrder);}else{System.out.println("The graph has a cycle. Topological sorting is not possible.");}}}
fromcollectionsimportdefaultdict,dequedeftopological_sort(graph):in_degree={node:0fornodeingraph}fornodeingraph:forneighboringraph[node]:in_degree[neighbor]+=1queue=deque([nodefornodeinin_degreeifin_degree[node]==0])result=[]whilequeue:node=queue.popleft()result.append(node)forneighboringraph[node]:in_degree[neighbor]-=1ifin_degree[neighbor]==0:queue.append(neighbor)iflen(result)==len(graph):returnresultelse:returnNone# Example Usagegraph={'A':['B','C'],'B':['D'],'C':['D']}sorted_order=topological_sort(graph)ifsorted_order:print("Topological Sorting Result:",sorted_order)else:print("The graph has a cycle. Topological sorting is not possible.")
packagemainimport("fmt")functopologicalSort(graphmap[string][]string)[]string{inDegree:=make(map[string]int)queue:=[]string{}// Step 1: Initialize in-degrees and queue
fornode:=rangegraph{inDegree[node]=0}// Calculate in-degrees
for_,neighbors:=rangegraph{for_,neighbor:=rangeneighbors{inDegree[neighbor]++}}// Find nodes with no incoming edges (in-degree == 0) and add them to the queue
fornode,degree:=rangeinDegree{ifdegree==0{queue=append(queue,node)}}// Step 2: Perform topological sorting
varresult[]stringforlen(queue)>0{node:=queue[0]queue=queue[1:]result=append(result,node)// Update in-degrees and add newly isolated nodes to the queue
for_,neighbor:=rangegraph[node]{inDegree[neighbor]--ifinDegree[neighbor]==0{queue=append(queue,neighbor)}}}// Step 3: Check if topological sorting is possible
iflen(result)==len(graph){returnresult}else{returnnil// Graph has a cycle, topological sorting is not possible
}}funcmain(){// Example Usage
graph:=map[string][]string{"A":{"B","C"},"B":{"D"},"C":{"D"},}sortedOrder:=topologicalSort(graph)ifsortedOrder!=nil{fmt.Println("Topological Sorting Result:",sortedOrder)}else{fmt.Println("The graph has a cycle. Topological sorting is not possible.")}}
Complexity Analysis
Kahn’s Algorithm for topological sorting has a time complexity of O(V + E), where V is the number of vertices and E is the number of edges in the graph.
The space complexity of Kahn’s Algorithm is O(V), primarily due to the data structures used to store in-degrees and the queue.
Depth First Search
importjava.util.*;publicclassTopologicalSortDFS{publicstaticList<Character>topologicalSort(Map<Character,List<Character>>graph){Set<Character>visited=newHashSet<>();Stack<Character>stack=newStack<>();// Step 1: Perform DFS on each unvisited nodefor(charnode:graph.keySet()){if(!visited.contains(node)){dfs(node,visited,stack,graph);}}// Step 2: Extract result from stackList<Character>result=newArrayList<>();while(!stack.isEmpty()){result.add(stack.pop());}returnresult;}privatestaticvoiddfs(charnode,Set<Character>visited,Stack<Character>stack,Map<Character,List<Character>>graph){visited.add(node);// Step 3: Visit neighbors recursivelyif(graph.containsKey(node)){for(charneighbor:graph.get(node)){if(!visited.contains(neighbor)){dfs(neighbor,visited,stack,graph);}}}// Step 4: Push the current node to the stackstack.push(node);}publicstaticvoidmain(String[]args){// Example UsageMap<Character,List<Character>>graph=newHashMap<>();graph.put('A',Arrays.asList('B','C'));graph.put('B',Collections.singletonList('D'));graph.put('C',Collections.singletonList('D'));List<Character>sortedOrder=topologicalSort(graph);if(!sortedOrder.isEmpty()){System.out.println("Topological Sorting Result: "+sortedOrder);}else{System.out.println("The graph has a cycle. Topological sorting is not possible.");}}}
deftopological_sort_dfs(graph):visited=set()stack=[]defdfs(node):visited.add(node)# Visit neighbors recursivelyifnodeingraph:forneighboringraph[node]:ifneighbornotinvisited:dfs(neighbor)# Push the current node to the stackstack.append(node)# Perform DFS on each unvisited nodefornodeingraph:ifnodenotinvisited:dfs(node)# Extract result from stackresult=stack[::-1]returnresult# Example Usagegraph={'A':['B','C'],'B':['D'],'C':['D']}sorted_order=topological_sort_dfs(graph)ifsorted_order:print("Topological Sorting Result:",sorted_order)else:print("The graph has a cycle. Topological sorting is not possible.")
packagemainimport"fmt"functopologicalSortDFS(graphmap[string][]string)[]string{visited:=make(map[string]bool)stack:=[]string{}vardfsfunc(nodestring)dfs=func(nodestring){visited[node]=true// Visit neighbors recursively
for_,neighbor:=rangegraph[node]{if!visited[neighbor]{dfs(neighbor)}}// Push the current node to the stack
stack=append(stack,node)}// Perform DFS on each unvisited node
fornode:=rangegraph{if!visited[node]{dfs(node)}}// Extract result from stack
result:=[]string{}fori:=len(stack)-1;i>=0;i--{result=append(result,stack[i])}returnresult}funcmain(){// Example Usage
graph:=map[string][]string{"A":{"B","C"},"B":{"D"},"C":{"D"},}sortedOrder:=topologicalSortDFS(graph)iflen(sortedOrder)>0{fmt.Println("Topological Sorting Result:",sortedOrder)}else{fmt.Println("The graph has a cycle. Topological sorting is not possible.")}}
Complexity Analysis
Time Complexity:
The time complexity of the DFS-based topological sorting algorithm is O(V + E), where V is the number of vertices and E is the number of edges in the graph. This is because, in the worst case, the algorithm must visit every vertex and edge once.
Visiting a vertex takes constant time (O(1)).
Visiting an edge takes constant time (O(1)).
Since we may visit every vertex and edge once, the overall time complexity is O(V + E).
Space Complexity:
The space complexity of the DFS implementation is O(V + E), where V is the number of vertices and E is the number of edges in the graph. This space is primarily used for:
The visited set (O(V)) to keep track of visited nodes.
The stack (O(V)) used for the depth-first search.
Recursive call stack for the DFS (O(V)).
In practice, the space complexity can vary depending on the specific graph structure and the order in which nodes are visited. However, the worst-case space complexity is O(V + E).