Introduction

Depth-first search (DFS) explores a graph by going as deep as possible along each path before backtracking. It assigns every vertex a discovery time d[u]d[u] and a finish time f[u]f[u], and it classifies every edge as a tree, back, forward or cross edge. Those timestamps and edge types are the foundation of topological sort and strongly connected components.

Where BFS expands by layers with a queue, DFS expands by a stack (explicit or the call stack). Both run in Θ(V+E)\Theta(V+E) time on adjacency lists; they answer different questions.

Prerequisites

Core concepts

Timestamps

A global clock time starts at 0. When DFS first discovers uu it sets d[u]d[u] and paints uu grey; when it finishes scanning Adj[u]Adj[u] it sets f[u]f[u] and paints uu black. Every vertex gets a unique pair with d[u]<f[u]d[u] < f[u], and the intervals [d[u],f[u]][d[u], f[u]] nest or are disjoint (parenthesis theorem).

Edge classification (directed graphs)

When DFS examines edge (u,v)(u,v):

TypeCondition when examinedTimestamps after DFSMeaning
Treevv whited[u]<d[v]<f[v]<f[u]d[u] < d[v] < f[v] < f[u]vv discovered from uu; (u,v)(u,v) in the DFS forest
Backvv greyd[v]≤d[u]<f[u]≤f[v]d[v] \le d[u] < f[u] \le f[v]vv is an ancestor of uu (cycle witness)
Forwardvv black and d[u]<d[v]d[u] < d[v]d[u]<d[v]<f[v]<f[u]d[u] < d[v] < f[v] < f[u]vv is a descendant already finished
Crossvv black and d[u]>d[v]d[u] > d[v]d[v]<f[v]<d[u]<f[u]d[v] < f[v] < d[u] < f[u]vv is in another subtree or earlier tree

The timestamp column (CLRS Exercise 22.3-5) classifies an edge after the search has finished. Tree and forward edges have the same timestamp pattern; they differ only in whether vv was discovered through this edge. A cross edge always points to a vertex that was discovered and finished earlier, so it goes "right to left" in a drawing whose trees are ordered by discovery.

In an undirected graph every edge is tree or back (forward and cross do not arise under the standard undirected classification: a non-tree edge always leads to a grey ancestor).

Parenthesis theorem

For any two vertices u,vu,v, exactly one of the following holds:

  1. the intervals [d[u],f[u]][d[u],f[u]] and [d[v],f[v]][d[v],f[v]] are disjoint, and neither vertex is a descendant of the other;
  2. [d[u],f[u]][d[u],f[u]] lies inside [d[v],f[v]][d[v],f[v]], and uu is a descendant of vv;
  3. [d[v],f[v]][d[v],f[v]] lies inside [d[u],f[u]][d[u],f[u]], and vv is a descendant of uu.

Crossing (partly overlapping) intervals such as d[u]<d[v]<f[u]<f[v]d[u] < d[v] < f[u] < f[v] never occur.

Subtree size from timestamps. Each vertex receives exactly two timestamps, and all of uu's descendants receive theirs between d[u]d[u] and f[u]f[u]. Hence f[u]−d[u]+1=2×f[u] - d[u] + 1 = 2 \times (number of vertices in uu's DFS subtree, uu included), whatever the non-tree edges are.

Algorithms and pseudocode

Pseudocode uses adjacency lists, <- for assignment and a global time.

Text
DFS(G)
1  for each vertex u in G.V
2      u.color <- WHITE
3      u.pi <- NIL
4  time <- 0
5  for each vertex u in G.V
6      if u.color = WHITE then
7          DFS-VISIT(G, u)

DFS-VISIT(G, u)
1  time <- time + 1
2  u.d <- time
3  u.color <- GREY
4  for each v in Adj[u]
5      if v.color = WHITE then
6          v.pi <- u
7          DFS-VISIT(G, v)
8  u.color <- BLACK
9  time <- time + 1
10 u.f <- time

Input. Graph G=(V,E)G=(V,E), directed or undirected.

Output. Discovery and finish times, predecessors π\pi forming a DFS forest, and (by the colour of the head when an edge is examined) an edge classification.

Time / space. Θ(V+E)\Theta(V+E) on adjacency lists: each vertex and each edge is processed a constant number of times. Stack depth O(V)O(V) in the worst case (a path). Extra space Θ(V)\Theta(V) for colours and timestamps.

