CS 440 · Lecture 2 · Uninformed search

Search

1 loop, 6 fringes8 MiniGrid environmentsEvery number measured

0 · Catch me up

One loop. Six algorithms. One decision.

Before any picture, agree on what a search is. Everything in this lecture is the same six-line loop with a different answer to one question.

Think

You are handed a problem and told to “search” for a solution. Before you can write a single line, what three things do you have to be given?

Think about the wolf, the goat and the cabbage from last time. What did you need to know to even start? (The full formulation and state space, if you want it in front of you.)

Here is the whole thing.

fringe ← { start }
while fringe is not empty:
    current ← fringe.remove()          ← the one decision
    if current is a goal:  return the path to it
    for each child of current:  fringe.add(child)
return no solution

Put the start on the fringe. While there is anything left to look at, take something off, test it, and put its children on. That is breadth-first search. It is also depth-first search, iterative deepening, uniform-cost search and bidirectional search. The only thing that changes is which thing fringe.remove() hands back.

Think

What is wrong with this loop? There are two things, and one of them will make it run forever on the very first maze we look at.

One simplification, declared up front

In every environment below the state is the cell the agent stands in, and the actions are the four compass moves. That is a choice, and it is not quite what the real MiniGrid robot does — it has a heading, and turning is an action. We will come back to what that costs at the very end; the first exercise of the homework is exactly that question.

1 · Nine cells

Take the oldest thing on the fringe

The smallest environment MiniGrid ships: a 3×3 room. Red triangle is the agent, green square is the goal. Do this one by hand before pressing anything.

Think

Say the fringe is a queue: the thing you remove is always the oldest thing on it. Starting from the agent's cell, which cells get expanded first? Group the nine cells by when they come out.

Which cells are exactly one move from the agent? Exactly two? Draw it.

MiniGrid-Empty-5x5-v0 · 5×5 · 9 free cells
0
agent goal wall
—PATH COST
—STEPS
—EXPANDED · time
—PEAK FRONTIER · memory

Pick an algorithm and step with ▶◀. The panel below the map is the fringe itself, drawn in the order it holds things, with what just came off and what just went on. That data structure is the entire difference between these six.

Think

Step BFS and DFS each to their second pop and compare the two fringes. They hold the same cells. So what is actually different?

Which cell will each one remove next?

That layer-by-layer order is not a coincidence of this map; it is what “oldest first” means. And it buys a guarantee: if there is a solution at all, there is a shallowest one, and because the queue finishes every layer before starting the next, it reaches that layer — and that solution — before any deeper one. This is breadth-first search, and what it promises is the path with the fewest steps. Hold on to the exact wording. It will matter.

2 · A bigger room

Take the newest thing instead

Same loop. Swap the queue for a stack, so the thing you remove is the newest. Six-by-six interior, thirty-six cells.

Think

The children of a cell are added north, east, south, west, and a stack hands back the last thing added. From the top-left corner, where does the search go first? Will the path it finds be worse than breadth-first's?

Follow it two or three moves by hand. Where is it heading?

MiniGrid-Empty-8x8-v0 · 8×8 · 36 free cells
0
agent goal wall
—PATH COST
—STEPS
—EXPANDED · time
—PEAK FRONTIER · memory

Try DFS, then DFS (west first). Nothing about the algorithm changed between them.

Think

Now switch to DFS (west first): identical algorithm, the children just added in the order west, north, east, south. Before you play it — will the answer change?

Park this

If breadth-first search is guaranteed to find the fewest-step path and depth-first search is not — and both of them, in the worst case, look at every reachable cell — why would anyone ever use depth-first search? Do not answer yet. We will earn the answer in a few pages.

3 · The first wall

The map changes. Does the algorithm?

MiniGrid's simplest crossing: one wall with one gap. Start top-left, goal bottom-right.

Think

There is a wall in the way now. What has to change in the six-line loop to handle it?

MiniGrid-SimpleCrossingS9N1-v0 · 9×9 · 43 free cells
0
agent goal wall
—PATH COST
—STEPS
—EXPANDED · time
—PEAK FRONTIER · memory

