UNPKG

@amarillion/helixgraph

Version:

A collection of graph algorithms for game development

67 lines (66 loc) 2.81 kB
import { toSet } from "./pathFinding.js"; function spliceLowest(queue, comparator) { let minElt = null; for (const elt of queue) { if (minElt === null || comparator(elt, minElt) < 0) { minElt = elt; } } if (minElt) queue.delete(minElt); return minElt; } /** * Given a weighted graph, find all paths from one source to one or more destinations * @param {*} source * @param {*} dest - the search destination node, or an array of destinations that must all be found * @param {*} getAdjacent * @param {*} * * @returns Map(to, { edge, from, to, cost }) */ export function dijkstra(source, dest, getAdjacent, { maxIterations = 0, getWeight = () => 1, } = {}) { // Mark all nodes unvisited. Create a set of all the unvisited nodes called the unvisited set. // Assign to every node a tentative distance value: set it to zero for our initial node and to infinity for all other nodes. Set the initial node as current.[13] const dist = new Map(); const visited = new Set(); const prev = new Map(); const remain = toSet(dest); // TODO: more efficient to use a priority queue here const open = new Set(); open.add(source); dist.set(source, 0); let i = maxIterations; while (open.size) { i--; // 0 -> -1 means Infinite. if (i === 0) break; // extract the element from Q with the lowest dist. Open is modified in-place. // TODO: optionally use PriorityQueue // O(N^2) like this, O(log N) with priority queue. But in my tests, priority queues only start pulling ahead in large graphs const current = spliceLowest(open, (a, b) => dist.get(a) - dist.get(b)); // check adjacents, calculate distance, or - if it already had one - check if new path is shorter for (const [edge, sibling] of getAdjacent(current)) { if (!(visited.has(sibling))) { const alt = dist.get(current) + getWeight(edge, current); // any node that is !visited and has a distance assigned should be in open set. open.add(sibling); // may be already in there, that is OK. const oldDist = dist.has(sibling) ? dist.get(sibling) : Infinity; if (alt < oldDist) { // set or update distance dist.set(sibling, alt); // build back-tracking map prev.set(sibling, { edge, from: current, to: sibling, cost: alt }); } } } // A visited node will never be checked again. visited.add(current); if (remain.has(current)) { remain.delete(current); if (remain.size === 0) break; } } return prev; }