Skip to content

Commit 9079ea8

Browse files
committed
Added Kahns Algorithm
1 parent 08d8c6b commit 9079ea8

File tree

2 files changed

+189
-0
lines changed

2 files changed

+189
-0
lines changed

Graphs/KahnsAlgorithm.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import Queue from '../Data-Structures/Queue/Queue'
2+
3+
/**
4+
* Author: Abhay Goel
5+
* Implementing Kahns Algortihm for a Directed Graph
6+
* This algorithm uses the in-degree count of a node to process
7+
* It can be used to find Topological ordering , Cycle in a DAG.
8+
* Tutorial on Lowest Common Ancestor: [https://www.geeksforgeeks.org/cpp/kahns-algorithm-in-cpp/](https://www.geeksforgeeks.org/cpp/kahns-algorithm-in-cpp/)
9+
*/
10+
export function KahnsAlgorithm(graph) {
11+
// Keeps a track of Indegree of All Nodes in the Graph
12+
let Indegree_of_Node = {}
13+
14+
/*
15+
16+
    Structure of Graph is like this
17+
    Graph =>
18+
    {
19+
        A : [B , C] ,
20+
        B: [C , D]
21+
    }
22+
u -> (v1 , v2 ..)
23+
24+
  */
25+
26+
for (const node in graph) {
27+
// initialising empty
28+
Indegree_of_Node[node] = 0
29+
}
30+
31+
// Calculating Indeg of Nodes
32+
for (const node in graph) {
33+
for (const neighbor of graph[node]) {
34+
// calculating the indegree of each node
35+
Indegree_of_Node[neighbor] = Indegree_of_Node[neighbor] + 1
36+
}
37+
}
38+
39+
// Queue For Traversal
40+
let queue_for_Indeg = new Queue()
41+
42+
// pusing all nodes
43+
for (const node in graph) {
44+
if (Indegree_of_Node[node] == 0) queue_for_Indeg.enqueue(node)
45+
}
46+
47+
let Ordered_Nodes = []
48+
49+
// Algorithm starts
50+
// loop till no nodes with are left with zero indegree
51+
while (queue_for_Indeg.isEmpty() === false) {
52+
let frontNode = queue_for_Indeg.peekFirst()
53+
Ordered_Nodes.push(frontNode)
54+
queue_for_Indeg.dequeue()
55+
56+
for (const neighbor of graph[frontNode]) {
57+
Indegree_of_Node[neighbor] = Indegree_of_Node[neighbor] - 1
58+
if (Indegree_of_Node[neighbor] === 0) {
59+
queue_for_Indeg.enqueue(neighbor)
60+
}
61+
}
62+
}
63+
64+
return Ordered_Nodes
65+
}

Graphs/test/KahnsAlgo.test.js

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { KahnsAlgorithm } from '../KahnsAlgorithm'
2+
3+
/**
4+
* Helper function to verify if an array is a valid topological sort.
5+
* For every directed edge from node U to node V, U must come before V in the sort.
6+
* @param {object} graph - The adjacency list representation of the graph.
7+
* @param {string[]} sortedNodes - The topologically sorted array of nodes.
8+
* @returns {boolean} - True if the sort is valid, otherwise false.
9+
*/
10+
const isValidTopologicalSort = (graph, sortedNodes) => {
11+
const nodePositions = new Map()
12+
sortedNodes.forEach((node, index) => {
13+
nodePositions.set(node, index)
14+
})
15+
16+
for (const node in graph) {
17+
if (!nodePositions.has(node)) {
18+
// This can occur if a cycle is present and not all nodes are sorted.
19+
continue
20+
}
21+
const u_pos = nodePositions.get(node)
22+
for (const neighbor of graph[node]) {
23+
// If a neighbor is missing or appears before its dependency, the sort is invalid.
24+
if (!nodePositions.has(neighbor) || nodePositions.get(neighbor) < u_pos) {
25+
return false
26+
}
27+
}
28+
}
29+
return true
30+
}
31+
32+
describe('KahnsAlgorithm', () => {
33+
/**
34+
* Test Case 1: A standard Directed Acyclic Graph (DAG).
35+
* Graph:
36+
* 5 -> 2, 0
37+
* 4 -> 0, 1
38+
* 2 -> 3
39+
* 3 -> 1
40+
*/
41+
test('should correctly sort a standard Directed Acyclic Graph', () => {
42+
const graph = {
43+
5: ['2', '0'],
44+
4: ['0', '1'],
45+
2: ['3'],
46+
3: ['1'],
47+
1: [],
48+
0: []
49+
}
50+
const result = KahnsAlgorithm(graph)
51+
// A topological sort isn't always unique, so we validate its properties.
52+
expect(result.length).toBe(Object.keys(graph).length)
53+
expect(isValidTopologicalSort(graph, result)).toBe(true)
54+
})
55+
56+
/**
57+
* Test Case 2: A graph with a clear cycle.
58+
* Kahn's algorithm detects cycles by failing to process all nodes.
59+
* Graph: A -> B -> C -> A
60+
*/
61+
test('should return an incomplete sort for a graph with a cycle', () => {
62+
const graph = {
63+
A: ['B'],
64+
B: ['C'],
65+
C: ['A']
66+
}
67+
const result = KahnsAlgorithm(graph)
68+
// No node has an in-degree of 0, so the queue is never populated.
69+
expect(result.length).toBeLessThan(Object.keys(graph).length)
70+
expect(result).toEqual([])
71+
})
72+
73+
/**
74+
* Test Case 3: A disconnected graph.
75+
* Component 1: A -> B
76+
* Component 2: C -> D
77+
*/
78+
test('should correctly sort a disconnected graph', () => {
79+
const graph = {
80+
A: ['B'],
81+
B: [],
82+
C: ['D'],
83+
D: []
84+
}
85+
const result = KahnsAlgorithm(graph)
86+
expect(result.length).toBe(Object.keys(graph).length)
87+
expect(isValidTopologicalSort(graph, result)).toBe(true)
88+
})
89+
90+
/**
91+
* Test Case 4: An empty graph.
92+
*/
93+
test('should return an empty array for an empty graph', () => {
94+
const graph = {}
95+
const result = KahnsAlgorithm(graph)
96+
expect(result).toEqual([])
97+
})
98+
99+
/**
100+
* Test Case 5: A graph with a single node.
101+
*/
102+
test('should return an array with the single node for a single-node graph', () => {
103+
const graph = { Z: [] }
104+
const result = KahnsAlgorithm(graph)
105+
expect(result).toEqual(['Z'])
106+
})
107+
108+
/**
109+
* Test Case 6: A cycle within a larger graph component.
110+
* Graph: A -> B -> C -> D -> B (cycle: B-C-D)
111+
*/
112+
test('should handle a cycle within a larger graph', () => {
113+
const graph = {
114+
A: ['B'],
115+
B: ['C'],
116+
C: ['D'],
117+
D: ['B']
118+
}
119+
const result = KahnsAlgorithm(graph)
120+
// Only 'A' can be processed before the algorithm stops at the cycle.
121+
expect(result.length).toBeLessThan(Object.keys(graph).length)
122+
expect(result).toEqual(['A'])
123+
})
124+
})

0 commit comments

Comments
 (0)