A* expands the node with the lowest estimated total cost:
where is the cost of the path from start to and is the heuristic estimate of cost-to-go from to a goal.
Algorithm
``` function A_star(initial, goal, h): frontier = PriorityQueue([(h(initial), 0, initial, [])]) visited = {} while frontier not empty: (f, g, state, path) = frontier.pop_min() if state is goal: return path if state in visited and visited[state] <= g: continue visited[state] = g for action in actions(state): next = transition(state, action) g_next = g + step_cost(state, action) f_next = g_next + h(next) frontier.push((f_next, g_next, next, path + [action])) return failure ```
Optimality
A* with an admissible heuristic finds the optimal solution. Sketch: at the moment we expand a goal node , every node with has been expanded. Since is admissible, actual cost of the path to . No cheaper path can exist or its -value would be lower and we'd have expanded its goal first.
With a consistent heuristic, A* is also optimally efficient: no algorithm using the same heuristic can guarantee finding the optimal solution by expanding fewer nodes (modulo ties).
Heuristic quality matters
- : A* degenerates to uniform-cost search.
- : A* expands only nodes on the optimal path. Perfect oracle.
- In between: A* expands nodes where depends on how informative is. Small improvements in heuristic quality compound exponentially.
Memory
A*'s biggest weakness: it stores the entire frontier in memory. For really large state spaces this exhausts RAM long before time runs out. Solutions:
- IDA*: iterative deepening with cost cutoffs. memory.
- SMA*: keeps a memory budget; drops worst nodes when full.
- Beam search: keep only the top- frontier nodes. Loses optimality but stays bounded.
When A* is the right answer
Any time you have:
- A discrete state space
- Known transitions and costs
- An admissible heuristic
It's the default; deviate only when memory or specific structure makes something else better.