Skip to content
Part IA Lent Term

Depth-First Search: Algorithm

Overview

Depth-First Search (DFS) explores as deeply as possible along each branch before backtracking. It is typically implemented recursively (the call stack acts as the LIFO stack). DFS produces a depth-first forest and assigns two integer timestamps per vertex: discovery time and finish time.

Unlike BFS, DFS does not require a source vertex; it discovers all vertices in the graph by restarting from any unvisited vertex until all have been explored.

Pseudocode

DFS(G):
  for each v in G.V:
    v.colour = WHITE
    v.π = NIL
  time = 0
  for each v in G.V:
    if v.colour == WHITE:
      DFS-VISIT(G, v)

DFS-VISIT(G, u):
  time = time + 1
  u.d = time              // discovery time
  u.colour = GREY
  for each v in G.Adj[u]:
    if v.colour == WHITE:
      v.π = u
      DFS-VISIT(G, v)
  u.colour = BLACK
  time = time + 1
  u.f = time              // finish time

Timestamps

Each vertex gets two timestamps:

  • d[v]d[v] (discovery time): when the vertex is first encountered (colour changes white → grey).
  • f[v]f[v] (finish time): when all descendants have been explored (colour changes grey → black).

Timestamps are integers from 1 to 2V2|V|. Every vertex is discovered exactly once and finished exactly once.

Worked Example

Consider a directed graph: V={1,2,3,4,5,6,7,8}V = \{1,2,3,4,5,6,7,8\} with edges: 121 \to 2, 131 \to 3, 232 \to 3, 242 \to 4, 353 \to 5, 454 \to 5, 565 \to 6, 585 \to 8, 676 \to 7, 767 \to 6, 848 \to 4.

Run DFS starting from vertex 1 (assume adjacency lists in numerical order):

Vertex12345678
Discover dd12345689
Finish ff161510712111413

The timestamps reveal ancestry: vertex 2 is a descendant of 1 because d[1]=1<d[2]=2<f[2]=15<f[1]=16d[1]=1 < d[2]=2 < f[2]=15 < f[1]=16.

Parenthesis Theorem

For any two vertices uu and vv, exactly one of the following holds:

  1. [d[u],f[u]][d[u], f[u]] and [d[v],f[v]][d[v], f[v]] are disjoint: neither is an ancestor of the other.
  2. [d[u],f[u]][d[u], f[u]] is fully contained within [d[v],f[v]][d[v], f[v]]: uu is a descendant of vv.
  3. [d[v],f[v]][d[v], f[v]] is fully contained within [d[u],f[u]][d[u], f[u]]: vv is a descendant of uu.

This theorem implies: vv is a proper descendant of uu in the depth-first forest iff d[u]<d[v]<f[v]<f[u]d[u] < d[v] < f[v] < f[u].

Complexity

Θ(V+E)\Theta(|V| + |E|) with adjacency lists. Each vertex is discovered once, and the for loop in DFS-VISIT iterates over each adjacency list entry exactly once (or twice for undirected graphs). With an adjacency matrix, the cost becomes Θ(V2)\Theta(|V|^2).

DFS Forest

When DFS restarts from an unvisited vertex in the outer loop, it begins a new tree. The set of all trees forms the depth-first forest. For an undirected graph, the number of DFS trees equals the number of connected components.

Comparison: BFS vs DFS

PropertyBFSDFS
Data structureQueue (FIFO)Stack (LIFO) / recursion
Source requiredYes (ss)No
Distance propertyShortest unweighted pathsTimestamps for ordering
Tree typeBreadth-first treeDepth-first forest
Edge classificationNot producedTree/back/forward/cross
ApplicationsShortest paths, bipartite testTopological sort, SCC, cycle detection

Summary

AspectDetail
Traversal orderDeepest-first, then backtracks
ColoursWhite → Grey → Black
Timestampsd[v]d[v] (discovery), f[v]f[v] (finish)
Parenthesis theoremIntervals are nested or disjoint
Time (adj. lists)Θ(V+E)\Theta(V+E)
OutputDF forest, timestamps, edge classification

Past paper questions: y2026p1q10 (topological sort via DFS), y2025p1q9(b)(i) (connected components).