Depth-first search (DFS)
Go as deep as possible along one path from the source, and when stuck, back up and try another way.
Depth-first search goes as far as it can along one path from the source. When the current node has no unseen neighbour left (a dead end), it is finished and DFS backs up one step to try another way from the previous node.
Read the full explanation ↓- Time:
O(V + E) - Space:
O(V) for the recursion - CLRS, section 20.3
- Horowitz–Sahni, chapter 6
A six-node directed graph with a cycle; DFS from U goes deep, hits a dead end and backs up.
def dfs(graph, src):seen = set()parent = {v: None for v in graph}order = []visit(graph, src, seen, parent, order)return order, parentdef visit(graph, u, seen, parent, order):seen.add(u)order.append(u)for v, _ in graph[u]:if v not in seen:parent[v] = uvisit(graph, v, seen, parent, order)return
Start
- Visited
- Current node
(u) - Search tree (from parent)
- Edge being checked
Recursion tree
Filled orange = running call · Orange outline = still on the stack · Grey = finished
Local variables
Orange = changed in this step
graph dict
- 'U': [('V', 1), ('X', 1)]
- 'V': [('Y', 1)]
- 'W': [('Y', 1), ('Z', 1)]
- 'X': [('V', 1)]
- 'Y': [('X', 1)]
- 'Z': []
Call stack
- dfs()line 1
How the work grows with input size
Average of 5 random graphs per n (fixed seeds). Hollow marker = the input on screen.
- check
- discover
- current input (n=6)
Show as a table
| n | check | discover |
|---|---|---|
| 4 | 5.2 | 4 |
| 8 | 13.8 | 8 |
| 12 | 20.8 | 12 |
| 16 | 28 | 16 |
| 20 | 35.4 | 20 |
| 24 | 37.2 | 24 |
| 28 | 47.2 | 28 |
Explanation
The idea
Depth-first search goes as far as it can along one path from the source. When the current node has no unseen neighbour left (a dead end), it is finished and DFS backs up one step to try another way from the previous node.
Recursion does the backing up for free: each visit(u) calls visit(v) for a new neighbour, and when that returns,
the work on u continues. So the call stack is exactly the path from the source to the current node.
The steps
- Add
utoseen(it is discovered). - For each neighbour
vofu: ifvis unseen, recordparent[v] = uand callvisit(v). - When every neighbour has been checked,
uis finished; return.
Cost
Every node is discovered once and every edge is checked once (twice in an undirected graph): O(V + E). The
recursion can be V deep in the worst case; see the "chain" input.
Uses
DFS is the basis of many graph algorithms: finding cycles, topological sort, strongly connected components and cut vertices.
What to watch in the trace
- Black nodes have been seen, and the orange node is
uin the running call. - The blue edges are the DFS tree (from
parent). - The "recursion tree" is exactly the DFS tree: watch the stack grow deep and unwind.
- Compare BFS and DFS on the same graph: BFS spreads wide, DFS goes deep.