Skip to content

Cycle issue fixed #2063

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,31 +1,64 @@
import DisjointSet from '../../../data-structures/disjoint-set/DisjointSet';

/**
* Detect cycle in undirected graph using disjoint sets.
*
* Detect and return the actual cycle path in an undirected graph using disjoint sets.
* @param {Graph} graph
* @returns {Array|null} - Returns an array of vertex keys forming the cycle, or null if no cycle found.
*/
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);
}
const parentMap = new Map();

graph.getAllVertices().forEach((vertex) => {
disjointSet.makeSet(vertex);
parentMap.set(vertex.getKey(), null); // Initialize with no parent
});

return cycleFound;
for (const edge of graph.getAllEdges()) {
const startKey = edge.startVertex.getKey();
const endKey = edge.endVertex.getKey();

if (disjointSet.inSameSet(edge.startVertex, edge.endVertex)) {
// Cycle detected: reconstruct cycle path
return constructCyclePath(startKey, endKey, parentMap);
}

// Save parent info (arbitrarily choosing one as child)
parentMap.set(endKey, startKey);
disjointSet.union(edge.startVertex, edge.endVertex);
}

return null;
}

/**
* Construct an ordered cycle path using parent map.
* @param {string} startKey
* @param {string} endKey
* @param {Map} parentMap
* @returns {string[]} Ordered array of vertex keys forming a cycle
*/
function constructCyclePath(startKey, endKey, parentMap) {
const pathToRoot = (key) => {
const path = [];
while (key !== null) {
path.push(key);
key = parentMap.get(key);
}
return path;
};

const pathStart = pathToRoot(startKey);
const pathEnd = pathToRoot(endKey);

// Find the last common ancestor
const setStart = new Set(pathStart);
const commonAncestor = pathEnd.find((key) => setStart.has(key));

// Slice paths up to the common ancestor
const cycleStart = pathStart.slice(0, pathStart.indexOf(commonAncestor) + 1);
const cycleEnd = pathEnd.slice(0, pathEnd.indexOf(commonAncestor)).reverse();

return [...cycleStart, ...cycleEnd, commonAncestor];
}