ABSTRACT
Breadth-First Search (BFS) is the primary algorithm for finding the shortest path in an unweighted graph. It explores a graph layer-by-layer, ensuring that it visits every node at distance before moving on to any node at distance . While DFS explores “deep,” BFS explores “wide.”
- Category: Graph Traversal
- Input: Graph , source vertex
- Output:
dist(v)for every vertex — the shortest number of edges from to- Paradigm: Iterative, FIFO-queue-based frontier expansion
- Typical use cases: shortest path on unweighted graphs, level-order traversal, “minimum number of hops” problems
Core Logic: Layer-by-Layer Exploration
The intuition behind BFS is similar to a ripple in a pond. Starting from a source node, the search expands outward in concentric circles:
- Level 0: The starting node .
- Level 1: All immediate neighbors of .
- Level 2: All neighbors of Level 1 nodes that haven’t been visited yet.
Key Idea
BFS is structured as a single iterative procedure rather than a recursive one because the level-by-level expansion follows a FIFO order, which maps naturally to a queue rather than a call stack. The outer loop continuously dequeues the earliest-discovered vertex and enqueues its unvisited neighbors, ensuring every vertex at distance is processed before any vertex at distance . This ordering is what guarantees shortest paths on unweighted graphs.
Why doesn't an "early out" improve worst-case time complexity?
Including an “early out” doesn’t change the worst-case complexity because, in the worst case, the destination node is the very last node visited (or is unreachable), forcing the algorithm to traverse the entire graph anyway.
Pseudocode (Mid-Level Implementation)
Full Graph BFS
Algorithm 24 Breadth First Search
procedure BFS()
for each vertex do
dist() =
dist() =
//queue that just containing
while is not empty do
= dequeue()
for all edges do
if dist() = then
enqueue()
dist() = dist() + 1
Variables & Data Structures
| Name | Type | Purpose |
|---|---|---|
Q | Queue (FIFO) | Holds the current frontier of discovered-but-unprocessed vertices |
dist | Array (vertex → int) | Distance from the starting vertex to the given vertex; until discovered |
u, v | Vertex | Current vertex being dequeued / candidate neighbor |
Helper Functions / Operations Used
dequeue(Q)— pops and returns the earliest inserted vertex; O(1) with an array/linked-list backed queueenqueue(Q, v)— inserts vertex into the queue ; O(1) amortizeddist(v)— the distance between the starting vertex and ; each vertex’sdistis set at most once, which is what keeps the algorithm at
Proof of Correctness
For each vertex , we want to show that is the minimum distance of all paths from to . Prove by induction on distance .
Claim: For each distance value , there is a moment in the algorithm when:
- All vertices at distance from have their distance values correctly set.
- All other vertices (distance from ) have distances set to .
- The queue contains exactly the nodes at distance .
Base Case ():
- is the correct distance value (the only vertex at distance from is itself).
- All other vertices have distances set to (initialization step).
- The queue contains only , which is the only vertex at distance .
Inductive Step: Let be arbitrary. Assume the claim holds for — all vertices at distance have been set correctly, and the queue contains exactly the vertices at distance .
Suppose is the next vertex popped from the queue (so ), and let be a neighbor of :
- If , then by the inductive hypothesis has already been set correctly, and it is not updated again.
- If , then . This is correct: since was unreachable before going through , and is the minimum distance from to , the minimum distance from to must be .
Therefore, after this step:
- All new vertices added to the queue have distance and are set correctly.
- All vertices at distance have been added to the queue.
- The queue contains exactly the nodes at distance .
This completes the induction — every vertex’s dist value equals its true shortest distance from .
Time & Space Complexity Analysis
General Case
| Complexity | Notes | |
|---|---|---|
| Time | Each vertex enters the queue at most one time, and each edge is examined at most twice (once from each endpoint) | |
| Space | dist array + queue holding the current frontier in the worst case |
Notice
In BFS, each vertex enters the queue at most one time — this is the assumption used when calculating the runtime for Graph Search in general, and it’s what keeps
dist(v)from being set more than once per vertex.
Implementation-Dependent Variations
Memory usage depends on the shape of the graph, since BFS must hold the entire current frontier in the queue at once:
| Shape | DFS Memory | BFS Memory |
|---|---|---|
| Wide, shallow | → small | → large |
| Narrow, deep | → large | → small |
- Wide, shallow graph — BFS’s frontier (queue) can balloon to hold most of the graph at once → large memory
- Narrow, deep graph — BFS only ever holds a thin frontier → small memory (DFS is the one that struggles here instead, storing the entire long path)
Best / Worst / Average Case
- Best / Worst / Average case: All — a full BFS from must enqueue and dequeue every reachable vertex exactly once and scan every incident edge, so there’s no meaningfully better/worse case unless searching for a specific target vertex with early exit (which, per the note above, doesn’t change the worst case).
Drawbacks / Constraints
- Preconditions: Requires accessible adjacency info for each vertex; assumes a FIFO queue implementation to preserve level-order correctness.
- Only works for shortest distance on graphs where each edge has equal weight. BFS’s correctness proof relies on every edge contributing exactly to distance.
- Not suitable for: Weighted graphs. One can attempt to force BFS to work by forming — adding new vertices between and for every edge — and running BFS on , but this is impractical when edge weights are large integers (the graph blows up in size).
- Alternatives to consider: Use Dijkstra’s Algorithm for weighted graphs with non-negative weights; Bellman-Ford if negative weights are present.