A small maze, explored from its top-left cell. Watch the search spread one layer at a time, with its queue below.
Spreading out one layer at a time
A breadth-first search through a small maze, with its queue below
void bfs(int s){ int ring[V]; int head = 0, count = 0; for (int v = 0; v < V; v++) { dist[v] = -1; parent[v] = -1; } dist[s] = 0; ring[(head + count) % V] = s; count++; while (count > 0) { int u = ring[head]; head = (head + 1) % V; count--; for (const struct enode *e = adj[u]; e != NULL; e = e->next) { if (dist[e->to] < 0) { dist[e->to] = dist[u] + 1; parent[e->to] = u; ring[(head + count) % V] = e->to; count++; } } }} int main(void){ const char *maze[ROWS] = { "...#.", ".#...", ".###.", "....." }; build(maze); bfs(START); for (int r = 0; r < ROWS; r++) { for (int c = 0; c < COLS; c++) { if (maze[r][c] == '#') printf(" #"); else printf(" %d", dist[r * COLS + c]); } printf("\n"); } int path[V]; int len = 0; for (int v = GOAL; v != -1; v = parent[v]) { path[len] = v; len++; } printf("route:"); for (int i = len - 1; i >= 0; i--) printf(" %d", path[i]); printf("\n"); for (int v = 0; v < V; v++) {A 4 × 5 maze: # is a wall. Cell r · 5 + c is a vertex; 15 cells are open.
- reached
- 0 of 15
- in the queue
- 0
Output
Describe this picture
The picture has two parts, one above the other. On top is the maze: 4 rows of 5 square cells, numbered 0 to 19 row by row from the top left, with 5 dark wall cells and 15 open ones. Under it is a strip of 20 boxes, ring, the search’s queue, with a “head” marker above it and a “tail” marker below; the boxes between them are lit, the others greyed. Under the picture are the lines of maze.c the clip runs, with the line about to run marked, one sentence on the current step, two readouts (the cells reached and the cells waiting in the queue), the control, and the output. The clip plays once, about 23 seconds. First every cell is marked unreached. Then cell 0 gets the number 0 and enters the ring. Again and again the cell at the head is taken out and ringed in the maze, the head marker moves one box right, and each open neighbour not yet reached gets a number one larger, turns to the frontier tone, and lands in the box at the tail. The numbers spread outwards in rings: 1 on cells 1 and 5, 2 on cells 2 and 10, and so on to 7 on cell 19, the far corner. Taken cells change to the visited tone. When the queue is empty, the route to cell 4, the goal at the top right, is read back along the parents, a cell at a time, as a line through the cells 4, 9, 8, 7, 2, 1 and 0. The clip rests on the output, the maze’s distances and route: 0 1 2 7 8 9 4. The control, “Start cell”, is a slider over the 15 open cells: a new start replays the search from there. The transport plays and pauses, steps one beat back or forward, and scrubs; a paused step is kept in the page address as bfs.step, and the start as bfs.start.
A wave from the start
Breadth-first search explores a graph from a start vertex in layers: first the start, then every vertex one edge away, then every vertex two edges away, and so on. Here the graph is a maze. Each open cell is a vertex, numbered r · 5 + c, and two open cells side by side or one above the other are joined by an edge. The maze’s lists are built as in storing a graph, an arc each way for each pair of open neighbours.
The search keeps a queue, first in, first out, as in ring buffers. It takes the vertex at the front, and puts each of its neighbours that has not been reached yet at the back.
The code and its cost
The queue and the marks
bfs keeps three arrays of 20: dist, a vertex’s distance in edges or −1 while it is unreached; parent, the vertex it was reached from; and ring, the queue.
dist[s] = 0;
ring[(head + count) % V] = s;
count++;
while (count > 0) {
int u = ring[head];
head = (head + 1) % V;
count--;
for (const struct enode *e = adj[u]; e != NULL; e = e->next) {
if (dist[e->to] < 0) {
dist[e->to] = dist[u] + 1;
parent[e->to] = u;
ring[(head + count) % V] = e->to;
count++;
}
}
}
A vertex’s distance is set when it is queued, not when it leaves the queue. That is what stops a second neighbour from queueing it again: by then its dist is no longer −1. So each vertex enters the queue at most once, and a ring of V cells never fills.
Layers
The queue holds at most two layers at once: the end of layer k, then the start of layer k + 1 behind it. So the vertices leave in order of distance, every vertex of layer k before any of layer k + 1. From cell 0 the layers are:
| Distance | Cells |
|---|---|
| 0 | 0 |
| 1 | 1, 5 |
| 2 | 2, 10 |
| 3 | 7, 15 |
| 4 | 8, 16 |
| 5 | 9, 17 |
| 6 | 4, 14, 18 |
| 7 | 19 |
The first time a vertex is reached, it is reached by a shortest route in edges, and nothing later improves its number. BFS does not try every route: it reaches each vertex once.
The tree of parents
Each reached vertex but the start has one parent, so the parents form a tree. Following them from any vertex leads back to the start by a shortest route. main reads the route to the goal, cell 4, backwards into path, then prints it from the start:
0 1 2 # 6
1 # 3 4 5
2 # # # 6
3 4 5 6 7
route: 0 1 2 7 8 9 4
The goal has one open neighbour, cell 9, so its route is the only shortest one.
The cost
Each vertex is queued once and taken once, and each list is walked once, when its vertex is taken. The lists of an undirected graph hold 2E nodes, one for each end of each edge, so that is V + 2E steps in all: O(V + E), for a graph stored as lists.
When it is not enough
BFS finds the route with the fewest edges. When edges have weights, such as road lengths, the fewest edges is not always the least total, and Dijkstra’s algorithm is needed.
Running it yourself
gcc -std=c11 -Wall -Wextra -pedantic maze.c -o maze
./maze
gcc -std=c11 -Wall -Wextra -pedantic -DSTART=19 maze.c -o maze
./maze
Worked example
The house graph. On the graph of storing a graph, from 0, visiting neighbours smallest first: the distances of vertices 0 to 5 are 0 1 1 2 2 3, the parents of 1 to 5 are 0 0 1 2 4, and the route to 5 is 0 2 4 5.
Another start. From cell 19, the goal is only 3 steps away, 19, 14, 9, 4, though 19 is the farthest cell from 0.
A large grid. On an open 1,000 × 1,000 grid, BFS from a corner reaches all 1,000,000 cells, each once, and the far corner at distance 1,998: 999 steps across and 999 down.
Two checks. Which cells are at distance 6 from 0? (4, 14 and 18.) Why is dist set when a vertex is queued, not when it leaves? (So that no later neighbour queues it again.)
Where it’s used
Routes with equal steps. Moving through a grid or a network where each step costs the same.
Puzzles. Each position of a puzzle is a vertex and each move an edge; BFS finds a solution in the fewest moves.
Social networks. Everyone within two links of a person is the first two layers from them.
Flood fill. A paint program’s fill spreads from the clicked pixel to every touching pixel of the same colour.
Reference card
| Code or idea | Meaning |
|---|---|
dist[v] = -1 | unreached |
| take from the head, queue at the tail | the queue, first in, first out |
dist[e->to] = dist[u] + 1 when queued | each vertex enters the queue once |
| layers | all of distance k before any of k + 1 |
parent[v] | the tree of shortest routes back to the start |
| V + E steps | the cost with adjacency lists |
Next, depth-first search goes deep before it goes wide.
Platform: C11, gcc on x86-64 Linux: int 4 bytes, pointers 8, little-endian. Addresses in the pictures are short stand-ins, by one rule for the whole course; sizes are the real ones.