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
5 changed files
with
127 additions
and
28 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
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
36 changes: 36 additions & 0 deletions
36
src/algorithms/graph/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)).toBeFalsy(); | ||
|
||
graph.addEdge(edgeDE); | ||
|
||
expect(detectUndirectedCycleUsingDisjointSet(graph)).toBeTruthy(); | ||
}); | ||
}); |
77 changes: 52 additions & 25 deletions
77
src/algorithms/graph/detect-cycle/detectUndirectedCycle.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 |
---|---|---|
@@ -1,32 +1,59 @@ | ||
import DisjointSet from '../../../data-structures/disjoint-set/DisjointSet'; | ||
import depthFirstSearch from '../depth-first-search/depthFirstSearch'; | ||
|
||
/** | ||
* Detect cycle in undirected graph using disjoint sets. | ||
* Detect cycle in undirected graph using Depth First Search. | ||
* | ||
* @param {Graph} graph | ||
*/ | ||
|
||
export default function detectUndirectedCycle(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; | ||
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
src/algorithms/graph/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; | ||
} |