Part I · Chapter 1

Search problems, BFS, DFS & UCS

Write any planning puzzle down as a formal search problem, then hand-trace breadth-first, depth-first and uniform-cost search on it — including the frontier bookkeeping step that everyone gets wrong.

Essentials

How to write a planning puzzle down as a formal search problem, and how BFS, DFS and UCS differ from each other by a single line.

Everything below is the explanation and the practice. If you are short on time, read this box, then go to the problems.

1. What a search problem actually is

An agent can always answer three local questions: where am I, what moves are legal here, and if I take one, where do I land and what did it cost? A search problem is those three, written down formally, plus a goal test.

Definition — search problem

A search problem consists of:

A solution is a sequence of actions — a plan — which transforms the start state into a goal state. An optimal solution is one of minimum total cost.

One symbol to read carefully: $\text{Result}(s,a)$ is a function — one state and one action in, one state out, deterministic. (A random ghost breaks this and needs expectimax.)

Notice what is not in the definition: no map, no global view. A search algorithm may only call $\text{Actions}$, $\text{Result}$, $c$ and $\text{IsGoal}$; everything it knows, it discovered by poking those four functions.

A solution is a plan, not a state

Asked “what is the solution?”, the answer is an action sequence — [North, North, East, East] — or the path of states it induces, S → A → C → G. Never just the goal state.

Common mistake

“The answer is $G$.” No — $G$ was given to you in the goal test. Search recovers the plan, and we recover it by following parent pointers back from the goal node, not by remembering paths as we go.

2. World states, search states, and how big the space is

This slide looks like arithmetic and is really about modelling. Pacman in a small maze:

Example — the Pacman state space (slide, verbatim)

World state:

TaskState representationState space size
World states$(p, f_1, \dots, f_{30}, g_1, g_2, d)$$120 \times 2^{30} \times 12^2 \times 4$
Eat the top-left dot$(p)$$120$
Eat all dots$(p, f_1, \dots, f_{30})$$120 \times 2^{30}$

$p$ is Pacman's position, one of 120 non-wall squares. $f_i$ is a boolean — is dot $i$ still there? — so 30 dots give $2^{30}$ combinations: the 30 is a count of bits, not a count of states. $g_1, g_2$ are the ghosts' positions, $12 \times 12 = 12^2$; $d$ is the facing, 4 values. Multiply: $120 \cdot 2^{30} \cdot 144 \cdot 4 \approx 7.4 \times 10^{13}$, seventy-four trillion.

Why the representation shrinks when the task shrinks

Key idea — a state records exactly what affects the future

A variable belongs in your state representation if and only if (a) it changes as you act, and (b) the legal actions, the costs or the goal test depend on it. Anything else is decoration and you must drop it, because every bit you keep doubles the size of the space.

For “eat the top-left dot” the goal test reads only Pacman's square. Ghosts do not block movement and dying is not modelled, so they go — factor $144$. Which other dots are eaten changes nothing we can do next, so the food bits go — factor $2^{30}$. Legal moves ignore the facing — factor $4$. State $= (p)$, 120 states.

For “eat all dots” the goal test is “are all 30 $f_i$ false?”, which mentions the food, so the food stays: $120 \cdot 2^{30} \approx 1.3 \times 10^{11}$. Ghosts and facing are still never read.

World state vs. search state

Definition — abstraction

A world state contains every detail of the environment; a search state contains only what the search problem needs. Choosing it is abstraction — the slide's “vertices are (abstracted) world configurations.” An abstraction must be sound: every plan that works in the abstract problem must correspond to one that works in the real one.

Abstraction is the highest-leverage decision in applied search: the algorithms are fixed and ten lines long, so the gap between a millisecond and never finishing is almost always the representation.

Guided problemSizing three Pacman variants

Same maze as the slide: 120 legal squares, 30 dots, 2 ghosts each in one of 12 squares, 4 facings. Give the state representation and the state-space size for each task.

  1. (a) “Reach the bottom-right corner without ever being caught by a ghost”, where ghosts move and catching means sharing a square.
  2. (b) “Eat all 30 dots” in a version where Pacman can only turn left or go straight (no reversing).
  3. (c) “Eat the 4 power pellets (a subset of the 30 dots), in any order.”
Step 1 — Run the relevance test on each variable

For each of $p$, the $f_i$, $g_1,g_2$ and $d$, ask: does this task's goal test, action set, or cost function read that variable? If the variable is read anywhere, or if it changes in a way that later affects one of those, it stays.

Step 2 — (a) the ghost-avoiding walk

The goal test reads only $p$. But the legality of the plan now depends on where the ghosts are — “caught” is defined by $p = g_i$ — so ghost positions are read by the problem. Food is never read; facing is never read.

State $= (p, g_1, g_2)$, size $120 \cdot 12 \cdot 12 = 120 \cdot 144 = \mathbf{17{,}280}$.

Still tiny. This is the classic lesson: adding adversaries costs you a polynomial factor, adding memory of what you've collected costs you an exponential one.

Step 3 — (b) all dots, but no reversing

