mirror of
https://github.com/trekhleb/javascript-algorithms.git
synced 2025-12-08 19:06:00 +00:00
94 lines
3.2 KiB
JavaScript
94 lines
3.2 KiB
JavaScript
import depthFirstSearch from '../depth-first-search/depthFirstSearch';
|
|
|
|
/**
|
|
* Detect cycle in directed graph using Depth First Search.
|
|
*
|
|
* @param {Graph} graph
|
|
*/
|
|
export default function detectDirectedCycle(graph) {
|
|
let cycle = null;
|
|
|
|
// Will store parents (previous vertices) for all visited nodes.
|
|
// This will be needed in order to specify what path exactly is a cycle.
|
|
const dfsParentMap = {};
|
|
|
|
// White set (UNVISITED) contains all the vertices that haven't been visited at all.
|
|
const whiteSet = {};
|
|
|
|
// Gray set (VISITING) contains all the vertices that are being visited right now
|
|
// (in current path).
|
|
const graySet = {};
|
|
|
|
// Black set (VISITED) contains all the vertices that has been fully visited.
|
|
// Meaning that all children of the vertex has been visited.
|
|
const blackSet = {};
|
|
|
|
// If we encounter vertex in gray set it means that we've found a cycle.
|
|
// Because when vertex in gray set it means that its neighbors or its neighbors
|
|
// neighbors are still being explored.
|
|
|
|
// Init white set and add all vertices to it.
|
|
/** @param {GraphVertex} vertex */
|
|
graph.getAllVertices().forEach((vertex) => {
|
|
whiteSet[vertex.getKey()] = vertex;
|
|
});
|
|
|
|
// Describe BFS callbacks.
|
|
const callbacks = {
|
|
enterVertex: ({ currentVertex, previousVertex }) => {
|
|
if (graySet[currentVertex.getKey()]) {
|
|
// If current vertex already in grey set it means that cycle is detected.
|
|
// Let's detect cycle path.
|
|
cycle = {};
|
|
|
|
let currentCycleVertex = currentVertex;
|
|
let previousCycleVertex = previousVertex;
|
|
|
|
while (previousCycleVertex.getKey() !== currentVertex.getKey()) {
|
|
cycle[currentCycleVertex.getKey()] = previousCycleVertex;
|
|
currentCycleVertex = previousCycleVertex;
|
|
previousCycleVertex = dfsParentMap[previousCycleVertex.getKey()];
|
|
}
|
|
|
|
cycle[currentCycleVertex.getKey()] = previousCycleVertex;
|
|
} else {
|
|
// Otherwise let's add current vertex to gray set and remove it from white set.
|
|
graySet[currentVertex.getKey()] = currentVertex;
|
|
delete whiteSet[currentVertex.getKey()];
|
|
|
|
// Update DFS parents list.
|
|
dfsParentMap[currentVertex.getKey()] = previousVertex;
|
|
}
|
|
},
|
|
leaveVertex: ({ currentVertex }) => {
|
|
// If all node's children has been visited let's remove it from gray set
|
|
// and move it to the black set meaning that all its neighbors are visited.
|
|
blackSet[currentVertex.getKey()] = currentVertex;
|
|
delete graySet[currentVertex.getKey()];
|
|
},
|
|
allowTraversal: ({ nextVertex }) => {
|
|
// If cycle was detected we must forbid all further traversing since it will
|
|
// cause infinite traversal loop.
|
|
if (cycle) {
|
|
return false;
|
|
}
|
|
|
|
// Allow traversal only for the vertices that are not in black set
|
|
// since all black set vertices have been already visited.
|
|
return !blackSet[nextVertex.getKey()];
|
|
},
|
|
};
|
|
|
|
// Start exploring vertices.
|
|
while (Object.keys(whiteSet).length) {
|
|
// Pick fist vertex to start BFS from.
|
|
const firstWhiteKey = Object.keys(whiteSet)[0];
|
|
const startVertex = whiteSet[firstWhiteKey];
|
|
|
|
// Do Depth First Search.
|
|
depthFirstSearch(graph, startVertex, callbacks);
|
|
}
|
|
|
|
return cycle;
|
|
}
|