UNPKG

@graphty/algorithms

Version:

Graph algorithms library for browser environments implemented in TypeScript

200 lines 7.35 kB
import { PriorityQueue } from "../../data-structures/priority-queue.js"; import { reconstructPath } from "../../utils/graph-utilities.js"; import { BidirectionalDijkstra } from "./bidirectional-dijkstra.js"; /** * Dijkstra's algorithm implementation for single-source shortest paths * * Finds shortest paths from a source node to all other nodes in a weighted graph * with non-negative edge weights using a priority queue for efficiency. */ /** * Find shortest paths from source to all reachable nodes using Dijkstra's algorithm */ export function dijkstra(graph, source, options = {}) { if (!graph.hasNode(source)) { throw new Error(`Source node ${String(source)} not found in graph`); } const distances = new Map(); const previous = new Map(); const visited = new Set(); const pq = new PriorityQueue(); // Initialize distances for (const node of Array.from(graph.nodes())) { const distance = node.id === source ? 0 : Infinity; distances.set(node.id, distance); previous.set(node.id, null); pq.enqueue(node.id, distance); } while (!pq.isEmpty()) { const currentNode = pq.dequeue(); if (currentNode === undefined) { break; } const currentDistance = distances.get(currentNode); if (currentDistance === undefined) { continue; } // Skip if already visited (can happen with priority queue updates) if (visited.has(currentNode)) { continue; } visited.add(currentNode); // Early termination if target reached if (options.target && currentNode === options.target) { break; } // Skip if this node is unreachable if (currentDistance === Infinity) { break; } // Check all neighbors for (const neighbor of Array.from(graph.neighbors(currentNode))) { if (visited.has(neighbor)) { continue; } const edge = graph.getEdge(currentNode, neighbor); if (!edge) { continue; } const edgeWeight = edge.weight ?? 1; if (edgeWeight < 0) { throw new Error("Dijkstra's algorithm does not support negative edge weights"); } const tentativeDistance = currentDistance + edgeWeight; const neighborDistance = distances.get(neighbor); if (neighborDistance === undefined) { continue; } // Found a shorter path if (tentativeDistance < neighborDistance) { distances.set(neighbor, tentativeDistance); previous.set(neighbor, currentNode); // Add to priority queue with new distance pq.enqueue(neighbor, tentativeDistance); } } } // Build results const results = new Map(); for (const [nodeId, distance] of distances) { if (distance < Infinity) { const path = reconstructPath(nodeId, previous); results.set(nodeId, { distance, path, predecessor: new Map(previous), }); } } return results; } /** * Find shortest path between two specific nodes using optimized Dijkstra's algorithm * * Uses bidirectional search by default for improved performance on point-to-point queries. * Automatically falls back to standard Dijkstra for very small graphs or when explicitly disabled. */ export function dijkstraPath(graph, source, target, options = {}) { 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 bidirectional search by default for point-to-point queries // Only disable if explicitly requested or graph is very small (overhead dominates) const useBidirectional = options.bidirectional !== false && graph.nodeCount > 10; if (useBidirectional) { const biDijkstra = new BidirectionalDijkstra(graph); return biDijkstra.findShortestPath(source, target); } // Fallback to standard dijkstra const results = dijkstra(graph, source, { target }); return results.get(target) ?? null; } /** * Single-source shortest paths with early termination optimization */ export function singleSourceShortestPath(graph, source, cutoff) { if (!graph.hasNode(source)) { throw new Error(`Source node ${String(source)} not found in graph`); } const distances = new Map(); const visited = new Set(); const pq = new PriorityQueue(); // Initialize distances for (const node of Array.from(graph.nodes())) { const distance = node.id === source ? 0 : Infinity; distances.set(node.id, distance); pq.enqueue(node.id, distance); } while (!pq.isEmpty()) { const currentNode = pq.dequeue(); if (currentNode === undefined) { break; } const currentDistance = distances.get(currentNode); if (currentDistance === undefined) { continue; } // Skip if already visited if (visited.has(currentNode)) { continue; } visited.add(currentNode); // Skip if unreachable or beyond cutoff if (currentDistance === Infinity || (cutoff !== undefined && currentDistance > cutoff)) { break; } // Check all neighbors for (const neighbor of Array.from(graph.neighbors(currentNode))) { if (visited.has(neighbor)) { continue; } const edge = graph.getEdge(currentNode, neighbor); if (!edge) { continue; } const edgeWeight = edge.weight ?? 1; const tentativeDistance = currentDistance + edgeWeight; const neighborDistance = distances.get(neighbor); if (neighborDistance === undefined) { continue; } // Found a shorter path if (tentativeDistance < neighborDistance) { distances.set(neighbor, tentativeDistance); pq.enqueue(neighbor, tentativeDistance); } } } // Filter out unreachable nodes and apply cutoff const result = new Map(); for (const [nodeId, distance] of distances) { if (distance < Infinity && (cutoff === undefined || distance <= cutoff)) { result.set(nodeId, distance); } } return result; } /** * All-pairs shortest paths using repeated Dijkstra * Note: For dense graphs, consider Floyd-Warshall algorithm instead */ export function allPairsShortestPath(graph) { const results = new Map(); for (const node of Array.from(graph.nodes())) { const distances = singleSourceShortestPath(graph, node.id); results.set(node.id, distances); } return results; } //# sourceMappingURL=dijkstra.js.map