@graphty/algorithms
Version:
Graph algorithms library for browser environments implemented in TypeScript
180 lines • 6.21 kB
JavaScript
import { MinPriorityQueue } from "../utils/priorityQueue.js";
import { pathfindingUtils } from "./utils.js";
/**
* A* pathfinding algorithm
* Finds the shortest path from start to goal using a heuristic function
*
* @param graph - The graph represented as an adjacency list
* @param start - The starting node
* @param goal - The goal node
* @param heuristic - Heuristic function that estimates distance from node to goal
* @returns Object containing the shortest path and its cost, or null if no path exists
*
* Time Complexity: O(E) with good heuristic, O(b^d) worst case
* Space Complexity: O(V)
*/
export function astar(graph, start, goal, heuristic) {
if (!graph.has(start) || !graph.has(goal)) {
return null;
}
// Priority queue ordered by f(n) = g(n) + h(n)
const openSet = new MinPriorityQueue();
const gScore = new Map(); // Cost from start to node
const fScore = new Map(); // Estimated total cost
const cameFrom = new Map();
const closedSet = new Set();
// Initialize start node
gScore.set(start, 0);
fScore.set(start, heuristic(start, goal));
const startFScore = fScore.get(start);
if (startFScore === undefined) {
return null;
}
openSet.insert({ node: start, distance: startFScore });
while (!openSet.isEmpty()) {
const current = openSet.extractMin();
if (!current) {
break;
}
const currentNode = current.node;
if (currentNode === goal) {
// Reconstruct path
const path = pathfindingUtils.reconstructPath(cameFrom, goal);
const goalCost = gScore.get(goal);
if (goalCost === undefined) {
return null;
}
return { path, cost: goalCost };
}
closedSet.add(currentNode);
const neighbors = graph.get(currentNode);
if (!neighbors) {
continue;
}
for (const [neighbor, weight] of neighbors) {
if (closedSet.has(neighbor)) {
continue;
}
const currentNodeGScore = gScore.get(currentNode);
if (currentNodeGScore === undefined) {
continue;
}
const tentativeGScore = currentNodeGScore + weight;
const currentGScore = gScore.get(neighbor) ?? Infinity;
if (tentativeGScore < currentGScore) {
// This path to neighbor is better
cameFrom.set(neighbor, currentNode);
gScore.set(neighbor, tentativeGScore);
fScore.set(neighbor, tentativeGScore + heuristic(neighbor, goal));
// Add to open set if not already there
const neighborFScore = fScore.get(neighbor);
if (neighborFScore !== undefined) {
openSet.insert({ node: neighbor, distance: neighborFScore });
}
}
}
}
return null; // No path found
}
/**
* A* pathfinding with path reconstruction details
* Returns detailed information about the search process
*/
export function astarWithDetails(graph, start, goal, heuristic) {
const openSet = new MinPriorityQueue();
const gScore = new Map();
const fScore = new Map();
const cameFrom = new Map();
const closedSet = new Set();
gScore.set(start, 0);
fScore.set(start, heuristic(start, goal));
const startFScore = fScore.get(start);
if (startFScore === undefined) {
return {
path: null,
cost: Infinity,
visited: new Set(),
gScores: new Map(),
fScores: new Map(),
};
}
openSet.insert({ node: start, distance: startFScore });
while (!openSet.isEmpty()) {
const current = openSet.extractMin();
if (!current) {
break;
}
const currentNode = current.node;
if (currentNode === goal) {
const path = pathfindingUtils.reconstructPath(cameFrom, goal);
return {
path,
cost: gScore.get(goal) ?? Infinity,
visited: closedSet,
gScores: gScore,
fScores: fScore,
};
}
closedSet.add(currentNode);
const neighbors = graph.get(currentNode);
if (!neighbors) {
continue;
}
for (const [neighbor, weight] of neighbors) {
if (closedSet.has(neighbor)) {
continue;
}
const currentNodeGScore = gScore.get(currentNode);
if (currentNodeGScore === undefined) {
continue;
}
const tentativeGScore = currentNodeGScore + weight;
const currentGScore = gScore.get(neighbor) ?? Infinity;
if (tentativeGScore < currentGScore) {
cameFrom.set(neighbor, currentNode);
gScore.set(neighbor, tentativeGScore);
fScore.set(neighbor, tentativeGScore + heuristic(neighbor, goal));
const neighborFScore = fScore.get(neighbor);
if (neighborFScore !== undefined) {
openSet.insert({ node: neighbor, distance: neighborFScore });
}
}
}
}
return {
path: null,
cost: Infinity,
visited: closedSet,
gScores: gScore,
fScores: fScore,
};
}
/**
* Common heuristic functions for A*
*/
export const heuristics = {
/**
* Manhattan distance heuristic for grid-based graphs
*/
manhattan: (a, b) => {
return Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]);
},
/**
* Euclidean distance heuristic
*/
euclidean: (a, b) => {
return Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2));
},
/**
* Chebyshev distance heuristic (diagonal movement allowed)
*/
chebyshev: (a, b) => {
return Math.max(Math.abs(a[0] - b[0]), Math.abs(a[1] - b[1]));
},
/**
* Zero heuristic (makes A* behave like Dijkstra)
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
zero: (_a, _b) => 0,
};
//# sourceMappingURL=astar.js.map