mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-12-25 14:36:10 +08:00
Add detect cycle.
This commit is contained in:
parent
97f8bd3a9f
commit
20bc442dcf
@ -68,7 +68,7 @@
|
||||
* [Breadth-First Search](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/breadth-first-search) (BFS)
|
||||
* [Dijkstra Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/dijkstra) - finding shortest path to all graph vertices
|
||||
* [Bellman-Ford Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/bellman-ford) - finding shortest path to all graph vertices
|
||||
* [Detect Cycle](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/detect-cycle)
|
||||
* [Detect Cycle](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/detect-cycle) - for both: directed and undirected graphs
|
||||
* Topological Sorting
|
||||
* Eulerian path, Eulerian circuit
|
||||
* Strongly Connected Component algorithm
|
||||
|
@ -0,0 +1,42 @@
|
||||
import GraphVertex from '../../../../data-structures/graph/GraphVertex';
|
||||
import GraphEdge from '../../../../data-structures/graph/GraphEdge';
|
||||
import Graph from '../../../../data-structures/graph/Graph';
|
||||
import detectDirectedCycle from '../detectDirectedCycle';
|
||||
|
||||
describe('detectDirectedCycle', () => {
|
||||
it('should detect directed cycle', () => {
|
||||
const vertexA = new GraphVertex('A');
|
||||
const vertexB = new GraphVertex('B');
|
||||
const vertexC = new GraphVertex('C');
|
||||
const vertexD = new GraphVertex('D');
|
||||
const vertexE = new GraphVertex('E');
|
||||
const vertexF = new GraphVertex('F');
|
||||
|
||||
const edgeAB = new GraphEdge(vertexA, vertexB);
|
||||
const edgeBC = new GraphEdge(vertexB, vertexC);
|
||||
const edgeAC = new GraphEdge(vertexA, vertexC);
|
||||
const edgeDA = new GraphEdge(vertexD, vertexA);
|
||||
const edgeDE = new GraphEdge(vertexD, vertexE);
|
||||
const edgeEF = new GraphEdge(vertexE, vertexF);
|
||||
const edgeFD = new GraphEdge(vertexF, vertexD);
|
||||
|
||||
const graph = new Graph(true);
|
||||
graph
|
||||
.addEdge(edgeAB)
|
||||
.addEdge(edgeBC)
|
||||
.addEdge(edgeAC)
|
||||
.addEdge(edgeDA)
|
||||
.addEdge(edgeDE)
|
||||
.addEdge(edgeEF);
|
||||
|
||||
expect(detectDirectedCycle(graph)).toBeNull();
|
||||
|
||||
graph.addEdge(edgeFD);
|
||||
|
||||
expect(detectDirectedCycle(graph)).toEqual({
|
||||
D: vertexF,
|
||||
F: vertexE,
|
||||
E: vertexD,
|
||||
});
|
||||
});
|
||||
});
|
93
src/algorithms/graph/detect-cycle/detectDirectedCycle.js
Normal file
93
src/algorithms/graph/detect-cycle/detectDirectedCycle.js
Normal file
@ -0,0 +1,93 @@
|
||||
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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user