Kahn's Algorithm vs DFS Approach: A Comparative Analysis
Last Updated :
19 Oct, 2023
Topological sorting is a common problem in computer science that involves arranging the vertices of a directed acyclic graph (DAG) in a linear order such that for every directed edge (u, v), vertex u comes before vertex v in the ordering.
Two important methods to solve are:
- Kahn's Algorithm
- Depth-First Search (DFS) Algorithm
Let us take the following example as input for both algorithms:
Directed Acyclic GraphKahn's Algorithm:
Kahn's Algorithm is a topological sorting algorithm that uses a queue-based approach to sort vertices in a DAG. It starts by finding vertices that have no incoming edges and adds them to a queue. It then removes a vertex from the queue and adds it to the sorted list. The algorithm continues this process, removing vertices with no incoming edges until all vertices have been sorted.
Below is the implementation of the Kahn's approach:
C++
// C++ Implementation
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
// Topological sort using bfs is called
// Kahn's Algorithm
void topo_sort(vector<int> adj[], int n)
{
// Here concept of indegree used
// as it represents number of incoming
// edges on a node
vector<int> indegree(n, 0);
// Code for make indegree
for (int i = 0; i < n; i++) {
for (auto it : adj[i]) {
indegree[it]++;
}
}
vector<int> ans;
queue<int> qrr;
// Initially insert elements who has
// indegree 0
for (int i = 0; i < n; i++) {
if (indegree[i] == 0) {
qrr.push(i);
}
}
while (!qrr.empty()) {
// push those elements in queue which
// poses 0 indegree
int node = qrr.front();
qrr.pop();
ans.push_back(node);
for (auto it : adj[node]) {
indegree[it]--;
if (indegree[it] == 0) {
qrr.push(it);
}
}
}
for (int i = 0; i < n; i++) {
cout << ans[i] << " ";
}
}
};
// Function to add edge
void addEdge(vector<int> adj[], int u, int v)
{
adj[u].push_back(v);
}
// Driver code
int main()
{
int n = 6;
vector<int> adj[n];
addEdge(adj, 5, 0);
addEdge(adj, 5, 2);
addEdge(adj, 2, 0);
addEdge(adj, 2, 3);
addEdge(adj, 3, 0);
addEdge(adj, 3, 1);
addEdge(adj, 1, 0);
addEdge(adj, 4, 0);
addEdge(adj, 4, 1);
Solution s1;
// Function call
s1.topo_sort(adj, n);
return 0;
}
Java
import java.util.*;
class Solution {
public void topo_sort(List<Integer>[] adj, int n)
{
List<Integer> indegree
= new ArrayList<>(Collections.nCopies(n, 0));
for (int i = 0; i < n; i++) {
for (int it : adj[i]) {
indegree.set(it, indegree.get(it) + 1);
}
}
List<Integer> ans = new ArrayList<>();
Queue<Integer> qrr = new LinkedList<>();
for (int i = 0; i < n; i++) {
if (indegree.get(i) == 0) {
qrr.add(i);
}
}
while (!qrr.isEmpty()) {
int node = qrr.poll();
ans.add(node);
for (int it : adj[node]) {
indegree.set(it, indegree.get(it) - 1);
if (indegree.get(it) == 0) {
qrr.add(it);
}
}
}
for (int i = 0; i < n; i++) {
System.out.print(ans.get(i) + " ");
}
}
}
class Main {
@SuppressWarnings("unchecked")
public static void main(String[] args)
{
int n = 6;
List<Integer>[] adj = new ArrayList[n];
for (int i = 0; i < n; i++) {
adj[i] = new ArrayList<>();
}
addEdge(adj, 5, 0);
addEdge(adj, 5, 2);
addEdge(adj, 2, 0);
addEdge(adj, 2, 3);
addEdge(adj, 3, 0);
addEdge(adj, 3, 1);
addEdge(adj, 1, 0);
addEdge(adj, 4, 0);
addEdge(adj, 4, 1);
Solution s1 = new Solution();
s1.topo_sort(adj, n);
}
private static void addEdge(List<Integer>[] adj, int u,
int v)
{
adj[u].add(v);
}
}
Python3
from typing import List, Deque
from collections import deque
class Solution:
def topo_sort(self, adj: List[int], n: int) -> List[int]:
# Create a list to store the in-degree of each node
indegree = [0] * n
# Calculate the in-degree of each node
for i in range(n):
for j in adj[i]:
indegree[j] += 1
# Create a queue to store nodes with in-degree 0
q = deque()
for i in range(n):
if indegree[i] == 0:
q.append(i)
# Perform topological sorting using BFS
ans = []
while q:
node = q.popleft()
ans.append(node)
for i in adj[node]:
indegree[i] -= 1
if indegree[i] == 0:
q.append(i)
# Return the sorted nodes
return ans
# Function to add an edge
def addEdge(adj: List[int], u: int, v: int) -> None:
adj[u].append(v)
# Driver code
if __name__ == "__main__":
n = 6
adj = [[] for _ in range(n)]
addEdge(adj, 5, 0)
addEdge(adj, 5, 2)
addEdge(adj, 2, 0)
addEdge(adj, 2, 3)
addEdge(adj, 3, 0)
addEdge(adj, 3, 1)
addEdge(adj, 1, 0)
addEdge(adj, 4, 0)
addEdge(adj, 4, 1)
s = Solution()
# Function call
print(s.topo_sort(adj, n))
C#
// C# Implementation
using System;
using System.Collections.Generic;
class Solution {
// Topological sort using bfs is called
// Kahn's Algorithm
public void TopoSort(List<int>[] adj, int n)
{
// Here concept of indegree used
// as it represents number of incoming
// edges on a node
int[] indegree = new int[n];
// Code for make indegree
for (int i = 0; i < n; i++) {
foreach(int it in adj[i]) { indegree[it]++; }
}
List<int> ans = new List<int>();
Queue<int> qrr = new Queue<int>();
// Initially insert elements who has
// indegree 0
for (int i = 0; i < n; i++) {
if (indegree[i] == 0) {
qrr.Enqueue(i);
}
}
while (qrr.Count > 0) {
// push those elements in queue which
// poses 0 indegree
int node = qrr.Dequeue();
ans.Add(node);
foreach(int it in adj[node])
{
indegree[it]--;
if (indegree[it] == 0) {
qrr.Enqueue(it);
}
}
}
for (int i = 0; i < n; i++) {
Console.Write(ans[i] + " ");
}
}
}
// Driver code
public class GFG {
static void Main(string[] args)
{
int n = 6;
List<int>[] adj = new List<int>[ n ];
for (int i = 0; i < n; i++) {
adj[i] = new List<int>();
}
adj[5].Add(0);
adj[5].Add(2);
adj[2].Add(0);
adj[2].Add(3);
adj[3].Add(0);
adj[3].Add(1);
adj[1].Add(0);
adj[4].Add(0);
adj[4].Add(1);
Solution s1 = new Solution();
// Function call
s1.TopoSort(adj, n);
}
}
JavaScript
// Javadcript code addition
class Solution {
topoSort(adj, n) {
let indegree = new Array(n).fill(0);
let ans = [];
let qrr = [];
// Code for make indegree
for (let i = 0; i < n; i++) {
for (let it of adj[i]) {
indegree[it]++;
}
}
// Initially insert elements who has indegree 0
for (let i = 0; i < n; i++) {
if (indegree[i] == 0) {
qrr.push(i);
}
}
while (qrr.length > 0) {
let node = qrr.shift();
ans.push(node);
for (let it of adj[node]) {
indegree[it]--;
if (indegree[it] == 0) {
qrr.push(it);
}
}
}
for (let i = 0; i < n; i++) {
process.stdout.write(ans[i] + " ");
}
}
}
// Function to add edge
function addEdge(adj, u, v) {
adj[u].push(v);
}
// Driver code
let n = 6;
let adj = Array.from({ length: n }, () => []);
addEdge(adj, 5, 0);
addEdge(adj, 5, 2);
addEdge(adj, 2, 0);
addEdge(adj, 2, 3);
addEdge(adj, 3, 0);
addEdge(adj, 3, 1);
addEdge(adj, 1, 0);
addEdge(adj, 4, 0);
addEdge(adj, 4, 1);
let s1 = new Solution();
// Function call
s1.topoSort(adj, n);
// The code is contributed by Nidhi goel.
Time Complexity: O( V + E)
Auxiliary Space: O(V)
DFS Approach:
DFS Approach is a recursive algorithm that performs a depth-first search on the DAG. It starts at a vertex, explores as far as possible along each branch before backtracking, and marks visited vertices. During the DFS traversal, vertices are added to a stack in the order they are visited. Once the DFS traversal is complete, the stack is reversed to obtain the topological ordering.
Below is the implementation of the DFS approach:
C++
#include <bits/stdc++.h>
using namespace std;
class Solution {
// Topo sort only exists in DAGs i.e.
// Direct Acyclic graph
void dfs(vector<int> adj[], vector<int>& vis, int node,
int n, stack<int>& stck)
{
vis[node] = 1;
for (auto it : adj[node]) {
if (!vis[it]) {
dfs(adj, vis, it, n, stck);
}
}
stck.push(node);
}
public:
// During the traversal u must
// be visited before v
stack<int> topo_sort(vector<int> adj[], int n)
{
vector<int> vis(n, 0);
// using stack ADT
stack<int> stck;
for (int i = 0; i < n; i++) {
if (!vis[i]) {
dfs(adj, vis, i, n, stck);
}
}
return stck;
}
};
void addEdge(vector<int> adj[], int u, int v)
{
adj[u].push_back(v);
}
// Drivers code
int main()
{
int n = 6;
vector<int> adj[n];
addEdge(adj, 5, 0);
addEdge(adj, 5, 2);
addEdge(adj, 2, 0);
addEdge(adj, 2, 3);
addEdge(adj, 3, 0);
addEdge(adj, 3, 1);
addEdge(adj, 1, 0);
addEdge(adj, 4, 0);
addEdge(adj, 4, 1);
Solution s1;
stack<int> ans = s1.topo_sort(adj, n);
for (int i = 0; i < n; i++) {
int n = ans.top();
ans.pop();
cout << n << " ";
}
return 0;
}
Java
// Java Implementation
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
class Solution {
// Topo sort only exists in DAGs i.e.
// Direct Acyclic Graph
void dfs(List<Integer>[] adj, List<Integer> vis, int node, int n, Stack<Integer> stck) {
vis.set(node, 1);
for (int it : adj[node]) {
if (vis.get(it) == 0) {
dfs(adj, vis, it, n, stck);
}
}
stck.push(node);
}
// During the traversal u must
// be visited before v
Stack<Integer> topo_sort(List<Integer>[] adj, int n) {
List<Integer> vis = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
vis.add(0);
}
// using stack ADT
Stack<Integer> stck = new Stack<>();
for (int i = 0; i < n; i++) {
if (vis.get(i) == 0) {
dfs(adj, vis, i, n, stck);
}
}
return stck;
}
}
public class Main {
static void addEdge(List<Integer>[] adj, int u, int v) {
adj[u].add(v);
}
public static void main(String[] args) {
int n = 6;
List<Integer>[] adj = new ArrayList[n];
for (int i = 0; i < n; i++) {
adj[i] = new ArrayList<>();
}
addEdge(adj, 5, 0);
addEdge(adj, 5, 2);
addEdge(adj, 2, 0);
addEdge(adj, 2, 3);
addEdge(adj, 3, 0);
addEdge(adj, 3, 1);
addEdge(adj, 1, 0);
addEdge(adj, 4, 0);
addEdge(adj, 4, 1);
Solution solution = new Solution();
Stack<Integer> ans = solution.topo_sort(adj, n);
while (!ans.isEmpty()) {
int node = ans.pop();
System.out.print(node + " ");
}
}
}
// This code is contributed by Utkarsh Kumar
Python3
# Python implementation of the above approach
class Solution:
# Topo sort only exists in DAGs i.e.
# Direct Acyclic graph
def dfs(self, adj, vis, node, n, stck):
vis[node] = 1
for it in adj[node]:
if not vis[it]:
self.dfs(adj, vis, it, n, stck)
stck.append(node)
# During the traversal u must
# be visited before v
def topo_sort(self, adj, n):
vis = [0] * n
# using stack ADT
stck = []
for i in range(n):
if not vis[i]:
self.dfs(adj, vis, i, n, stck)
return stck
def addEdge(adj, u, v):
adj[u].append(v)
# Drivers code
n = 6
adj = [[] for _ in range(n)]
addEdge(adj, 5, 0)
addEdge(adj, 5, 2)
addEdge(adj, 2, 0)
addEdge(adj, 2, 3)
addEdge(adj, 3, 0)
addEdge(adj, 3, 1)
addEdge(adj, 1, 0)
addEdge(adj, 4, 0)
addEdge(adj, 4, 1)
s1 = Solution()
ans = s1.topo_sort(adj, n)
for i in range(n):
num = ans.pop()
print(num, end=" ")
# This code is contributed by Tapesh(tapeshdua420)
C#
using System;
using System.Collections.Generic;
class Solution
{
// Topo sort only exists in DAGs i.e.
// Direct Acyclic Graph
void DFS(List<int>[] adj, List<int> vis, int node, int n, Stack<int> stck)
{
vis[node] = 1;
foreach (int it in adj[node])
{
if (vis[it] == 0)
{
DFS(adj, vis, it, n, stck);
}
}
stck.Push(node);
}
// During the traversal, u must
// be visited before v
public Stack<int> TopoSort(List<int>[] adj, int n)
{
List<int> vis = new List<int>(n);
for (int i = 0; i < n; i++)
{
vis.Add(0);
}
// using stack ADT
Stack<int> stck = new Stack<int>();
for (int i = 0; i < n; i++)
{
if (vis[i] == 0)
{
DFS(adj, vis, i, n, stck);
}
}
return stck;
}
}
class Program
{
static void AddEdge(List<int>[] adj, int u, int v)
{
adj[u].Add(v);
}
static void Main(string[] args)
{
int n = 6;
List<int>[] adj = new List<int>[n];
for (int i = 0; i < n; i++)
{
adj[i] = new List<int>();
}
AddEdge(adj, 5, 0);
AddEdge(adj, 5, 2);
AddEdge(adj, 2, 0);
AddEdge(adj, 2, 3);
AddEdge(adj, 3, 0);
AddEdge(adj, 3, 1);
AddEdge(adj, 1, 0);
AddEdge(adj, 4, 0);
AddEdge(adj, 4, 1);
Solution solution = new Solution();
Stack<int> ans = solution.TopoSort(adj, n);
while (ans.Count > 0)
{
int node = ans.Pop();
Console.Write(node + " ");
}
}
}
JavaScript
class Solution {
// Topo sort only exists in DAGs i.e.
// Direct Acyclic Graph
dfs(adj, vis, node, n, stck) {
vis[node] = 1;
for (let it of adj[node]) {
if (!vis[it]) {
this.dfs(adj, vis, it, n, stck);
}
}
stck.push(node);
}
// During the traversal, u must
// be visited before v
topo_sort(adj, n) {
const vis = new Array(n).fill(0);
// Using stack data structure
const stck = [];
for (let i = 0; i < n; i++) {
if (!vis[i]) {
this.dfs(adj, vis, i, n, stck);
}
}
return stck.reverse();
}
}
function addEdge(adj, u, v) {
adj[u].push(v);
}
const n = 6;
const adj = Array.from({ length: n }, () => []);
addEdge(adj, 5, 0);
addEdge(adj, 5, 2);
addEdge(adj, 2, 0);
addEdge(adj, 2, 3);
addEdge(adj, 3, 0);
addEdge(adj, 3, 1);
addEdge(adj, 1, 0);
addEdge(adj, 4, 0);
addEdge(adj, 4, 1);
const solution = new Solution();
const ans = solution.topo_sort(adj, n);
console.log(ans.join(" "));
Time Complexity: O( V + E)
Auxiliary Space: O(V)
Comparative Analysis :
- Both Kahn's Algorithm and DFS Approach have their strengths and weaknesses. Kahn's Algorithm is easy to implement and guarantees a topological sort, but it can be slower for large graphs. On the other hand, DFS Approach is more efficient for larger graphs, but it requires more memory and can be more complex to implement.
- Kahn's Algorithm is a good choice when dealing with smaller graphs, where simplicity and correctness are more important than performance. It is also useful in situations where you need to detect cycles in the graph. If a cycle is detected during the sorting process, the algorithm will terminate early and report that the graph is not a DAG.
- DFS Approach, on the other hand, is a better choice when dealing with larger graphs where performance is more important than simplicity. It is also useful in situations where you need to find a topological sort quickly, and memory is not a constraint.
Advantages of Kahn's Algorithm over the DFS Approach:
- Guaranteed to find a topological ordering if one exists.
- Simple and easy to implement.
- Works well for small and medium-sized DAGs.
Disadvantages of Kahn's Algorithm compared to the DFS Approach:
- Can be slower than DFS Approach for large DAGs.
- Requires more memory than DFS Approach.
- Requires a queue data structure, which may not be available in some programming languages.
Advantages of the DFS Approach over Kahn's Algorithm:
- Can be faster for larger DAGs.
- Requires less memory than Kahn's Algorithm.
- Does not require a queue data structure.
Disadvantages of the DFS Approach compared to Kahn's Algorithm:
- May not find a topological ordering if the graph has cycles.
- More complex to implement than Kahn's Algorithm.
- Can be less efficient for small and medium-sized DAGs.
Conclusion:
In conclusion, both Kahn's Algorithm and DFS Approach are effective algorithms for topological sorting in DAGs. The choice between the two depends on the size of the graph, the available memory, and the required performance. In general, Kahn's Algorithm is simpler and more reliable, but DFS Approach can be more efficient for larger graphs. Both algorithms have their strengths and weaknesses, and the best approach depends on the specific requirements of the problem at hand.
Similar Reads
Bellman-Ford vs Floyd-Warshall's algorithm: A Comparative Analysis
Bellman-Ford Algorithm:The Bellman-Ford algorithm is a single-source shortest-path algorithm that works by iteratively relaxing edges in the graph until the shortest path to all vertices is found. It is especially useful for graphs with negative edge weights, as it can detect negative cycles and ret
15+ min read
Time and Space Complexity Analysis of Kruskal Algorithm
Kruskal's algorithm is a popular algorithm for finding the Minimum Spanning Tree (MST) of a connected, undirected graph. The time complexity of Kruskal's algorithm is O(E log E), where E is the number of edges in the graph. The space complexity of Kruskal's algorithm is O(V + E), where V is the numb
2 min read
Design and Analysis of Algorithm Tutorial
Design and Analysis of Algorithms is a fundamental area in computer science that focuses on understanding how to solve problems efficiently using algorithms. It is about designing algorithms that are not only correct but also optimal, taking into account factors like time and space efficiency. Algor
2 min read
Comparison between Tarjan's and Kosaraju's Algorithm
Tarjan's Algorithm: The Tarjan's Algorithm is an efficient graph algorithm that is used to find the Strongly Connected Component(SCC) in a directed graph by using only one DFS traversal in linear time complexity. Working: Perform a DFS traversal over the nodes so that the sub-trees of the Strongly C
15+ min read
Time and Space Complexity of DFS and BFS Algorithm
The time complexity of both Depth-First Search (DFS) and Breadth-First Search (BFS) algorithms is O(V + E), where V is the number of vertices and E is the number of edges in the graph. The space complexity of DFS is O(V), where V represents the number of vertices in the graph, and for BFS, it is O(V
2 min read
Comparison between Shortest Path Algorithms:
Finding the shortest way is becoming more important in a world where time and distance matter. There are multiple shorted path algorithms exists. Therefore, in this article, we will compare the shortest path algorithms on the basis of their complexity and their performance, which will help us to use
4 min read
Dial's Algorithm (Optimized Dijkstra for small range weights)
Dijkstraâs shortest path algorithm runs in O(Elog V) time when implemented with adjacency list representation (See C implementation and STL based C++ implementations for details). Input : Source = 0, Maximum Weight W = 14Output : Vertex Distance from Source 0 0 1 4 2 12 3 19 4 21 5 11 6 9 7 8 8 14 C
15+ min read
Approximation Algorithms
Overview :An approximation algorithm is a way of dealing with NP-completeness for an optimization problem. This technique does not guarantee the best solution. The goal of the approximation algorithm is to come as close as possible to the optimal solution in polynomial time. Such algorithms are call
3 min read
Kahn's Algorithm in Python
Kahn's Algorithm is used for topological sorting of a directed acyclic graph (DAG). The algorithm works by repeatedly removing nodes with no incoming edges and recording them in a topological order. Here's a step-by-step implementation of Kahn's Algorithm in Python. Example: Input:Â V=6 , E = {{2, 3}
4 min read
Kosaraju's Algorithm in Python
What is Kosaraju's Algorithm?Kosaraju's Algorithm is a classic graph theory algorithm used to find the Strongly Connected Components (SCCs) in a directed graph. A strongly connected component is a maximal subgraph where every vertex is reachable from every other vertex. Kosaraju's algorithm is effic
3 min read