Edge cases. Disconnected graphs produce a forest (outer loop starts a new tree at each white vertex). Self-loops in a directed graph are back edges. The forest (and edge types that depend on discovery order) depends on the order of vertices in the outer loop and inside adjacency lists; the parenthesis structure always holds.

How the recursion stack mirrors the forest

At any moment the grey vertices on the call stack form a tree path from the root of the current DFS tree to the vertex now being explored. Discovering a white neighbour extends that path; finishing a vertex pops it. An explicit-stack implementation pushes a frame with the next adjacency-list index and obtains the same forest, which is useful when the language limits recursion depth.

The discovery time of a vertex is the step at which it first becomes grey; the finish time is the step at which it becomes black. Between those two times every descendant is discovered and finished, which is exactly the parenthesis theorem.

Mathematical explanation

White-path theorem

Vertex vv is a descendant of uu in the DFS forest if and only if, at the time d[u]d[u], there is a path from uu to vv consisting entirely of white vertices. This characterises the tree edges DFS will grow.

A path is not enough. A path from uu to vv together with d[u]<d[v]d[u] < d[v] does not make vv a descendant of uu (CLRS Exercise 22.3-8). Edges w→uw \to u, u→wu \to w, w→vw \to v, with DFS from ww and Adj[w]=u,vAdj[w] = u, v: DFS discovers ww (1), then uu (2), sees u→wu \to w as a back edge, finishes uu (3), then discovers vv (4). The path u→w→vu \to w \to v exists and d[u]=2<4=d[v]d[u] = 2 < 4 = d[v], but vv is not a descendant of uu: when uu was discovered, the path went through the grey vertex ww.

Cycle detection

A directed graph has a cycle if and only if DFS finds a back edge. An undirected graph has a cycle if and only if DFS finds a back edge (to a grey ancestor other than the immediate parent, depending on how undirected adjacency is represented).

Running-time recurrence view

DFS is not analysed by the master theorem; the Θ(V+E)\Theta(V+E) bound follows from an aggregate count: the body of DFS-VISIT runs once per vertex, and the adjacency-list scan touches each edge once (twice in undirected representations that store both directions).

Worked examples

Directed graph on four vertices

Vertices u,v,w,xu,v,w,x. Edges: u→vu\to v, u→wu\to w, v→wv\to w, w→xw\to x, x→vx\to v. Outer loop order u,v,w,xu,v,w,x; adjacency lists in alphabetical order.

DFS starting at uu:

Eventtimenote
discover uu1
discover vv2tree u→vu\to v
discover ww3tree v→wv\to w
discover xx4tree w→xw\to x
examine x→vx\to vvv grey: back edge
finish xx5
finish ww6
finish vv7
examine u→wu\to www black, d[u]<d[w]d[u]<d[w]: forward
finish uu8

Timestamps: d/f=u:1/8,  v:2/7,  w:3/6,  x:4/5d/f = u:1/8,\; v:2/7,\; w:3/6,\; x:4/5. The edge x→vx\to v is a back edge, so the graph has a cycle v→w→x→vv\to w\to x\to v.

DFS discovery and finish times with edge types
Figure 1. DFS on the four-vertex digraph from uu: discovery/finish times u:1/8u:1/8, v:2/7v:2/7, w:3/6w:3/6, x:4/5x:4/5; tree edges solid, the back edge x→vx\to v dashed, the forward edge u→wu\to w dotted.

Subtree check: f[u]−d[u]+1=8=2×4f[u] - d[u] + 1 = 8 = 2 \times 4 and f[w]−d[w]+1=4=2×2f[w] - d[w] + 1 = 4 = 2 \times 2 (ww and xx).

Adding a cross edge. Add a fifth vertex yy with the single edge y→uy \to u, placed last in the outer loop. After uu's tree finishes at time 8, the outer loop starts a new tree at yy: d[y]=9d[y] = 9. Edge y→uy \to u finds uu black with d[u]=1<9d[u] = 1 < 9, so it is a cross edge, and f[y]=10f[y] = 10. If instead yy came first in the outer loop, y→uy \to u would be a tree edge and all five vertices would form one tree. Edge types depend on the vertex order.

Which orders are DFS orders?

