UNPKG

@graphty/algorithms

Version:

Graph algorithms library for browser environments implemented in TypeScript

259 lines 8.52 kB
import { Graph } from "../../core/graph.js"; /** * Perform depth-first search starting from a given node */ export function depthFirstSearch(graph, startNode, options = {}) { if (!graph.hasNode(startNode)) { throw new Error(`Start node ${String(startNode)} not found in graph`); } const visited = new Set(); const order = []; const tree = new Map(); if (options.recursive) { dfsRecursive(graph, startNode, visited, order, tree, options, 0); } else { dfsIterative(graph, startNode, visited, order, tree, options); } return { visited, order, tree }; } /** * Iterative DFS implementation (safer for browsers) */ function dfsIterative(graph, startNode, visited, order, tree, options) { if (options.preOrder === false) { // For post-order, use a simpler recursive approach to ensure correctness dfsRecursive(graph, startNode, visited, order, tree, options, 0, null); return; } const stack = []; stack.push({ node: startNode, parent: null, depth: 0 }); while (stack.length > 0) { const current = stack.pop(); if (!current) { break; } if (!visited.has(current.node)) { visited.add(current.node); tree.set(current.node, current.parent); // Pre-order processing order.push(current.node); // Call visitor callback if provided if (options.visitCallback) { options.visitCallback(current.node, current.depth); } // Early termination if target found if (options.targetNode && current.node === options.targetNode) { break; } // Add neighbors to stack in reverse order to maintain left-to-right traversal const neighbors = Array.from(graph.neighbors(current.node)); for (let i = neighbors.length - 1; i >= 0; i--) { const neighbor = neighbors[i]; if (neighbor !== undefined && !visited.has(neighbor)) { stack.push({ node: neighbor, parent: current.node, depth: current.depth + 1 }); } } } } } /** * Recursive DFS implementation */ function dfsRecursive(graph, node, visited, order, tree, options, depth, parent = null) { visited.add(node); tree.set(node, parent); // Pre-order processing if (options.preOrder !== false) { order.push(node); if (options.visitCallback) { options.visitCallback(node, depth); } // Early termination if target found if (options.targetNode && node === options.targetNode) { return; } } // Recursively visit neighbors for (const neighbor of Array.from(graph.neighbors(node))) { if (!visited.has(neighbor)) { dfsRecursive(graph, neighbor, visited, order, tree, options, depth + 1, node); } } // Post-order processing if (options.preOrder === false) { order.push(node); if (options.visitCallback) { options.visitCallback(node, depth); } } } /** * Detect cycles in a graph using DFS */ export function hasCycleDFS(graph) { const visited = new Set(); // Check each unvisited node for cycles for (const node of Array.from(graph.nodes())) { if (!visited.has(node.id)) { if (graph.isDirected) { const recursionStack = new Set(); if (hasCycleUtilDirected(graph, node.id, visited, recursionStack)) { return true; } } else { if (hasCycleUtilUndirected(graph, node.id, visited, null)) { return true; } } } } return false; } /** * Utility function for cycle detection in directed graphs */ function hasCycleUtilDirected(graph, node, visited, recursionStack) { visited.add(node); recursionStack.add(node); // Check all neighbors for (const neighbor of Array.from(graph.neighbors(node))) { if (!visited.has(neighbor)) { if (hasCycleUtilDirected(graph, neighbor, visited, recursionStack)) { return true; } } else if (recursionStack.has(neighbor)) { // Back edge found - cycle detected return true; } } recursionStack.delete(node); return false; } /** * Utility function for cycle detection in undirected graphs */ function hasCycleUtilUndirected(graph, node, visited, parent) { visited.add(node); // Check all neighbors for (const neighbor of Array.from(graph.neighbors(node))) { if (!visited.has(neighbor)) { if (hasCycleUtilUndirected(graph, neighbor, visited, node)) { return true; } } else if (neighbor !== parent) { // Found a visited node that's not the parent - cycle detected return true; } } return false; } /** * Topological sorting using DFS (for directed acyclic graphs) */ export function topologicalSort(graph) { if (!graph.isDirected) { throw new Error("Topological sort requires a directed graph"); } // First check if graph has cycles if (hasCycleDFS(graph)) { return null; // Cannot topologically sort a graph with cycles } const visited = new Set(); const stack = []; // Perform DFS from each unvisited node for (const node of Array.from(graph.nodes())) { if (!visited.has(node.id)) { topologicalSortUtil(graph, node.id, visited, stack); } } // Return nodes in reverse order of finishing times return stack.reverse(); } /** * Utility function for topological sorting */ function topologicalSortUtil(graph, node, visited, stack) { visited.add(node); // Visit all neighbors first for (const neighbor of Array.from(graph.neighbors(node))) { if (!visited.has(neighbor)) { topologicalSortUtil(graph, neighbor, visited, stack); } } // Add current node to stack after visiting all neighbors stack.push(node); } /** * Find strongly connected components using DFS (for directed graphs) */ export function findStronglyConnectedComponents(graph) { if (!graph.isDirected) { throw new Error("Strongly connected components require a directed graph"); } const visited = new Set(); const finishOrder = []; // Step 1: Get nodes in order of finishing times for (const node of Array.from(graph.nodes())) { if (!visited.has(node.id)) { dfsFinishOrder(graph, node.id, visited, finishOrder); } } // Step 2: Create transpose graph (reverse all edges) const transposeGraph = createTransposeGraph(graph); // Step 3: DFS on transpose graph in reverse finish order const visited2 = new Set(); const components = []; for (let i = finishOrder.length - 1; i >= 0; i--) { const node = finishOrder[i]; if (node !== undefined && !visited2.has(node)) { const component = []; dfsCollectComponent(transposeGraph, node, visited2, component); components.push(component); } } return components; } /** * DFS to record finish order */ function dfsFinishOrder(graph, node, visited, finishOrder) { visited.add(node); for (const neighbor of Array.from(graph.neighbors(node))) { if (!visited.has(neighbor)) { dfsFinishOrder(graph, neighbor, visited, finishOrder); } } finishOrder.push(node); } /** * DFS to collect nodes in a component */ function dfsCollectComponent(graph, node, visited, component) { visited.add(node); component.push(node); for (const neighbor of Array.from(graph.neighbors(node))) { if (!visited.has(neighbor)) { dfsCollectComponent(graph, neighbor, visited, component); } } } /** * Create transpose (reverse) of a directed graph */ function createTransposeGraph(graph) { const transpose = new Graph({ directed: true }); // Add all nodes for (const node of Array.from(graph.nodes())) { transpose.addNode(node.id, node.data); } // Add reverse edges for (const edge of Array.from(graph.edges())) { transpose.addEdge(edge.target, edge.source, edge.weight, edge.data); } return transpose; } //# sourceMappingURL=dfs.js.map