forked from trekhleb/javascript-algorithms
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
136 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
src/algorithms/graph/detect-cycle/__test__/detectDirectedCycle.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |