Registry Synced

Week 9: Depth-First Search (DFS) — Traversal, Reachability & Backtracking (Layered)

1784 words
9 min read

Reading compass

Now · 1. Orientation & Intuition: The Thread of Ariadne

Week 9: Depth-First Search (DFS) — Traversal, Reachability & Backtracking

Layered Deep-Dive: This chapter reconstructs Graph Traversal from first principles. We explore how recursive descent systematically crawls networks, why the call stack acts as an automatic breadcrumb tracker, how to avoid infinite cycle loops with a visited dictionary, and how IIT Madras tests DFS execution order and component counting in Quiz 3 and end-term examinations.

1. Orientation & Intuition: The Thread of Ariadne

Imagine navigating an ancient labyrinth. At every junction, you face multiple tunnels. If you choose blindly, you risk wandering in circles for eternity or missing the exit entirely.
To explore the labyrinth systematically without getting trapped:
  1. Mark every junction you enter with chalk (visited[u] = True).
  2. Take the first open path and plunge as deep as possible down that tunnel.
  3. If you reach a dead end or a junction already marked with chalk, unwind your steps (backtrack) to the immediately preceding junction and try the next unexplored opening.
In computer science, this strategy is known as Depth-First Search (DFS). In computational thinking, graphs represent networks—railway stations, social followers, dependency trees, or web pages. DFS is the primary algorithm used to answer fundamental questions:
  • Can station BB be reached from station AA? (Reachability)
  • Is the entire network connected in one piece, or split into islands? (Connected Components)
  • Does the graph contain closed loops? (Cycle Detection)
       (1) Source
      /   \
    (2)   (3)
    / \     \
  (4) (5)   (6)

DFS Traversal Order (exploring left branch deeply first):
1 -> 2 -> 4 -> [backtrack to 2] -> 5 -> [backtrack to 1] -> 3 -> 6

2. Terminology, Notation & Mathematical Formalism

