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)
|
||||
* [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
|
||||
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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
|
||||
*/
|
||||
|
||||
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;
|
||||
} else {
|
||||
disjointSet.union(graphEdge.startVertex, graphEdge.endVertex);
|
||||
}
|
||||
});
|
||||
// List of vertices that we have visited.
|
||||
const visitedVertices = {};
|
||||
|
||||
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