The goal test reads all the $f_i$, so keep the food bits: $2^{30}$. New wrinkle: $\text{Actions}(s)$ now depends on which way you are facing, because “go straight” and “turn left” are defined relative to $d$. Since the action set reads $d$, $d$ is now part of the state.

State $= (p, f_1,\dots,f_{30}, d)$, size $120 \cdot 2^{30} \cdot 4 \approx \mathbf{5.2 \times 10^{11}}$.

Moral: a rule change that doesn't touch the goal can still force a variable back into the state, through the action set.

Answer & the takeaway

(c) Only 4 of the 30 dots are ever read by the goal test, so you only need 4 booleans, not 30:

State $= (p, f_{i_1}, f_{i_2}, f_{i_3}, f_{i_4})$, size $120 \cdot 2^4 = 120 \cdot 16 = \mathbf{1{,}920}$.

Takeaway: $10^{11}$ became $10^3$ purely by noticing that 26 of the booleans are never read. Before you optimise an algorithm, delete a variable.

In the “eat all dots” representation the size is $120 \times 2^{30}$. Why $2^{30}$ rather than $30$, or $30!$?
Why: The state is a snapshot, not a history. It doesn't record the order you ate dots in (that's the plan, not the state) — only the current configuration, which is one boolean per dot. Order matters for cost, but not for identifying the state, which is exactly what lets two different plans meet at the same state and be compared.

3. State space graphs vs. search trees

Two pictures, constantly confused, drawn from the same problem.

Definition — state space graph

The state space graph is a mathematical representation of a search problem: vertices are the (abstracted) world configurations, edges are transitions resulting from actions, edge weights are step costs, and the goal test picks out a set of goal vertices (maybe only one). In a state space graph, each state occurs exactly once.

We can rarely build this full graph in memory — the Pacman graph has $10^{13}$ vertices — but it is the right mental object.

Definition — search tree

The search tree is what an algorithm actually builds. Its root is the start state. Each node corresponds to a path from the start, and its children are the results of the legal actions at the end of that path. A state appears once in the graph but once per path in the tree.

The search tree is the graph unrolled along every path: if two routes reach $B$, the graph has one $B$ and the tree has two nodes labelled $B$ — same state, different histories, usually different path costs.

Figure 1 · The graph on the left unrolls into the tree on the right
STATE SPACE GRAPH SEARCH TREE (from S) S A B G 4 vertices · each state once S A B B A G A G B ⋮ ⋮ infinitely many nodes · A and B recur forever
What to notice: the graph has one vertex B; the tree already has three nodes labelled B, one per distinct path (S-B, S-A-B, S-B-A-B). Because A and B point at each other, the tree never ends — a finite graph can unroll into an infinite tree. The only thing stopping it is an algorithm that refuses to re-expand a state it has already seen.
Key idea

Tree nodes are paths; graph vertices are states. The tree is infinite whenever the graph has a cycle, even with three vertices. “Graph search” is just tree search plus a memory of which states we already expanded.

