11.3 BFS, DFS, and Shortest Paths
Section 11.2 found a connected component by repeatedly expanding from reached vertices. An algorithm must make one more choice: when several reached vertices still have unexplored neighbors, which one should be processed next?
Two different answers produce breadth-first search and depth-first search. Both reach exactly the vertices in the start vertex's component, but their exploration shapes are different.
Search state: discovered, processed, and frontier
Fix a start vertex . During a graph search, each vertex is in one of three conceptual states:
- undiscovered: the search has not reached it;
- discovered: the search has reached it but may not have inspected all its neighbors;
- processed: all its neighbors have been inspected.
The frontier stores discovered vertices waiting to be processed. Mark a vertex discovered when it first enters the frontier, not when it later leaves. Otherwise, two vertices could add the same neighbor repeatedly.
Whenever a vertex is first discovered from , record as its predecessor, written . The predecessor edges form a tree rooted at , called a search tree. This does not claim the original graph is a tree; it records only the first-discovery route to each reached vertex.
To make every trace reproducible, we will inspect each adjacency list in alphabetical order. A different neighbor order may produce a different valid search tree.
Breadth-first search uses a queue
A queue is a first-in, first-out structure: the earliest inserted item leaves first. Breadth-first search (BFS) stores its frontier in a queue.
Starting at :
1. mark discovered and enqueue it;
2. dequeue the front vertex ;
3. inspect each neighbor of ;
4. whenever is undiscovered, mark it discovered, set , and enqueue it;
5. mark processed and repeat until the queue is empty.
Consider the adjacency lists
| Vertex | Neighbors in inspection order |
|---|---|
BFS from evolves as follows:
| Processed vertex | Newly discovered | Queue afterward |
|---|---|---|
| none | ||
| none | ||
| none |
The resulting layers are
Every vertex in is exactly edges from along a shortest path.
Why BFS finds unweighted shortest paths
In an unweighted graph, the distance is the minimum number of edges in any path from to . If no path exists, define .
BFS processes all vertices at distance before any vertex at distance . When an edge from layer first discovers , it creates a path of length . A shorter path would have had to reach from an earlier layer, so would already have been discovered.
Following predecessor pointers backward reconstructs a shortest path:
Reverse that sequence to obtain the path from to .
Depth-first search uses a stack
A stack is a last-in, first-out structure: the most recently inserted item leaves first. Depth-first search (DFS) uses a stack, either explicitly or through recursive function calls.
DFS follows one route as far as possible. When the current vertex has no undiscovered neighbor, it backtracks to the previous vertex and tries the next neighbor.
Using the same alphabetical adjacency order, one possible DFS discovery sequence from is
The route first dives , backtracks to , and then continues . DFS builds a valid search tree but does not generally give shortest paths. In the example, it reaches through a long tree route even though edge exists.
DFS is useful when the shape of deep dependencies matters—for example, detecting cycles, ordering prerequisites, or exploring every branch of a puzzle. BFS is the natural choice when the number of unweighted steps matters.
Running time with adjacency lists
Chapter 5 introduced asymptotic running time. In either BFS or DFS, every vertex is discovered at most once. With adjacency lists, every undirected edge appears in two neighbor lists, so it is inspected at most twice. Therefore
With an adjacency matrix, inspecting all possible neighbors of every reached vertex can take time, even for a sparse graph. The representation affects the algorithm's cost.
Run BFS and DFS explorers through the same network. Predict the next processed vertex, inspect the live queue or stack, replay backtracking, and compare the two search trees and discovered routes.
Weighted shortest paths need accumulated cost
BFS minimizes the number of edges. It does not minimize total weight when edges have different costs. A two-edge route of weights and costs , while a one-edge route of weight costs .
For a weighted path
define its total weight by
The weighted distance from to is the minimum over all -to- paths.
Tentative distances and relaxation
Dijkstra's algorithm solves the single-source shortest-path problem when every edge weight is nonnegative.
It maintains a tentative distance , the best path cost to found so far. Initially,
Suppose the algorithm knows a route to of cost and inspects edge . Traveling through would give a route to of cost . The update
is called relaxing edge . If the second value is smaller, also set .
Dijkstra's settled frontier
A vertex is settled when its tentative distance is declared final. Dijkstra repeats:
1. choose the unsettled vertex with the smallest ;
2. settle ;
3. relax every edge from to an unsettled neighbor;
4. stop when every reachable vertex is settled, or when the desired target is settled.
For edges
start at . First settle , giving and . Settle next; relaxing improves from to , and gives . Settle next; improves from to . Then settle and obtain .
The predecessor chain for is
so a shortest path is with total weight .
Why nonnegative weights matter
When Dijkstra settles the smallest tentative distance , every unsettled route must first reach some unsettled vertex with tentative cost at least . Nonnegative remaining edges cannot reduce that cost below .
A negative edge breaks this reasoning. A route discovered later might use a negative weight to become cheaper than an already settled route. Dijkstra's algorithm must therefore reject graphs with negative edge weights. Other algorithms can handle some negative-weight graphs, but they are beyond this section.
A simple array implementation scans all vertices to find the smallest unsettled tentative distance and runs in time. A priority queue is a structure that removes the item with the smallest key; using one can avoid scanning every vertex on sparse graphs.
Dispatch weighted rescue routes by choosing which tentative vertex to settle and which edges to relax. The map preserves old and improved labels, reconstructs the predecessor route, and includes a negative-edge incident that exposes exactly why Dijkstra's guarantee fails.
Choosing the right search
Use BFS to minimize the number of edges in an unweighted graph. Use DFS to explore deeply, backtrack, and reveal structural dependencies. Use Dijkstra to minimize accumulated nonnegative edge weight.
Section 11.4 changes the scale of the optimization. Instead of finding one shortest route, it asks which set of edges connects every vertex with no redundancy and, in a weighted graph, with the least total cost.