UNPKG

@graphty/algorithms

Version:

Graph algorithms library for browser environments implemented in TypeScript

333 lines 11.1 kB
import { DirectionOptimizedBFS } from "../../optimized/direction-optimized-bfs.js"; import { toCSRGraph } from "../../optimized/graph-adapter.js"; import { reconstructPath } from "../../utils/graph-utilities.js"; /** * Unified BFS implementation that automatically optimizes for large graphs * * This module provides a single, user-friendly BFS implementation that: * - Automatically uses Direction-Optimized BFS for large graphs (>10k nodes) * - Maintains backward compatibility with existing APIs * - Requires no configuration or understanding of optimization thresholds * - Provides the best performance by default */ // Cache for CSR conversions to avoid repeated conversions const csrCache = new WeakMap(); /** * Get or create CSR representation of a graph */ function getCSRGraph(graph) { let csrGraph = csrCache.get(graph); if (!csrGraph) { csrGraph = toCSRGraph(graph); csrCache.set(graph, csrGraph); } return csrGraph; } /** * Perform breadth-first search starting from a given node * * Automatically uses the most optimized implementation based on graph size. * No configuration needed - just call this function for the best performance. */ export function breadthFirstSearch(graph, startNode, options = {}) { if (!graph.hasNode(startNode)) { throw new Error(`Start node ${String(startNode)} not found in graph`); } // Automatically use optimized implementation for large graphs if (graph.nodeCount > 10000) { return breadthFirstSearchOptimized(graph, startNode, options); } // Standard implementation for smaller graphs return breadthFirstSearchStandard(graph, startNode, options); } /** * Standard BFS implementation for smaller graphs */ function breadthFirstSearchStandard(graph, startNode, options = {}) { const visited = new Set(); const queue = []; const order = []; const tree = new Map(); // Initialize with start node queue.push({ node: startNode, level: 0 }); visited.add(startNode); tree.set(startNode, null); while (queue.length > 0) { const current = queue.shift(); if (!current) { break; } order.push(current.node); // Call visitor callback if provided if (options.visitCallback) { options.visitCallback(current.node, current.level); } // Early termination if target found if (options.targetNode && current.node === options.targetNode) { break; } // Explore neighbors for (const neighbor of graph.neighbors(current.node)) { if (!visited.has(neighbor)) { visited.add(neighbor); tree.set(neighbor, current.node); queue.push({ node: neighbor, level: current.level + 1 }); } } } return { visited, order, tree }; } /** * Optimized BFS implementation using Direction-Optimized BFS */ function breadthFirstSearchOptimized(graph, startNode, options = {}) { const csrGraph = getCSRGraph(graph); const dobfs = new DirectionOptimizedBFS(csrGraph, { alpha: 15.0, beta: 20.0, }); const result = dobfs.search(startNode); // Convert result to TraversalResult format const visited = new Set(); const order = []; const tree = new Map(); // Build traversal order using BFS from distances const nodesByDistance = new Map(); let maxDistance = 0; for (const [nodeId, distance] of result.distances) { visited.add(nodeId); tree.set(nodeId, result.parents.get(nodeId) ?? null); if (!nodesByDistance.has(distance)) { nodesByDistance.set(distance, []); } nodesByDistance.get(distance)?.push(nodeId); maxDistance = Math.max(maxDistance, distance); } // Reconstruct BFS order for (let d = 0; d <= maxDistance; d++) { const nodes = nodesByDistance.get(d); if (nodes) { order.push(...nodes); } } // Handle visit callback if provided if (options.visitCallback) { for (const [nodeId, distance] of result.distances) { options.visitCallback(nodeId, distance); } } return { visited, order, tree }; } /** * Find shortest path between two nodes using BFS * * Automatically optimized for large graphs. Returns null if no path exists. */ export function shortestPathBFS(graph, source, target) { if (!graph.hasNode(source)) { throw new Error(`Source node ${String(source)} not found in graph`); } if (!graph.hasNode(target)) { throw new Error(`Target node ${String(target)} not found in graph`); } // Special case: source equals target if (source === target) { return { distance: 0, path: [source], predecessor: new Map([[source, null]]), }; } // Use optimized implementation for large graphs if (graph.nodeCount > 10000) { return shortestPathBFSOptimized(graph, source, target); } // Standard implementation return shortestPathBFSStandard(graph, source, target); } /** * Standard shortest path BFS */ function shortestPathBFSStandard(graph, source, target) { const visited = new Set(); const queue = []; const predecessor = new Map(); // Initialize BFS queue.push({ node: source, distance: 0 }); visited.add(source); predecessor.set(source, null); while (queue.length > 0) { const current = queue.shift(); if (!current) { break; } // Target found if (current.node === target) { const path = reconstructPath(target, predecessor); return { distance: current.distance, path, predecessor, }; } // Explore neighbors for (const neighbor of graph.neighbors(current.node)) { if (!visited.has(neighbor)) { visited.add(neighbor); predecessor.set(neighbor, current.node); queue.push({ node: neighbor, distance: current.distance + 1 }); } } } // No path found return null; } /** * Optimized shortest path using Direction-Optimized BFS */ function shortestPathBFSOptimized(graph, source, target) { const csrGraph = getCSRGraph(graph); const dobfs = new DirectionOptimizedBFS(csrGraph, { alpha: 15.0, beta: 20.0, }); // Perform BFS const result = dobfs.search(source); // Check if target was reached const distance = result.distances.get(target); if (distance === undefined) { return null; // No path found } // Reconstruct path const path = reconstructPath(target, result.parents); return { distance, path, predecessor: result.parents, }; } /** * Find shortest paths from source to all reachable nodes * * Automatically optimized for large graphs. */ export function singleSourceShortestPathBFS(graph, source) { if (!graph.hasNode(source)) { throw new Error(`Source node ${String(source)} not found in graph`); } // Use optimized implementation for large graphs if (graph.nodeCount > 10000) { return singleSourceShortestPathBFSOptimized(graph, source); } // Standard implementation return singleSourceShortestPathBFSStandard(graph, source); } /** * Standard single-source shortest paths */ function singleSourceShortestPathBFSStandard(graph, source) { const results = new Map(); const visited = new Set(); const queue = []; const predecessor = new Map(); const distances = new Map(); // Initialize BFS queue.push({ node: source, distance: 0 }); visited.add(source); predecessor.set(source, null); distances.set(source, 0); while (queue.length > 0) { const current = queue.shift(); if (!current) { break; } // Explore neighbors for (const neighbor of graph.neighbors(current.node)) { if (!visited.has(neighbor)) { visited.add(neighbor); predecessor.set(neighbor, current.node); distances.set(neighbor, current.distance + 1); queue.push({ node: neighbor, distance: current.distance + 1 }); } } } // Build results after BFS completes // This avoids copying the predecessor map for each node for (const [node, distance] of distances) { const path = reconstructPath(node, predecessor); results.set(node, { distance, path, predecessor, // Share the same predecessor map }); } return results; } /** * Optimized single-source shortest paths */ function singleSourceShortestPathBFSOptimized(graph, source) { const csrGraph = getCSRGraph(graph); const dobfs = new DirectionOptimizedBFS(csrGraph, { alpha: 15.0, beta: 20.0, }); const bfsResult = dobfs.search(source); // Convert to expected format const results = new Map(); for (const [nodeId, distance] of bfsResult.distances) { // Reconstruct path for each node const path = reconstructPath(nodeId, bfsResult.parents); results.set(nodeId, { distance, path, predecessor: bfsResult.parents, // Share the same predecessor map }); } return results; } /** * Check if the graph is bipartite using BFS coloring * * Note: This function does not use Direction-Optimized BFS as the * coloring logic is specific and doesn't benefit from the optimization. */ export function isBipartite(graph) { if (graph.isDirected) { throw new Error("Bipartite test requires an undirected graph"); } const color = new Map(); const visited = new Set(); // Check each connected component for (const node of Array.from(graph.nodes())) { if (!visited.has(node.id)) { const queue = [node.id]; color.set(node.id, 0); visited.add(node.id); while (queue.length > 0) { const current = queue.shift(); if (!current) { break; } const currentColor = color.get(current); if (currentColor === undefined) { continue; } for (const neighbor of Array.from(graph.neighbors(current))) { if (!visited.has(neighbor)) { // Color with opposite color color.set(neighbor, currentColor === 0 ? 1 : 0); visited.add(neighbor); queue.push(neighbor); } else if (color.get(neighbor) === currentColor) { // Same color as current node - not bipartite return false; } } } } } return true; } //# sourceMappingURL=bfs-unified.js.map