Algorithms and complexity · A level · OCR H446 2.3.1 · about 30 min
g, h and f, open and closed lists, admissible heuristics, tracing A*, and A* against Dijkstra on the mat's grid.
[1 mark]In A*, what is f(n)?
[1 mark]What makes a heuristic admissible?
[1 mark]What happens to A* if the heuristic is 0 for every vertex?
[1 mark]On a grid where moves are up, down, left or right, what is the Manhattan distance from square (row 2, column 3) to square (row 7, column 1)?
[1 mark]What is the name for the list of vertices that A* has found but not yet expanded?
[1 mark]A robot needs the shortest distance from its base to every charging point on a map. Which algorithm fits best?
The mat in the starter has rough squares (~) as well as walls (#). Entering a normal square (., S or G) costs 1 and entering a rough square costs 3. Moves are up, down, left and right only.
Write a_star(grid, use_heuristic) that returns a tuple (cost, expanded): the cost of the cheapest route from S to G, and the number of squares expanded. Use the Manhattan distance as the heuristic when use_heuristic is True and 0 when it is False (which makes it Dijkstra's algorithm). To make the count exact:
- always expand the open square with the smallest f; break a tie by the smaller h, and then by the smaller row and then column, which is what a heap of (f, h, row, col) tuples does;
- a square is expanded when it is taken off the open list and closed. Skip (and do not count) a square that is already closed. Stop as soon as G is expanded, and count it.
Print exactly three lines:
- cost: <n>
- A* expanded: <n>
- Dijkstra expanded: <n>
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()
import heapq
grid = [
"............",
"............",
"....~~~~....",
".S..~~~~..G.",
"....~~~~....",
"....#####...",
"............",
"............",
]The hint students can ask for: Keep a heap of (f, h, row, col) entries and a dictionary of the best g for each square. Each time round, pop the smallest, skip it if it is already closed, close and count it, and stop if it is G. For each open neighbour, g goes up by the cost of the square you step onto; push the neighbour only when that g beats the best so far. Run the same function twice, with the heuristic on and off.
from bugbot import *
connect()
import heapq
grid = [
"............",
"............",
"....~~~~....",
".S..~~~~..G.",
"....~~~~....",
"....#####...",
"............",
"............",
]
def a_star(grid, use_heuristic):
for r, row in enumerate(grid):
if "S" in row:
start = (r, row.index("S"))
if "G" in row:
goal = (r, row.index("G"))
def h(r, c):
return abs(r - goal[0]) + abs(c - goal[1]) if use_heuristic else 0
best = {start: 0}
heap = [(h(*start), h(*start), start[0], start[1])]
closed = set()
while heap:
f, hh, r, c = heapq.heappop(heap)
if (r, c) in closed:
continue
closed.add((r, c))
if (r, c) == goal:
return best[goal], len(closed)
for nr, nc in [(r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)]:
if 0 <= nr < len(grid) and 0 <= nc < len(grid[0]) and grid[nr][nc] != "#" and (nr, nc) not in closed:
step = 3 if grid[nr][nc] == "~" else 1
g = best[(r, c)] + step
if g < best.get((nr, nc), float("inf")):
best[(nr, nc)] = g
heapq.heappush(heap, (g + h(nr, nc), h(nr, nc), nr, nc))
return None, len(closed)
cost, expanded = a_star(grid, True)
print("cost:", cost)
print("A* expanded:", expanded)
print("Dijkstra expanded:", a_star(grid, False)[1])
Any program that meets the task's checks is marked correct in the simulator; this is one way, not the only way.