A state space graph has 8 vertices and no cycles at all (it's a DAG). How many nodes can the corresponding search tree have?
Why: No cycles ⇒ finitely many paths ⇒ a finite tree. But a DAG can have exponentially many distinct paths: a “diamond chain” of $k$ diamonds has $2k+1$ vertices and $2^k$ paths. You'll see this concretely in §11, where a 12-vertex graph unrolls into a 28-node tree that visits the state q four separate times.

4. The generic search algorithm

Every algorithm on this page — and greedy search and A* on the next page — is the same seven lines with one word changed.

The frontier

Definition — frontier and explored set

The frontier (fringe, open list) holds the nodes that have been generated but not yet expanded — the rim of what we know. The explored set (closed list) holds the states already expanded. To expand a node: take its state, call $\text{Actions}$ and $\text{Result}$, and put the children on the frontier.

Tree search

function TREE-SEARCH(problem) returns a solution, or failure
    initialize the frontier using the initial state of problem
    loop do
        if the frontier is empty then return failure
        choose a leaf node and remove it from the frontier      <-- THE ONLY CHOICE
        if the node contains a goal state then
            return the corresponding solution
        expand the chosen node, adding the resulting nodes to the frontier

The comment marks the entire design space of uninformed search. Choose the shallowest node and you have BFS. Choose the deepest and you have DFS. Choose the cheapest-so-far and you have UCS. Choose by $g+h$ and you have A*. That is the whole story.

Graph search

function GRAPH-SEARCH(problem) returns a solution, or failure
    initialize the frontier using the initial state of problem
    initialize the explored set to be empty
    loop do
        if the frontier is empty then return failure
        choose a leaf node and remove it from the frontier
        if the node contains a goal state then
            return the corresponding solution
        add the node's state to the explored set
        expand the chosen node, adding the resulting nodes to the frontier
            only if not already in the frontier or the explored set

Two lines added, one enormous consequence: each state is expanded at most once, so the search is finite whenever the state space is. The cost is memory — the explored set can grow to the size of the state space.

Common mistake

“Graph search is strictly better, always use it.” Not quite. It needs $O(|S|)$ memory for the explored set, which kills it on huge spaces (that $10^{13}$-vertex Pacman graph). And the “only if not already in the frontier” clause is the subtle one: for UCS and A* you may not simply discard a duplicate — you must compare costs and possibly replace (§10). Getting that wrong is the most common error on this material.

What a node actually stores

From the slide “A Note on Implementation”. A node is a record with four fields:

FieldMeaningValue for a child of parent via action $a$
statethe state this node sits atResult(parent.state, a)
parentthe node we came fromparent
actionthe action taken to get herea
path-costtotal cost from the start to here; written $g(n)$parent.path_cost + c(parent.state, a, state)

The last row is the one to memorise, because it is the definition of $g$:

$$g(\text{child}) \;=\; g(\text{parent}) \;+\; c(\text{parent.state},\, a,\, \text{child.state}).$$

In words: a path costs its prefix plus the one new step. $g(n)$ is the backward cost — what you have already spent — as against the forward estimate $h(n)$ on the next page. We store parent rather than whole paths to save memory.

Key idea — extracting the solution

Start at the goal node. Repeatedly follow parent, collecting the action field at each hop, until you reach the node whose parent is null (the start). Reverse the collected list. That is your plan.

Strategy: expand a shallowest node first. Implementation: the frontier is a FIFO queue.

FIFO = first in, first out: children join the back, we pop from the front, so the queue drains depth 0, then all of depth 1, then all of depth 2 — BFS sweeps the tree in complete tiers. It is a ripple on a pond, except that the ripple travels by number of actions, not by cost, which is exactly why UCS exists.

Properties, with the reasoning

Write $b$ for the branching factor (max children per node), $d$ for the depth of the shallowest goal, $m$ for the maximum depth of the tree (possibly $\infty$).

Example — why $O(b^d)$ space is fatal

$b=10$, one million nodes per second, 100 bytes per node, $d=12$: time $\approx 10^{12}$ nodes $= 13$ days; memory $\approx 10^{14}$ bytes $= 100$ terabytes. BFS runs out of RAM long before it runs out of patience. Remember this when you meet iterative deepening in alpha-beta: memory, not time, is usually what stops you.

Strategy: expand a deepest node first. Implementation: the frontier is a LIFO stack.

Change one word — FIFO becomes LIFO — and the behaviour inverts: children go on top of the stack and one is popped immediately, so the search plunges down a branch until it dead-ends, then backs up one level. Like a maze-walker with chalk, you mark only the current path — DFS's $O(bm)$ memory advantage.

Properties, with the reasoning

Intuition — the trade in one line

BFS buys optimality-in-steps with exponential memory; DFS buys linear memory by giving up optimality and (in cyclic spaces) completeness. Iterative deepening buys back BFS's guarantees at DFS's memory; UCS buys back true cost-optimality.

7. Iterative deepening beyond syllabus

Not in the Lecture 2 slides, but standard pre-reading (R&N §3.4.5), and alpha-beta leans on it for move ordering.

Run a depth-limited DFS with limit $0$; no goal, run it again with limit $1$, then $2$, stopping at the first limit that finds a goal.

Re-searching from scratch every round sounds wasteful. It is not, and the arithmetic is worth seeing. Nodes generated by IDS down to depth $d$:

$$N_{\text{IDS}} = (d+1)b^0 + d\,b^1 + (d-1)b^2 + \dots + 1\cdot b^d,$$

because the root is regenerated $d+1$ times, tier 1 $d$ times, and the bottom tier exactly once. BFS generates $N_{\text{BFS}} = b^0 + b^1 + \dots + b^d$. For $b = 10, d = 5$: $N_{\text{IDS}} = 123{,}456$ versus $N_{\text{BFS}} = 111{,}111$ — about 11% more work, and in general the overhead factor tends to $\dfrac{b}{b-1}$.

The bottom tier dominates, and IDS generates it only once. So IDS is complete, optimal when step costs are uniform, $O(b^d)$ time and only $O(bd)$ memory — the standard choice for large uniform-cost spaces of unknown depth.

BFS optimises the wrong thing: number of actions. Real problems have costs — time, fuel, money, risk. UCS fixes exactly this and nothing else.

Key idea

Strategy: expand a cheapest node first. Implementation: the frontier is a priority queue, with priority = cumulative path cost $g(n)$.

That's it. Same skeleton as BFS, different queue discipline. When all step costs are equal, $g(n)$ is proportional to depth and UCS degenerates into BFS.

Here is the course's pseudocode, from the slides:

function UNIFORM-COST-SEARCH(problem) returns a solution, or failure
    initialize the explored set to be empty
    initialize the frontier as a priority queue using g(n) as the priority
    add initial state of problem to frontier with priority g(S) = 0
    loop do
        if the frontier is empty then
            return failure
        choose a node and remove it from the frontier          <-- the MINIMUM-g node
        if the node contains a goal state then
            return the corresponding solution
        add the node state to the explored set
        for each resulting child from node
            if the child state is not already in the frontier or explored set then
                add child to the frontier
            else if the child is already in the frontier with higher g(n) then
                replace that frontier node with child

Three lines deserve a sentence each.

Properties, with the reasoning

Let $C^*$ be the cost of the optimal solution and $\varepsilon > 0$ a lower bound on any single step cost.

Common mistake

Writing UCS's complexity as $O(b^d)$. The goal's depth is irrelevant to UCS; what matters is how many cheap nodes exist. A goal 2 steps away costing $1000$ is found only after every node reachable for less than $1000$ has been expanded — possibly millions, at depth 500.

During UCS the goal state $G$ is generated and put on the frontier with $g = 11$. The frontier also holds nodes with $g = 6$ and $g = 9$. What does UCS do next?
Why: The goal test is on pop. Expanding the $g=6$ node might generate a cheaper route to $G$ — say $g=7$ — which then replaces the 11 on the frontier. This is exactly what happens in Figure 2: try the UCS mode and watch $G$ enter at 11 and get overwritten with 7. Returning at generation time would have given you the 11-cost answer and destroyed optimality.

9. Watch the frontier move

Press Step and follow three columns: what got popped, what is on the frontier, and what is already explored. Then switch algorithm and run the same graph again.

Figure 2 · Interactive — BFS vs DFS vs UCS on one graph
1 6 2 4 2 2 6 3 SAB CED G
|
Frontier
Explored
What just happened
What to notice: the frontier list is printed in pop order, so the node UCS will take next is always the one at the left. Orange = just popped (current), teal ring = on the frontier, grey = explored. The bold orange edges trace the current node's path back to S. Run all three and compare the answers — they return three different paths through the same graph.
Guided problemPredict all three answers before you press Step

Using the graph in Figure 2 (edges $S\!-\!A\,{=}\,1$, $S\!-\!B\,{=}\,6$, $A\!-\!C\,{=}\,2$, $A\!-\!E\,{=}\,4$, $C\!-\!D\,{=}\,2$, $D\!-\!G\,{=}\,2$, $E\!-\!G\,{=}\,6$, $B\!-\!G\,{=}\,3$; neighbours are tried in the order listed in the adjacency list $S\!:\![A,B]$, $A\!:\![S,E,C]$, $B\!:\![S,G]$, $C\!:\![A,D]$, $D\!:\![C,G]$, $E\!:\![A,G]$), predict:

  1. Which path does each of BFS, DFS and UCS return, and at what cost?
  2. How many nodes does each one pop before returning?
Step 1 — What is each algorithm actually minimising?

BFS minimises the number of edges. Look for the shortest path by hop count: $S\!-\!B\!-\!G$ is 2 hops; everything else is 3 or 4. So BFS must return $S\!-\!B\!-\!G$, whatever it costs.

DFS minimises nothing. It returns whatever it bumps into while diving. Follow the adjacency order: from $S$ it takes $A$ first; from $A$ it skips $S$ (explored) and takes $E$ first; from $E$ it takes $G$. Done — $S\!-\!A\!-\!E\!-\!G$.

UCS minimises total cost. Enumerate the four paths: $S\!-\!A\!-\!C\!-\!D\!-\!G = 1{+}2{+}2{+}2 = 7$; $S\!-\!B\!-\!G = 6{+}3 = 9$; $S\!-\!A\!-\!E\!-\!G = 1{+}4{+}6 = 11$; $S\!-\!B\!-\!D\!-\!G$ doesn't exist. So UCS returns the 7.

Step 2 — Count BFS's pops

Frontier as a FIFO. Pop $S$ → push $A(1), B(6)$. Pop $A$ → push $E, C$. Pop $B$ → push $G$. Pop $E$ → $G$ already on the frontier, skip. Pop $C$ → push $D$. Pop $G$ → goal.

6 pops: S, A, B, E, C, G. Cost of the answer: $9$. BFS found a path with fewer edges but 29% more cost than optimal.

Step 3 — Count DFS's and UCS's pops

DFS: pop $S$ (push $B$ then $A$ so that $A$ is on top), pop $A$ (push $C$ then $E$), pop $E$ (push $G$), pop $G$. 4 pops — by far the fastest — for the worst answer, cost $11$.

UCS: pop $S(0)$; pop $A(1)$ pushing $E(5), C(3)$; pop $C(3)$ pushing $D(5)$; tie between $E(5)$ and $D(5)$ — the frontier had $E$ first, so pop $E(5)$, pushing $G(11)$; pop $D(5)$, which generates $G$ at $5{+}2 = 7 < 11$, so it replaces the frontier entry; pop $B(6)$, which generates $G$ at $6{+}3 = 9 > 7$, so it is discarded; pop $G(7)$ → goal. 7 pops.

Answer & the takeaway
Path returnedCostEdgesPops
BFSS-B-G926
DFSS-A-E-G1134
UCSS-A-C-D-G747

Takeaway: each algorithm wins on the thing it optimises and loses on the others. BFS wins on hops, DFS wins on effort, UCS wins on cost — and cost is usually what the problem actually asked for. Also note that the optimal path is the longest one in edges: this is precisely the situation where BFS silently gives you a wrong answer that looks plausible.

10. The UCS walkthrough, and the step everyone gets wrong

This is the slide trace, reproduced exactly and then explained. The graph:

Figure 3 · The walkthrough graph (directed, as drawn on the slide)
1 4 2 4 1 3 SAB CDG
Edges: $S\!\to\!A = 1$, $S\!\to\!B = 4$, $A\!\to\!C = 2$, $C\!\to\!D = 4$, $B\!\to\!D = 1$, $D\!\to\!G = 3$. Two routes reach $D$ — a short-hop expensive one through $C$ and a long-hop cheap one through $B$ — and the whole lesson lives in what UCS does when the second one shows up.

The slide's table, complete:

Frontier (added, with $g$)ExploredWhat happened
S: 0Sstart; pop $S$, expand it
S-A: 1Apushed by $S$; cheapest, so popped next
S-B: 4Cpushed by $S$; waits
S-A-C: 3Bpushed by $A$; $3 < 4$ so popped before $B$
S-A-C-D: 7Dpushed by $C$
S-B-D: 5replaced the 7 — better path to D!
S-B-D-G: 8pushed by $D$; popped; goal

Result: S-B-D-G, path cost 8.

The step you must understand: “replaced by a better path to D!”

Freeze the algorithm right after $C$ has been expanded. The state of the world is:

UCS pops the minimum, which is S-B at $4$. It expands $B$. $B$'s only child is $D$, reached with

$$g = g(\texttt{S-B}) + c(B, \text{go}, D) = 4 + 1 = 5.$$

Now run the pseudocode's last two lines against this child:

  1. “if the child state is not already in the frontier or explored set” — $D$ is already in the frontier, as S-A-C-D with $g = 7$, so we do not simply add it.
  2. “else if the child is already in the frontier with higher $g(n)$, replace that frontier node with child” — the entry has $g = 7$, our new child has $g = 5$, and $5 < 7$, so we throw the old node away entirely and put the new one in its place.

The frontier now holds exactly one node for state $D$: S-B-D with $g = 5$, and S-A-C-D no longer exists anywhere. Hence the slide's annotation “Replaced by better path to D!”.

Key idea — one node per state on the frontier

A state gets at most one frontier node, and it carries the cheapest path found so far. Rediscovery either upgrades that node (new $g$ smaller) or is discarded (new $g$ no smaller). The upgrade also rewrites the parent pointer — which is how the final answer comes out as $S\!-\!B\!-\!D\!-\!G$ and not $S\!-\!A\!-\!C\!-\!D\!-\!G$.

Common mistake — three of them, actually
  1. Keeping both. Writing a frontier with S-A-C-D: 7 and S-B-D: 5 on it. You then expand $D$ twice and the trace never matches the answer key. One state, one frontier node.
  2. Discarding the new node because “$D$ is already there.” That is the rule for BFS and DFS, where all that matters is reaching a state. For UCS, how expensively you reached it matters, so you must compare.
  3. Forgetting to update the parent pointer. Students update the number 7 → 5 but leave the path reading S-A-C-D, then report the final answer as $S\!-\!A\!-\!C\!-\!D\!-\!G$ with cost 8 — a path that costs 10 and an answer that is internally inconsistent.

Why is the replacement safe? $D$'s future cost does not depend on how we reached $D$, so the only difference between the two nodes is the prefix, and $5 < 7$: any completion of the expensive prefix is beaten, edge for edge, by the same completion of the cheap one. That optimal substructure fails as soon as costs depend on history (a toll charged only if you passed through $C$) — your cue that the history belongs in the state.

Note also that $S\!-\!A$ is the cheapest first move, and UCS briefly preferred it — it popped $A$ before $B$ — before the arithmetic overturned it. Greedy first steps are not optimal plans.

Guided problemA complete UCS hand-trace, including a replacement and a rejection

Trace UCS (graph search, goal test on pop) on this graph. Write down, for every iteration: the node popped, the explored set after it, and the full frontier. Then give the returned path and its cost.

2 3 7 9 1 1 6 SAB CDG

Edges: $S\!\to\!A=2$, $S\!\to\!B=3$, $A\!\to\!C=7$, $A\!\to\!D=9$, $B\!\to\!D=1$, $C\!\to\!G=1$, $D\!\to\!G=6$.

Step 1 — Set up and do the first two pops

Frontier: S:0. Explored: $\{\}$.

Pop 1: $S$ ($g=0$). Not a goal. Explored $=\{S\}$. Children: $A$ at $0{+}2=2$ (new → push), $B$ at $0{+}3=3$ (new → push).
Frontier: S-A:2, S-B:3.

Pop 2: minimum is S-A:2. Not a goal. Explored $=\{S,A\}$. Children: $C$ at $2{+}7=9$ (new → push), $D$ at $2{+}9=11$ (new → push).
Frontier: S-B:3, S-A-C:9, S-A-D:11.

Step 2 — The replacement

Pop 3: minimum is S-B:3. Not a goal. Explored $=\{S,A,B\}$. Its only child is $D$, at $3{+}1 = 4$.

$D$ is already on the frontier as S-A-D with $g=11$. Compare: $4 < 11$, so replace. The node S-A-D:11 is deleted; S-B-D:4 takes its place, with its parent pointer now aimed at $B$.

Frontier: S-B-D:4, S-A-C:9. Note the frontier got shorter, not longer — replacement is not insertion.

Step 3 — The rejection, and why we don't stop at the first goal

Pop 4: minimum is S-B-D:4. Not a goal. Explored $=\{S,A,B,D\}$. Child $G$ at $4{+}6 = 10$; $G$ is new → push.
Frontier: S-A-C:9, S-B-D-G:10.

A goal node is now on the frontier at cost 10. We do not return it. $9 < 10$, so something cheaper is still pending.

Pop 5: minimum is S-A-C:9. Not a goal. Explored $=\{S,A,B,D,C\}$. Child $G$ at $9{+}1 = 10$. $G$ is on the frontier with $g=10$; the new $g$ is $10$, which is not lower, so we discard the new node and keep the existing one. (Had the $C\!\to\!G$ edge cost $0.5$, we would have replaced instead.)
Frontier: S-B-D-G:10.

Answer & the takeaway

Pop 6: S-B-D-G at $g=10$. Goal test passes → return.

Answer: the plan $S \to B \to D \to G$, cost 10. Verify by brute force: $S\!-\!A\!-\!C\!-\!G = 2{+}7{+}1 = 10$; $S\!-\!A\!-\!D\!-\!G = 2{+}9{+}6 = 17$; $S\!-\!B\!-\!D\!-\!G = 3{+}1{+}6 = 10$. Two optima at 10; UCS returned the one that reached the front of the queue first. Optimal does not mean unique.

Takeaway: in six pops you exercised all three frontier rules — push a new state, replace a worse one, discard a no-better one — plus the goal-test-on-pop rule. If you can do this trace cold, you can do A* on the next page, because A* is literally this with the priority changed from $g$ to $g+h$.

11. Cost contours: how UCS really spreads

The slides summarise UCS as “UCS explores increasing cost contours”, beside a picture of concentric rings. Here is what it means.

The contour at cost $c$ is the set of states whose cheapest path from the start costs at most $c$. UCS pops the minimum $g$ and popped $g$-values never decrease (§13), so it expands states in non-decreasing order of optimal cost: the explored set always is a contour.

Figure 4 · UCS expands in rings of equal cost — in every direction at once
c₁ c₂ c₃ c = C* · the goal's contour Start Goal
What to notice: teal dots are states UCS expands; grey dots are states it never touches. Everything inside the dashed ring — every state reachable for less than $C^*$, in every direction, including straight backwards away from the goal — is expanded before the goal is popped. The wasted work is the entire teal area that isn't on the dashed line. On the next page, a heuristic squashes these circles into ellipses pointing at the goal.

This also explains the bound $O(b^{1+\lfloor C^*/\varepsilon\rfloor})$ from §8: the expansions are the states inside the $C^*$ contour, and with a minimum step cost $\varepsilon$ you can only be $C^*/\varepsilon$ steps deep inside it.

The contours on a real search tree

The lecture's lettered graph:

Figure 5 · The lecture's 12-state graph
3 9 1 1 8 2 2 2 8 2 3 1 2 4 4 15 aGb cde fSh prq
Edges, as the tree on the slide implies them: $S\!\to\!d\,3$, $S\!\to\!e\,9$, $S\!\to\!p\,1$; $d\!\to\!b\,1$, $d\!\to\!c\,8$, $d\!\to\!e\,2$; $e\!\to\!h\,8$, $e\!\to\!r\,2$; $p\!\to\!q\,15$; $b\!\to\!a\,2$; $c\!\to\!a\,2$; $h\!\to\!p\,4$, $h\!\to\!q\,4$; $r\!\to\!f\,1$; $f\!\to\!c\,3$, $f\!\to\!G\,2$. Twelve states, sixteen edges.

Unrolled into a tree the way the slide does it, those twelve states explode to 28 nodes — $q$ appears four times, $a$ three times, $c$, $f$ and $p$ twice each. Drag the slider to sweep the cost contour:

Figure 6 · Interactive — the same graph as a search tree, with $g$ on every node
What to notice: 28 tree nodes from 12 states. Slide the contour down and the surviving nodes are exactly what UCS has expanded at that moment. At $c = 10$ the goal G (g = 10) lights up on the path $S\!-\!d\!-\!e\!-\!r\!-\!f\!-\!G$ — note how many nodes at costs 1–9 had to light up first, scattered across every branch, and that the duplicate G at $14$ and c at 11 and 15 are states the tree cannot tell apart.
Guided problemUCS on the 12-state graph: graph search vs tree search

Using Figure 5:

  1. Which path does UCS (graph search) return, and what does it cost?
  2. Which states does it expand, in order?
  3. Which states are in the contour $c \le 5$?
  4. How much work did the explored set save compared with tree search?
Step 1 — Pops 1 to 4

Frontier S:0. Pop $S$; push $d\!:\!3$, $e\!:\!9$, $p\!:\!1$.

Pop $p$ (1). Child $q$ at $1{+}15 = 16$ → push. Frontier: $d\,3$, $e\,9$, $q\,16$.

Pop $d$ (3). Children: $b$ at $4$ (new), $c$ at $11$ (new), and $e$ at $3{+}2 = 5$. But $e$ is already on the frontier at $9$, and $5 < 9$ → replace: $e$ becomes $5$ with parent $d$. Frontier: $b\,4$, $e\,5$, $c\,11$, $q\,16$.

Pop $b$ (4). Child $a$ at $6$ → push.

Step 2 — Pops 5 to 9, and the answer

Pop $e$ (5). Children $h$ at $5{+}8 = 13$, $r$ at $5{+}2 = 7$ → push both.

Pop $a$ (6). No outgoing edges; nothing happens.

Pop $r$ (7). Child $f$ at $8$ → push.

Pop $f$ (8). Children: $c$ at $8{+}3 = 11$ — $c$ is on the frontier at $11$ already and $11 \not< 11$, so discard; $G$ at $8{+}2 = 10$ → push.

Frontier now: $G\,10$, $c\,11$, $h\,13$, $q\,16$. Pop the minimum, $G$ at $10$ → goal test passes.

Answer: $S \to d \to e \to r \to f \to G$, cost $3{+}2{+}2{+}1{+}2 = 10$.

Step 3 — The contour at $c \le 5$

The states whose optimal cost from $S$ is at most 5 are $S(0)$, $p(1)$, $d(3)$, $b(4)$, $e(5)$. Five of the twelve states. Those are exactly the first five pops, in exactly that order — the pop order is the sorted contour.

Set the Figure 6 slider to 5 and you will see these five states, but spread over seven tree nodes, because $e$ and $p$ each appear more than once at low cost.

Answer & the takeaway

Work comparison. Graph-search UCS expanded 8 nodes ($S, p, d, b, e, a, r, f$) and then popped the goal — and it never touched $c$, $h$ or $q$ at all, because their costs exceed $C^* = 10$.

Tree search on the same graph generates all 28 nodes shown in Figure 6 if run to exhaustion — more than triple the work, for the same answer, because it re-expands $q$ four times, $a$ three times, and so on. On a graph with a cycle the ratio would be infinite.

Takeaway: the explored set is not a micro-optimisation; it is the difference between polynomial and exponential blow-up on graphs with many paths between the same pair of states. This is the real content of the pre-reading's “tree search vs graph search.”

12. The comparison table, and how to reconstruct it

Figure 7 · Properties of the uninformed search strategies
AlgorithmFrontierComplete?Optimal?TimeSpace
BFSFIFO queue Yes ($b$ finite)Only if step costs are all equal $O(b^{d})$$O(b^{d})$
DFS (tree)LIFO stack No — cycles or infinite depth trap itNo $O(b^{m})$$O(bm)$
DFS (graph, finite space)LIFO stack + explored set YesNo $O(|S|)$$O(|S|)$
IDSLIFO stack, restarted Yes ($b$ finite)Only if step costs are all equal $O(b^{d})$$O(bd)$
UCSPriority queue on $g(n)$ Yes (step costs $\ge \varepsilon > 0$)Yes $O\!\left(b^{1+\lfloor C^{*}/\varepsilon\rfloor}\right)$$O\!\left(b^{1+\lfloor C^{*}/\varepsilon\rfloor}\right)$
$b$ = branching factor · $d$ = depth of the shallowest goal · $m$ = maximum depth of the tree · $|S|$ = number of states · $C^*$ = optimal solution cost · $\varepsilon$ = smallest step cost. What to notice: only the bottom row has an unqualified “yes” in the Optimal column, and it pays for it in the two rightmost columns.
Intuition — never memorise this table

Derive it from two questions. (1) What does the frontier hold at its worst moment? A whole tier ⇒ $b^d$ space (BFS, UCS); one path plus siblings ⇒ $bm$ space (DFS, IDS). (2) What must the algorithm look at before it can stop? Everything shallower than the goal ⇒ $b^d$ time; everything cheaper ⇒ $b^{C^*/\varepsilon}$ time. Optimality falls out of (2): if you examine everything better than what you return, you cannot have missed anything better.

You have a state space with branching factor $b = 4$, an unknown but finite goal depth, all step costs equal to 1, and 8 GB of RAM. Which algorithm should you reach for?
Why: BFS and IDS give identical answers here (uniform costs ⇒ both optimal), and both take $O(b^d)$ time — IDS only about $b/(b-1) = 1.33\times$ more node generations at $b=4$. But BFS's frontier will exhaust 8 GB at roughly $d = 16$, while IDS's stack stays kilobyte-sized. Plain DFS is rejected because it returns a non-optimal path and may not terminate at all if the space has cycles and you use tree search.

13. Why UCS is complete and optimal — the proof

The slides assert “UCS is complete and optimal!” and move on. Here is the argument. Assume every step cost satisfies $c(s,a,s') \ge \varepsilon > 0$.

Lemma 1 — popped costs never decrease

Claim. If UCS pops node $n_1$ and then later pops $n_2$, then $g(n_1) \le g(n_2)$.

Proof. When $n_1$ is popped it is the minimum of the frontier, so every node then on the frontier has $g \ge g(n_1)$. The only nodes ever added afterwards are children of expanded nodes, and a child's cost is $g(\text{parent}) + c \ge g(\text{parent}) \ge g(n_1)$, using $c \ge 0$ and the fact that every subsequently expanded parent was itself popped at or after $n_1$. Replacement only ever lowers a frontier node's $g$ — but never below the cost of the path that produced it, which by the same argument is $\ge g(n_1)$. Hence every node popped after $n_1$ has $g \ge g(n_1)$. $\blacksquare$

This is where non-negative costs are essential: a negative edge could produce a child cheaper than the node just popped, un-nesting the rings — which is why Dijkstra's algorithm (UCS by another name) fails on negative weights.

Theorem — when UCS pops a node, its $g$ is optimal for that state

Claim. At the moment UCS pops a node $n$ with state $s$, $g(n)$ equals the cost of a cheapest path from the start to $s$.

Proof (by contradiction). Suppose some cheaper path $P^*$ to $s$ exists, with total cost $g^* < g(n)$.

Walk along $P^*$ from the start and let $u$ be the first state on it that is not in the explored set at this moment. Such a $u$ exists, because $s$ itself is not yet explored (we are popping it right now).

Case A: $u$ is the start state. Then the start was never expanded, which is impossible unless $n$ is the start node itself, in which case $g(n) = 0$ and nothing can be cheaper.

Case B: $u$ has a predecessor $u'$ on $P^*$, and by the choice of $u$, $u'$ is explored. So $u'$ was expanded at some earlier iteration, and at that moment $u$ was generated as a child with cost

$$g(u) \;\le\; \text{cost of the prefix of } P^* \text{ up to } u.$$

(“$\le$” and not “$=$”, because $u'$ might itself have been reached by an even cheaper route, and because a later replacement can only lower $g(u)$ further.) Therefore $u$ is on the frontier right now with

$$g(u) \;\le\; \text{prefix cost} \;\le\; g^* \;<\; g(n),$$

where the middle inequality holds because the remaining suffix of $P^*$ has non-negative cost.

But that contradicts the fact that $n$ was popped as the frontier's minimum: $u$ was sitting right there with a strictly smaller key. Hence no cheaper path exists and $g(n)$ is optimal. $\blacksquare$

Two corollaries you should be able to state

Optimality. The first goal node popped has optimal $g$ by the theorem, and by Lemma 1 nothing popped later could be cheaper, so the returned plan is a cheapest plan. This uses goal-test-on-pop: if we returned on generation the theorem would not apply, because a generated node's $g$ need not be optimal yet.

Safety of the explored set. A state's $g$ is already optimal when it is popped, so rediscovering it later can never improve it — which is precisely why the pseudocode may ignore children already in the explored set. This is the step that breaks for A* with a merely admissible (non-consistent) heuristic, the subject of the next chapter.

Completeness

Suppose a solution of finite cost $C^*$ exists. Every pop has $g \ge$ the previous pop's $g$ (Lemma 1), and a $k$-step path costs at least $k\varepsilon$, so only finitely many nodes have $g \le C^*$ — at most the states within depth $\lfloor C^*/\varepsilon \rfloor$. UCS can therefore make only finitely many pops with $g \le C^*$, and it cannot pop anything with $g > C^*$ while the goal's node (key $\le C^*$) is on the frontier. Hence the goal is popped within finitely many steps. $\blacksquare$

If $\varepsilon = 0$ is allowed the argument collapses: infinitely many nodes can share cost 0, and UCS can spin forever inside a zero-cost loop without raising the contour.

14. What's wrong with UCS — and what fixes it

Two complaints, both visible in Figure 4:

The fix is a second number in the priority: keep $g(n)$, the backward cost paid, and add $h(n)$, an estimate of the forward cost still to pay. Order by $h$ alone and you get greedy best-first search (fast, not optimal); order by $f(n) = g(n) + h(n)$ and you get A*, whose contours stretch from circles into ellipses aimed at the goal, with optimality surviving as long as $h$ never overestimates.

Where this is going

UCS is A* with $h \equiv 0$: the frontier, the explored set, the replacement rule, goal-test-on-pop, the contour argument and even the shape of the optimality proof all carry over unchanged. Only the priority changes. Continue with Chapter 2: Heuristics, Greedy search and A*.

15. Recap and self-check

The Essentials box at the top of this page is the recap — read it once more, then take these two checks cold.

Your classmate's UCS trace on the §10 walkthrough graph ends with the frontier containing both S-A-C-D: 7 and S-B-D: 5, and reports the answer S-A-C-D-G with cost 8. Name every error.
Why: The cost 8 is correct — it is $g$ of S-B-D plus the 3 for $D \to G$ — but it was attached to the wrong path because the parent pointer was never updated. The root cause is the first error: keeping two nodes for state $D$. Fix the frontier and the path fixes itself. Cost of the path they actually wrote down: $1+2+4+3 = 10$, which doesn't match their own 8 — always sanity-check your answer by re-adding the edge costs along the path you report.
True or false: in a graph where every edge costs exactly 1, BFS and UCS expand the same states in the same order.
Why: Unit costs make $g(n) = \text{depth}(n)$, so UCS's contours are exactly BFS's tiers, and UCS degenerates into BFS (with a slower queue and one extra guarantee: it still handles the replacement rule correctly if you later change a cost). This is also why BFS is optimal only for uniform costs — it is secretly running UCS on a problem where depth happens to equal cost.