This viewer starts in the Undiscovered view: every cell the search has not reached yet is fogged. Play it.

The fog is the honest picture. You and I can see the whole map and eyeball a route; the algorithm cannot. It has the start cell and nothing else, and every expansion peels back a little more of the world. It only learns there is a wall by asking for a cell's children and not getting one. Any time you find yourself thinking “obviously it should go left, the gap is on the left” — that is you using a cell the algorithm has not discovered yet. It cannot use what it has not seen. Keeping that fog in mind is most of understanding why these searches cost what they cost.

4 · Told a limit

Refuse to go deeper than ℓ

A harder crossing: three walls, one gap each. Depth-first search will happily follow a corridor forever; give it a leash.

Think

Someone tells you the goal is at most 8 steps away, so you run depth-first search and forbid it from going deeper than 8. It comes back saying no solution. The goal is 12 steps away. Is the algorithm wrong?

MiniGrid-SimpleCrossingS9N3-v0 · 9×9 · 33 free cells
0
agent goal wall
—PATH COST
—STEPS
—EXPANDED · time
—PEAK FRONTIER · memory

Depth-limited search with the wrong limit, the right limit, and then all of them in turn.

Think

You do not know the right limit. What is the dumbest thing that would work?

If 8 is too small, what would you try next? And after that?

Think

Say every cell has b children and the answer is at depth d. An exhaustive search to depth d looks at about bd nodes. Iterative deepening looks at b0 + b1 + … + bd. How much bigger is that?

5 · The parked question

Which of the three questions have we not asked?

For any algorithm you are handed: does it work? how long does it take? how much memory does it need? We have been avoiding the third.

Think

What is the main thing that takes up memory in the six-line loop? And at its biggest, how much is on there for breadth-first search versus depth-first search?

What does each one hold when it is deepest into the search?

Peak frontier, the largest the fringe ever got. On a grid the two are close, and the reason why is the question.
EnvironmentBFSDFS
Nine cells34
A bigger room610
The first wall610
The full maze (later)106
Think

Look at the measured peak frontiers above. The theory says exponential versus linear. The numbers say “a bit smaller.” What is different about a grid?

6 · Water

What if a step is not a step?

The smallest map that can show the next idea: two cells of water on the direct route, five times the cost of dry ground to wade through. Every algorithm so far assumed every action costs the same.

Think

Breadth-first search finds the 6-step path straight through the water. Is that the best path?

Best with respect to what?

MapEnv · 9×5 · 16 free cells
0
agent goal water, cost 5 to enter wall
—PATH COST
—STEPS
—EXPANDED · time
—PEAK FRONTIER · memory

Compare BFS and UCS. Same loop, one more number on each fringe entry.

Think

You want the loop to find the cheapest path, not the shortest. What single change to fringe.remove() does it?

7 · Four rooms

Two frontiers, and what forgetting costs

MiniGrid's four-rooms environment: a 19×19 world, 260 free cells, doorways between rooms. Big enough for two of our algorithms to behave very differently from the theory.

Think

You know exactly where the goal is. So run two searches: one outward from the start, one outward from the goal, and stop when they meet. Theory says each only needs to go half the depth, so the work drops from bd to 2·bd/2. Breadth-first expanded 82 cells here. Predict the bidirectional count.

MiniGrid-FourRooms-v0 · 19×19 · 260 free cells
0
agent goal wall
—PATH COST
—STEPS
—EXPANDED · time
—PEAK FRONTIER · memory

Then select DFS. The number in the expanded box is not a typo.

Think

Depth-first search keeps its memory small by not keeping a closed set — it only checks the path it is currently on. Now put it in a big open room. What goes wrong?

8 · The full maze

All six, one map

A 19×13 maze with a direct route through deep water and a longer way round on dry ground. This is the example everything has been building toward.

Think

Before you press anything: rank the six by the cost of the path they return. Then by how many cells they expand. Then by peak memory. Write it down.

