Skip to the lesson
FixTheRobot Explore Robotics / Navigation / M3 Global Planning: Graph Search / M3-T3

Navigation · Theory · M3

M3-T3 — A*: Search with a Heuristic

Dijkstra expands in every direction. A* adds one number, an estimate of what is left, and the search starts to face the goal.

  • Lesson 3 of 7
  • Module 3: Global Planning: Graph Search
  • Theory
  • About 9 minutes to read

Dijkstra does not know where the goal is: it floods outwards, and on a long hallway half of what it explores lies behind the start. A* keeps Dijkstra's bookkeeping and adds a single piece of knowledge, an optimistic estimate of the cost still to go. The estimate bends the search towards the goal, and as long as it never promises more than is possible the answer stays the cheapest route.

Learning objectives

After this lesson, you should be able to:

  • Write the A* priority f = g + h and trace the algorithm on a small grid.
  • Define an admissible and a consistent heuristic and say what each guarantees.
  • Explain why A* with an admissible heuristic returns an optimal path.
  • Describe which nodes A* expands and compare its work with Dijkstra and greedy search.

The intuition

Imagine walking to the pharmacy with a compass in your pocket. Dijkstra is the walker who explores every corridor to the same radius before committing. Greedy best-first search is the walker who always takes the step that gets closest to the pharmacy by compass, and who walks straight into a dead end because a wall is in the way. A* is the sensible walker: she weighs the cost of the way she has already walked against the compass distance still to go, and so she heads for the goal while still noticing when the way ahead is much more expensive than the way back.

The compass distance is the heuristic. It must be honest in one direction: it may say the goal is closer than it really is, never farther. An optimistic compass costs a little extra searching and never costs the right answer. A pessimistic one makes the search faster and the answer wrong.

Cells expanded by A* on a small gridA grid with five rows and six columns and a wall of three cells in column 3. Start at the left of the middle row, goal at the right. A* expanded 15 cells, shown shaded with their f values 5, 7 or 9; the 12 unshaded free cells were never expanded. Dijkstra expands all 27 free cells. The cheapest route goes over the top of the wall and costs 9.f=9f=9f=9f=9f=7f=7f=7f=9f=5f=5f=5f=9f=7f=7f=7SG A* expands 15 cellsDijkstra expands all 27 f = g + h with h = Manhattanf = 5: on a straight line to the goal f = 7: one detour stepf = 9: the cost of the cheapest route orange line: the cheapest route (cost 9)Unshaded free cells were never touched: A* does not search behind the start.
On a 5 × 6 grid with a wall, A* expands 15 cells. Dijkstra expands all 27. Both return the cost-9 route over the wall.

The mathematics

The A* priority

For every node n, A* tracks g(n), the cost of the cheapest route found so far from the start to n, and uses a heuristic h(n), an estimate of the cheapest cost from n to the goal. It always expands the open node with the smallest f(n) = g(n) + h(n).

f(n) = g(n) + h(n)

A*(start, goal):
  g[start] ← 0;  open ← {(h(start), start)};  closed ← ∅
  while open not empty:
    n ← open node with the smallest f;  if n is in closed: continue
    add n to closed
    if n is the goal: return the route from parent pointers
    for each neighbour m of n with step cost c:
      if g[n] + c < g[m]:  g[m] ← g[n] + c;  parent[m] ← n;  push (g[m] + h(m), m)

With h = 0 for every node this is exactly Dijkstra. With g left out of the priority it is greedy best-first search.

Admissible and consistent

admissible
h(n) ≤ h*(n) for every n, where h*(n) is the true cheapest cost from n to the goal: the heuristic never overestimates.
consistent
h(n) ≤ c(n, m) + h(m) for every edge (n, m) (the triangle inequality), and h(goal) = 0. Every consistent heuristic is admissible.

Admissibility makes A* return an optimal route. Consistency makes it do so efficiently: f never decreases along a path, so a node is expanded once, with its final g, and a closed set is safe.

Why admissible gives optimal

