Part II · Undirected graphs · week 1
Depth-first search
Mark a vertex, then recur into any unmarked neighbour. One call per vertex, two looks per edge — and the recursion stack is the path you took to get here.
Run the animation, step by step → generated live from any input you type — nothing is pre-recorded
Cost and properties
| time | O(V + E) |
|---|---|
| space | O(V) |
| finds | connectivity, paths, cycles, bridges |
| path found | not the shortest |
Reference: Sedgewick & Wayne, §4.1; Trémaux 1882.
Depth-first search in Python
Runnable as-is, and written for reading. Watch the highlighted line move through it in the animation, or send it straight to the visualizer — it arrives with a test case ready to run, yours to edit.
class DepthFirstPaths:
# Marks every vertex reachable from s, and records the edge that first
# reached each one -- edge_to[] is a TREE rooted at s.
def __init__(self, graph, s):
self.marked = [False] * graph.V
self.edge_to = [None] * graph.V
self.s = s
self._dfs(graph, s)
def _dfs(self, graph, v):
self.marked[v] = True # visit v
for w in graph.adj(v):
if not self.marked[w]: # every edge is looked at twice
self.edge_to[w] = v # remember how we reached w
self._dfs(graph, w) # ... and go deeper immediately
def has_path_to(self, v):
return self.marked[v]
def path_to(self, v): # walk edge_to back to the source
if not self.marked[v]:
return None
path, x = [], v
while x != self.s:
path.append(x)
x = self.edge_to[x]
path.append(self.s)
return list(reversed(path))
Why it works
The whole algorithm in two lines
Mark v. For each unmarked neighbour w, record how you got there and recur. The marked[] array is what stops the recursion looping forever, and edge_to[] quietly builds a spanning tree of everything reachable from s: follow it backwards from any vertex and you have a path to the source.
Why it is linear
_dfs runs at most once per vertex (the mark guarantees that), and inside it every entry of every adjacency list is examined once. Each undirected edge appears in two lists, so it is looked at twice: total work O(V + E). Note that is V plus E, not V times E — the adjacency-list representation is what makes it so.
What the recursion stack means
The stack holds exactly the vertices on the current path from s. That is why DFS is the natural tool for questions about paths and structure: cycle detection (an edge back to a vertex still on the stack), bridges and articulation points, planarity, and Euler tours. It is also the classic maze-solving rule — Trémaux's, from 1882, string and all.
What it is not
DFS paths are not shortest. Run the a cycle preset: reaching vertex 5 from 0 takes the long way round. If you want shortest paths in an unweighted graph, the only change needed is to replace the recursion with a queue — that is BFS.
DFS also recurses as deep as the longest path, so on a large graph the real implementation must use an explicit stack or it will overflow.
What to try in the animation
Undirected edges v-w. Adjacency lists are visited in ascending order, so the traversal is reproducible.
The animation is generated from the input box, in your browser — press Animate after editing it. To execute the Python itself, use Open in Visualizer above.
- tinyCG (course) —
vertices: 6 · edges: 0-5 2-4 2-3 1-2 0-1 3-4 3-5 0-2 · source: 0 - tinyG — 3 components —
vertices: 13 · edges: 0-5 4-3 0-1 9-12 6-4 5-4 0-2 11-12 9-10 0-6 7-8 9-11 5-3 · source: 0 - a long path —
vertices: 8 · edges: 0-1 1-2 2-3 3-4 4-5 5-6 6-7 · source: 0 - a cycle —
vertices: 6 · edges: 0-1 1-2 2-3 3-4 4-5 5-0 · source: 0
The rest of Undirected graphs
- Breadth-first search Swap the recursion for a queue and vertices come off in order of distance.
- Connected components Run DFS from each vertex no earlier search reached.