Who promised the fewest steps? Who promised the cheapest? Who promised nothing?

MapEnv · 19×13 · 118 free cells
0
agent goal water, cost 5 to enter wall
—PATH COST
—STEPS
—EXPANDED · time
—PEAK FRONTIER · memory

Every algorithm in the lecture on the same map. Use the Undiscovered view to see how much of the world each one had to reveal.

The same maze, six ways. Ties broken by a fixed neighbour order (north, east, south, west), so every number is reproducible.
AlgorithmPath costStepsExpanded Peak frontier
BFS1182611010
DFS8828296
DLS (limit 20)none—1827
IDDFS118262,5779
UCS4238949
Bidirectional118267611

9 · What each one promises

The table you have already watched

Nothing here is new. Every row is something you saw happen on a map above; the table just names it. b is the branching factor, d the depth of the shallowest goal, m the maximum depth, ℓ the limit, C* the optimal cost, ε the smallest action cost.

Completeness and optimality are worst-case guarantees, not observations about one map. An algorithm can get lucky and still have no guarantee — you watched DFS do exactly that.
AlgorithmComplete?Cost-optimal? TimeSpace
BFS Yes (b finite) Only if every step costs the same O(bd)O(bd)
DFS No (infinite depth, loops) No O(bm)O(bm)
Depth-limited No (fails if d > ℓ) No O(bℓ)O(bℓ)
IDDFS Yes (b finite) Only if every step costs the same O(bd)O(bd)
UCS Yes (costs ≥ ε > 0) Yes O(b1+⌊C*/ε⌋) O(b1+⌊C*/ε⌋)
Bidirectional Yes (BFS both ways) Only if every step costs the same O(bd/2)O(bd/2)

Three things worth saying out loud, because they are the usual mistakes. “Breadth-first is optimal” — only for steps, and only when every step costs the same, which is exactly when uniform-cost search would have done the same thing. “Iterative deepening wastes most of its work” — a constant factor, because the last round dominates; what wastes work on a grid is tree search, not deepening. “Bidirectional is twice as fast” — it is exponentially better where the frontier grows exponentially, a constant factor where it does not, and useless when you cannot name the goal state or run actions backwards.

10 · Where each one lives

The problem's shape picks the algorithm

These are not preferences. Each algorithm wins where the problem has a particular structure, and the use cases are that structure showing through.

BFS Breadth-first search

Expand the shallowest node. A queue.

Used for: Web crawling and search-engine indexing, spreading out level by level from a seed page; “degrees of separation” in a social graph; peer discovery in early peer-to-peer networks.

All those graphs are unweighted and you want the fewest hops. That is exactly BFS's guarantee, and a crawler wants breadth before depth so it does not vanish down one site.

DFS Depth-first search

Expand the deepest node. A stack.

Used for: Japanese nonograms and Sudoku; maze generation; topological sorting and cycle detection in compilers and build systems.

Constraint puzzles need any solution, not the shallowest, and every solution sits at the same depth. DFS keeps one partial assignment at a time, so memory is linear — and backtracking is exactly what you want when a partial fill becomes impossible.

DLS Depth-limited search

DFS that refuses to go past depth ℓ.

Used for: Crawling a site with a depth cap so a calendar page cannot generate links forever; fixed-ply lookahead in a game engine.

It fixes DFS's one fatal flaw, an infinite or cyclic space. The price is that you must guess ℓ — guess too low and it returns nothing even though a solution exists.

IDDFS Iterative deepening

Depth-limited search at 0, 1, 2, …

Used for: Chess and other game engines, which deepen until the clock runs out and play the best line found so far; any search where the depth is unknown and memory is tight.

BFS's guarantee at DFS's memory cost, and the repeated work is a constant factor, not an extra exponential. In a game engine the repetition is a feature: you always hold a complete answer for the depth you finished.

UCS Uniform-cost search

Expand the cheapest node. A priority queue.

Used for: Road routing where edges are distances, times or tolls; least-latency network routing; cheapest multi-leg flights.

