Skip to content

Commit

Permalink
Add detect cycle.
Browse files Browse the repository at this point in the history
  • Loading branch information
trekhleb committed May 6, 2018
1 parent 84ed7e4 commit eec2df9
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 28 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
* [Breadth-First Search](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/breadth-first-search) (BFS)
* [Dijkstra Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/dijkstra) - finding shortest path to all graph vertices
* [Bellman-Ford Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/bellman-ford) - finding shortest path to all graph vertices
* [Detect Cycle](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/detect-cycle) - for both directed and undirected graphs
* [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)
* Topological Sorting
* Eulerian path, Eulerian circuit
* Strongly Connected Component algorithm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,15 @@ describe('detectUndirectedCycle', () => {
.addEdge(edgeBC)
.addEdge(edgeCD);

expect(detectUndirectedCycle(graph)).toBeFalsy();
expect(detectUndirectedCycle(graph)).toBeNull();

graph.addEdge(edgeDE);

expect(detectUndirectedCycle(graph)).toBeTruthy();
expect(detectUndirectedCycle(graph)).toEqual({
B: vertexC,
C: vertexD,
D: vertexE,
E: vertexB,
});
});
});
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 src/algorithms/graph/detect-cycle/detectUndirectedCycle.js
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;
}
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;
}

0 comments on commit eec2df9

Please sign in to comment.