A* Search

A* is Dijkstra's algorithm guided by a heuristic. Each node is prioritized by f = g + h, where g is the known cost from the start and h is an estimate of the remaining cost to the goal. With an admissible heuristic it finds an optimal path while exploring far fewer nodes.

Time O((V + E) log V) Weighted Heuristic Informed

How it works

A* is Dijkstra's algorithm guided by a heuristic. Each node is prioritized by f = g + h, where g is the known cost from the start and h is an estimate of the remaining cost to the goal. With an admissible heuristic it finds an optimal path while exploring far fewer nodes.

On a grid the Manhattan (or octile, for diagonal movement) distance is the usual heuristic.

Implementation

function astarPathfind(grid, start, end, allowDiag, heuristicFn) {
  const rows = grid.length;
  const cols = grid[0].length;
  const h = heuristicFn || HEURISTICS.manhattan;
  const gScore = Matrix(rows, cols, Infinity);
  const visited = new Set();
  const prev = new Map();
  const heap = PriorityQueue("f");
  gScore[start[0]][start[1]] = 0;
  priorityQueuePush(heap, {
    f: h(start[0], start[1], end[0], end[1]),
    g: 0,
    row: start[0],
    col: start[1]
  });
  while (priorityQueueSize(heap)) {
    const __pattern1 = priorityQueuePop(heap);
    const cr = __pattern1.row;
    const cc = __pattern1.col;
    const cg = __pattern1.g;
    const currentKey = key(cr, cc);
    if (visited.has(currentKey)) continue;
    visited.add(currentKey);
    visitNode(cr, cc);
    if (cr === end[0] && cc === end[1]) {
      reconstructPath(prev, start, end);
      return;
    }
    for (const [nr, nc] of getNeighbors(grid, cr, cc, allowDiag)) {
      const neighborKey = key(nr, nc);
      if (visited.has(neighborKey)) continue;
      const ng = cg + stepCost(grid, cr, cc, nr, nc);
      if (ng < gScore[nr][nc]) {
        gScore[nr][nc] = ng;
        prev.set(neighborKey, [cr, cc]);
        priorityQueuePush(heap, {
          f: ng + h(nr, nc, end[0], end[1]),
          g: ng,
          row: nr,
          col: nc
        });
        pushFrontier(nr, nc);
      }
    }
  }
  reportNoPath();
}

Advantages

  • Optimal with an admissible heuristic, and usually much faster than Dijkstra.
  • Tunable: the heuristic lets you trade optimality for speed when needed.

Disadvantages

  • Optimality depends on a well-chosen, admissible heuristic.
  • Memory use grows with the open set on large maps.

When to use it

The default for goal-directed pathfinding on grids and game maps whenever a good heuristic exists.