Bidirectional A*

Bidirectional A* pathfinding algorithm — interactive grid visualization, pseudocode in five languages, and complexity reference.

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

Implementation

function biastarPathfind(grid, start, end, allowDiag, heuristicFn) {
  const h = heuristicFn || HEURISTICS.manhattan;
  const rows = grid.length;
  const cols = grid[0].length;
  const gA = Matrix(rows, cols, Infinity);
  const gB = Matrix(rows, cols, Infinity);
  const prevA = new Map();
  const prevB = new Map();
  const visitedA = new Set();
  const visitedB = new Set();
  const openA = PriorityQueue("f");
  const openB = PriorityQueue("f");
  gA[start[0]][start[1]] = 0;
  gB[end[0]][end[1]] = 0;
  priorityQueuePush(openA, {
    f: h(start[0], start[1], end[0], end[1]),
    row: start[0],
    col: start[1]
  });
  priorityQueuePush(openB, {
    f: h(end[0], end[1], start[0], start[1]),
    row: end[0],
    col: end[1]
  });
  let best = Infinity;
  let meet = null;
  while (priorityQueueSize(openA) || priorityQueueSize(openB)) {
    const pickA = !priorityQueueSize(openB) || priorityQueueSize(openA) && priorityQueuePeekValue(openA, "f") <= priorityQueuePeekValue(openB, "f");
    const heap = pickA ? openA : openB;
    const gTable = pickA ? gA : gB;
    const otherG = pickA ? gB : gA;
    const visited = pickA ? visitedA : visitedB;
    const otherVisited = pickA ? visitedB : visitedA;
    const prev = pickA ? prevA : prevB;
    const dirTag = pickA ? 'visit-a' : 'visit-b';
    const node = heap.pop();
    const currentKey = key(node.row, node.col);
    if (visited.has(currentKey)) continue;
    visited.add(currentKey);
    pickA ? visitForwardNode(node.row, node.col) : visitBackwardNode(node.row, node.col);
    if (otherVisited.has(currentKey)) {
      const total = gTable[node.row][node.col] + otherG[node.row][node.col];
      if (total < best) {
        best = total;
        meet = currentKey;
      }
    }
    if (best < Infinity) {
      const minOpen = Math.min(priorityQueuePeekValue(openA, "f", Infinity), priorityQueuePeekValue(openB, "f", Infinity));
      if (minOpen >= best) break;
    }
    for (const [nr, nc] of getNeighbors(grid, node.row, node.col, allowDiag)) {
      const tentative = gTable[node.row][node.col] + stepCost(grid, node.row, node.col, nr, nc);
      if (tentative < gTable[nr][nc]) {
        gTable[nr][nc] = tentative;
        prev.set(key(nr, nc), [node.row, node.col]);
        const f = tentative + h(nr, nc, pickA ? end[0] : start[0], pickA ? end[1] : start[1]);
        heap.push({
          f,
          row: nr,
          col: nc
        });
        pushFrontier(nr, nc);
        const meetTotal = tentative + otherG[nr][nc];
        if (meetTotal < best) {
          best = meetTotal;
          meet = key(nr, nc);
        }
      }
    }
  }
  if (meet) {
    reconstructBidirectionalPath(prevA, prevB, meet, start, end);
  } else {
    reportNoPath();
  }
}