mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-12-26 23:21:18 +08:00
Add detect cycle.
This commit is contained in:
parent
84ed7e409d
commit
eec2df994c
@ -68,7 +68,7 @@
|
|||||||
* [Breadth-First Search](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/breadth-first-search) (BFS)
|
* [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
|
* [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
|
* [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
|
* Topological Sorting
|
||||||
* Eulerian path, Eulerian circuit
|
* Eulerian path, Eulerian circuit
|
||||||
* Strongly Connected Component algorithm
|
* Strongly Connected Component algorithm
|
||||||
|
@ -27,10 +27,15 @@ describe('detectUndirectedCycle', () => {
|
|||||||
.addEdge(edgeBC)
|
.addEdge(edgeBC)
|
||||||
.addEdge(edgeCD);
|
.addEdge(edgeCD);
|
||||||
|
|
||||||
expect(detectUndirectedCycle(graph)).toBeFalsy();
|
expect(detectUndirectedCycle(graph)).toBeNull();
|
||||||
|
|
||||||
graph.addEdge(edgeDE);
|
graph.addEdge(edgeDE);
|
||||||
|
|
||||||
expect(detectUndirectedCycle(graph)).toBeTruthy();
|
expect(detectUndirectedCycle(graph)).toEqual({
|
||||||
|
B: vertexC,
|
||||||
|
C: vertexD,
|
||||||
|
D: vertexE,
|
||||||
|
E: vertexB,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
@ -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
|
* @param {Graph} graph
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default function detectUndirectedCycle(graph) {
|
export default function detectUndirectedCycle(graph) {
|
||||||
// Create initial singleton disjoint sets for each graph vertex.
|
let cycle = null;
|
||||||
/** @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
|
// List of vertices that we have visited.
|
||||||
// different sets. In this case joint those sets together. Do this until you find
|
const visitedVertices = {};
|
||||||
// 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;
|
// 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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user