This is Dijkstra's algorithm. The moment your actions have different costs it is the only algorithm here that returns the cheapest solution — which is why the water defeats every other one.

Bidirectional Bidirectional search

Two frontiers, meeting in the middle.

Used for: Word ladders; Rubik's cube and other puzzles with a known solved state; the shortest connection between two named people in a social graph.

It needs a specific goal state and reversible actions. Where the frontier grows exponentially, two half-depth searches are vastly cheaper than one; on a grid, where it grows like a circle's edge, the saving is only a constant.

Where depth-first search actually lives

You have watched depth-first search lose on every grid in this lecture, so it is worth seeing the one kind of problem it was made for. A nonogram: fill cells so every row and column matches its run clues. Solve it one row at a time and backtrack the moment a column clue becomes impossible.

Nonogram · 10×10 · solved row by row
0
—DFS · EXPANDED
—BFS · EXPANDED
—DFS · PEAK FRONTIER
—BFS · PEAK FRONTIER

Watch rows appear and then vanish: that is backtracking. The search commits to a filling of one row, discovers a few rows later that no legal row exists, and unwinds.

Every solution sits at exactly the same depth — one per row — so breadth-first's guarantee buys nothing, and it must hold a whole level of partial grids to get there. There are no costs, so uniform-cost search is BFS. And there is no goal state to search back from, so bidirectional search is undefined. The problem's shape leaves one tool.

11 · How long does it take?

The room gets bigger

One last environment, and the reason there is a next lecture. An open room, agent in one corner, goal in the opposite one. No walls, no water, no trick — just size.

Think

The room is 64×64: 3,844 free cells. The goal is 122 steps away in a dead straight diagonal. How many cells does breadth-first search expand before it finds it?

Where does BFS think the goal might be? Where does it not look?

MapEnv · 64×64 · 3844 free cells
0
agent goal wall
—PATH COST
—STEPS
—EXPANDED · time
—PEAK FRONTIER · memory

Play BFS to the end. Then select A* and play that. Same room, same start, same goal, same answer.

Open rooms, corner to corner. BFS work tracks the area of the room; A*'s tracks the distance. Wall-clock is this laptop, Python, single core.
RoomFree cellsPath lengthBFS expanded BFS timeA* expandedA* timeRatio
8×83610340 ms110.0 ms3×
16×16196261940 ms270.1 ms7×
32×32900588982 ms590.2 ms15×
64×643,8441223,8427 ms1230.4 ms31×
128×12815,87625015,87434 ms2510.8 ms63×
256×25664,51650664,514128 ms5071.6 ms127×
512×512260,1001018260,098564 ms1,0193.0 ms255×
1024×10241,044,48420421,044,4822,465 ms2,0436.5 ms511×
Think

Breadth-first's work goes up with the area of the room; double the side and it quadruples. At 1024×1024 it examined 1,044,482 cells and took 2.5 seconds to find a goal 2,042 steps away in a straight line. Keep doubling: a 4096×4096 room — a city block at one metre per cell — is 16,760,836 cells, and at this rate about 40 seconds; a 65,536-cell-a-side map is about 3 hours. Every algorithm in this lecture would do roughly the same. What did none of them use?

And a grid is the kind case

Cells only grow with area. Give the robot a heading and every cell becomes four states. Give it a key to carry and a door to open and they double again. Move off grids entirely — a Rubik's cube has about 43 quintillion states and a branching factor of 18 — and a blind loop that examines every state before the goal does not finish in your lifetime. Uninformed search is what you do when you know nothing about where the goal is. The next lecture is about what changes when you know a little.

Next time

Informed search. Where does that number come from, when can you trust it, and what does “trust” even mean for a guess? That is A*. Then: what happens when the map changes while you are walking it — a door closes, a wall appears — and you would rather repair a plan than throw it away. That is D*.

And one debt from the very first page: the real MiniGrid robot has a heading, and turning costs a step. On the nine-cell room, the straight-line distance says 4 and the true cost is 5. The homework opens with exactly that room, and by the end of it you will know why that missing 1 changes which heuristic you are allowed to use.