Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit eec2df9

Browse files
committedMay 6, 2018
Add detect cycle.
1 parent 84ed7e4 commit eec2df9

5 files changed

+127
-28
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) - for both directed and undirected graphs
71+
* [Detect Cycle](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/detect-cycle) - for both directed and undirected graphs (DFS and Disjoint Set based versions)
7272
* Topological Sorting
7373
* Eulerian path, Eulerian circuit
7474
* Strongly Connected Component algorithm

‎src/algorithms/graph/detect-cycle/__test__/detectUndirectedCycle.test.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,15 @@ describe('detectUndirectedCycle', () => {
2727
.addEdge(edgeBC)
2828
.addEdge(edgeCD);
2929

30-
expect(detectUndirectedCycle(graph)).toBeFalsy();
30+
expect(detectUndirectedCycle(graph)).toBeNull();
3131

3232
graph.addEdge(edgeDE);
3333

34-
expect(detectUndirectedCycle(graph)).toBeTruthy();
34+
expect(detectUndirectedCycle(graph)).toEqual({
35+
B: vertexC,
36+
C: vertexD,
37+
D: vertexE,
38+
E: vertexB,
39+
});
3540
});
3641
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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 detectUndirectedCycleUsingDisjointSet from '../detectUndirectedCycleUsingDisjointSet';
5+
6+
describe('detectUndirectedCycleUsingDisjointSet', () => {
7+
it('should detect undirected 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 edgeAF = new GraphEdge(vertexA, vertexF);
16+
const edgeAB = new GraphEdge(vertexA, vertexB);
17+
const edgeBE = new GraphEdge(vertexB, vertexE);
18+
const edgeBC = new GraphEdge(vertexB, vertexC);
19+
const edgeCD = new GraphEdge(vertexC, vertexD);
20+
const edgeDE = new GraphEdge(vertexD, vertexE);
21+
22+
const graph = new Graph();
23+
graph
24+
.addEdge(edgeAF)
25+
.addEdge(edgeAB)
26+
.addEdge(edgeBE)
27+
.addEdge(edgeBC)
28+
.addEdge(edgeCD);
29+
30+
expect(detectUndirectedCycleUsingDisjointSet(graph)).toBeFalsy();
31+
32+
graph.addEdge(edgeDE);
33+
34+
expect(detectUndirectedCycleUsingDisjointSet(graph)).toBeTruthy();
35+
});
36+
});
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,59 @@
1-
import DisjointSet from '../../../data-structures/disjoint-set/DisjointSet';
1+
import depthFirstSearch from '../depth-first-search/depthFirstSearch';
22

33
/**
4-
* Detect cycle in undirected graph using disjoint sets.
4+
* Detect cycle in undirected graph using Depth First Search.
55
*
66
* @param {Graph} graph
77
*/
8-
98
export default function detectUndirectedCycle(graph) {
10-
// Create initial singleton disjoint sets for each graph vertex.
11-
/** @param {GraphVertex} graphVertex */
12-
const keyExtractor = graphVertex => graphVertex.getKey();
13-
const disjointSet = new DisjointSet(keyExtractor);
14-
graph.getAllVertices().forEach(graphVertex => disjointSet.makeSet(graphVertex));
15-
16-
// Go trough all graph edges one by one and check if edge vertices are from the
17-
// different sets. In this case joint those sets together. Do this until you find
18-
// an edge where to edge vertices are already in one set. This means that current
19-
// edge will create a cycle.
20-
let cycleFound = false;
21-
/** @param {GraphEdge} graphEdge */
22-
graph.getAllEdges().forEach((graphEdge) => {
23-
if (disjointSet.inSameSet(graphEdge.startVertex, graphEdge.endVertex)) {
24-
// Cycle found.
25-
cycleFound = true;
26-
} else {
27-
disjointSet.union(graphEdge.startVertex, graphEdge.endVertex);
28-
}
29-
});
30-
31-
return cycleFound;
9+
let cycle = null;
10+
11+
// List of vertices that we have visited.
12+
const visitedVertices = {};
13+
14+
// List of parents vertices for every visited vertex.
15+
const parents = {};
16+
17+
// Callbacks for DFS traversing.
18+
const callbacks = {
19+
allowTraversal: ({ currentVertex, nextVertex }) => {
20+
// Don't allow further traversal in case if cycle has been detected.
21+
if (cycle) {
22+
return false;
23+
}
24+
25+
// Don't allow traversal from child back to its parent.
26+
const currentVertexParent = parents[currentVertex.getKey()];
27+
const currentVertexParentKey = currentVertexParent ? currentVertexParent.getKey() : null;
28+
29+
return currentVertexParentKey !== nextVertex.getKey();
30+
},
31+
enterVertex: ({ currentVertex, previousVertex }) => {
32+
if (visitedVertices[currentVertex.getKey()]) {
33+
// Compile cycle path based on parents of previous vertices.
34+
cycle = {};
35+
36+
let currentCycleVertex = currentVertex;
37+
let previousCycleVertex = previousVertex;
38+
39+
while (previousCycleVertex.getKey() !== currentVertex.getKey()) {
40+
cycle[currentCycleVertex.getKey()] = previousCycleVertex;
41+
currentCycleVertex = previousCycleVertex;
42+
previousCycleVertex = parents[previousCycleVertex.getKey()];
43+
}
44+
45+
cycle[currentCycleVertex.getKey()] = previousCycleVertex;
46+
} else {
47+
// Add next vertex to visited set.
48+
visitedVertices[currentVertex.getKey()] = currentVertex;
49+
parents[currentVertex.getKey()] = previousVertex;
50+
}
51+
},
52+
};
53+
54+
// Start DFS traversing.
55+
const startVertex = graph.getAllVertices()[0];
56+
depthFirstSearch(graph, startVertex, callbacks);
57+
58+
return cycle;
3259
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import DisjointSet from '../../../data-structures/disjoint-set/DisjointSet';
2+
3+
/**
4+
* Detect cycle in undirected graph using disjoint sets.
5+
*
6+
* @param {Graph} graph
7+
*/
8+
export default function detectUndirectedCycleUsingDisjointSet(graph) {
9+
// Create initial singleton disjoint sets for each graph vertex.
10+
/** @param {GraphVertex} graphVertex */
11+
const keyExtractor = graphVertex => graphVertex.getKey();
12+
const disjointSet = new DisjointSet(keyExtractor);
13+
graph.getAllVertices().forEach(graphVertex => disjointSet.makeSet(graphVertex));
14+
15+
// Go trough all graph edges one by one and check if edge vertices are from the
16+
// different sets. In this case joint those sets together. Do this until you find
17+
// an edge where to edge vertices are already in one set. This means that current
18+
// edge will create a cycle.
19+
let cycleFound = false;
20+
/** @param {GraphEdge} graphEdge */
21+
graph.getAllEdges().forEach((graphEdge) => {
22+
if (disjointSet.inSameSet(graphEdge.startVertex, graphEdge.endVertex)) {
23+
// Cycle found.
24+
cycleFound = true;
25+
} else {
26+
disjointSet.union(graphEdge.startVertex, graphEdge.endVertex);
27+
}
28+
});
29+
30+
return cycleFound;
31+
}

0 commit comments

Comments
 (0)
Please sign in to comment.