Sign in or create a free account to continue.

ROB-350 · Lesson 4 of 12 · 31 min

Graph search II: A* and heuristics

Dijkstra's algorithm spreads out evenly from the start, exploring cells behind the robot just as eagerly as cells toward the goal. When the goal's location is known, which in planning it always is, this wastes effort. The A* algorithm, introduced by Hart, Nilsson and Raphael in 1968 for the Shakey robot at the Stanford Research Institute, fixes this by adding an estimate of the remaining cost to each node's priority. With a good estimate, A* expands far fewer nodes than Dijkstra while still guaranteeing the cheapest path. It is the default search in grid and lattice planners, and the ideas behind it, heuristics, admissibility and consistency, run through the rest of planning.

This lesson explains how A* works and why it remains optimal, how to design heuristics for grids and lattices, and what happens when we deliberately trade optimality for speed with weighted A*. It then covers two refinements widely used on robots: Jump Point Search, which speeds up uniform grids, and incremental algorithms such as D* Lite, which repair a plan when the map changes instead of starting again.

The A* algorithm

A* orders the priority queue by \( f(n) = g(n) + h(n) \), where \( g(n) \) is the cost of the best known path from the start to n, as in Dijkstra, and \( h(n) \) is a heuristic estimate of the cost from n to the goal. Nodes that are cheap to reach and look close to the goal come out of the queue first. Everything else is the same as Dijkstra: pop the node with the smallest f, stop if it is the goal, otherwise relax its edges. With \( h = 0 \), A* is exactly Dijkstra. With a perfect heuristic, equal to the true remaining cost, A* expands only nodes on an optimal path. Real heuristics lie in between, and the better the heuristic, the fewer nodes are expanded.

\[f(n) = g(n) + h(n)\]

Worked problem

Computing f values

On an 8-connected grid with unit cells, the goal is at (10, 4). Node A at (6, 4) has \( g = 7.0 \); node B at (3, 8) has \( g = 4.4 \). Using the octile heuristic \( h = \max(\Delta_x, \Delta_y) + (\sqrt{2} - 1)\min(\Delta_x, \Delta_y) \), which node does A* expand first?

Show solution
  1. A: \( \Delta_x = 4 \), \( \Delta_y = 0 \), so \( h = 4 \) and \( f = 11.0 \).
  2. B: \( \Delta_x = 7 \), \( \Delta_y = 4 \), so \( h = 7 + 0.414 \times 4 = 8.66 \) and \( f = 13.06 \).
  3. A has the smaller f.

Answer: Node A is expanded first, even though B has the smaller g. Dijkstra, using g alone, would have expanded B first, spending effort on a node that is further from the goal.

Admissible and consistent heuristics

A* returns an optimal path if its heuristic is admissible, meaning it never overestimates the true remaining cost: \( h(n) \le h^*(n) \) for every node. The argument is simple. When the goal is popped with cost C, every node still in the queue has \( f \ge C \), and since f underestimates the cost of any path through that node, no unexplored path can be cheaper than C. A stronger property, consistency (or monotonicity), requires that for every edge from n to n′, \( h(n) \le c(n, n') + h(n') \): the estimate cannot drop by more than the cost of a step. Consistent heuristics are admissible, and they guarantee that each node's g is final when it is first popped, so A* never needs to reopen a node. Almost all heuristics used in practice are consistent.

