Skip to content

Commit c1a1b2f

Browse files
committed
feat(graphs): add Tarjan's algorithm for SCC
Fix mgechev#98
1 parent 1c292fe commit c1a1b2f

File tree

4 files changed

+111
-2
lines changed

4 files changed

+111
-2
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"jshint-stylish": "^1.0.0"
1717
},
1818
"scripts": {
19-
"test": "echo \"Error: no test specified\" && exit 1"
19+
"test": "gulp test"
2020
},
2121
"repository": {
2222
"type": "git",
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
(function (exports) {
2+
3+
/**
4+
* Tarjan's algorithm for finding the connected components in a graph.<br><br>
5+
* Time complexity: O(|E| + |V|) where E is a number of edges and |V|
6+
* is the number of nodes.
7+
*
8+
* @public
9+
* @module graphs/others/tarjan-connected-components
10+
* @param {Array} graph Adjacency list, which represents the graph.
11+
* @returns {Array} Connected components.
12+
*
13+
* @example
14+
* var tarjanConnectedComponents =
15+
* require('path-to-algorithms/src/graphs/' +
16+
* 'others/tarjan-connected-components').tarjanConnectedComponents;
17+
* var graph = {
18+
* v1: ['v2', 'v5'],
19+
* v2: [],
20+
* v3: ['v1', 'v2', 'v4', 'v5'],
21+
* v4: [],
22+
* v5: []
23+
* };
24+
* var vertices = topsort(graph); // ['v3', 'v4', 'v1', 'v5', 'v2']
25+
*/
26+
function tarjanConnectedComponents(graph) {
27+
graph = graph || {};
28+
const indexes = {};
29+
const lowIndexes = {};
30+
const onStack = {};
31+
const result = [];
32+
const stack = [];
33+
let index = 1;
34+
35+
const connectedComponent = node => {
36+
stack.push(node);
37+
onStack[node] = true;
38+
indexes[node] = index;
39+
lowIndexes[node] = index;
40+
index += 1;
41+
graph[node].forEach(n => {
42+
if (indexes[n] === undefined) {
43+
connectedComponent(n);
44+
lowIndexes[node] = Math.min(lowIndexes[n], lowIndexes[node]);
45+
} else if (onStack[n]) {
46+
lowIndexes[node] = Math.min(lowIndexes[node], indexes[n]);
47+
}
48+
});
49+
// This is a "root" node
50+
const cc = [];
51+
if (indexes[node] === lowIndexes[node]) {
52+
let current;
53+
do {
54+
current = stack.pop();
55+
onStack[current] = false;
56+
cc.push(current);
57+
} while (stack.length > 0 && node !== current);
58+
result.push(cc);
59+
}
60+
};
61+
62+
Object.keys(graph)
63+
.forEach(n => !indexes[n] && connectedComponent(n));
64+
65+
return result;
66+
}
67+
68+
exports.tarjanConnectedComponents = tarjanConnectedComponents;
69+
70+
}(typeof exports === 'undefined' ? window : exports));

src/graphs/others/topological-sort.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222

2323
/**
2424
* Topological sort algorithm of a directed acyclic graph.<br><br>
25-
* Time complexity: O(|E|) where E is a number of edges.
25+
* Time complexity: O(|E| + |V|) where E is a number of edges
26+
* and |V| is the number of nodes.
2627
*
2728
* @public
2829
* @module graphs/others/topological-sort
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
var tj = require('../../../src/graphs/others/tarjan-connected-components').tarjanConnectedComponents;
2+
3+
var nonConnected = {
4+
v1: [],
5+
v2: [],
6+
v3: [],
7+
v4: [],
8+
v5: []
9+
};
10+
11+
var cyclicGraph = {
12+
v1: ['v2'],
13+
v2: ['v3'],
14+
v3: ['v4'],
15+
v4: ['v5'],
16+
v5: ['v1']
17+
};
18+
19+
20+
describe('Tarjan\'s algorithm for finding connected components', function () {
21+
'use strict';
22+
it('should be defined', function () {
23+
expect(typeof tj).toBe('function');
24+
});
25+
26+
it('should return an array', function () {
27+
expect(tj() instanceof Array).toBeTruthy();
28+
});
29+
30+
it('should work with non-connected graphs', function () {
31+
expect(tj(nonConnected)).toEqual([['v1'], ['v2'], ['v3'], ['v4'], ['v5']]);
32+
});
33+
34+
it('should workw ith cycles', () => {
35+
expect(tj(cyclicGraph)).toEqual([['v5', 'v4', 'v3', 'v2', 'v1']]);
36+
});
37+
38+
});

0 commit comments

Comments
 (0)