Add detect cycle.

This commit is contained in:
Oleksii Trekhleb 2018-05-06 17:57:45 +03:00
parent 84ed7e409d
commit eec2df994c
5 changed files with 125 additions and 26 deletions

View File

@ -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

View File

@ -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,
});
});
});

View File

@ -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();
});
});

View File

@ -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));
let cycle = null;
// 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;
// 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 {
disjointSet.union(graphEdge.startVertex, graphEdge.endVertex);
// Add next vertex to visited set.
visitedVertices[currentVertex.getKey()] = currentVertex;
parents[currentVertex.getKey()] = previousVertex;
}
});
},
};
return cycleFound;
// Start DFS traversing.
const startVertex = graph.getAllVertices()[0];
depthFirstSearch(graph, startVertex, callbacks);
return cycle;
}

View File

@ -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;
}