Skip to content

Commit 20d642b

Browse files
committedMay 14, 2018
Add SCC.
1 parent 0c25611 commit 20d642b

File tree

5 files changed

+253
-2
lines changed

5 files changed

+253
-2
lines changed
 

‎README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
* [Articulation Points](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/articulation-points) - Tarjan's algorithm (DFS based)
7676
* [Bridges](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/bridges) - DFS based algorithm
7777
* [Eulerian Path and Eulerian Circuit](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/eulerian-path) - Fleury's algorithm
78-
* Strongly Connected Component algorithm
78+
* [Strongly Connected Components](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/strongly-connected-components) - Kosaraju's algorithm
7979
* Shortest Path Faster Algorithm (SPFA)
8080
* **Uncategorized**
8181
* Union-Find
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Strongly Connected Component
2+
3+
A directed graph is called **strongly connected** if there is a path
4+
in each direction between each pair of vertices of the graph.
5+
In a directed graph G that may not itself be strongly connected,
6+
a pair of vertices `u` and `v` are said to be strongly connected
7+
to each other if there is a path in each direction between them.
8+
9+
![Strongly Connected](https://upload.wikimedia.org/wikipedia/commons/5/5c/Scc.png)
10+
11+
Graph with strongly connected components marked
12+
13+
## References
14+
15+
- [Wikipedia](https://en.wikipedia.org/wiki/Strongly_connected_component)
16+
- [YouTube](https://www.youtube.com/watch?v=RpgcYiky7uw)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import GraphVertex from '../../../../data-structures/graph/GraphVertex';
2+
import GraphEdge from '../../../../data-structures/graph/GraphEdge';
3+
import Graph from '../../../../data-structures/graph/Graph';
4+
import stronglyConnectedComponents from '../stronglyConnectedComponents';
5+
6+
describe('stronglyConnectedComponents', () => {
7+
it('should detect strongly connected components in simple graph', () => {
8+
const vertexA = new GraphVertex('A');
9+
const vertexB = new GraphVertex('B');
10+
const vertexC = new GraphVertex('C');
11+
const vertexD = new GraphVertex('D');
12+
13+
const edgeAB = new GraphEdge(vertexA, vertexB);
14+
const edgeBC = new GraphEdge(vertexB, vertexC);
15+
const edgeCA = new GraphEdge(vertexC, vertexA);
16+
const edgeCD = new GraphEdge(vertexC, vertexD);
17+
18+
const graph = new Graph(true);
19+
20+
graph
21+
.addEdge(edgeAB)
22+
.addEdge(edgeBC)
23+
.addEdge(edgeCA)
24+
.addEdge(edgeCD);
25+
26+
const components = stronglyConnectedComponents(graph);
27+
28+
expect(components).toBeDefined();
29+
expect(components.length).toBe(2);
30+
31+
expect(components[0][0].getKey()).toBe(vertexA.getKey());
32+
expect(components[0][1].getKey()).toBe(vertexC.getKey());
33+
expect(components[0][2].getKey()).toBe(vertexB.getKey());
34+
35+
expect(components[1][0].getKey()).toBe(vertexD.getKey());
36+
});
37+
38+
it('should detect strongly connected components in graph', () => {
39+
const vertexA = new GraphVertex('A');
40+
const vertexB = new GraphVertex('B');
41+
const vertexC = new GraphVertex('C');
42+
const vertexD = new GraphVertex('D');
43+
const vertexE = new GraphVertex('E');
44+
const vertexF = new GraphVertex('F');
45+
const vertexG = new GraphVertex('G');
46+
const vertexH = new GraphVertex('H');
47+
const vertexI = new GraphVertex('I');
48+
const vertexJ = new GraphVertex('J');
49+
const vertexK = new GraphVertex('K');
50+
51+
const edgeAB = new GraphEdge(vertexA, vertexB);
52+
const edgeBC = new GraphEdge(vertexB, vertexC);
53+
const edgeCA = new GraphEdge(vertexC, vertexA);
54+
const edgeBD = new GraphEdge(vertexB, vertexD);
55+
const edgeDE = new GraphEdge(vertexD, vertexE);
56+
const edgeEF = new GraphEdge(vertexE, vertexF);
57+
const edgeFD = new GraphEdge(vertexF, vertexD);
58+
const edgeGF = new GraphEdge(vertexG, vertexF);
59+
const edgeGH = new GraphEdge(vertexG, vertexH);
60+
const edgeHI = new GraphEdge(vertexH, vertexI);
61+
const edgeIJ = new GraphEdge(vertexI, vertexJ);
62+
const edgeJG = new GraphEdge(vertexJ, vertexG);
63+
const edgeJK = new GraphEdge(vertexJ, vertexK);
64+
65+
const graph = new Graph(true);
66+
67+
graph
68+
.addEdge(edgeAB)
69+
.addEdge(edgeBC)
70+
.addEdge(edgeCA)
71+
.addEdge(edgeBD)
72+
.addEdge(edgeDE)
73+
.addEdge(edgeEF)
74+
.addEdge(edgeFD)
75+
.addEdge(edgeGF)
76+
.addEdge(edgeGH)
77+
.addEdge(edgeHI)
78+
.addEdge(edgeIJ)
79+
.addEdge(edgeJG)
80+
.addEdge(edgeJK);
81+
82+
const components = stronglyConnectedComponents(graph);
83+
84+
expect(components).toBeDefined();
85+
expect(components.length).toBe(4);
86+
87+
expect(components[0][0].getKey()).toBe(vertexG.getKey());
88+
expect(components[0][1].getKey()).toBe(vertexJ.getKey());
89+
expect(components[0][2].getKey()).toBe(vertexI.getKey());
90+
expect(components[0][3].getKey()).toBe(vertexH.getKey());
91+
92+
expect(components[1][0].getKey()).toBe(vertexK.getKey());
93+
94+
expect(components[2][0].getKey()).toBe(vertexA.getKey());
95+
expect(components[2][1].getKey()).toBe(vertexC.getKey());
96+
expect(components[2][2].getKey()).toBe(vertexB.getKey());
97+
98+
expect(components[3][0].getKey()).toBe(vertexD.getKey());
99+
expect(components[3][1].getKey()).toBe(vertexF.getKey());
100+
expect(components[3][2].getKey()).toBe(vertexE.getKey());
101+
});
102+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import Stack from '../../../data-structures/stack/Stack';
2+
import depthFirstSearch from '../depth-first-search/depthFirstSearch';
3+
4+
/**
5+
* @param {Graph} graph
6+
* @return {Stack}
7+
*/
8+
function getVerticesSortedByDfsFinishTime(graph) {
9+
// Set of all visited vertices during DFS pass.
10+
const visitedVerticesSet = {};
11+
12+
// Stack of vertices by finish time.
13+
// All vertices in this stack are ordered by finished time in decreasing order.
14+
// Vertex that has been finished first will be at the bottom of the stack and
15+
// vertex that has been finished last will be at the top of the stack.
16+
const verticesByDfsFinishTime = new Stack();
17+
18+
// Set of all vertices we're going to visit.
19+
const notVisitedVerticesSet = {};
20+
graph.getAllVertices().forEach((vertex) => {
21+
notVisitedVerticesSet[vertex.getKey()] = vertex;
22+
});
23+
24+
// Specify DFS traversal callbacks.
25+
const dfsCallbacks = {
26+
enterVertex: ({ currentVertex }) => {
27+
// Add current vertex to visited set.
28+
visitedVerticesSet[currentVertex.getKey()] = currentVertex;
29+
30+
// Delete current vertex from not visited set.
31+
delete notVisitedVerticesSet[currentVertex.getKey()];
32+
},
33+
leaveVertex: ({ currentVertex }) => {
34+
// Push vertex to the stack when leaving it.
35+
// This will make stack to be ordered by finish time in decreasing order.
36+
verticesByDfsFinishTime.push(currentVertex);
37+
},
38+
allowTraversal: ({ nextVertex }) => {
39+
// Don't allow to traverse the nodes that have been already visited.
40+
return !visitedVerticesSet[nextVertex.getKey()];
41+
},
42+
};
43+
44+
// Do FIRST DFS PASS traversal for all graph vertices to fill the verticesByFinishTime stack.
45+
while (Object.values(notVisitedVerticesSet).length) {
46+
// Peek any vertex to start DFS traversal from.
47+
const startVertexKey = Object.keys(notVisitedVerticesSet)[0];
48+
const startVertex = notVisitedVerticesSet[startVertexKey];
49+
delete notVisitedVerticesSet[startVertexKey];
50+
51+
depthFirstSearch(graph, startVertex, dfsCallbacks);
52+
}
53+
54+
return verticesByDfsFinishTime;
55+
}
56+
57+
/**
58+
* @param {Graph} graph
59+
* @param {Stack} verticesByFinishTime
60+
* @return {*[]}
61+
*/
62+
function getSCCSets(graph, verticesByFinishTime) {
63+
// Array of arrays of strongly connected vertices.
64+
const stronglyConnectedComponentsSets = [];
65+
66+
// Array that will hold all vertices that are being visited during one DFS run.
67+
let stronglyConnectedComponentsSet = [];
68+
69+
// Visited vertices set.
70+
const visitedVerticesSet = {};
71+
72+
// Callbacks for DFS traversal.
73+
const dfsCallbacks = {
74+
enterVertex: ({ currentVertex }) => {
75+
// Add current vertex to SCC set of current DFS round.
76+
stronglyConnectedComponentsSet.push(currentVertex);
77+
78+
// Add current vertex to visited set.
79+
visitedVerticesSet[currentVertex.getKey()] = currentVertex;
80+
},
81+
leaveVertex: ({ previousVertex }) => {
82+
// Once DFS traversal is finished push the set of found strongly connected
83+
// components during current DFS round to overall strongly connected components set.
84+
// The sign that traversal is about to be finished is that we came back to start vertex
85+
// which doesn't have parent.
86+
if (previousVertex === null) {
87+
stronglyConnectedComponentsSets.push([...stronglyConnectedComponentsSet]);
88+
}
89+
},
90+
allowTraversal: ({ nextVertex }) => {
91+
// Don't allow traversal of already visited vertices.
92+
return !visitedVerticesSet[nextVertex.getKey()];
93+
},
94+
};
95+
96+
while (!verticesByFinishTime.isEmpty()) {
97+
/** @var {GraphVertex} startVertex */
98+
const startVertex = verticesByFinishTime.pop();
99+
100+
// Reset the set of strongly connected vertices.
101+
stronglyConnectedComponentsSet = [];
102+
103+
// Don't do DFS on already visited vertices.
104+
if (!visitedVerticesSet[startVertex.getKey()]) {
105+
// Do DFS traversal.
106+
depthFirstSearch(graph, startVertex, dfsCallbacks);
107+
}
108+
}
109+
110+
return stronglyConnectedComponentsSets;
111+
}
112+
113+
/**
114+
* Kosaraju's algorithm.
115+
*
116+
* @param {Graph} graph
117+
* @return {*[]}
118+
*/
119+
export default function stronglyConnectedComponents(graph) {
120+
// In this algorithm we will need to do TWO DFS PASSES overt the graph.
121+
122+
// Get stack of vertices ordered by DFS finish time.
123+
// All vertices in this stack are ordered by finished time in decreasing order:
124+
// Vertex that has been finished first will be at the bottom of the stack and
125+
// vertex that has been finished last will be at the top of the stack.
126+
const verticesByFinishTime = getVerticesSortedByDfsFinishTime(graph);
127+
128+
// Reverse the graph.
129+
graph.reverse();
130+
131+
// Do DFS once again on reversed graph.
132+
return getSCCSets(graph, verticesByFinishTime);
133+
}

‎src/data-structures/stack/Stack.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default class Stack {
3131
}
3232

3333
/**
34-
* @return {LinkedListNode}
34+
* @return {*}
3535
*/
3636
pop() {
3737
const removedTail = this.linkedList.deleteTail();

0 commit comments

Comments
 (0)
Please sign in to comment.