AI Search · Exam notes
Each exam gives a map or graph, a start \(S\), a goal \(G\), a MoveGen order, a heuristic, and then asks: path by Best-First? path by A*? path by B and B? which is shortest? is the heuristic admissible? These notes teach exactly that skill, nothing more.
State space is the full graph of all possible states and moves. It exists implicitly. Search space is the part you actually generate and keep in memory via parent pointers while running the algorithm. The exam graph is the state space; the tree of parent pointers you draw is the search space.
The graph is generated on the fly. You never hold the whole map. You hold two lists:
Solved mini-example. Start \(S\), neighbours \(A,B\). OPEN = \(\{S\}\). Expand \(S\), then CLOSED = \(\{S\}\), OPEN = \(\{A,B\}\). The search space so far is two parent pointers \(A \to S, B \to S\). The rest of the state space does not exist in memory yet.
MoveGen(n) returns the neighbours of \(n\), in this course in alphabetical order. GoalTest(n) returns true iff \(n\) is the goal; it is checked when a node is picked for expansion, not when it is generated.
OPEN = [S]; CLOSED = []
parent = {}
while OPEN not empty:
n = pick(OPEN) # pick rule = the algorithm
if GoalTest(n): return path(n) # follow parent pointers to S
move n to CLOSED
for m in MoveGen(n): # alphabetical
if m not in OPEN+CLOSED or found-cheaper-path(m):
parent[m] = n; add/update m in OPEN
Solved mini-example. MoveGen(S) = [A, B]. GoalTest(G) = true only. If OPEN = [A, B] and the algorithm picks B first, B is expanded before A. That ordering difference is the entire difference between BFS, DFS, Best, A*, and B and B. The loop is identical; only pick() changes.
Let \(g(n)\) = actual edge cost from \(S\) to \(n\) along the best known parent chain. Let \(h(n)\) = heuristic estimate from \(n\) to \(G\). Let \(f(n) = g(n) + h(n)\).
| Algorithm | Picks smallest | OPEN is a | Finishes | Optimal path |
|---|---|---|---|---|
| BFS | fewest edges from S (level) | queue (FIFO) | yes, on finite graphs | only if all edges cost equal |
| DFS | most recently added (deepest) | stack (LIFO) | no in general (loops, infinite depth); yes on finite graphs with visited check | no |
| Best-First | \(h(n)\), looks closest to goal | sorted by \(h\) | no in general (can loop); yes on finite graphs with visited check | no, even if \(h\) is admissible |
| A* | \(f(n) = g(n) + h(n)\) | sorted by \(f\) | yes, on finite graphs | yes, iff \(h\) is admissible (point 8) |
| Branch and Bound | \(g(n)\), cheapest from start | sorted by \(g\) | yes, on finite graphs | yes, always |
Memory hook: Best looks forward (\(h\)), B and B looks backward (\(g\)), A* looks both ways (\(f\)). BFS and DFS do not look at costs at all.
Solved mini-example (finish vs optimal). DFS on a finite graph with a visited check finishes, but the first path it finds to \(G\) is the deepest one, not the cheapest. So "finishes = yes" and "optimal = no" are two separate columns. Students lose marks by mixing them.
Path vs. inspection order. Inspection order is the sequence of nodes picked out of OPEN. The path is only the parent chain from \(G\) back to \(S\), a subsequence of the inspection order. Exam trap: writing the full inspection order when asked for the path. Always reconstruct via parent pointers.
Teaching graph used below. It is designed so the algorithms disagree: Best-First is lured by \(C\) (\(h=2\)) down the expensive \(S \to C \to F \to G\) corridor (cost 13), while A* and B and B find \(S \to A \to D \to G\) (cost 9). BFS and DFS ignore costs entirely. MoveGen is alphabetical, tie-break by label.
Heuristic h(n)
| Node | S | A | B | C | D | E | F | G |
|---|---|---|---|---|---|---|---|---|
| h | 8 | 6 | 6 | 2 | 3 | 3 | 3 | 0 |
Edge costs
| Edge | Cost | Edge | Cost |
|---|---|---|---|
| S-A | 2 | B-F | 5 |
| S-B | 3 | C-F | 2 |
| S-C | 8 | D-G | 4 |
| A-D | 3 | E-G | 3 |
| A-E | 6 | F-G | 3 |
| B-D | 4 |
Trace
OPEN / CLOSED at this step
| Alg | Inspection order | Returned path (cost) | Why |
|---|---|---|---|
| BFS | S, A, B, C, D, E, F, G | S,A,D,G (2+3+4 = 9). Level order; F generated by B before the copy from C; G first reached via D. | Ignores weights; finds the fewest-edge path (3 edges). Optimal here only by luck. |
| DFS | S, A, D, G | S,A,D,G (9). Goes deep down the first alphabetical branch and hits G immediately. | Fast but fragile: on another MoveGen order it dives down S, C, F, G (13) and returns that. |
| Best | S, C, F, G | S,C,F,G (8+2+3 = 13). OPEN after S: C:2, A:6, B:6, so C first; then F(3) beats A and B; then G(0). | Greedy on \(h\). Never adds the 8 already spent, so it walks into the expensive corridor. Not optimal even though the \(h\) below is admissible. |
| A* | S, A, D, B, G (C, F, E never expanded) | S,A,D,G (9). f values: S-A:8, S-B:9, S-C:10, so A first; A-D: f=2+3+3=8 beats B f=9, so D; G via D f=9 ties B f=9, label B first, then G f=9 wins over C (f=10), F (f=11), E (f=12). | \(f=g+h\) corrects the lure of C: C with f=10 sits behind A, D, B, and G. Optimal because \(h\) is admissible. |
| B and B | S, S-A(2), S-B(3), S-A-D(5), S-B-D(7), S-C(8), S-A-E(8), S-B-F(8), S-A-D-G(9) | S,A,D,G (9). Expands cheapest partial path by \(g\); first complete path to G popped is optimal; prunes anything with \(g\) at 9 or above. | Behaves like Dijkstra on the implicit graph. Always optimal, explores in cost circles around S, with no sense of direction. |
5a. Full question
Comprehension: SEARCH. The figure shows a map on a uniform grid where each tile is 1x1 in size. The start node is S and the goal node is G. The MoveGen function returns nodes in alphabetical order. Use Manhattan Distance as the heuristic function. Tie-breaker: if several nodes have the same cost, use node labels to break the tie.
Based on the above data, answer the sub questions.
5b. Answers first
Best: S,D,A,G. A*: S,F,A,G. B and B: S,F,E,C,G. Shortest: Branch and Bound. Heuristic: Inadmissible.
Schematic of the PYQ situation (not to scale): Best dives straight at G through D; A* corrects one step via F; only B and B walks the long way round the river via E and C. Toggle the paths below.
Why this step
Tracker
5c. Step by step solution
Some papers replace the map with three jugs. A state is a digit string ABC = litres in jugs a, b, c. Capacities: a = 5, b = 3, c = 2. Start 320, goal 401. Two sub-questions: shortest path as states, and the move sequence producing it.
| Move | Meaning | Example |
|---|---|---|
| xTy | pour x into y until y is full; x keeps the rest | 410 --aTb--> 230 (b 1 to 3, a 4 to 2) |
| xEy | empty all of x into y; y still has room left | 131 --bEa--> 401 (b 3 to 0, a 1 to 4) |
| xETy | empty all of x into y; y ends exactly full | 230 --bETa--> 500 (b 3 to 0, a 2 to 5) |
Sanity check on MoveGen: MoveGen(212) = {032, 302, 410, 230}. From (2,1,2): aETb empties a into b ending exactly full (032); bEa empties b into a with room to spare (302); cEa empties c into a short of full (410); cETb empties c into b ending exactly full (230). Read each neighbour the same way: which jug emptied, which got topped, what is left.
Answers first: path 320,122,131,401; moves aTc,cTb,bEa. Trace: 320 --aTc--> 122 (c topped 0 to 2, a 3 to 1). 122 --cTb--> 131 (b topped 2 to 3, c 2 to 1). 131 --bEa--> 401 (b emptied 3 to 0 into a with room left, a 1 to 4).
6b. Walkthrough: BFS jug by jug (separate from the graph visualiser above)
Why this step
OPEN queue
Three recurring theory questions sit beside the map. They need no graph, only the rules below. Answers are official keys, verified against the key symbols and re-derived by hand and by script.
| DFID variant | Shortest path guaranteed? |
|---|---|
| inspects only new nodes | No (official key marks it wrong) |
| inspects new as well as open nodes | No (official key marks it wrong) |
| inspects new as well as closed nodes | Yes (official key, both variants) |
Why this step. DFID re-runs depth-first search at each depth limit. If a node already on CLOSED is pruned, a shorter route discovered later is lost. Classic miss: \(D\) gets closed via the long route \(S \to A \to C \to D\), then \(B\) is refused as a second parent of \(D\), so the short route \(S \to B \to D \to G\) is never built and a longer path to \(G\) is returned instead. "Inspects" means generates and considers, not prunes. The lecture fix is exactly the keyed variant: let closed nodes back into OPEN and keep CLOSED only for parent info (or store the full path at each node).
| Heuristic unknown, need optimal | Suitable (official keys) | Out |
|---|---|---|
| Unit edge costs | Breadth-First, Dijkstra, Branch and Bound | DFS, A*, WA*, SMGS |
| Costs general (may not be Euclidean) | Dijkstra, Branch and Bound | DFS, BFS, A*, WA*, SMGS |
Why this step. A* and WA* are optimal only if \(h\) is admissible, which is unknown here, so both are out. SMGS trades optimality for memory, so it is out. DFS is never optimal. BFS is optimal only for unit costs, so it drops out in the general-costs variant. Dijkstra and B and B use only \(g\) and stay optimal regardless of \(h\).
| wA* with \(f(n) = g(n) + w \cdot h(n)\) | Behaves like | Optimal? |
|---|---|---|
| w large (tending to infinity) | Best-First Search | Not guaranteed, even if \(h\) is admissible (both keyed) |
| w = 0 | Dijkstra | Always optimal (both keyed) |
Why this step. As \(w\) grows, \(w \cdot h\) swamps \(g\) and the OPEN order converges to pure \(h\) order, which is Best-First (verified by script: the \(f\)-sorted order equals the \(h\)-sorted order for large \(w\)). At \(w = 0\), \(f = g\), which is exactly Dijkstra (verified: the orders match). Greedy search on \(h\) alone can walk past the optimum, so optimality goes even with an admissible \(h\).
Q5. Under what cases does DFID guarantee to find the shortest path if one exists? Options: it inspects only new nodes / new as well as open nodes / new as well as closed nodes / none of these. (Second variant: which DFID variant is guaranteed to find the shortest path? Same four options.)
Q6. Given a finite state space with unit edge costs (second variant: edge costs that may or may not be Euclidean) and a heuristic whose properties are not known, which algorithms are suitable for finding the optimal path? Options: Depth First Search / Breadth First Search / Dijkstra / Branch and Bound / A* / WA* for some w / Sparse Memory Graph Search.
Q7. If \(w\) is set to a large value (tending to infinity) then wA* will ___ . Options: behave like Best-First / behave like Dijkstra / guarantee an optimal path if \(h\) is admissible / not guarantee an optimal path even if \(h\) is admissible. (Second variant: if \(w\) is set to zero, with options: behave like Best-First / behave like Dijkstra / always find the optimal path / sometimes find a suboptimal path.)
Answers first: Q5 = new as well as closed nodes (both variants). Q6 = BFS, Dijkstra, B and B for unit costs; Dijkstra, B and B for general costs. Q7 = Best-First plus not-optimal for large \(w\); Dijkstra plus always-optimal for \(w = 0\).
\(h\) is admissible iff \(h(n) \le h^*(n)\) for every node \(n\), where \(h^*(n)\) is the true cheapest cost from \(n\) to \(G\). G itself has \(h(G)=0\). Consequences: A* is optimal iff \(h\) is admissible; B and B is optimal regardless; Best is never guaranteed optimal.
Solved mini-example. Node X has \(h(X)=6\). True cheapest X to G is 4 (say X to Y to G costs 1+3). Since 6 is above 4, \(h\) is inadmissible. One counterexample kills admissibility. If the true cost were 7 instead, this node passes, but you must still check all nodes.
Exam shortcut: if A* and B and B return different paths with A* worse, \(h\) is certainly inadmissible, because an admissible \(h\) would have forced A* onto the optimum. If they agree, \(h\) may be admissible (still verify with \(h \le h^*\)). Older papers add sometimes admissible and not enough information distractors: one violating node still kills admissibility outright, and mere agreement of A* with B and B never proves it, so pick a concrete verdict whenever the graph decides one.