-
Notifications
You must be signed in to change notification settings - Fork 0
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
1 parent
4a22ae0
commit 7ce9f3c
Showing
41 changed files
with
3,796 additions
and
40 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
# Detect Cycle in Graphs | ||
|
||
In graph theory, a **cycle** is a path of edges and vertices | ||
wherein a vertex is reachable from itself. There are several | ||
different types of cycles, principally a **closed walk** and | ||
a **simple cycle**. | ||
|
||
## Definitions | ||
|
||
A **closed walk** consists of a sequence of vertices starting | ||
and ending at the same vertex, with each two consecutive vertices | ||
in the sequence adjacent to each other in the graph. In a directed graph, | ||
each edge must be traversed by the walk consistently with its direction: | ||
the edge must be oriented from the earlier of two consecutive vertices | ||
to the later of the two vertices in the sequence. | ||
The choice of starting vertex is not important: traversing the same cyclic | ||
sequence of edges from different starting vertices produces the same closed walk. | ||
|
||
A **simple cycle may** be defined either as a closed walk with no repetitions of | ||
vertices and edges allowed, other than the repetition of the starting and ending | ||
vertex, or as the set of edges in such a walk. The two definitions are equivalent | ||
in directed graphs, where simple cycles are also called directed cycles: the cyclic | ||
sequence of vertices and edges in a walk is completely determined by the set of | ||
edges that it uses. In undirected graphs the set of edges of a cycle can be | ||
traversed by a walk in either of two directions, giving two possible directed cycles | ||
for every undirected cycle. A circuit can be a closed walk allowing repetitions of | ||
vertices but not edges; however, it can also be a simple cycle, so explicit | ||
definition is recommended when it is used. | ||
|
||
## Example | ||
|
||
![Cycles](https://upload.wikimedia.org/wikipedia/commons/e/e7/Graph_cycle.gif) | ||
|
||
A graph with edges colored to illustrate **path** `H-A-B` (green), closed path or | ||
**walk with a repeated vertex** `B-D-E-F-D-C-B` (blue) and a **cycle with no repeated edge** or | ||
vertex `H-D-G-H` (red) | ||
|
||
### Cycle in undirected graph | ||
|
||
![Undirected Cycle](https://www.geeksforgeeks.org/wp-content/uploads/cycleGraph.png) | ||
|
||
### Cycle in directed graph | ||
|
||
![Directed Cycle](https://cdncontribute.geeksforgeeks.org/wp-content/uploads/cycle.png) | ||
|
||
## References | ||
|
||
General information: | ||
|
||
- [Wikipedia](https://en.wikipedia.org/wiki/Cycle_(graph_theory)) | ||
|
||
Cycles in undirected graphs: | ||
|
||
- [Detect Cycle in Undirected Graph on GeeksForGeeks](https://www.geeksforgeeks.org/detect-cycle-undirected-graph/) | ||
- [Detect Cycle in Undirected Graph Algorithm on YouTube](https://www.youtube.com/watch?v=n_t0a_8H8VY&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) | ||
|
||
Cycles in directed graphs: | ||
|
||
- [Detect Cycle in Directed Graph on GeeksForGeeks](https://www.geeksforgeeks.org/detect-cycle-in-a-graph/) | ||
- [Detect Cycle in Directed Graph Algorithm on YouTube](https://www.youtube.com/watch?v=rKQaZuoUR4M&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) |
42 changes: 42 additions & 0 deletions
42
algorithms/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, | ||
}); | ||
}); | ||
}); |
41 changes: 41 additions & 0 deletions
41
algorithms/detect-cycle/__test__/detectUndirectedCycle.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,41 @@ | ||
import GraphVertex from '../../../../data-structures/graph/GraphVertex'; | ||
import GraphEdge from '../../../../data-structures/graph/GraphEdge'; | ||
import Graph from '../../../../data-structures/graph/Graph'; | ||
import detectUndirectedCycle from '../detectUndirectedCycle'; | ||
|
||
describe('detectUndirectedCycle', () => { | ||
it('should detect undirected 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 edgeAF = new GraphEdge(vertexA, vertexF); | ||
const edgeAB = new GraphEdge(vertexA, vertexB); | ||
const edgeBE = new GraphEdge(vertexB, vertexE); | ||
const edgeBC = new GraphEdge(vertexB, vertexC); | ||
const edgeCD = new GraphEdge(vertexC, vertexD); | ||
const edgeDE = new GraphEdge(vertexD, vertexE); | ||
|
||
const graph = new Graph(); | ||
graph | ||
.addEdge(edgeAF) | ||
.addEdge(edgeAB) | ||
.addEdge(edgeBE) | ||
.addEdge(edgeBC) | ||
.addEdge(edgeCD); | ||
|
||
expect(detectUndirectedCycle(graph)).toBeNull(); | ||
|
||
graph.addEdge(edgeDE); | ||
|
||
expect(detectUndirectedCycle(graph)).toEqual({ | ||
B: vertexC, | ||
C: vertexD, | ||
D: vertexE, | ||
E: vertexB, | ||
}); | ||
}); | ||
}); |
36 changes: 36 additions & 0 deletions
36
algorithms/detect-cycle/__test__/detectUndirectedCycleUsingDisjointSet.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,36 @@ | ||
import GraphVertex from '../../../../data-structures/graph/GraphVertex'; | ||
import GraphEdge from '../../../../data-structures/graph/GraphEdge'; | ||
import Graph from '../../../../data-structures/graph/Graph'; | ||
import detectUndirectedCycleUsingDisjointSet from '../detectUndirectedCycleUsingDisjointSet'; | ||
|
||
describe('detectUndirectedCycleUsingDisjointSet', () => { | ||
it('should detect undirected 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 edgeAF = new GraphEdge(vertexA, vertexF); | ||
const edgeAB = new GraphEdge(vertexA, vertexB); | ||
const edgeBE = new GraphEdge(vertexB, vertexE); | ||
const edgeBC = new GraphEdge(vertexB, vertexC); | ||
const edgeCD = new GraphEdge(vertexC, vertexD); | ||
const edgeDE = new GraphEdge(vertexD, vertexE); | ||
|
||
const graph = new Graph(); | ||
graph | ||
.addEdge(edgeAF) | ||
.addEdge(edgeAB) | ||
.addEdge(edgeBE) | ||
.addEdge(edgeBC) | ||
.addEdge(edgeCD); | ||
|
||
expect(detectUndirectedCycleUsingDisjointSet(graph)).toBe(false); | ||
|
||
graph.addEdge(edgeDE); | ||
|
||
expect(detectUndirectedCycleUsingDisjointSet(graph)).toBe(true); | ||
}); | ||
}); |
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; | ||
} |
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,59 @@ | ||
import depthFirstSearch from '../depth-first-search/depthFirstSearch'; | ||
|
||
/** | ||
* Detect cycle in undirected graph using Depth First Search. | ||
* | ||
* @param {Graph} graph | ||
*/ | ||
export default function detectUndirectedCycle(graph) { | ||
let cycle = null; | ||
|
||
// List of vertices that we have visited. | ||
const visitedVertices = {}; | ||
|
||
// List of parents vertices for every visited vertex. | ||
const parents = {}; | ||
|
||
// Callbacks for DFS traversing. | ||
const callbacks = { | ||
allowTraversal: ({ currentVertex, nextVertex }) => { | ||
// Don't allow further traversal in case if cycle has been detected. | ||
if (cycle) { | ||
return false; | ||
} | ||
|
||
// Don't allow traversal from child back to its parent. | ||
const currentVertexParent = parents[currentVertex.getKey()]; | ||
const currentVertexParentKey = currentVertexParent ? currentVertexParent.getKey() : null; | ||
|
||
return currentVertexParentKey !== nextVertex.getKey(); | ||
}, | ||
enterVertex: ({ currentVertex, previousVertex }) => { | ||
if (visitedVertices[currentVertex.getKey()]) { | ||
// Compile cycle path based on parents of previous vertices. | ||
cycle = {}; | ||
|
||
let currentCycleVertex = currentVertex; | ||
let previousCycleVertex = previousVertex; | ||
|
||
while (previousCycleVertex.getKey() !== currentVertex.getKey()) { | ||
cycle[currentCycleVertex.getKey()] = previousCycleVertex; | ||
currentCycleVertex = previousCycleVertex; | ||
previousCycleVertex = parents[previousCycleVertex.getKey()]; | ||
} | ||
|
||
cycle[currentCycleVertex.getKey()] = previousCycleVertex; | ||
} else { | ||
// Add next vertex to visited set. | ||
visitedVertices[currentVertex.getKey()] = currentVertex; | ||
parents[currentVertex.getKey()] = previousVertex; | ||
} | ||
}, | ||
}; | ||
|
||
// Start DFS traversing. | ||
const startVertex = graph.getAllVertices()[0]; | ||
depthFirstSearch(graph, startVertex, callbacks); | ||
|
||
return cycle; | ||
} |
31 changes: 31 additions & 0 deletions
31
algorithms/detect-cycle/detectUndirectedCycleUsingDisjointSet.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,31 @@ | ||
import DisjointSet from '../../../data-structures/disjoint-set/DisjointSet'; | ||
|
||
/** | ||
* Detect cycle in undirected graph using disjoint sets. | ||
* | ||
* @param {Graph} graph | ||
*/ | ||
export default function detectUndirectedCycleUsingDisjointSet(graph) { | ||
// Create initial singleton disjoint sets for each graph vertex. | ||
/** @param {GraphVertex} graphVertex */ | ||
const keyExtractor = (graphVertex) => graphVertex.getKey(); | ||
const disjointSet = new DisjointSet(keyExtractor); | ||
graph.getAllVertices().forEach((graphVertex) => disjointSet.makeSet(graphVertex)); | ||
|
||
// Go trough all graph edges one by one and check if edge vertices are from the | ||
// different sets. In this case joint those sets together. Do this until you find | ||
// an edge where to edge vertices are already in one set. This means that current | ||
// edge will create a cycle. | ||
let cycleFound = false; | ||
/** @param {GraphEdge} graphEdge */ | ||
graph.getAllEdges().forEach((graphEdge) => { | ||
if (disjointSet.inSameSet(graphEdge.startVertex, graphEdge.endVertex)) { | ||
// Cycle found. | ||
cycleFound = true; | ||
} else { | ||
disjointSet.union(graphEdge.startVertex, graphEdge.endVertex); | ||
} | ||
}); | ||
|
||
return cycleFound; | ||
} |
Oops, something went wrong.