-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
/
Copy pathTarjanSCC.js
109 lines (95 loc) · 3.15 KB
/
TarjanSCC.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/*
Tarjan's Algorithm to find all Strongly Connected Components (SCCs) in a directed graph.
It performs a DFS traversal while keeping track of the discovery and low-link values
to identify root nodes of SCCs.
Complexity:
Time: O(V + E), where V: vertices and E: edges.
Space: O(V), for stack | discovery arrays | result.
Reference:
https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
https://cp-algorithms.com/graph/strongly-connected-components.html
*/
/**
* Finds all strongly connected components in a directed graph using Tarjan's algorithm.
*
* @param {Object} graph - Directed graph represented as an adjacency list.
* @returns {Array<Array<string|number>>} - List of strongly connected components (each SCC is a list of nodes).
* @throws {Error} If the input graph is invalid or empty
*/
function TarjanSCC(graph) {
// Input validation
if (!graph || typeof graph !== 'object' || Array.isArray(graph)) {
throw new Error(
'Graph must be a non-null object representing an adjacency list'
)
}
if (Object.keys(graph).length === 0) {
return []
}
const ids = {} // Discovery time of each node
const low = {} // Lowest discovery time reachable from the node
const onStack = {} // To track if a node is on the recursion stack
const stack = [] // Stack to hold the current path
const result = [] // Array to store SCCs
let time = 0 // Global timer for discovery time
/**
* Convert node to its proper type (number if numeric string, otherwise string)
* @param {string|number} node
* @returns {string|number}
*/
function convertNode(node) {
return !isNaN(node) && String(Number(node)) === String(node)
? Number(node)
: node
}
/**
* Recursive DFS function to explore the graph and find SCCs
* @param {string|number} node
*/
function dfs(node) {
if (!(node in graph)) {
throw new Error(`Node ${node} not found in graph`)
}
ids[node] = low[node] = time++
stack.push(node)
onStack[node] = true
// Explore all neighbours
const neighbors = graph[node]
if (!Array.isArray(neighbors)) {
throw new Error(`Neighbors of node ${node} must be an array`)
}
for (const neighbor of neighbors) {
const convertedNeighbor = convertNode(neighbor)
if (!(convertedNeighbor in ids)) {
dfs(convertedNeighbor)
low[node] = Math.min(low[node], low[convertedNeighbor])
} else if (onStack[convertedNeighbor]) {
low[node] = Math.min(low[node], ids[convertedNeighbor])
}
}
// If the current node is the root of an SCC
if (ids[node] === low[node]) {
const scc = []
let current
do {
current = stack.pop()
onStack[current] = false
scc.push(convertNode(current))
} while (current !== node)
result.push(scc)
}
}
// Run DFS for all unvisited nodes
try {
for (const node in graph) {
const convertedNode = convertNode(node)
if (!(convertedNode in ids)) {
dfs(convertedNode)
}
}
} catch (error) {
throw new Error(`Error during graph traversal: ${error.message}`)
}
return result
}
export { TarjanSCC }