Skip to content

Commit 6e78b6c

Browse files
authored
feat: Add kosajura algorithm for finding strong connected components (TheAlgorithms#218)
1 parent 4acf117 commit 6e78b6c

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

graph/kosajaru.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Compute the node priorities, which will be used to determine the order in which we perform transposed DFS.
2+
const getNodePriorities = (graph: number[][], visited: boolean[], stack: number[], node: number) => {
3+
if (visited[node]) {
4+
return;
5+
}
6+
visited[node] = true;
7+
8+
for (const dest of graph[node]) {
9+
getNodePriorities(graph, visited, stack, dest);
10+
}
11+
// Nodes that end their DFS earlier are pushed onto the stack first and have lower priority.
12+
stack.push(node);
13+
}
14+
15+
// Return the transpose of graph. The tranpose of a directed graph is a graph where each of the edges are flipped.
16+
const transpose = (graph: number[][]): number[][] => {
17+
let transposedGraph = Array(graph.length);
18+
for (let i = 0; i < graph.length; ++i) {
19+
transposedGraph[i] = [];
20+
}
21+
22+
for (let i = 0; i < graph.length; ++i) {
23+
for (let j = 0; j < graph[i].length; ++j) {
24+
transposedGraph[graph[i][j]].push(i);
25+
}
26+
}
27+
28+
return transposedGraph;
29+
}
30+
31+
// Computes the SCC that contains the given node
32+
const gatherScc = (graph: number[][], visited: boolean[], node: number, scc: number[]) => {
33+
if (visited[node]) {
34+
return;
35+
}
36+
visited[node] = true;
37+
scc.push(node);
38+
39+
for (const dest of graph[node]) {
40+
gatherScc(graph, visited, dest, scc);
41+
}
42+
}
43+
44+
/**
45+
* @function kosajaru
46+
* @description Given a graph, find the strongly connected components(SCC). A set of nodes form a SCC if there is a path between all pairs of points within that set.
47+
* @Complexity_Analysis
48+
* Time complexity: O(V + E). We perform two DFS twice, and make sure to visit each disconnected graph. Each DFS is O(V + E).
49+
* Space Complexity: O(V + E). This space is required for the transposed graph.
50+
* @param {[number, number][][]} graph - The graph in adjacency list form
51+
* @return {number[][]} - An array of SCCs, where an SCC is an array with the indices of each node within that SCC.
52+
* @see https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm
53+
*/
54+
export const kosajaru = (graph: number[][]): number[][] => {
55+
let visited = Array(graph.length).fill(false);
56+
57+
let stack: number[] = [];
58+
for (let i = 0; i < graph.length; ++i) {
59+
getNodePriorities(graph, visited, stack, i);
60+
}
61+
62+
const transposedGraph = transpose(graph);
63+
64+
let sccs = [];
65+
visited.fill(false);
66+
for (let i = stack.length - 1; i >= 0; --i) {
67+
if (!visited[stack[i]]) {
68+
let scc: number[] = [];
69+
gatherScc(transposedGraph, visited, stack[i], scc);
70+
sccs.push(scc);
71+
}
72+
}
73+
return sccs;
74+
}
75+

graph/test/kosajaru.test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { kosajaru } from "../kosajaru";
2+
3+
describe("kosajaru", () => {
4+
5+
it("it should return no sccs for empty graph", () => {
6+
expect(kosajaru([])).toStrictEqual([]);
7+
});
8+
9+
it("it should return one scc for graph with one element", () => {
10+
expect(kosajaru([[]])).toStrictEqual([[0]]);
11+
});
12+
13+
it("it should return one scc for graph with element that points to itself", () => {
14+
expect(kosajaru([[0]])).toStrictEqual([[0]]);
15+
});
16+
17+
it("it should return one scc for two element graph with cycle", () => {
18+
expect(kosajaru([[1], [0]])).toStrictEqual([[0, 1]]);
19+
});
20+
21+
it("should return one scc for each element for straight line", () => {
22+
expect(kosajaru([[1], [2], [3], []])).toStrictEqual([[0], [1], [2], [3]]);
23+
});
24+
25+
it("should return sccs for straight line with backedge in middle", () => {
26+
expect(kosajaru([[1], [2], [3, 0], []])).toStrictEqual([[0, 2, 1], [3]]);
27+
});
28+
29+
it("should return sccs for straight line with backedge from end to middle", () => {
30+
expect(kosajaru([[1], [2], [3], [1]])).toStrictEqual([[0], [1, 3, 2]]);
31+
});
32+
33+
it("should return scc for each element for graph with no edges", () => {
34+
expect(kosajaru([[], [], [], []])).toStrictEqual([[3], [2], [1], [0]]);
35+
});
36+
37+
it("should return sccs disconnected graph", () => {
38+
expect(kosajaru([[1, 2], [0, 2], [0, 1], []])).toStrictEqual([[3], [0, 1, 2]]);
39+
});
40+
41+
it("should return sccs disconnected graph", () => {
42+
expect(kosajaru([[1, 2], [0, 2], [0, 1], [4], [5], [3]])).toStrictEqual([[3, 5, 4], [0, 1, 2]]);
43+
});
44+
45+
it("should return single scc", () => {
46+
expect(kosajaru([[1], [2], [3], [0, 4], [3]])).toStrictEqual([[0, 3, 2, 1, 4]]);
47+
});
48+
49+
it("should return one scc for complete connected graph", () => {
50+
const input = [[1, 2, 3, 4], [0, 2, 3, 4], [0, 1, 3, 4], [0, 1, 2, 4], [0, 1, 2, 3]];
51+
expect(kosajaru(input)).toStrictEqual([[0, 1, 2, 3, 4]]);
52+
});
53+
54+
it("should return sccs", () => {
55+
const input = [[1], [2], [0, 3], [4], []];
56+
expect(kosajaru(input)).toStrictEqual([[0, 2, 1], [3], [4]]);
57+
});
58+
59+
it("should return sccs", () => {
60+
const input = [[1], [2], [0, 3, 4], [0], [5], [6, 7], [2, 4], [8], [5, 9], [5]];
61+
const expected = [[0, 2, 1, 6, 5, 4, 8, 7, 9, 3]];
62+
expect(kosajaru(input)).toStrictEqual(expected);
63+
});
64+
65+
it("should return sccs", () => {
66+
const input = [[1], [0, 2], [0, 3], [4], [5, 7], [6], [4, 7], []];
67+
const expected = [[0, 1, 2], [3], [4, 6, 5], [7]];
68+
expect(kosajaru(input)).toStrictEqual(expected);
69+
});
70+
71+
})
72+

0 commit comments

Comments
 (0)