\[h(n) \le c(n, n') + h(n') \quad \text{for every edge } (n, n')\]

The standard heuristics are distances that ignore obstacles. On a 4-connected grid, the Manhattan distance \( |\Delta_x| + |\Delta_y| \) is exact in open space and admissible. On an 8-connected grid, the octile distance in the example above is exact in open space. Euclidean distance is admissible on any grid but underestimates on 4- and 8-connected grids, so A* expands more nodes than it needs to. For a car-like robot that must turn, Euclidean distance is a weak heuristic, and lattice planners use better ones, such as the length of the shortest Dubins or Reeds-Shepp curve ignoring obstacles (lesson 10), or a precomputed 2-D Dijkstra distance that accounts for obstacles but not turning. The maximum of two admissible heuristics is also admissible, which lets planners combine them.

Common heuristics and where they are admissible
HeuristicFormulaAdmissible onNotes
Zero0Any graphA* becomes Dijkstra
Manhattan|Δx| + |Δy|4-connected gridsOverestimates with diagonal moves
Octilemax + (√2 − 1)·min8-connected gridsExact in open space
Euclidean√(Δx² + Δy²)Any metric graphWeaker on grids, so more expansions
Dubins / Reeds-Shepp lengthShortest feasible curve, no obstaclesCar-like latticesAccounts for turning
2-D Dijkstra distancePrecomputed cost-to-go ignoring heading(x, y, θ) latticesAccounts for obstacles

Worked problem

An inadmissible heuristic

A planner on an 8-connected grid mistakenly uses the Manhattan distance. The goal is 5 cells right and 5 cells up of a node in open space. What is the true cost, what does the heuristic say, and what can go wrong?

Show solution
  1. True cost with diagonals: \( 5\sqrt{2} \approx 7.07 \).
  2. Manhattan estimate: 10.
  3. The heuristic overestimates by 41 percent, so it is not admissible.
  4. A* may then pop the goal through a path that is not optimal, because a cheaper route looked too expensive and stayed in the queue.

Answer: The heuristic says 10 where the truth is 7.07. A* runs faster but can return a suboptimal path, typically one that avoids diagonals.

How much A* saves

The saving depends on how well the heuristic reflects the real cost. In open space with an exact heuristic, A* expands roughly the cells on a single straight corridor to the goal, compared with a disc of cells for Dijkstra. Obstacles reduce the saving: when a wall lies between the start and the goal, the heuristic is fooled into exploring the pocket in front of the wall until its f values rise to the true cost of going around. These 'heuristic depressions' are the main weakness of A*, and cost maps with many high-cost regions make them worse, because the heuristic, which assumes the minimum cost per cell, underestimates badly in expensive areas.

Two grids side by side with the same start on the left, goal on the right and an L-shaped wall between them. In the left grid, labeled Dijkstra, a large roughly circular region of explored cells surrounds the start. In the right grid, labeled A star, the explored region is a narrower band stretching from the start toward the goal, swelling where it fills the pocket in front of the wall before flowing around its end. Both grids show the same final path.
A* explores toward the goal and only fills the pocket in front of the wall; Dijkstra explores in every direction
Dijkstra, A* and weighted A* on the same grid: the heuristic cuts the expansions, and weighting cuts them further
import heapq, math
grid = ["....................",
        "....................",
        "........#...........",
        "........#...........",
        "..S.....#........G..",
        "........#...........",
        "........#...........",
        "....................",
        "...................."]
R, C = len(grid), len(grid[0])
S = (4, 2); G = (4, 17)
moves = [(dr, dc, math.hypot(dr, dc)) for dr in (-1, 0, 1) for dc in (-1, 0, 1) if dr or dc]
def octile(n):
    dx, dy = abs(n[1] - G[1]), abs(n[0] - G[0])
    return max(dx, dy) + (math.sqrt(2) - 1) * min(dx, dy)
def search(w):                      # w = 0: Dijkstra; w = 1: A*; w > 1: weighted A*
    g = {S: 0.0}; pq = [(w * octile(S), S)]; done = set()
    while pq:
        f, u = heapq.heappop(pq)
        if u in done: continue
        done.add(u)
        if u == G: return g[u], len(done)
        for dr, dc, c in moves:
            v = (u[0] + dr, u[1] + dc)
            if 0 <= v[0] < R and 0 <= v[1] < C and grid[v[0]][v[1]] != "#" and g[u] + c < g.get(v, 1e9):
                g[v] = g[u] + c; heapq.heappush(pq, (g[v] + w * octile(v), v))
for name, w in (("Dijkstra", 0), ("A*", 1), ("weighted A*, w=2", 2)):
    cost, n = search(w)
    print("%-17s cost %.3f  expanded %3d" % (name, cost, n))
Output
Dijkstra          cost 17.485  expanded 159
A*                cost 17.485  expanded  60
weighted A*, w=2  cost 17.485  expanded  22

Multiplying the heuristic by a weight \( w > 1 \), so that \( f = g + w\,h \), makes A* greedier: it trusts the heuristic more and expands fewer nodes. The price is optimality, but in a controlled way. If h is admissible, the path found by weighted A* costs at most w times the optimal cost. A weight of 1.5 to 3 often cuts expansions by an order of magnitude in large maps while returning paths within a few percent of optimal in practice. Anytime planners exploit this: Anytime Repairing A* (ARA*, Likhachev, Gordon and Thrun, 2003) first plans quickly with a large weight, then lowers the weight and improves the path, reusing previous work, until time runs out. A robot can start moving on the first path and switch to better ones as they arrive.

Worked problem

The weighted A* bound

The optimal path in a warehouse map costs 84 m. A planner uses weighted A* with \( w = 1.8 \) and an admissible heuristic. What is the worst-case cost of the returned path? If ARA* then lowers w to 1.2, what is the new bound?

Show solution
  1. Bound: \( w \times C^* = 1.8 \times 84 = 151.2 \) m.
  2. With \( w = 1.2 \): \( 1.2 \times 84 = 100.8 \) m.
  3. In practice the first path is usually much closer to optimal than the bound.

Answer: At most 151 m at first, then at most 101 m. The bound guarantees the planner never returns anything absurd, while the robot gets a usable path quickly.

On a uniform-cost grid, many equal-cost paths differ only in the order of their horizontal and diagonal moves, and A* wastes time expanding all of them. Jump Point Search (Harabor and Grastien, 2011) prunes these symmetries. From each node it 'jumps' in a straight line, horizontally, vertically or diagonally, skipping over cells until it reaches a jump point: a cell where a forced neighbor appears because an obstacle ends, or the goal itself. Only jump points are placed in the queue. JPS returns optimal paths on uniform 8-connected grids and is often ten times faster than A* there. It does not apply directly to grids with varying costs, which limits its use on cost maps with inflation gradients.

Replanning: D* and D* Lite

Robots discover obstacles as they move: a pallet left in an aisle, a door that is closed. Replanning from scratch each time works but wastes the previous search. Incremental search algorithms repair it instead. Stentz's D* (1994), developed for outdoor vehicles at Carnegie Mellon and later used in Mars rover research, and Koenig and Likhachev's simpler D* Lite (2002) search backward from the goal, so that the robot's changing position does not invalidate the search tree. When edge costs change, only the nodes whose costs are affected are updated and re-expanded. D* Lite keeps two values per node, g and a one-step lookahead rhs, and processes nodes whose values disagree. When a new obstacle appears near the robot, only a small region needs repair; when it appears near the goal, more of the tree changes, and the saving over replanning from scratch is smaller.

Modern mobile robots often simply replan from scratch at a few hertz, because maps are small and A* is fast. Incremental methods remain valuable for large outdoor maps, for lattice planners where each expansion is expensive, and for robots exploring unknown terrain where the map changes continually.

Worked problem

When to replan

A global planner takes 40 ms for a full A* search on a 400 by 400 grid. The cost map updates at 5 Hz, and an incremental repair after a typical local change takes 6 ms. What fraction of one processor core does each approach use if the planner runs after every map update?

Show solution
  1. Full replanning: \( 5 \times 40 = 200 \) ms per second, 20 percent of a core.
  2. Incremental: \( 5 \times 6 = 30 \) ms per second, 3 percent of a core.
  3. Both fit within one core.

Answer: 20 percent versus 3 percent. On this map either approach works, which is why many systems use simple replanning; on a map ten times larger, the full search would take about a whole core and incremental repair would matter.

Common mistakes

  • MistakeUsing the Manhattan heuristic on an 8-connected grid

    InsteadIt overestimates diagonal paths; use the octile distance.

  • MistakeAdding cost-map penalties without adjusting expectations of A*'s speed

    InsteadThe heuristic underestimates in high-cost areas, so expansions grow; consider weighted A* or better heuristics.

  • MistakeAssuming weighted A* paths are optimal

    InsteadThey are within w times optimal; state the bound.

  • MistakeApplying Jump Point Search to a grid with varying cell costs

    InsteadJPS assumes uniform costs; use A* on cost maps.

  • MistakeRunning D* Lite from start to goal

    InsteadIt searches backward from the goal so the robot's motion does not invalidate the tree.

Summary and key terms

  • A* orders nodes by f = g + h, adding a heuristic estimate of remaining cost to Dijkstra's g.
  • With an admissible heuristic, which never overestimates, A* returns an optimal path.
  • Consistent heuristics also make each node final on its first expansion.
  • Manhattan, octile and Euclidean distances suit different grids; car-like lattices need turning-aware heuristics.
  • Weighted A* trades optimality for speed with a guaranteed bound of w times optimal; ARA* improves the path over time.
  • Jump Point Search prunes symmetric paths on uniform grids.
  • D* and D* Lite repair a backward search when edge costs change instead of replanning from scratch.
A*
Best-first search ordered by g + h.
Heuristic
An estimate of the remaining cost from a node to the goal.
Admissible
A heuristic that never overestimates the true remaining cost.
Consistent
A heuristic that drops by no more than the cost of each edge.
Octile distance
The exact open-space distance on an 8-connected grid.
Heuristic depression
A region where the heuristic underestimates badly, trapping the search.
Weighted A*
A* with an inflated heuristic, faster but bounded suboptimal.
ARA*
An anytime planner that lowers the heuristic weight over time.
Jump Point Search
A grid search that skips symmetric paths by jumping to key cells.
D* Lite
An incremental backward search that repairs plans when costs change.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. What is A* with h = 0?

    Show answer

    Dijkstra's algorithm.

  2. Octile distance for Δx = 6, Δy = 2?

    Show answer

    6 + 0.414 × 2 ≈ 6.83.

  3. What does admissibility guarantee?

    Show answer

    That A* returns an optimal path.

  4. Is the Euclidean distance admissible on an 8-connected grid?

    Show answer

    Yes, but it underestimates, so A* expands more nodes.

  5. Optimal cost 50, weight 2: worst-case weighted A* cost?

    Show answer

    100.

  6. Why does a wall between start and goal slow A*?

    Show answer

    The heuristic underestimates and the search fills the pocket in front of the wall.

  7. What kind of grid does Jump Point Search require?

    Show answer

    A uniform-cost grid.

  8. Why does D* Lite search from the goal?

    Show answer

    So the robot's changing position does not invalidate the search.

  9. Name a heuristic for a car-like robot.

    Show answer

    Dubins or Reeds-Shepp curve length.

  10. Is the maximum of two admissible heuristics admissible?

    Show answer

    Yes.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz