@dxzmpk/js-algorithms-data-structures
Version:
Algorithms and data-structures implemented on JavaScript
134 lines (112 loc) • 4.67 kB
JavaScript
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);
}