forked from trekhleb/javascript-algorithms
-
Notifications
You must be signed in to change notification settings - Fork 8
/
stronglyConnectedComponents.js
133 lines (112 loc) · 4.56 KB
/
stronglyConnectedComponents.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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import Stack from '../../../data-structures/stack/Stack';
import depthFirstSearch from '../depth-first-search/depthFirstSearch';
/**
* @param {Graph} graph
* @return {Stack}
*/
function getVerticesSortedByDfsFinishTime(graph) {
// Set of all visited vertices during DFS pass.
const visitedVerticesSet = {};
// Stack of vertices by finish time.
// All vertices in this stack are ordered by finished time in decreasing order.
// Vertex that has been finished first will be at the bottom of the stack and
// vertex that has been finished last will be at the top of the stack.
const verticesByDfsFinishTime = new Stack();
// Set of all vertices we're going to visit.
const notVisitedVerticesSet = {};
graph.getAllVertices().forEach((vertex) => {
notVisitedVerticesSet[vertex.getKey()] = vertex;
});
// Specify DFS traversal callbacks.
const dfsCallbacks = {
enterVertex: ({ currentVertex }) => {
// Add current vertex to visited set.
visitedVerticesSet[currentVertex.getKey()] = currentVertex;
// Delete current vertex from not visited set.
delete notVisitedVerticesSet[currentVertex.getKey()];
},
leaveVertex: ({ currentVertex }) => {
// Push vertex to the stack when leaving it.
// This will make stack to be ordered by finish time in decreasing order.
verticesByDfsFinishTime.push(currentVertex);
},
allowTraversal: ({ nextVertex }) => {
// Don't allow to traverse the nodes that have been already visited.
return !visitedVerticesSet[nextVertex.getKey()];
},
};
// Do FIRST DFS PASS traversal for all graph vertices to fill the verticesByFinishTime stack.
while (Object.values(notVisitedVerticesSet).length) {
// Peek any vertex to start DFS traversal from.
const startVertexKey = Object.keys(notVisitedVerticesSet)[0];
const startVertex = notVisitedVerticesSet[startVertexKey];
delete notVisitedVerticesSet[startVertexKey];
depthFirstSearch(graph, startVertex, dfsCallbacks);
}
return verticesByDfsFinishTime;
}
/**
* @param {Graph} graph
* @param {Stack} verticesByFinishTime
* @return {*[]}
*/
function getSCCSets(graph, verticesByFinishTime) {
// Array of arrays of strongly connected vertices.
const stronglyConnectedComponentsSets = [];
// Array that will hold all vertices that are being visited during one DFS run.
let stronglyConnectedComponentsSet = [];
// Visited vertices set.
const visitedVerticesSet = {};
// Callbacks for DFS traversal.
const dfsCallbacks = {
enterVertex: ({ currentVertex }) => {
// Add current vertex to SCC set of current DFS round.
stronglyConnectedComponentsSet.push(currentVertex);
// Add current vertex to visited set.
visitedVerticesSet[currentVertex.getKey()] = currentVertex;
},
leaveVertex: ({ previousVertex }) => {
// Once DFS traversal is finished push the set of found strongly connected
// components during current DFS round to overall strongly connected components set.
// The sign that traversal is about to be finished is that we came back to start vertex
// which doesn't have parent.
if (previousVertex === null) {
stronglyConnectedComponentsSets.push([...stronglyConnectedComponentsSet]);
}
},
allowTraversal: ({ nextVertex }) => {
// Don't allow traversal of already visited vertices.
return !visitedVerticesSet[nextVertex.getKey()];
},
};
while (!verticesByFinishTime.isEmpty()) {
/** @var {GraphVertex} startVertex */
const startVertex = verticesByFinishTime.pop();
// Reset the set of strongly connected vertices.
stronglyConnectedComponentsSet = [];
// Don't do DFS on already visited vertices.
if (!visitedVerticesSet[startVertex.getKey()]) {
// Do DFS traversal.
depthFirstSearch(graph, startVertex, dfsCallbacks);
}
}
return stronglyConnectedComponentsSets;
}
/**
* Kosaraju's algorithm.
*
* @param {Graph} graph
* @return {*[]}
*/
export default function stronglyConnectedComponents(graph) {
// In this algorithm we will need to do TWO DFS PASSES overt the graph.
// Get stack of vertices ordered by DFS finish time.
// All vertices in this stack are ordered by finished time in decreasing order:
// Vertex that has been finished first will be at the bottom of the stack and
// vertex that has been finished last will be at the top of the stack.
const verticesByFinishTime = getVerticesSortedByDfsFinishTime(graph);
// Reverse the graph.
graph.reverse();
// Do DFS once again on reversed graph.
return getSCCSets(graph, verticesByFinishTime);
}