4. Uninformed Search Strategies

Source: AIMA 4th Ed., Chapter 3 (Section 3.4) Status: Complete


Overview

Uninformed search (also called blind search) algorithms have access only to the problem definition — the state space, actions, transition model, and action costs. They have no domain-specific knowledge about which states are closer to the goal. Every strategy below is a special case of BEST-FIRST-SEARCH with a particular choice of f(n).


1. Breadth-First Search (BFS)

Core idea: Expand the shallowest unexpanded node. Explore all nodes at depth d before any node at depth d+1.

Evaluation function: f(n) = depth(n) — frontier is a FIFO queue.

Algorithm

function BREADTH-FIRST-SEARCH(problem) returns solution node or failure
    node <- NODE(problem.INITIAL)
    if problem.IS-GOAL(node.STATE) then return node   -- early goal test
    frontier <- FIFO queue, with node as element
    reached <- {problem.INITIAL}
    while not IS-EMPTY(frontier) do
        node <- POP(frontier)
        for each child in EXPAND(problem, node) do
            s <- child.STATE
            if problem.IS-GOAL(s) then return child   -- early goal test
            if s not in reached then
                add s to reached
                add child to frontier
    return failure

Note on goal test timing: BFS uses an early goal test (check when generating, not when expanding). This is safe for BFS because all actions have cost 1 (or equal cost) — the first time we generate a goal state, it is guaranteed to be via the shallowest path.

Properties

Property Value Notes
Complete Yes If b is finite and solution exists at finite depth
Cost optimal Yes* Only for unit action costs (or equal-cost actions)
Time complexity O(b^d) Must generate entire level d before finding goal
Space complexity O(b^d) All nodes at level d must be in frontier/reached simultaneously

b = branching factor, d = depth of shallowest solution.

Why BFS is cost-optimal only for unit costs

BFS guarantees fewest steps, not lowest cost. If action costs differ (e.g., some roads are longer), a shallower path may cost more than a deeper one. Use UCS for general action costs.

Practical note

Space is BFS’s critical weakness. For b=10, d=12: - Time: 10^12 = 1 trillion nodes - Space: 10^12 nodes in memory simultaneously

At modern speeds and memory, d > 15 becomes completely infeasible without heuristics.


2. Uniform-Cost Search (UCS) / Dijkstra’s Algorithm

Core idea: Expand the node with the lowest path cost g(n). Equivalent to Dijkstra’s single-source shortest path algorithm.

Evaluation function: f(n) = g(n) = PATH-COST(n)

function UNIFORM-COST-SEARCH(problem) returns solution node or failure
    return BEST-FIRST-SEARCH(problem, PATH-COST)

Uses late goal test (check when popping, not when generating). This is required for optimality with general action costs.

Why late goal test is required for UCS

When a node is generated, we know a path to it, but not necessarily the cheapest one — a cheaper path may still be on the frontier. A node is guaranteed to have its optimal cost only when it is popped from the priority queue (because all remaining frontier nodes have equal or higher cost).

Properties

Property Value Notes
Complete Yes If action costs ≥ ε > 0 (not zero or negative)
Cost optimal Yes Expands nodes in non-decreasing cost order
Time complexity O(b^(1 + floor(C*/ε))) C* = optimal cost, ε = min action cost
Space complexity O(b^(1 + floor(C*/ε))) Same order as time

Explanation of time complexity: The algorithm explores all states with cost ≤ C. In the worst case, if each action costs ε, that’s up to C/ε actions, giving depth ≈ C/ε and O(b^(C/ε)) nodes.

UCS vs BFS


3. Depth-First Search (DFS)

Core idea: Expand the deepest unexpanded node (most recently added to frontier). Implemented with a LIFO stack.

Evaluation function: f(n) = -depth(n) — always expand deepest node.

Properties

Property Value Notes
Complete No Can loop forever in infinite/cyclic graphs; yes on finite acyclic graphs
Cost optimal No May find a deep, expensive solution before a shallow cheap one
Time complexity O(b^m) m = max depth; terrible if m >> d
Space complexity O(b*m) Only needs to store one path + siblings at each level

Key advantage of DFS: its space complexity is linear in depth. For very deep state spaces, DFS uses far less memory than BFS, even if it is slower.

Key weakness: DFS can go infinitely deep in graphs with cycles or infinite depth. Graph search (with reached table) prevents infinite loops but restores O(b^m) space. Tree-like DFS with a cycle check (IS-CYCLE) limits space to O(m) but requires the cycle check at each step.


4. Depth-Limited Search (DLS)

Core idea: DFS with a hard depth cutoff l. Do not expand any node at depth > l.

function DEPTH-LIMITED-SEARCH(problem, l) returns node or failure or cutoff
    frontier <- LIFO queue (stack) with NODE(problem.INITIAL) as element
    result <- failure
    while not IS-EMPTY(frontier) do
        node <- POP(frontier)
        if problem.IS-GOAL(node.STATE) then return node
        if DEPTH(node) > l then
            result <- cutoff           -- record that we were cut off
        else if not IS-CYCLE(node) do
            for each child in EXPAND(problem, node) do
                add child to frontier
    return result

Three return values: - A solution node — success - failure — no solution exists in the explored space (not due to cutoff) - cutoff — all paths at depth ≤ l were explored, no solution found (but one may exist deeper)

