The A* Algorithm (OCR A-Level Computer Science): Revision Notes
The A* Algorithm
Overview
A* is a graph traversal and pathfinding algorithm widely used in computer science for finding the shortest path from a source node to a target node. It is an enhancement of Dijkstra's algorithm that incorporates heuristics to optimise the search process, making it more efficient in certain scenarios.
How A Algorithm Works*
-
Inputs:
- A weighted graph or grid.
- A source node (start) and a target node (goal).
-
Outputs:
- The shortest path from the source to the target, if it exists. A* uses a combination of:
-
g(n): The cost from the start node to the current node.
-
h(n): A heuristic estimate of the cost from the current node to the target node.
-
f(n) = g(n) + h(n): The total estimated cost of the cheapest path through the current node. Steps:
- Initialise the open set (nodes to be evaluated) with the start node.
- Track the costs and previous nodes for backtracking the path.
- While the open set is not empty:
- Select the node with the lowest f(n) value.
- If this node is the target, reconstruct and return the path.
- Otherwise, evaluate its neighbours:
- Update g(n) for each neighbour.
- Recalculate f(n).
- If a shorter path to a neighbour is found, update it.
- Move the current node to the closed set (evaluated nodes).
- Repeat until the target is reached or all nodes are evaluated.
Heuristic Function
The heuristic h(n) plays a critical role in guiding the algorithm:
Manhattan Distance: Used in grids where movement is restricted to horizontal and vertical.
Euclidean Distance: Used when diagonal movement is allowed.
The heuristic must be admissible (never overestimates the actual cost) and ideally consistent for optimal performance.
Example: Finding the Shortest Path
Given a graph:
Nodes: A, B, C, D, E
Edges with weights:
-
A → B: 1
-
A → C: 4
-
B → C: 2
-
B → D: 6
-
C → D: 3
-
C → E: 5
-
D → E: 1 Heuristic (h) values (to target E):
-
A: 7, B: 6, C: 2, D: 1, E: 0 Step-by-Step Execution:
- Start at A:
- g(A) = 0, h(A) = 7, f(A) = 7
- Evaluate neighbours B and C:
- g(B) = 1, h(B) = 6, f(B) = 7
- g(C) = 4, h(C) = 2, f(C) = 6
- Move to C (lowest f):
- Evaluate neighbours D and E.
- g(D) = 7, h(D) = 1, f(D) = 8
- g(E) = 9, h(E) = 0, f(E) = 9
- Move to B (next lowest f):
- Evaluate neighbour D.
- g(D) = 7 (already calculated; no shorter path).
- Move to D:
- Evaluate neighbour E.
- g(E) = 8, h(E) = 0, f(E) = 8
- Move to E (target reached). Shortest path: A → B → D → E, Total cost = 8.
Pseudocode for A* Algorithm
METHOD AStar(graph, start, goal)
openSet ← PRIORITY_QUEUE containing start
cameFrom ← EMPTY_MAP
gScore ← MAP with default value ∞
gScore[start] ← 0
fScore ← MAP with default value ∞
fScore[start] ← heuristic(start, goal)
WHILE openSet IS NOT EMPTY
current ← node in openSet with lowest fScore
IF current = goal
RETURN ReconstructPath(cameFrom, current)
REMOVE current FROM openSet
FOR each neighbour of current
tentative_gScore ← gScore[current] + distance(current, neighbour)
IF tentative_gScore < gScore[neighbour]
cameFrom[neighbour] ← current
gScore[neighbour] ← tentative_gScore
fScore[neighbour] ← gScore[neighbour] + heuristic(neighbour, goal)
IF neighbour NOT IN openSet
ADD neighbour TO openSet
RETURN "No Path Found"
Python Implementation
import heapq
def a_star(graph, start, goal, heuristic):
open_set = []
heapq.heappush(open_set, (0, start))
came_from = {}
g_score = {node: float('inf') for node in graph}
g_score[start] = 0
f_score = {node: float('inf') for node in graph}
f_score[start] = heuristic[start]
while open_set:
_, current = heapq.heappop(open_set)
if current == goal:
return reconstruct_path(came_from, current)
for neighbour, weight in graph[current]:
tentative_g_score = g_score[current] + weight
if tentative_g_score < g_score[neighbour]:
came_from[neighbour] = current
g_score[neighbour] = tentative_g_score
f_score[neighbour] = g_score[neighbour] + heuristic[neighbour]
heapq.heappush(open_set, (f_score[neighbour], neighbour))
return "No Path Found"
def reconstruct_path(came_from, current):
path = []
while current in came_from:
path.append(current)
current = came_from[current]
path.reverse()
return path
Example: Tracing A Algorithm*
Given a grid:
Start: S, Goal: G
Step 1: Initialise.
Step 2: Expand S, and calculate costs for neighbours.
Step 3: Expand the next lowest f(n)
Repeat until goal G is reached.
Note Summary
Common Mistakes
- Using a Non-Admissible Heuristic: An overestimating heuristic may cause the algorithm to miss the shortest path.
- Failing to Update gScore Properly: Incorrect gScore calculations can lead to incorrect path reconstruction.
- Not Reconstructing the Path Correctly: Ensure the cameFrom map is used to backtrack from the goal to the start.
Key Takeaways
- The A* Algorithm combines the best aspects of Dijkstra's and Greedy Best-First Search.
- It uses both the actual cost ( g(n) ) and a heuristic estimate ( h(n) ).
- Heuristic plays a critical role in guiding the search efficiently.
- Correct implementation of priority queue and cost updates ensures optimal paths.