Skip to content

Commit

Permalink
Strongly Connected Component (#121)
Browse files Browse the repository at this point in the history
* Added reverseGraph method to Graph  (and its test)

* Added Strongly Connected Component Algorithm (and its test)

* Fix code style problems

* Fix code style problems

* Fix code style problems

* Improved documentation

* Use addEdge method instead of hard coding

* Use stack more properly

* Move function creation out of loop

* Fix style problem
  • Loading branch information
CtheSky authored and felipernb committed Feb 12, 2017
1 parent 8181e05 commit 0e1766f
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 1 deletion.
72 changes: 72 additions & 0 deletions src/algorithms/graph/strongly_connected_component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
'use strict';

var Stack = require('../../data_structures/stack');
var depthFirstSearch = require('../../algorithms/graph/depth_first_search');

/**
* Kosaraju's Strongly Connected Component algorithm, https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm
* Complexity: O(V + E).
*
* @param {Graph} graph
* @return {{count: number, id: Object.<string, number>}}
* count is the number of strongly connected components in the graph
* id is a Object, receives a vertex and returns id of the strongly
* connected component vertex belongs to, ranges from 0 to count - 1.
* note: 1.if v and w are in same scc, then id[v] == id[w]
* 2.if v and w are in different scc and there is a path from v to w, then id[v] > id[w].
*
* Usage:
* var scc = stronglyConnectedComponent(g);
* scc.count; // count of strongly connected components
* scc.id[v]; // id of the strongly connected component which v belongs to
*/
var stronglyConnectedComponent = function(graph) {
var reverse = graph.reverse();
var stack = new Stack();
var visited = {};
var count = 0;
var id = Object.create(null);

reverse.vertices.forEach(function(node) {
if (!visited[node]) {
depthFirstSearch(reverse, node, {
allowTraversal: function(node, neighbor) {
return !visited[neighbor];
},
enterVertex: function(node) {
visited[node] = true;
},
leaveVertex: function(node) {
stack.push(node);
}
});
}
});

visited = {};
var allowTraversal = function(node, neighbor) {
return !visited[neighbor];
};
var enterVertex = function(node) {
visited[node] = true;
id[node] = count;
};

while (!stack.isEmpty()) {
var node = stack.pop();
if (!visited[node]) {
depthFirstSearch(graph, node, {
allowTraversal: allowTraversal,
enterVertex: enterVertex
});
++count;
}
}

return {
count: count,
id: id
};
};

module.exports = stronglyConnectedComponent;
17 changes: 17 additions & 0 deletions src/data_structures/graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,21 @@ Graph.prototype.edge = function(a, b) {
return this.adjList[_(a)][_(b)];
};

Graph.prototype.reverse = function() {
var self = this;
var r = new Graph(this.directed);

self.vertices.forEach(function(v) {
r.addVertex(v);
});

self.vertices.forEach(function(a) {
self.neighbors(a).forEach(function(b) {
r.addEdge(b, a, self.edge(a, b));
});
});

return r;
};

module.exports = Graph;
4 changes: 3 additions & 1 deletion src/graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ module.exports = {
breadthFirstSearch: require('./algorithms/graph/breadth_first_search'),
bfsShortestPath: require('./algorithms/graph/bfs_shortest_path'),
prim: require('./algorithms/graph/prim'),
floydWarshall: require('./algorithms/graph/floyd_warshall')
floydWarshall: require('./algorithms/graph/floyd_warshall'),
strongConnectedComponent: require('./algorithms/graph/' +
'strongly_connected_component')
};
81 changes: 81 additions & 0 deletions src/test/algorithms/graph/strongly_connected_component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
'use strict';

var root = require('../../../');
var Graph = root.DataStructures.Graph;
var stronglyConnectedComponent = root.Graph.strongConnectedComponent;
var assert = require('assert');

describe('Strongly Connected Component', function() {
it('should correctly compute strongly connected components', function() {
// graph: 0 -> 1 -> 2
var graph = new Graph();
graph.addEdge(0, 1);
graph.addEdge(1, 2);

var scc = stronglyConnectedComponent(graph);
assert.equal(scc.count, 3);
assert(scc.id[0] > scc.id[1]);
assert(scc.id[1] > scc.id[2]);

// graph: 0 <-> 1 -> 2
graph = new Graph();
graph.addEdge(0, 1);
graph.addEdge(1, 0);
graph.addEdge(1, 2);

scc = stronglyConnectedComponent(graph);
assert.equal(scc.count, 2);
assert.equal(scc.id[0], scc.id[1]);
assert(scc.id[1] > scc.id[2]);

// graph: http://algs4.cs.princeton.edu/42digraph/images/transitive-closure.png
graph = new Graph();
graph.addEdge(0, 1);
graph.addEdge(0, 5);
graph.addEdge(2, 0);
graph.addEdge(2, 3);
graph.addEdge(3, 2);
graph.addEdge(3, 5);
graph.addEdge(4, 2);
graph.addEdge(4, 3);
graph.addEdge(5, 4);
graph.addEdge(6, 0);
graph.addEdge(6, 4);
graph.addEdge(6, 9);
graph.addEdge(7, 6);
graph.addEdge(7, 8);
graph.addEdge(8, 7);
graph.addEdge(8, 9);
graph.addEdge(9, 10);
graph.addEdge(9, 11);
graph.addEdge(10, 12);
graph.addEdge(11, 4);
graph.addEdge(11, 12);
graph.addEdge(12, 9);

scc = stronglyConnectedComponent(graph);
assert.equal(scc.count, 5);

// scc no.0
assert(scc.id[0] > scc.id[1]);

// scc no.1
assert.equal(scc.id[0], scc.id[2]);
assert.equal(scc.id[0], scc.id[3]);
assert.equal(scc.id[0], scc.id[4]);
assert.equal(scc.id[0], scc.id[5]);

// scc no.2
assert(scc.id[9] > scc.id[0]);
assert.equal(scc.id[9], scc.id[10]);
assert.equal(scc.id[9], scc.id[11]);
assert.equal(scc.id[9], scc.id[12]);

// scc no.3
assert(scc.id[6] > scc.id[9]);

// scc no.4
assert(scc.id[7] > scc.id[6]);
assert.equal(scc.id[7], scc.id[8]);
});
});
26 changes: 26 additions & 0 deletions src/test/data_structures/graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,32 @@ describe('Graph - Adjacency list', function() {
assert.equal(g.edge('b', 'a'), 2);
});

it('should have reversed edges with same weight for a reverse directed graph',
function() {
var g = new Graph();
g.addVertex('a');
g.addVertex('b');
g.addVertex('c');
g.addVertex('d');
g.addEdge('a', 'b', 10);
g.addEdge('a', 'c', 5);
g.addEdge('c', 'd', 2);

var r = g.reverse();
assert(r.directed);
assert.equal(r.edge('a', 'b'), undefined);
assert.equal(r.edge('b', 'a'), 10);
assert.equal(r.edge('a', 'c'), undefined);
assert.equal(r.edge('c', 'a'), 5);
assert.equal(r.edge('c', 'd'), undefined);
assert.equal(r.edge('d', 'c'), 2);

assert.equal(r.edge('a', 'd'), undefined);
r.addEdge('a', 'b', 2);
assert.equal(r.edge('a', 'b'), 2);
assert.equal(r.edge('b', 'a'), 10);
});

it('should have a list of vertices', function() {
var g = new Graph();
assert.equal(g.vertices.size, 0);
Expand Down

0 comments on commit 0e1766f

Please sign in to comment.