Let C* be the cost of the cheapest route. Suppose A* is about to pop the goal with g(goal) > C*. At that moment some node n on a cheapest route is still open, with its cheapest g, so f(n) = g(n) + h(n) ≤ g(n) + h*(n) = C*. That is smaller than the f of the goal, which is g(goal) > C*, so n would have been popped first. The contradiction shows the goal is only popped with g = C*.

What gets expanded

With a consistent heuristic A* expands every node with f(n) < C*, possibly some with f(n) = C* (which ones depends on how ties are broken), and none with f(n) > C*. A better heuristic (closer to h*, still admissible) raises f of more nodes above C* and shrinks the search. In a precise sense A* is optimally efficient: no algorithm that is guaranteed to return an optimal route with the same heuristic knowledge expands fewer nodes apart from ties (Dechter and Pearl, 1985).

A* as Dijkstra with new costs

Define a reduced cost c′(n, m) = c(n, m) + h(m) − h(n). If h is consistent, c′ is not negative, and A* is Dijkstra run with the costs c′: the heuristic only changes which of the equal-looking routes is cheaper, by making routes towards the goal cheaper and routes away from it dearer.

A worked example

A trace on a small grid. The grid of the figure has five rows and six columns. The cells (1,3), (2,3) and (3,3) are wall. Scout starts at (2,0) and the goal is (2,5). Moves are up, down, left and right with cost 1. The heuristic is the Manhattan distance, which is exact in an open 4-connected grid and therefore admissible.

A* expansions
expanded(cell: g+h=f)\text{expanded}(\text{cell}:\,g+h=f)
(2, 0): 0+5=5(2,\,0):\,0+5=5
(2, 1): 1+4=5(2,\,1):\,1+4=5
(2, 2): 2+3=5(2,\,2):\,2+3=5
(1, 2): 3+4=7(1,\,2):\,3+4=7
(3, 2): 3+4=7(3,\,2):\,3+4=7
(1, 1): 2+5=7(1,\,1):\,2+5=7
(3, 1): 2+5=7(3,\,1):\,2+5=7
(1, 0): 1+6=7(1,\,0):\,1+6=7
(3, 0): 1+6=7(3,\,0):\,1+6=7
(0, 2): 4+5=9(0,\,2):\,4+5=9
(0, 3): 5+4=9(0,\,3):\,5+4=9
(0, 4): 6+3=9(0,\,4):\,6+3=9
(0, 5): 7+2=9(0,\,5):\,7+2=9
(1, 5): 8+1=9(1,\,5):\,8+1=9
(2, 5): 9+0=9 goal(2,\,5):\,9+0=9\,\text{goal}

The search goes straight for the goal while f = 5. At (2,2) the wall is in the way and every continuation has f of at least 7, so the other cells with f = 7 are tried: the cells around the start, all of which are cheaper to explore than f = 9. When f reaches 9 the search follows the route over the top of the wall and pops the goal with g = 9, which is the cheapest cost. In total 15 cells are expanded. Dijkstra, with h = 0, expands every cell whose g is at most 9, which is all 27 free cells: 12 more than A*, 80% more work.

The 12 cells that were never expanded include the entire bottom row and cells such as (0,0) and (0,1), which have f = 9 as well. Whether tied cells are expanded before the goal depends on how ties are broken; here cells with a larger g were preferred (lesson M3-T4).

Greedy search on the same grid. Ordered by h alone, the search would run to (2,2), try the cells next to it in order of their distance to the goal, and go round the wall by whichever side it reaches first. On this small grid it still finds the cost-9 route. With a longer wall or a pocket it can return a route much longer than the cheapest, which is the failure shown by the starter code in lab M3-L2.

Engineering implications and limitations

The open list is a heap, the closed list is a set

Store open nodes in a binary heap keyed on f, and keep a set of expanded nodes (or a per-cell flag). Skip popped entries that are already closed, as in Dijkstra's lazy deletion. Memory, not time, is usually the limit: A* stores every node it generates.

When the heuristic is only admissible

If h is admissible but not consistent, a node can be reached again with a smaller g after it has been expanded. A correct A* must then reopen it. Grid distance heuristics (Manhattan for 4-connected grids, octile for 8-connected) are consistent, so a simple closed set is fine; heuristics that come from learned models or from a coarser map need checking.

The cost function and the heuristic must match

