Search & Problem Solving
A surprising number of classic AI problems reduce to the same shape: you have a starting situation, a set of moves you're allowed to make, and a goal — and solving the problem means searching through the possibilities to find a path from start to goal.
State spaces
Picture a maze. Each position in the maze is a state. From any state, a small number of moves (up, down, left, right) lead to other states. The full set of every state reachable this way is the state space, and solving the maze means finding a sequence of moves — a path through that space — from the start state to the goal state. This same shape describes far more than mazes: a sliding tile puzzle, a route-planning app finding a route between two addresses, and a chess engine considering possible move sequences are all searching a state space, just a much larger and more complex one.
Breadth-first search: finding the shortest path
One straightforward way to search a state space is breadth-first search: explore all states one step away from the start, then all states two steps away, and so on, until the goal turns up. Because it explores in order of distance from the start, the first path it finds to the goal is guaranteed to be a shortest one:
from collections import deque
graph = {
"A": ["B", "C"],
"B": ["A", "D"],
"C": ["A", "D"],
"D": ["B", "C", "E"],
"E": ["D"],
}
def bfs(start, goal):
visited = {start}
queue = deque([[start]])
while queue:
path = queue.popleft()
node = path[-1]
if node == goal:
return path
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(path + [neighbor])
return None
print(bfs("A", "E"))
['A', 'B', 'D', 'E']
The search explores every neighbor of A first (B and C), then every unvisited neighbor of those, and so on outward — which is exactly why the first path it finds to E is guaranteed to be the shortest one, without ever needing to check every possible path.
Why brute-force search doesn't scale
Breadth-first search works well when the state space is small. The problem is that most interesting state spaces are enormous: a Rubik's Cube has over 43 quintillion possible states, and a chess position has, on average, roughly 35 legal moves available, compounding with every additional move considered. Exploring every possibility exhaustively — a combinatorial explosion — becomes computationally impossible almost immediately as the space grows.
Heuristics: searching smarter, not exhaustively
The practical fix is a heuristic: a rule of thumb that estimates how promising a state is, so the search can prioritize exploring the most promising branches first instead of blindly exploring everything. A well-known algorithm called A* (pronounced "A-star") combines this idea with breadth-first search's shortest-path guarantee, and is the basis of most real-world pathfinding, including the route-planning behind mapping apps.