Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 21

DFS: Exploration and Connectivity

Recommended resources

Hello! Welcome back to our study of graphs.

In the last lesson, you learned how to represent graphs in memory using adjacency matrices and adjacency lists. You analyzed their trade-offs and understood why adjacency lists are the preferred choice for the sparse graphs commonly found in real-world applications. This was a crucial first step, giving us the "blueprint" for a graph.

Now that we can store a graph, our next task is to explore it. This lesson introduces our first major graph traversal algorithm: Depth-First Search (DFS). Your learning outcome is to implement DFS and understand its applications, such as pathfinding and connectivity analysis. DFS is a fundamental tool that forms the basis for many advanced graph algorithms. Its "go deep, then backtrack" strategy is a powerful concept you'll see applied in many areas of computer science.

1. The Core Idea: Exploring a Labyrinth

Imagine you're in a labyrinth and want to find the exit. One natural strategy is to pick a path and follow it as far as you can. If you hit a dead end, you backtrack to the last intersection where you had an unexplored path and try that one. You keep doing this, marking your path so you don't go in circles, until you find the exit or have explored every passage.

This is precisely the intuition behind Depth-First Search. It explores as far as possible along each branch before backtracking.

To get a feel for this process, let's watch a short, intuitive explanation.

Depth First Search (DFS) Explained: Algorithm, Examples, and Code

This video from Reducible builds up the idea of DFS from first principles, demonstrating how one might naturally explore a graph.

Watch the first 4 minutes and 43 seconds (00:00 - 04:43). Focus on the philosophy of exploring new vertices until you hit a 'dead end' and then retracing your steps.

The most critical takeaway from that demonstration is the need to mark vertices as "visited." Without this, if the graph contains a cycle (e.g., A -> B -> C -> A), the algorithm would loop forever.

Depth-First Search Graph Traversal Example
This image shows a DFS traversal on a directed graph starting from node S. The dashed lines show the order of exploration, demonstrating the 'deep' path taken (S→A→D→G) before backtracking to explore other branches.

2. Implementing Depth-First Search

DFS can be implemented in two primary ways: recursively (which is often more intuitive) and iteratively (which avoids potential recursion depth limits). Since you're interested in building a solid foundational understanding, we will cover both. For both implementations, we'll assume the graph is represented by an adjacency list, which you learned about in the previous lesson.

Recursive Implementation

The recursive approach is elegant because the program's call stack naturally handles the backtracking process. When a function calls itself for a neighbor, it goes "deeper." When that function returns, it automatically "backtracks" to the previous state.

Depth First Search (DFS) Explained: Algorithm, Examples, and Code

Let's continue with the Reducible video, which now explains the recursive implementation and the role of the 'visited' set.

Watch from 06:21 to 10:57. Pay close attention to how the recursive calls mirror the exploration path and how checking the 'visited' status prevents infinite loops.

Here is the logic in pseudocode, which captures the essence of the algorithm independent of any programming language:

function DFS(vertex, visited_set):
  mark vertex as visited
  // Process the vertex here (e.g., print it)
  
  for each neighbor in adjacency_list_of(vertex):
    if neighbor is not in visited_set:
      DFS(neighbor, visited_set)

To start the search on a graph G from a start_node, you would initialize an empty visited_set and call DFS(start_node, visited_set).

Iterative Implementation

The iterative approach achieves the same result but uses an explicit stack data structure instead of the implicit call stack. This can be more efficient in terms of memory management for very deep or skewed graphs and avoids stack overflow errors. Your knowledge of stacks from earlier in the course is directly applicable here.

The logic is as follows:

  1. Initialize a stack and push the starting vertex onto it.
  2. Initialize a visited set.
  3. While the stack is not empty:
    a. Pop a vertex from the stack.
    b. If it hasn't been visited, mark it as visited and process it.
    c. Push all its unvisited neighbors onto the stack.

Depth First Search (DFS) Explained: Algorithm, Examples, and Code

The Reducible video also provides an excellent walkthrough of the iterative version.

Watch from 10:57 to 13:56. Notice how the stack keeps track of the 'frontiers' of exploration, and the last one added is the first one explored next (LIFO).

Here's the iterative pseudocode:

function DFS_iterative(start_node):
  create an empty stack
  push start_node onto stack
  create an empty visited_set

  while stack is not empty:
    vertex = pop from stack
    
    if vertex is not in visited_set:
      mark vertex as visited
      // Process the vertex here
      
      for each neighbor in adjacency_list_of(vertex):
        if neighbor is not in visited_set:
          push neighbor onto stack

To see these algorithms written out more formally, you can review the following resource.

Depth-First Search (DFS) Algorithm Explained

This article from Codecademy provides clean pseudocode and Python implementations for both the recursive and iterative strategies. Seeing the code helps solidify the abstract steps.

Review the sections 'Depth-First Search pseudocode' and 'Depth-First Search Python implementation'. You don't need to run the code, but trace its logic against the pseudocode we just discussed.

Test your understanding!

