@graphty/algorithms
Version:
Graph algorithms library for browser environments implemented in TypeScript
200 lines • 7.35 kB
JavaScript
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