Properties

Property Value
Complete No (if solution is deeper than l)
Cost optimal No
Time complexity O(b^l)
Space complexity O(b*l)

5. Iterative Deepening Search (IDS)

Core idea: Run depth-limited search with increasing depth limits (l = 0, 1, 2, …) until a solution is found. This combines DFS’s space efficiency with BFS’s completeness and optimality (for unit costs).

function ITERATIVE-DEEPENING-SEARCH(problem) returns solution node or failure
    for depth = 0 to infinity do
        result <- DEPTH-LIMITED-SEARCH(problem, depth)
        if result != cutoff then return result

Why re-expansion is not wasteful

IDS appears to waste work by re-expanding nodes at every depth limit. The actual overhead is small. Consider the total nodes generated:

At depth d, BFS generates: 1 + b + b^2 + … + b^d ≈ b^d nodes.

IDS generates at each iteration: - Iteration d: b^d nodes at depth d (dominant term) - Iteration d-1: b^(d-1) nodes - …

Total = b^d + b^(d-1) + … + b ≈ b^d * (b/(b-1)) ≈ b/(b-1) * b^d

For b=10: overhead factor = 10/9 ≈ 1.11 (11% more nodes than BFS). This is negligible.

Properties

Property Value Notes
Complete Yes With cycle checking; will eventually reach solution depth
Cost optimal Yes* For unit action costs only
Time complexity O(b^d) Same order as BFS
Space complexity O(b*d) Same as DFS — only linear in depth

IDS is the preferred uninformed search method when the search space is large and the depth of solution is unknown, because it has BFS’s completeness and near-BFS time complexity with DFS’s linear space.


Core idea: Run two simultaneous searches — one forward from the initial state, one backward from the goal state — and stop when the two frontiers meet.

Key savings: Each search needs only go to depth d/2, so time is O(b^(d/2)) instead of O(b^d). For b=10, d=10: unidirectional needs 10^10 = 10B nodes; bidirectional needs 2 × 10^5 = 200K nodes.

BIBF-SEARCH (Bidirectional Best-First Search) — Figure 3.14

The forward search uses evaluation function f_F; backward search uses f_B. The key challenge: when the two frontiers meet, the joined path may not be optimal — we must keep searching until we can certify optimality.

function BIBF-SEARCH(problem_F, f_F, problem_B, f_B) returns solution or failure
    node_F <- NODE(problem_F.INITIAL)
    node_B <- NODE(problem_B.INITIAL)    -- problem_B starts from goal
    frontier_F <- priority queue with node_F, ordered by f_F
    frontier_B <- priority queue with node_B, ordered by f_B
    reached_F <- {problem_F.INITIAL: node_F}
    reached_B <- {problem_B.INITIAL: node_B}
    solution <- failure
    while not TERMINATED(solution, frontier_F, frontier_B) do
        if f_F(TOP(frontier_F)) <= f_B(TOP(frontier_B)) then
            solution <- PROCEED(F, problem_F, frontier_F, reached_F,
                                reached_B, solution)
        else
            solution <- PROCEED(B, problem_B, frontier_B, reached_B,
                                reached_F, solution)
    return solution

function PROCEED(dir, problem, frontier, reached, reached2, solution)
    node <- POP(frontier)
    for each child in EXPAND(problem, node) do
        s <- child.STATE
        if s not in reached or child.PATH-COST < reached[s].PATH-COST then
            reached[s] <- child
            add child to frontier
            if s in reached2 then
                -- Found a path joining forward and backward frontiers
                solution2 <- JOIN-NODES(dir, child, reached2[s])
                if PATH-COST(solution2) < PATH-COST(solution) then
                    solution <- solution2
    return solution

TERMINATED condition: stop when the best path found so far is at most as long as the minimum f-value in either frontier. Once both frontiers have expanded past C*/2, no better path can be found.

Properties

Property Value Notes
Complete Yes With admissible heuristics
Cost optimal Yes* With unit costs or careful termination condition
Time complexity O(b^(d/2)) Exponential savings over unidirectional
Space complexity O(b^(d/2)) Must store both frontiers

Challenge with backward search: we need to know the predecessors of a state (which states can reach it), requiring an explicit inverse transition model or a fully reversible problem.


Algorithm Comparison Summary

Algorithm Complete Cost Optimal Time Space Key Condition
BFS Yes Yes* O(b^d) O(b^d) *unit costs only
UCS Yes Yes O(b^(1+C*/ε)) O(b^(1+C*/ε)) action costs ≥ ε > 0
DFS No No O(b^m) O(b*m)
Depth-Limited No No O(b^l) O(b*l)
IDS Yes Yes* O(b^d) O(b*d) *unit costs only
Bidirectional Yes Yes* O(b^(d/2)) O(b^(d/2)) *requires reverse model

Common Exam Pitfalls

  1. BFS is not cost-optimal for unequal action costs — use UCS instead.
  2. DFS is not complete in general — only in finite acyclic state spaces.
  3. IDS re-expansion overhead is O(b/(b-1)), not a doubling — it is negligible for large b.
  4. UCS requires late goal test — early goal test breaks optimality.
  5. Bidirectional search requires an inverse model — for most problems this is natural, but not always.
  6. The 8-puzzle has 181,440 states, not 362,880 — exactly half of 9! are reachable from any given state.