Week 9: Depth-First Search (DFS) — Traversal, Reachability & Backtracking (Layered)
1784 words
9 min read
Visual companion
Python
Type and operator map
Python Week 1: the first filter for runtime behavior
View
Revision summary
What this note is really saying
Short form
# 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 cycl...

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:
- Mark every junction you enter with chalk (
visited[u] = True). - Take the first open path and plunge as deep as possible down that tunnel.
- 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 B be reached from station A? (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 / Term | Formal Definition | Computational Meaning |
|---|---|---|
| G=(V,E) | Graph with vertex set V and edge set E | The network structure ($ |
| A[i][j] | Adjacency Matrix (1 if edge i→j exists, else 0) | Fast binary edge lookup in O(1) time. |
| visited | Dictionary or boolean array: visited[u]∈{True,False} | Memoization of visited states; prevents re-processing and infinite loops. |
| Call Stack | LIFO (Last-In, First-Out) runtime activation frames | Stores the return address and local variables during recursive descent. |
| Backtracking | Returning control to the parent caller after exhausting children | Returning from a completed recursive invocation. |
| Tree Edge | Edge (u,v) where vertex v is discovered for the first time | Forms the DFS spanning forest. |
| Back Edge | Edge (u,v) pointing to an ancestor currently on the call stack | In directed graphs, back edges indicate the presence of cycles. |
| Connected Component | Maximal subgraph where every pair of vertices has a path | Found 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:pseudoProcedure 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→2, 1→3, 2→4, 4→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 t is reachable from vertex s:
pseudoProcedure 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 V:
pseudoProcedure 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 v is marked
visited, but its DFS call has not yet finished (in_stack = True), then edge u→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} and the following adjacency matrix:
Neighbors are always scanned in ascending numerical order (1,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:
- Start at vertex
1. Markvisited[1] = True. Order:[1]. - Scan row 1: neighbors are
2(since A[1][2]=1) and3(since A[1][3]=1). Lowest first: choose2. - Call
DFS(2). Markvisited[2] = True. Order:[1, 2]. - Scan row 2: neighbor is
4(A[2][4]=1). - Call
DFS(4). Markvisited[4] = True. Order:[1, 2, 4]. - Scan row 4: all entries are 0. Backtrack to
2. - In
2: no more neighbors. Backtrack to1. - In
1: next neighbor is3(A[1][3]=1).3is not visited. - Call
DFS(3). Markvisited[3] = True. Order:[1, 2, 4, 3]. - Scan row 3: neighbors are
4and5.- Check
4: already visited! Skip. - Check
5: not visited!
- Check
- Call
DFS(5). Markvisited[5] = True. Order:[1, 2, 4, 3, 5]. - Scan row 5: neighbor is
2. Already visited! Skip. Backtrack out.
Final Discovery Order: 1→2→4→3→5
6. Common Traps & Pitfalls in IITM CT Exams
- Forgetting to Skip Visited Nodes:
If a neighbor is already marked in
visited, DFS must not call itself on that neighbor. Tracing without checkingnot(isKey(visited, j))will lead to infinite loops on cyclic graphs. - 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".
- Discovery Order (Pre-order): When a vertex is first entered (
- Directed vs Undirected Symmetry: In directed graphs, an edge u→v does not permit traversal v→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 Vi vertices, it contains Ei=Vi−1 edges. Summing over all k components:
∑Ei=∑(Vi−1)⟹E=V−k⟹5=7−k⟹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 n vertices, a single DFS traversal will visit all n 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 n.
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 n distinct activation frames can exist simultaneously.
8. Active Recall Flashcards
- What data structure implicitly maintains state during recursive DFS? Answer: The runtime Call Stack (Last-In, First-Out).
- What happens if you remove the
visitedcheck from recursive DFS on a cyclic graph? Answer: The procedure enters an infinite recursive loop, eventually crashing with a call stack overflow. - 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. - 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.