Graph traversal: BFS and DFS
Breadth-first explores level by level with a queue; depth-first plunges with a stack; both visit every reachable vertex in O(V + E).
Searching a list is easy because there is only one way to move. In a graph you must choose a direction at every node, and the choice determines the order in which you see things. There are two canonical strategies: explore broadly with a queue, or dive deeply with a stack. Everything else is a variation.
The only difference is what you take next
Both algorithms keep a container of discovered-but-unvisited nodes. BFS takes the oldest (a queue); DFS takes the newest (a stack). Swap the container and you swap the exploration order — the code is otherwise the same.
Pick a starting node and step through both orders on the same graph.
Traverse the same graph with a queue and with a stack
queue (front left)
B , C
visit order
A
Step 1 of 6.
BFS reaches the nearest nodes first, which is what you want for shortest paths in an unweighted graph. DFS commits to a branch and backtracks, which suits cycle detection and topological order. Both mark a node the moment they first reach it, or they revisit forever in a graph with cycles.
Breadth-first search
BFS visits every node at distance before any node at distance , so it finds the shortest path in unweighted graphs — measured in number of edges. It uses a queue and records each node's distance when first discovered:
from collections import deque
def bfs(graph, start):
seen = {start}
queue = deque([start])
order = []
while queue:
node = queue.popleft()
order.append(node)
for neighbour in graph[node]:
if neighbour not in seen:
seen.add(neighbour)
queue.append(neighbour)
return orderMark a node as seen when you enqueue it, not when you dequeue it; otherwise a node can be queued several times before it is first processed.
Depth-first search
DFS follows one branch as far as it goes, then backtracks. It is naturally recursive — the call stack is the stack — or written iteratively with an explicit stack. It is the right tool for:
- detecting cycles,
- topological sorting a directed acyclic graph,
- finding connected components,
- solving mazes and puzzles where any path will do.
def dfs(graph, node, seen=None):
if seen is None:
seen = set()
seen.add(node)
for neighbour in graph[node]:
if neighbour not in seen:
dfs(graph, neighbour, seen)
return seenCost and correctness
Both run in time: every vertex enters the container once, and every edge is examined once for each endpoint. That is linear in the size of the graph, which is optimal — you cannot do better than looking at the input. The only correctness trap is the cycle: without a visited set, traversal loops forever.
Weighted graphs need more than BFS
BFS finds the fewest edges, not the smallest total weight. When edges carry weights, the shortest path is Dijkstra's algorithm (a priority queue instead of a plain queue), and with negative weights you need Bellman-Ford. Using BFS on a weighted graph gives a confidently wrong answer.
Illustrative vs real
The demo graph has six nodes and is small enough to draw. Real graphs have millions and are stored as adjacency lists; recursion depth can exceed Python's limit on long paths, so production DFS is often iterative. The bound and the queue-versus-stack distinction are unchanged.
Check yourself
Eduspheria wiki · Programming & Data Structures, Algorithms
0 / 5 answered
From the assignment paper
Modeled on NITJ AI-507, Assignment/Quiz
0 / 5 answered
Where next: string matching — searching for a pattern inside a text, with the same halving and re-use ideas in a different guise.