Graph Algorithms8 min read

A* Search

Smarter than Dijkstra β€” use a hint to head in the right direction
time:\(O(E)\) best case, \(O((V+E)\) log V) worst caseoptimality:Guaranteed if heuristic is admissibleheuristic:h(n) ≀ actual cost to goal

Imagine you're navigating with a GPS. Dijkstra's algorithm is like a GPS that has no idea where the destination is β€” it expands outward in all directions equally, like ripples in a pond, until it happens to reach your destination.

A* (A-star) is a GPS that knows roughly which direction the destination is. Instead of expanding in all directions, it biases the search toward the goal using an estimate called a heuristic. The result: it reaches the destination while exploring far fewer nodes.

The Evaluation Function: f(n) = g(n) + h(n)

Every node n gets a priority score:

  • g(n) β€” the actual cost of the best known path from start to n so far
  • h(n) β€” the heuristic estimate of cost from n to the goal
  • f(n) = g(n) + h(n) β€” the estimated total cost of the cheapest path through n

The priority queue is ordered by f(n). Nodes with low estimated total cost are expanded first. This naturally steers the search toward the goal.

When h(n) = 0 for all n, f(n) = g(n), and A* reduces exactly to Dijkstra's algorithm. A* is a strict generalization.

Admissibility β€” The Key to Correctness

A heuristic h is admissible if it never overestimates the true cost: h(n) ≀ actual_cost(n, goal) for all n.

If h overestimates, A* may deprioritize the optimal path (thinking it's more expensive than it is) and settle on a suboptimal one. Admissibility guarantees the first time A* reaches the goal, it's via the optimal path.

Examples of admissible heuristics:

  • Manhattan distance |Ξ”x| + |Ξ”y| β€” for 4-directional grid movement
  • Euclidean distance √(Ξ”\(x^2\) + Ξ”\(y^2\)) β€” for free-direction movement

Consistency (Monotonicity) β€” Efficiency Guarantee

A stronger property: h is consistent if for every edge (n, n') with cost c, we have h(n) ≀ c + h(n'). This is the triangle inequality applied to the heuristic. Consistency implies admissibility, and additionally ensures that each node is processed only once β€” no re-expansions needed, just like Dijkstra.

Practical Impact

On a 1000Γ—1000 grid, Dijkstra might explore 500,000 nodes before finding a path across. A* with Manhattan distance might explore only 5,000 β€” a 100Γ— speedup. The better the heuristic (closer to the true cost without exceeding it), the fewer nodes A* explores.

When to Use A* vs. Dijkstra

Use A* when: you have a single target, and a meaningful heuristic is available. Use Dijkstra when: you need shortest paths to all destinations, or no good heuristic exists (h = 0 degenerates to Dijkstra anyway). A* is the standard algorithm in game pathfinding, GPS navigation, and robotics.

Note: The heuristic is a promise: 'I will never overestimate how far we are from the goal.' Break that promise and A* may return a suboptimal path. Keep it, and A* is both optimal and potentially much faster than Dijkstra.

A* on a Grid (Manhattan Distance Heuristic)

import heapq
def astar(grid, start, goal):
"""
grid: 2D list, 0=free, 1=wall
Returns shortest path length, or -1 if unreachable.
"""
rows, cols = len(grid), len(grid[0])
def h(r, c): # Manhattan distance heuristic
return abs(r - goal[0]) + abs(c - goal[1])
dist = {start: 0}
# heap: (f, g, node)
heap = [(h(*start), 0, start)]
while heap:
f, g, (r, c) = heapq.heappop(heap)
if (r, c) == goal:
return g
if g > dist.get((r, c), float('inf')):
continue # stale entry
for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
nr, nc = r+dr, c+dc
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 0:
ng = g + 1
if ng < dist.get((nr, nc), float('inf')):
dist[(nr, nc)] = ng
heapq.heappush(heap, (ng + h(nr, nc), ng, (nr, nc)))
return -1
grid = [
[0, 0, 0, 1, 0],
[1, 1, 0, 1, 0],
[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0]
]
print(astar(grid, (0,0), (4,4))) # 12
Output
12

Complexity

🎯 Best case (perfect heuristic)
If h = exact remaining cost, A* expands only nodes on the optimal path β€” as good as it gets
Explores only the optimal path \(O(path length)\)
🌊 Worst case (h = 0)
With no heuristic information, A* explores all directions equally like Dijkstra
Degrades to Dijkstra \(O((V+E)\) log V)
πŸ’Ύ Space
Open and closed sets can hold all vertices in the worst case
Proportional to nodes explored \(O(V)\)
βœ… Optimality
Inadmissible heuristics may find suboptimal paths but run faster β€” useful when approximate is OK
Requires admissible heuristic h(n) ≀ true cost

Quick check

What does f(n) = g(n) + h(n) represent?

Continue reading