Skip to content

Added Kann's algorithm #1676

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
44 changes: 44 additions & 0 deletions Graphs/KannsAlgorithm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* @author {RaviSadam}
* @name kannsAlgorithm
* @description -
* Kann's Algorithm implementation in JavaScript
* @summary
* Kann's Algorithm is used for topological sorting in directed acyclic graphs
*
* @param graph - Graph [[v1,v2],[v3,v4,v5]..]
* @param n - number of vertices
* @returns {Array} - Empty array if cycle is detected or else result array;
*
*/

export function kannsAlgorithm(graph, n) {
if (n === null || n === undefined) throw Error('Invalid n was given')
const inorder = Array(n).fill(0)
const result = []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(By the way, you could preallocate this array.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preallocate could increase the time complexity. At the end we need to check array filled or not.

Copy link
Collaborator

@appgurueu appgurueu Jul 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preallocate could increase the time complexity.

No, not really. It's still linear time in the size of the graph - O(n + m) where n is the number of nodes and m is the number of edges. (Allocation can also optimistically be assumed to be constant time.)

At the end we need to check array filled or not.

Yes, and that can be done in constant time, if you keep an index to the last element. It'd look something like this:

const topoOrder = Array(n) // preallocate
const idx = 0
...
// to push:
topoOrder[idx++] = node
...
if (idx < n) return null; // cyclic

Or another variant to write this would be:

const topoOrder = Array(n) // preallocate
for (let i = 0; i < n; ++i) {
    if (stack.length === 0) return null; // cyclic
    const node = stack.pop();
    topoOrder[i] = node;
    ...
}

The benefit is that when a topological order exists (which should usually be the case; this is the "happy path"), you can save some reallocations of the array.

But this is just a suggestion, I'm fine with pushing to keep the code simple as well. Though perhaps the preallocation variant is a bit more readable, since it makes explicit that we want to have all n nodes be in this array.

for (let entry of graph) {
for (let edge of entry) {
inorder[edge] += 1
}
}
const queue = []
console.log(inorder)
for (let i = 0; i < n; i++) {
if (inorder[i] === 0) {
queue.push(i)
}
}
while (queue.length != 0) {
const node = queue[0]
result.push(node)
queue.splice(0, 1)
for (let nei of graph[node]) {
inorder[nei] -= 1
if (inorder[nei] == 0) {
queue.push(nei)
}
}
}
if (result.length != n) return []
return result
}
13 changes: 13 additions & 0 deletions Graphs/test/KannsAlgorithm.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { kannsAlgorithm } from '../KannsAlgorithm'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You forgot to delete this file.


test('Test Case 1: Graph without cycle', () => {
const graph = [[], [], [3], [1], [0, 1], [0, 2]]

expect(kannsAlgorithm(graph, 6)).toEqual([4, 5, 0, 2, 3, 1])
})

test('Test Case 2: Graph with cycle', () => {
const graph = [[2], [], [3, 5], [0, 1], [0, 2]]

expect(kannsAlgorithm(graph, 6)).toEqual([])
})