If the step cost is scaled, as when costmap values are added to the distance (lesson M3-T7), the heuristic must be at most the cheapest possible cost per unit of distance. A distance heuristic in metres is admissible against a cost that is at least one per metre; it is not against a cost that can be lower.

Where it is used

Grid planners in navigation stacks are mostly A* with a distance heuristic or Dijkstra. The Nav2 NavFn planner offers both and has a parameter that selects A* instead of Dijkstra; the Smac planners are A* variants on grids and on lattices of motion primitives (module M4). Check the planner's documentation for the exact heuristic it uses.

Debug by replay

The easiest way to see a heuristic bug is to draw which cells the search expanded, in order. A thin line towards the goal that then floods a pocket is greedy behaviour; a round blob around the start is Dijkstra behaviour. Lab M3-L2 gives you a slider to scrub through the expansions.

Common misconceptions

“A* is just faster Dijkstra.”

It is Dijkstra with extra knowledge, and the knowledge has to be right: an inadmissible heuristic makes it fast and wrong. With a perfectly informative heuristic it expands only the cells of the route.

“A better heuristic is any larger one.”

Larger is better only up to the true cost. Above it, the heuristic is inadmissible and the route may not be the cheapest.

“A* always expands fewer nodes than Dijkstra.”

Never more, if the heuristic is consistent and ties are broken the same way, but in a maze that forces the route away from the goal the saving can be close to zero.

“The closed set is optional.”

Without it, a node can be expanded many times by different routes. With a consistent heuristic the closed set is what keeps the work proportional to the number of nodes.

Engineering takeaways

  1. A* expands the open node with the smallest f = g + h, where g is the cost so far and h an estimate of the cost to go.
  2. An admissible heuristic (never above the true cost) guarantees an optimal route; a consistent one also makes every node expand once.
  3. A* expands all nodes with f below the optimal cost and none above it; a better heuristic shrinks the search.
  4. With h = 0 A* is Dijkstra, and with g left out it is greedy best-first search, which is fast and not optimal.
  5. Draw the expansions: the shape of the searched region tells you which algorithm you really ran.

References and further reading

The paper that introduced A*, and the analyses of its optimality and efficiency.

  • P. E. Hart, N. J. Nilsson and B. Raphael, “A Formal Basis for the Heuristic Determination of Minimum Cost Paths”, IEEE Transactions on Systems Science and Cybernetics, 4(2), 100-107, 1968.
  • R. Dechter and J. Pearl, “Generalized Best-First Search Strategies and the Optimality of A*”, Journal of the ACM, 32(3), 505-536, 1985.
  • J. Pearl, Heuristics: Intelligent Search Strategies for Computer Problem Solving, Addison-Wesley, 1984.

Knowledge check

Three conceptual questions. Write an answer, then reveal the explanation. Your answers stay in your browser.

Question 1

A node has g = 12 and h = 7. What is f? Another node has g = 9 and h = 10. Which is expanded first?

Explanation

f = 19 for the first, and 19 for the second (9 + 10). They tie, and the tie-break decides; preferring the node with the larger g (the smaller h) is a common choice.

Question 2

A heuristic returns 20 for a node whose true cost to the goal is 14. What can go wrong?

Explanation

It overestimates, so it is not admissible. A* may pop the goal by a route that is not the cheapest because a node on the cheapest route has an inflated f and waits behind it.

Question 3

In the example, why is (0,5) expanded with f = 9 although it is further from the start than (1,1), which has f = 7?

Explanation

A* orders by f, not by distance from the start. Every node with f = 7 is expanded before any node with f = 9, and (0,5) lies on the cheapest route, so its f stays at 9 from the moment it is generated.

What’s next

Next: how to choose a heuristic for grids, what consistency buys, how ties are broken, and how weighted A* trades optimality for speed.

Practice this lesson

Implement A* with Search Replay

Make the same route faster: implement A*, then scrub through the search to see what it skipped.

Next lesson

M3-T4 — Heuristics: Admissible, Consistent, Informed

Choose Manhattan, octile or Euclidean distance for a grid, break ties well, and trade optimality for speed with weighted A*.

Nothing below is required to finish this lesson.