Breadth-First Search (BFS)
Explores a graph layer by layer from the start and finds shortest paths in unweighted graphs.
#How does it work?
Breadth-first search (BFS) visits a graph's nodes layer by layer, in order of their distance from the start: first all neighbours, then the neighbours' neighbours, and so on.
A queue gives this order: discovered nodes join the back, and the next node is taken from the front. A visited set makes sure no node is added twice.
In an unweighted graph, the path on which BFS first discovers a node is a shortest path to it. Record where each node was discovered from (prev) and you can trace the path back from the target.
#Try it yourself
Adjacency list
- 1: 2, 3
- 2: 1, 4, 5
- 3: 1, 5, 6
- 4: 2, 7
- 5: 2, 3, 7
- 6: 3, 7
- 7: 4, 5, 6
Pick a start node and watch BFS explore the graph layer by layer. Give two nodes to find a shortest path.
#Step by step
- 01Put the start node in the queue and mark it visited.
- 02Until the queue is empty, take a node (u) from the front.
- 03For each neighbour v of u not seen yet: mark it, set prev[v] = u and add it to the back of the queue.
- 04For a shortest path: stop when the target leaves the queue and follow prev[] from the target back to the start.
#Example code
The same algorithm in 8 languages. Every program is compiled, run and checked to print the same output.
from collections import deque
# Undirected graph as adjacency lists (neighbours in increasing order).
EDGES = [(1, 2), (1, 3), (2, 4), (2, 5), (3, 5), (3, 6), (4, 7), (5, 7), (6, 7)]
graph = {v: [] for v in range(1, 8)}
for a, b in EDGES:
graph[a].append(b)
graph[b].append(a)
for v in graph:
graph[v].sort()
def bfs(start: int) -> list:
order, visited, queue = [], {start}, deque([start])
while queue:
u = queue.popleft()
order.append(u)
for v in graph[u]:
if v not in visited:
visited.add(v) # mark when queued, so nothing is queued twice
queue.append(v)
return order
def shortest_path(start: int, target: int) -> list:
prev, queue = {start: None}, deque([start])
while queue:
u = queue.popleft()
if u == target:
break
for v in graph[u]:
if v not in prev:
prev[v] = u
queue.append(v)
if target not in prev:
return []
path = [target]
while prev[path[-1]] is not None:
path.append(prev[path[-1]])
return path[::-1]
print(" ".join(map(str, bfs(1)))) # layer by layer
print(" ".join(map(str, shortest_path(1, 7)))) # fewest edges from 1 to 7#Complexity
| Operation | Time |
|---|---|
| Time (adjacency list) | O(V + E) |
| Time (adjacency matrix) | O(V²) |
| Memory (queue + visited) | O(V) |
| Shortest path (unweighted) | O(V + E) |
V is the number of nodes and E the number of edges. Each node enters the queue at most once and each edge is looked at once from each end. With different edge weights BFS can't find shortest paths; that takes Dijkstra.
#When to use it?
Shortest paths in unweighted graphs (mazes, game maps, "how many steps" questions), friend-of-a-friend suggestions in social networks, web crawlers exploring layer by layer, broadcasting in a network and checking whether a graph is bipartite all use BFS.
#Practice
Log in to answer questions and save your progress.
Easy
Which data structure does BFS use to hold the next nodes?
- 1A queue
- 2A stack
- 3A heap
- 4A hash table
What is BFS's time complexity with adjacency lists?
- 1O(V + E)
- 2O(V · E)
- 3O(V²)
- 4O(E log V)
In which graphs does BFS guarantee shortest paths?
- 1Unweighted (or equal-weight) graphs
- 2Graphs with negative weights
- 3Any weighted graph
- 4None
Medium
Starting BFS at 1 on edges 1–2, 1–3, 2–4, 3–4, in which order are nodes visited (neighbours in increasing order)?
- 11, 2, 3, 4
- 21, 2, 4, 3
- 31, 3, 4, 2
- 41, 4, 2, 3
Hard
Why is a node marked visited when it is queued rather than when it is dequeued?
- 1So the same node can't enter the queue more than once
- 2Otherwise BFS gives wrong distances
- 3To look at fewer edges
- 4To keep the queue sorted
#Up next
Lessons that build on this one: