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 2∣V∣2|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: 1→21 \to 2, 1→31 \to 3, 2→32 \to 3, 2→42 \to 4, 3→53 \to 5, 4→54 \to 5, 5→65 \to 6, 5→85 \to 8, 6→76 \to 7, 7→67 \to 6, 8→48 \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 Θ(∣V∣2)\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).