Uninformed Search (BFS and DFS)

Context: FIT1061_MOC · Block A’s two blind searches — no sense of direction, only an ordering rule Parent Framework: Search Problem Formulation

Quick Revision

  • 🎯 Objective: ONE loop, ONE parameter ➔ the frontier’s discipline — FIFO (Queue (ADT)) gives BFS, LIFO (Stack (ADT)) gives DFS.
  • 📦 Core Components: frontier ➔ nodes waiting · visited ➔ stops re-exploration · came_from ➔ records the path · nodes_expanded ➔ the comparison metric.
  • ⚠️ Key Constraint: BFS’s shortest-path guarantee needs FIFO order AND uniform step cost — lose either and it evaporates.

📝 How It Works

1. The Shared Skeleton

  • One loop, four moves ➔ take a node off the frontier ➔ count it ➔ goal-test it ➔ push its unvisited neighbours on.
  • Only the frontier differs ➔ every other line of BFS and DFS is character-identical; the data structure is the algorithm.
  • Domain-blind ➔ needs only the successor function, so grid, maze and road network run the same code (Search Problem Formulation).
  • Termination ➔ goal dequeued ➔ reconstruct path · frontier empties ➔ failure (goal unreachable), not “no path exists yet”.

2. Frontier = Queue ➔ BFS

  • FIFO ordering ➔ nodes discovered first are expanded first ➔ all of depth before any of depth .
  • Shape ➔ radiates outward in layers, like ripples in a pond.
  • Shortest-path guarantee ➔ nodes are reached in nondecreasing distance order, so the first arrival at any node is via a shortest path — every shorter route was already checked.
  • The price ➔ it explores nearly every open cell before the goal surfaces, and the fraction explored grows with grid size.

3. Frontier = Stack ➔ DFS

  • LIFO ordering ➔ the newest node is popped ➔ commits to one branch and drives it to the wall before backtracking.
  • Shape ➔ a snaking corridor, not a wave.
  • No guarantee ➔ swapping FIFO for LIFO destroys the layer ordering that produced optimality — DFS finds a path, not the shortest.
  • Why keep it ➔ it is the engine of backtracking search, topological sort and cycle detection, and its frontier stays thin.

4. The Two Bookkeeping Structures

  • visited (a set) ➔ membership test before enqueueing; initialise with start. Without it a cyclic graph re-enqueues forever and the frontier never empties.
  • came_from (a dict)came_from[B] = A records B was reached from A; walk it backwards from the goal and reverse ➔ the path.
  • Mark on discovery, not on expansion ➔ add to visited at the moment of enqueue/push; marking at pop lets one node enter the frontier many times.
  • Handout variant ➔ the handout drops visited and tests neighbour not in came_from instead — came_from’s keys are the visited set, since every discovered node gets an entry. Both forms are correct; the lecture’s explicit set is the traceable one.

⚙️ Core Implementation

🔹 BFS — unit pseudocode, final form

🔹 DFS — the three-line diff

🔹 Path reconstruction

⚖️ Complexity

ResourceBFSDFS
Nodes expanded (worst case)
Frontier sizea whole layer ➔ memory grows with widthone branch ➔ thin
visited + came_from
Optimal (unweighted)✅ shortest❌ any path
  • Neither scales to chess, so depth is nodes years at nodes/s — for either algorithm. Blind search is the problem, not the choice between these two.

⚖️ Core Decision Matrix

FrontierTrigger conditionProConExploration shape
Queue (FIFO) ➔ BFSshortest path required, edges unweightedprovably optimal; finds a path if one existsexplores broadly; frontier holds a full layerradial wave
Stack (LIFO) ➔ DFSany path suffices; deep/narrow space; backtracking basetiny frontier; sometimes far fewer expansionsno optimality; can walk long dead-end corridorssnaking corridor

When It Flips: on a dead-end grid DFS commits to the corridor, dead-ends and backtracks ➔ a longer path. On a corridor-shaped space with the goal deep along one branch, DFS reaches it after expansions while BFS pays for the entire radius. Uniform cost is the hinge — introduce edge weights and BFS's guarantee dies too.

📊 Exam Execution Trace & Applied Exercises

Lecture grid, one wall at the centre. Nodes are walkable cells; edges join -neighbours.

S  1  2
3  ■  4
5  6  G

Neighbour order fixed as right → down → left → up. Exploration order is undefined without it — state your order before tracing.

Manual Execution Trace

BFS, queue written front→back.

StepDequeuedUnvisited neighbours enqueuedQueue aftercame_from added
S1, 3[1, 3]1←S, 3←S
12[3, 2]2←1
35[2, 5]5←3
24[5, 4]4←2
56[4, 6]6←5
4G[6, G]G←4
6— (G already visited)[G]
Ggoal ➔ reconstruct

Reconstruction: G ← 4 ← 2 ← 1 ← S ➔ path · length edges · nodes_expanded .

DFS on the identical grid, stack written bottom→top.

StepPoppedPushedStack after
S1, 3[1, 3]
35[1, 5]
56[1, 6]
6G[1, G]
Ggoal

Reconstruction: G ← 6 ← 5 ← 3 ← S ➔ path · length edges · nodes_expanded .

  • Read the result honestly ➔ DFS matched BFS’s length here because this grid is symmetric, and expanded nodes against . DFS lacks the guarantee, which is not the same as always producing a worse path — the dead-end grid in the lab is engineered to make the gap appear.

⚠️ Common Mistakes

  • 💡 Omitting the visited checkS, A, B, D get re-enqueued after processing, the queue grows without bound and on a cyclic graph the search never terminates — this is exactly what the lecture’s first trace exposed.
  • 💡 Returning success instead of a path ➔ at the moment G is dequeued every step that led there is gone; the deliverable is the move sequence, not a boolean.
  • 💡 Exporting BFS optimality to weighted graphs ➔ FIFO orders by hop count; roads with different travel times break the guarantee immediately.

🧠 Active Recall