|
1 |
| -import DisjointSet from '../../../data-structures/disjoint-set/DisjointSet'; |
| 1 | +import depthFirstSearch from '../depth-first-search/depthFirstSearch'; |
2 | 2 |
|
3 | 3 | /**
|
4 |
| - * Detect cycle in undirected graph using disjoint sets. |
| 4 | + * Detect cycle in undirected graph using Depth First Search. |
5 | 5 | *
|
6 | 6 | * @param {Graph} graph
|
7 | 7 | */
|
8 |
| - |
9 | 8 | 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; |
32 | 59 | }
|
0 commit comments