Use the undirected graph of the BFS note: edges s−as{-}a, s−bs{-}b, a−ca{-}c, b−cb{-}c, b−db{-}d, c−ec{-}e, d−ed{-}e. Over every ordering of the adjacency lists, DFS from ss can discover vertices in exactly five orders: s,a,c,b,d,es,a,c,b,d,e; s,a,c,e,d,bs,a,c,e,d,b; s,b,c,a,e,ds,b,c,a,e,d; s,b,c,e,d,as,b,c,e,d,a; s,b,d,e,c,as,b,d,e,c,a. The order s,a,c,e,b,ds,a,c,e,b,d is impossible: after ee is discovered, ee still has the white neighbour dd, so DFS must go to dd before backtracking to cc. IMPERATIVE RULE. The next discovered vertex must be a white neighbour of the deepest grey vertex that still has a white neighbour.

Undirected cycle

Triangle a−b−c−aa{-}b{-}c{-}a. DFS from aa with lists b,cb,c for aa: tree edges a−ba{-}b, b−cb{-}c; the edge c−ac{-}a is a back edge to the grey ancestor aa. Finish order c,b,ac,b,a.

Discovery order versus finish order

Discovery order is a preorder walk of the DFS forest: a vertex is listed before its descendants. Finish order is a postorder walk: a vertex is listed after its descendants. Topological sort of a DAG is exactly the reverse of the finish order, which is why a single DFS suffices when the graph has no back edges.

Important properties and observations

  • Finish times are the key output for later algorithms: topological sort emits vertices in decreasing finish time; Kosaraju's SCC algorithm runs DFS twice and uses finish order on the transpose.
  • Tree edges form a forest, not necessarily a single tree.
  • Forward and cross edges appear only in directed graphs.
  • The number of tree edges is ∣V∣−k|V| - k, where kk is the number of DFS trees. In an undirected graph, kk is the number of connected components (CLRS Exercise 22.3-12), and the other ∣E∣−∣V∣+k|E| - |V| + k edges are all back edges.
  • A vertex with both incoming and outgoing edges can still form a DFS tree on its own: all of its out-neighbours may already be black when the outer loop reaches it (CLRS Exercise 22.3-11).
  • The discovery order is a preorder of the DFS forest; the finish order is a postorder.

Common misconceptions.

  • "DFS always produces a single tree." Only if the graph is connected (undirected) or every vertex is reachable from the first source (directed).
  • "Back edges exist in every DFS of a DAG." A DAG has no back edges; that is equivalent to acyclicity.
  • "A path from uu to vv with d[u]<d[v]d[u] < d[v] makes vv a descendant of uu." False; the white-path condition at time d[u]d[u] is what matters (example above).
  • "Replacing BFS's queue by a stack gives the same order as recursive DFS." Pushing every neighbour when a vertex is popped produces a traversal, but its order and timestamps can differ from recursive DFS. A faithful iterative DFS keeps, for each grey vertex, the position reached in its adjacency list.

GATE examination perspective

  • CLASSIFY THE EDGE. Given a DFS forest and timestamps, name each remaining edge.
  • PARENTHESIS CHECK. Intervals nest or are disjoint; a crossing pair is impossible.
  • CYCLE WITNESS. A back edge is necessary and sufficient for a directed cycle.
  • ORDER DEPENDENCE. State the adjacency-list order; different orders change the forest but not the Θ(V+E)\Theta(V+E) bound.
  • FINISH ORDER. Topological sort and SCC questions start from a correct finish-time list.
  • VALID DFS ORDER. Check each step against the deepest grey vertex that still has a white neighbour.
  • TIMESTAMP ARITHMETIC. f[u]−d[u]+1f[u] - d[u] + 1 is twice the subtree size; times run from 1 to 2∣V∣2|V|.
  • COUNT EDGES. Tree edges ∣V∣−k|V| - k; in an undirected graph every other edge is a back edge.

Summary

Sources

  1. T. H. Cormen, C. E. Leiserson, R. L. Rivest, C. Stein, Introduction to Algorithms, 3rd ed., MIT Press, 2009, Chapter 22, Section 22.3 (depth-first search); Exercises 22.3-5 (classification by timestamps), 22.3-8 (path with d[u] < d[v] counterexample), 22.3-11 (single-vertex tree) and 22.3-12 (components).

Next steps