8000 feat: add tarjan algorithm for strongly connected components (#220) · Algorithms-Learn/TypeScript@90212fa · GitHub
[go: up one dir, main page]

Skip to content

Commit 90212fa

Browse files
authored
feat: add tarjan algorithm for strongly connected components (TheAlgorithms#220)
* feat: add tarjan algorithm for strongly connected components * Address review. Add notes on topological sort.
1 parent 6e78b6c commit 90212fa

File tree

2 files changed

+148
-0
lines changed

2 files changed

+148
-0
lines changed

graph/tarjan.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* @function tarjan
3+
* @description Given a graph, find the strongly connected components(SCC) in reverse topological order. A set of nodes form a SCC if there is a path between all pairs of points within that set.
4+
* @Complexity_Analysis
5+
* Time complexity: O(V + E). We perform a DFS of (V + E)
6+
* Space Complexity: O(V). We hold numerous structures all of which at worst holds O(V) nodes.
7+
* @param {[number, number][][]} graph - The graph in adjacency list form
8+
* @return {number[][]} - An array of SCCs, where an SCC is an array with the indices of each node within that SCC. The order of SCCs in the array are in reverse topological order.
9+
* @see https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
10+
*/
11+
export const tarjan = (graph: number[][]): number[][] => {
12+
if (graph.length === 0) {
13+
return [];
14+
}
15+
16+
let index = 0;
17+
// The order in which we discover nodes
18+
let discovery: number[] = Array(graph.length);
19+
// For each node, holds the furthest ancestor it can reach
20+
let low: number[] = Array(graph.length).fill(undefined);
21+
// Holds the nodes we have visited in a DFS traversal and are considering to group into a SCC
22+
let stack: number[] = [];
23+
// Holds the elements in the stack.
24+
let stackContains = Array(graph.length).fill(false);
25+
let sccs: number[][] = [];
26+
27+
const dfs = (node: number) => {
28+
discovery[node] = index;
29+
low[node] = index;
30+
++index;
31+
stack.push(node);
32+
stackContains[node] = true;
33+
34+
for (const child of graph[node]) {
35+
if (low[child] === undefined) {
36+
dfs(child);
37+
if (low[child] < low[node]) {
38+
// Child node loops back to this node's ancestor. Update the low node.
39+
low[node] = low[child];
40+
}
41+
} else if (stackContains[child] && low[node] > discovery[child]) {
42+
// Found a backedge. Update the low for this node if needed.
43+
low[node] = discovery[child];
44+
}
45+
}
46+
47+
if (discovery[node] == low[node]) {
48+
// node is the root of a SCC. Gather the SCC's nodes from the stack.
49+
let scc: number[] = [];
50+
let i;
51+
for (i = stack.length - 1; stack[i] != node; --i) {
52+
scc.push(stack[i]);
53+
stackContains[stack[i]] = false;
54+
stack.pop();
55+
}
56+
scc.push(stack[i]);
57+
stackContains[stack[i]] = false;
58+
stack.pop();
59+
sccs.push(scc);
60+
}
61+
}
62+
63+
for (let i = 0; i < graph.length; ++i) {
64+
if (low[i] === undefined) {
65+
dfs(i);
66+
}
67+
}
68+
return sccs;
69+
}
70+

graph/test/tarjan.test.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { tarjan } from "../tarjan";
2+
3+
describe("tarjan", () => {
4+
5+
it("it should return no sccs for empty graph", () => {
6+
expect(tarjan([])).toStrictEqual([]);
7+
});
8+
9+
it("it should return one scc for graph with one element", () => {
10+
expect(tarjan([[]])).toStrictEqual([[0]]);
11+
});
12+
13+
it("it should return one scc for graph with element that points to itself", () => {
14+
expect(tarjan([[0]])).toStrictEqual([[0]]);
15+
});
16+
17+
it("it should return one scc for two element graph with cycle", () => {
18+
expect(tarjan([[1], [0]])).toStrictEqual([[1, 0]]);
19+
});
20+
21+
it("should return one scc for each element for straight line", () => {
22+
expect(tarjan([[1], [2], [3], []])).toStrictEqual([[3], [2], [1], [0]]);
23+
});
24+
25+
it("should return sccs for straight line with backedge in middle", () => {
26+
expect(tarjan([[1], [2], [3, 0], []])).toStrictEqual([[3], [2, 1, 0]]);
27+
});
28+
29+
it("should return sccs for straight line with backedge from end to middle", () => {
30+
expect(tarjan([[1], [2], [3], [1]])).toStrictEqual([[3, 2, 1], [0]]);
31+
});
32+
33+
it("should return scc for each element for graph with no edges", () => {
34+
expect(tarjan([[], [], [], []])).toStrictEqual([[0], [1], [2], [3]]);
35+
});
36+
37+
it("should return sccs disconnected graph", () => {
38+
expect(tarjan([[1, 2], [0, 2], [0, 1], []])).toStrictEqual([[2, 1, 0], [3]]);
39+
});
40+
41+
it("should return sccs disconnected graph", () => {
42+
expect(tarjan([[1, 2], [0, 2], [0, 1], [4], [5], [3]])).toStrictEqual([[2, 1, 0], [5, 4, 3]]);
43+
});
44+
45+
it("should return single scc", () => {
46+
expect(tarjan([[1], [2], [3], [0, 4], [3]])).toStrictEqual([[4, 3, 2, 1, 0]]);
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(tarjan(input)).toStrictEqual([[4, 3, 2, 1, 0]]);
52+
});
53+
54+
it("should return sccs", () => {
55+
const input = [[1], [2], [0, 3], [4], []];
56+
expect(tarjan(input)).toStrictEqual([[4], [3], [2, 1, 0]]);
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 = [[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]];
62+
expect(tarjan(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 = [[7], [6, 5, 4], [3], [2, 1, 0]];
68+
expect(tarjan(input)).toStrictEqual(expected);
69+
});
70+
71+
it("should return sccs where first scc cannot reach second scc", () => {
72+
const input = [[1], [2], [0], [4], [5], [2, 3]];
73+
const expected = [[2, 1, 0], [5, 4, 3]];
74+
expect(tarjan(input)).toStrictEqual(expected);
75+
});
76+
77+
})
78+

0 commit comments

Comments
 (0)
0