Symbol / TermFormal DefinitionComputational Meaning
G=(V,E)G = (V, E)Graph with vertex set VV and edge set EEThe network structure ($
A[i][j]A[i][j]Adjacency Matrix (11 if edge i→ji \to j exists, else 00)Fast binary edge lookup in O(1)O(1) time.
visited\text{visited}Dictionary or boolean array: visited[u]∈{True,False}\text{visited}[u] \in \{\text{True}, \text{False}\}Memoization of visited states; prevents re-processing and infinite loops.
Call StackLIFO (Last-In, First-Out) runtime activation framesStores the return address and local variables during recursive descent.
BacktrackingReturning control to the parent caller after exhausting childrenReturning from a completed recursive invocation.
Tree EdgeEdge (u,v)(u, v) where vertex vv is discovered for the first timeForms the DFS spanning forest.
Back EdgeEdge (u,v)(u, v) pointing to an ancestor currently on the call stackIn directed graphs, back edges indicate the presence of cycles.
Connected ComponentMaximal subgraph where every pair of vertices has a pathFound by running DFS repeatedly on unvisited nodes.

3. Algorithmic Mechanics: How DFS Traverses the Graph

3.1 The Canonical Recursive Procedure

In IIT Madras Computational Thinking pseudocode, DFS is formulated around a persistent visited collection that accumulates discovered vertices across recursive calls:
pseudo
Procedure DFS(graph, visited, current)
    // 1. Mark the current node as visited
    visited[current] = True
    
    // 2. Scan all possible destination vertices
    foreach neighbor in vertices(graph) {
        // If an edge exists and the neighbor has not been seen:
        if (graph[current][neighbor] == 1 and not(isKey(visited, neighbor))) {
            visited = DFS(graph, visited, neighbor)
        }
    }
    
    // 3. Backtrack: return the accumulated visited registry
    return(visited)
End DFS

3.2 Trace of the Call Stack Execution

Let us trace DFS(graph, visited, 1) on a 4-node directed graph:
  • Edges: 1→21 \to 2, 1→31 \to 3, 2→42 \to 4, 4→14 \to 1 (cycle).
Step 1: DFS(1) invoked. visited = {1: True}.
        Examining neighbors of 1:
        neighbor = 2: edge (1,2)==1 and 2 not visited.
        --> Push DFS(2) onto stack.

Step 2:   DFS(2) invoked. visited = {1: True, 2: True}.
          Examining neighbors of 2:
          neighbor = 4: edge (2,4)==1 and 4 not visited.
          --> Push DFS(4) onto stack.

Step 3:     DFS(4) invoked. visited = {1: True, 2: True, 4: True}.
            Examining neighbors of 4:
            neighbor = 1: edge (4,1)==1, BUT 1 is already in visited! Skip.
            No more unvisited neighbors for 4.
            --> Pop DFS(4) from stack. Return to DFS(2).

Step 4:   Back in DFS(2). No other unvisited neighbors.
          --> Pop DFS(2) from stack. Return to DFS(1).

Step 5: Back in DFS(1).
        Examining next neighbor:
        neighbor = 3: edge (1,3)==1 and 3 not visited.
        --> Push DFS(3) onto stack.

Step 6:   DFS(3) invoked. visited = {1: True, 2: True, 4: True, 3: True}.
          No outgoing edges from 3.
          --> Pop DFS(3) from stack. Return to DFS(1).

Step 7: Back in DFS(1). All neighbors evaluated.
        --> Pop DFS(1). Final visited = {1: True, 2: True, 3: True, 4: True}.

4. Key Applications & Core Theorems

4.1 Reachability Test

To determine if vertex tt is reachable from vertex ss:
pseudo
Procedure isReachable(graph, s, t)
    visited = {}
    visited = DFS(graph, visited, s)
    if (isKey(visited, t)) {
        return(True)
    } else {
        return(False)
    }
End isReachable

4.2 Counting Connected Components (Undirected Graphs)

An undirected graph may consist of several disconnected sub-networks. We count how many independent DFS invocations are needed to cover every vertex in VV:
pseudo
Procedure CountComponents(graph, numVertices)
    visited = {}
    componentCount = 0
    
    foreach i in [1 ... numVertices] {
        if (not(isKey(visited, i))) {
            // Found a fresh unvisited component!
            componentCount = componentCount + 1
            visited = DFS(graph, visited, i)
        }
    }
    
    return(componentCount)
End CountComponents

4.3 Cycle Detection in Directed Graphs

A graph contains a cycle if and only if DFS encounters a back edge—an edge pointing to a vertex currently active on the recursion stack (an ancestor).
  • If vertex vv is marked visited, but its DFS call has not yet finished (in_stack = True), then edge u→vu \to v completes a cycle!

5. Worked Examples: Step-by-Step Exam Walkthroughs

Example 1: Directed Traversal with Lexicographical Tie-Breaking

Problem: A directed graph has 5 vertices {1,2,3,4,5}\{1, 2, 3, 4, 5\} and the following adjacency matrix:
A=(0110000010000110000001000)A = \begin{pmatrix} 0 & 1 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 & 1 \\ 0 & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 \end{pmatrix}
Neighbors are always scanned in ascending numerical order (1,2,3,4,51, 2, 3, 4, 5). What is the exact order in which vertices are first marked visited when running DFS(graph, {}, 1)?
Step-by-step Solution:
  1. Start at vertex 1. Mark visited[1] = True. Order: [1].
  2. Scan row 1: neighbors are 2 (since A[1][2]=1A[1][2]=1) and 3 (since A[1][3]=1A[1][3]=1). Lowest first: choose 2.
  3. Call DFS(2). Mark visited[2] = True. Order: [1, 2].
  4. Scan row 2: neighbor is 4 (A[2][4]=1A[2][4]=1).
  5. Call DFS(4). Mark visited[4] = True. Order: [1, 2, 4].
  6. Scan row 4: all entries are 00. Backtrack to 2.
  7. In 2: no more neighbors. Backtrack to 1.
  8. In 1: next neighbor is 3 (A[1][3]=1A[1][3]=1). 3 is not visited.
  9. Call DFS(3). Mark visited[3] = True. Order: [1, 2, 4, 3].
  10. Scan row 3: neighbors are 4 and 5.
    • Check 4: already visited! Skip.
    • Check 5: not visited!
  11. Call DFS(5). Mark visited[5] = True. Order: [1, 2, 4, 3, 5].
  12. Scan row 5: neighbor is 2. Already visited! Skip. Backtrack out.
Final Discovery Order: 1→2→4→3→5\text{Final Discovery Order: } 1 \to 2 \to 4 \to 3 \to 5

6. Common Traps & Pitfalls in IITM CT Exams

  1. Forgetting to Skip Visited Nodes: If a neighbor is already marked in visited, DFS must not call itself on that neighbor. Tracing without checking not(isKey(visited, j)) will lead to infinite loops on cyclic graphs.
  2. Confusion Between Discovery Order vs Finish/Backtrack Order:
    • Discovery Order (Pre-order): When a vertex is first entered (visited[v] = True).
    • Finish Order (Post-order): When all outgoing neighbors of a vertex have been explored and the function returns. IITM questions almost always ask for the discovery order unless explicitly stating "order of return".
  3. Directed vs Undirected Symmetry: In directed graphs, an edge u→vu \to v does not permit traversal v→uv \to u. Never traverse against the arrow!

7. Quiz & Practice Questions

Question 1 (MCQ)

An undirected graph has 7 vertices and 5 edges. It has no cycles. How many connected components does this graph have?
  • (A) 1
  • (B) 2
  • (C) 3
  • (D) 4
Answer: (B) Detailed Explanation: An acyclic undirected graph is a collection of trees (a forest). For any tree with ViV_i vertices, it contains Ei=Vi−1E_i = V_i - 1 edges. Summing over all kk components: ∑Ei=∑(Vi−1)  ⟹  E=V−k  ⟹  5=7−k  ⟹  k=2\sum E_i = \sum (V_i - 1) \implies E = V - k \implies 5 = 7 - k \implies k = 2. Therefore, the graph consists of exactly 2 connected components.

Question 2 (MSQ)

Which of the following statements regarding Depth-First Search on finite graphs are TRUE?
  • (A) In an undirected connected graph with nn vertices, a single DFS traversal will visit all nn vertices.
  • (B) DFS always finds the shortest path (minimum number of edges) between two vertices.
  • (C) Using a visited registry ensures DFS terminates even if the graph contains directed cycles.
  • (D) The maximum depth of the call stack in DFS cannot exceed the total number of vertices nn.
Explanation:
  • (A) True: In a connected graph, every vertex is reachable from any start node.
  • (B) False: Breadth-First Search (BFS) finds shortest paths in unweighted graphs; DFS explores deeply and may find long winding paths first.
  • (C) True: Each vertex is marked once, so no vertex is visited multiple times.
  • (D) True: In a simple path, at most nn distinct activation frames can exist simultaneously.

8. Active Recall Flashcards

  1. What data structure implicitly maintains state during recursive DFS? Answer: The runtime Call Stack (Last-In, First-Out).
  2. What happens if you remove the visited check from recursive DFS on a cyclic graph? Answer: The procedure enters an infinite recursive loop, eventually crashing with a call stack overflow.
  3. How do you determine if an undirected graph is connected using DFS? Answer: Run DFS from vertex 1. If len(keys(visited)) == total_vertices, the graph is connected.
  4. In DFS, what is a "backtrack"? Answer: Returning execution control to the calling parent vertex once all incident edges of the current vertex have been fully explored.
Document outline

Keep your place and jump directly to a heading.

Table of contents

No note selected

Open a chapter to see its outline, backlinks, and sources here.