Let's use the same undirected graph from our last lesson:

  • Vertices: {0, 1, 2, 3, 4}
  • Adjacency List:
    • 0: [1, 2]
    • 1: [0, 2, 3]
    • 2: [0, 1]
    • 3: [1, 4]
    • 4: [3]

Assuming you start at vertex 0, and when exploring neighbors, you always choose the smallest-numbered vertex first. What is the order in which vertices are visited (i.e., processed)?

Show answer

The visited order would be: 0, 1, 2, 3, 4.

Here's a trace of the recursive DFS(vertex):

  1. DFS(0): Visit 0. Neighbors are 1, 2.
  2. DFS(1): Visit 1. Neighbors are 0, 2, 3. 0 is visited.
  3. DFS(2): Visit 2. Neighbors are 0, 1. Both are visited. Return.
  4. Back at DFS(1), next unvisited neighbor is 3.
  5. DFS(3): Visit 3. Neighbors are 1, 4. 1 is visited.
  6. DFS(4): Visit 4. Neighbor is 3. 3 is visited. Return.
  7. Back at DFS(3), no more unvisited neighbors. Return.
  8. Back at DFS(1), no more unvisited neighbors. Return.
  9. Back at DFS(0), next unvisited neighbor is 2. But 2 is already visited.
  10. The search is complete.

The iterative version with the rule of pushing neighbors in reverse order (to match recursion) would produce the same result.


3. Complexity Analysis

As you requested, let's integrate complexity analysis. Understanding the performance of DFS is crucial for making architectural decisions.

  • Time Complexity:
    • V is the number of vertices, and E is the number of edges.
    • Each vertex is visited (pushed onto the stack/recursively called) exactly once, thanks to the visited set. This accounts for the part.
    • When we are at a vertex, we iterate through its adjacency list. Over the entire run of the algorithm, each edge in the graph will be examined exactly once (for a directed graph) or twice (for an undirected graph). This accounts for the part.
    • Therefore, the total time complexity is linear in the size of the graph representation, .
DFS: Time Complexity Analysis with Pseudocode and Graph Traversal Visualization
This diagram illustrates the time complexity of DFS. The algorithm must visit every vertex (O(V)) and traverse every edge (O(E)), leading to a total time complexity of O(V+E).
  • Space Complexity:
    • The visited set will store up to V vertices in the worst case.
    • The stack (either the call stack for recursion or the explicit stack for iteration) will, in the worst case, hold up to V vertices. This happens in a graph that is a single long chain. The depth of recursion or the size of the stack would be V.
    • Thus, the space complexity is dominated by these factors, resulting in .

4. Applications of DFS

Now that you can implement and analyze DFS, let's explore what you can do with it. This is where the algorithm's power truly shines.

Pathfinding

Can we get from node A to node B? DFS can answer this easily. You start a DFS from node A and stop if you ever encounter node B. If the DFS completes without ever visiting node B, no path exists.

Connectivity

DFS is excellent for exploring the structure of a graph.

  • Finding Connected Components: In an undirected graph, a "connected component" is a subgraph where every vertex is reachable from every other vertex. You can find all components by:
    1. Picking an arbitrary unvisited vertex.
    2. Running DFS from it. All vertices visited by this DFS form one component.
    3. If there are still unvisited vertices in the graph, repeat step 1.
  • Cycle Detection: A simple modification to DFS can detect cycles. During a traversal, if you encounter an edge that points to a vertex that is already in the current recursion stack (not just 'visited', but actively being explored), you have found a back edge, which indicates a cycle. We will cover this in more detail in a future lesson.
  • Topological Sorting: For a Directed Acyclic Graph (DAG), DFS can be used to produce a linear ordering of vertices such that for every directed edge from u to v, u comes before v in the ordering. This is critical for scheduling tasks with dependencies.

Depth First Search (DFS) Explained: Algorithm, Examples, and Code

To see a summary of these applications, let's watch the final segment of the Reducible video.

Watch from 16:59 to 20:24. This section briefly touches on cycle detection, connected components, and topological sorting, giving you a glimpse of the versatility of DFS.


Conclusion

In this lesson, you have taken a deep dive into Depth-First Search, a cornerstone algorithm for graph traversal. You have moved from the high-level concept of "exploring a maze" to concrete recursive and iterative implementations, along with a formal analysis of their performance.

Key Takeaways:

  • DFS explores deeply, following a path to its end before backtracking.
  • It is implemented either recursively (using the call stack) or iteratively (using an explicit stack).
  • A visited set is essential to prevent infinite loops and redundant work.
  • The time complexity is , and space complexity is , making it efficient for most graphs.
  • DFS is a versatile tool used for pathfinding, analyzing connectivity, detecting cycles, and more.

Preview of the Next Lesson:
DFS explores a graph by going deep. But what if we wanted to explore layer by layer, like ripples in a pond? In the next lesson, we will cover Breadth-First Search (BFS). You will learn how it uses a queue instead of a stack to achieve this "wide" traversal and discover its unique application in finding the shortest path in unweighted graphs.