Skip to content

Commit 20bc442

Browse files
committedMay 5, 2018
Add detect cycle.
1 parent 97f8bd3 commit 20bc442

File tree

3 files changed

+136
-1
lines changed

3 files changed

+136
-1
lines changed
 

‎README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
* [Breadth-First Search](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/breadth-first-search) (BFS)
6969
* [Dijkstra Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/dijkstra) - finding shortest path to all graph vertices
7070
* [Bellman-Ford Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/bellman-ford) - finding shortest path to all graph vertices
71-
* [Detect Cycle](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/detect-cycle)
71+
* [Detect Cycle](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/detect-cycle) - for both: directed and undirected graphs
7272
* Topological Sorting
7373
* Eulerian path, Eulerian circuit
7474
* Strongly Connected Component algorithm
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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 detectDirectedCycle from '../detectDirectedCycle';
5+
6+
describe('detectDirectedCycle', () => {
7+
it('should detect directed cycle', () => {
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+
const vertexE = new GraphVertex('E');
13+
const vertexF = new GraphVertex('F');
14+
15+
const edgeAB = new GraphEdge(vertexA, vertexB);
16+
const edgeBC = new GraphEdge(vertexB, vertexC);
17+
const edgeAC = new GraphEdge(vertexA, vertexC);
18+
const edgeDA = new GraphEdge(vertexD, vertexA);
19+
const edgeDE = new GraphEdge(vertexD, vertexE);
20+
const edgeEF = new GraphEdge(vertexE, vertexF);
21+
const edgeFD = new GraphEdge(vertexF, vertexD);
22+
23+
const graph = new Graph(true);
24+
graph
25+
.addEdge(edgeAB)
26+
.addEdge(edgeBC)
27+
.addEdge(edgeAC)
28+
.addEdge(edgeDA)
29+
.addEdge(edgeDE)
30+
.addEdge(edgeEF);
31+
32+
expect(detectDirectedCycle(graph)).toBeNull();
33+
34+
graph.addEdge(edgeFD);
35+
36+
expect(detectDirectedCycle(graph)).toEqual({
37+
D: vertexF,
38+
F: vertexE,
39+
E: vertexD,
40+
});
41+
});
42+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import depthFirstSearch from '../depth-first-search/depthFirstSearch';
2+
3+
/**
4+
* Detect cycle in directed graph using Depth First Search.
5+
*
6+
* @param {Graph} graph
7+
*/
8+
export default function detectDirectedCycle(graph) {
9+
let cycle = null;
10+
11+
// Will store parents (previous vertices) for all visited nodes.
12+
// This will be needed in order to specify what path exactly is a cycle.
13+
const dfsParentMap = {};
14+
15+
// White set (UNVISITED) contains all the vertices that haven't been visited at all.
16+
const whiteSet = {};
17+
18+
// Gray set (VISITING) contains all the vertices that are being visited right now
19+
// (in current path).
20+
const graySet = {};
21+
22+
// Black set (VISITED) contains all the vertices that has been fully visited.
23+
// Meaning that all children of the vertex has been visited.
24+
const blackSet = {};
25+
26+
// If we encounter vertex in gray set it means that we've found a cycle.
27+
// Because when vertex in gray set it means that its neighbors or its neighbors
28+
// neighbors are still being explored.
29+
30+
// Init white set and add all vertices to it.
31+
/** @param {GraphVertex} vertex */
32+
graph.getAllVertices().forEach((vertex) => {
33+
whiteSet[vertex.getKey()] = vertex;
34+
});
35+
36+
// Describe BFS callbacks.
37+
const callbacks = {
38+
enterVertex: ({ currentVertex, previousVertex }) => {
39+
if (graySet[currentVertex.getKey()]) {
40+
// If current vertex already in grey set it means that cycle is detected.
41+
// Let's detect cycle path.
42+
cycle = {};
43+
44+
let currentCycleVertex = currentVertex;
45+
let previousCycleVertex = previousVertex;
46+
47+
while (previousCycleVertex.getKey() !== currentVertex.getKey()) {
48+
cycle[currentCycleVertex.getKey()] = previousCycleVertex;
49+
currentCycleVertex = previousCycleVertex;
50+
previousCycleVertex = dfsParentMap[previousCycleVertex.getKey()];
51+
}
52+
53+
cycle[currentCycleVertex.getKey()] = previousCycleVertex;
54+
} else {
55+
// Otherwise let's add current vertex to gray set and remove it from white set.
56+
graySet[currentVertex.getKey()] = currentVertex;
57+
delete whiteSet[currentVertex.getKey()];
58+
59+
// Update DFS parents list.
60+
dfsParentMap[currentVertex.getKey()] = previousVertex;
61+
}
62+
},
63+
leaveVertex: ({ currentVertex }) => {
64+
// If all node's children has been visited let's remove it from gray set
65+
// and move it to the black set meaning that all its neighbors are visited.
66+
blackSet[currentVertex.getKey()] = currentVertex;
67+
delete graySet[currentVertex.getKey()];
68+
},
69+
allowTraversal: ({ nextVertex }) => {
70+
// If cycle was detected we must forbid all further traversing since it will
71+
// cause infinite traversal loop.
72+
if (cycle) {
73+
return false;
74+
}
75+
76+
// Allow traversal only for the vertices that are not in black set
77+
// since all black set vertices have been already visited.
78+
return !blackSet[nextVertex.getKey()];
79+
},
80+
};
81+
82+
// Start exploring vertices.
83+
while (Object.keys(whiteSet).length) {
84+
// Pick fist vertex to start BFS from.
85+
const firstWhiteKey = Object.keys(whiteSet)[0];
86+
const startVertex = whiteSet[firstWhiteKey];
87+
88+
// Do Depth First Search.
89+
depthFirstSearch(graph, startVertex, callbacks);
90+
}
91+
92+
return cycle;
93+
}

0 commit comments

Comments
 (0)
Please sign in to comment.