Skip to main content

Welcome to a Grasp lesson

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

Lesson 6

Graph Traversal with BFS and DFS

Recommended resources

Welcome to the next lesson in our "Data Structures & Algorithms" module. In our last session, we dissected Quicksort, analyzing its powerful in-place partitioning strategy and its performance trade-offs against Merge Sort. This gave us a deep appreciation for how different algorithmic approaches can have significant practical implications.

Today, we shift our focus from sorting disconnected items to exploring interconnected data. We will tackle two of the most fundamental algorithms in computer science: Breadth-First Search (BFS) and Depth-First Search (DFS). These are the primary methods for traversing graphs and trees, data structures that model everything from social networks and computer networks to file systems and dependency chains. Your goal is to understand how each algorithm works, learn to implement them, and, most importantly, know when to apply one over the other to solve a given problem.

Graphs: The Language of Connection

Before we can traverse a graph, we need a common language to describe one. A graph is simply a collection of nodes (or vertices) connected by edges. Many real-world engineering problems can be modeled this way.

The video below offers an excellent, concise introduction to graph terminology and the common ways we represent them in code. The most important representation for our purposes today is the adjacency list.

Graphs: Edge List, Adjacency Matrix, Adjacency List, DFS, BFS - DSA Course in Python Lecture 11

This video from Greg Hogg's DSA course clearly explains graph basics and compares the three main ways to represent them programmatically.

Please watch the following two segments: Graph Terminology: This covers the essential concepts like vertices, edges, and directed vs. undirected graphs. Graph Representations: Pay close attention to the comparison between an edge list, adjacency matrix, and adjacency list. Understand why the adjacency list is often the most practical choice for traversal algorithms, as it directly maps a node to its neighbors.

An adjacency list is efficient because it gives us immediate access to a node's neighbors. In practice, this is often implemented using a hash map (or dictionary in Python), where each key is a node and its value is a list of its adjacent nodes.

On BFS and DFS - Jake Tae

This article by Jake Tae demonstrates a clean and simple setup for an adjacency list using a Python dictionary.

Read the Setup section to see how a graph can be represented with a hash map. This is the data structure we'll assume for the rest of this lesson.


The Two Traversal Strategies: Wide vs. Deep

BFS and DFS are two distinct strategies for visiting every node in a graph. The fundamental difference lies in the order they explore nodes.

  • Breadth-First Search (BFS) explores layer by layer. It visits all of a node's immediate neighbors before moving on to the next level of neighbors. Think of it as the ripples spreading from a stone dropped in a pond.
  • Depth-First Search (DFS) explores as far as possible along one path before backtracking. It dives deep into a branch, and only when it hits a dead end does it return to explore an alternative path. Think of this as navigating a maze.

The image below provides a clear visual comparison of their traversal orders on the same graph structure.

This image shows how BFS explores level by level (0, then 1-2-3, then 4-5-6-7), while DFS follows a single path to its end before backtracking (0-1-4, then back to 1 to explore 5, etc.).

Breadth-First Search (BFS): The Shortest Path Finder

Because BFS expands level by level, it has a very important property: in an unweighted graph, it naturally finds the shortest path from the starting node to all other nodes. This makes it invaluable for problems like finding the shortest route in a network or the fewest steps to solve a puzzle.

To achieve this level-by-level traversal, BFS uses a Queue, which follows a First-In, First-Out (FIFO) principle.

The algorithm works as follows:

  1. Initialize a queue and add the starting node.
  2. Create a visited set and add the starting node to it to avoid cycles and redundant work.
  3. While the queue is not empty:
    a. Dequeue a node (let's call it current).
    b. Process current.
    c. For each neighbor of current:
    i. If the neighbor has not been visited, add it to the visited set and enqueue it.

The following video from Abdul Bari provides an excellent, detailed walkthrough of this process.

5.1 Graph Traversals - BFS & DFS -Breadth First Search and Depth First Search

This classic lecture clearly demonstrates the BFS algorithm using a queue and builds the resulting "spanning tree" step by step.

Watch the detailed explanation of Breadth-First Search. Pay close attention to how nodes are added to and removed from the queue, and how that corresponds to the level-by-level exploration.

Depth-First Search (DFS): The Path Explorer

DFS is the algorithm of choice for problems where you need to explore every possible path, such as maze-solving, finding connected components in a network, or checking for cycles. It's also the foundation for more advanced algorithms like backtracking.

To achieve its deep-diving behavior, DFS uses a Stack, which follows a Last-In, First-Out (LIFO) principle. In many cases, this stack is managed implicitly by the program's call stack when using a recursive implementation.

The algorithm can be implemented both iteratively and recursively.

Iterative DFS (with an explicit stack):

  1. Initialize a stack and add the starting node.
  2. Create a visited set.
  3. While the stack is not empty:
    a. Pop a node from the stack (current).
    b. If current has been visited, skip to the next iteration.
    c. Mark current as visited and process it.
    d. For each neighbor of current, push it onto the stack.

Recursive DFS:

  1. Define a function dfs(node, visited_set).
  2. Mark the node as visited and process it.
  3. For each neighbor of node:
    a. If the neighbor has not been visited, recursively call dfs(neighbor, visited_set).

The following video provides a very clear explanation of both the recursive and iterative approaches to DFS.

Graphs: Edge List, Adjacency Matrix, Adjacency List, DFS, BFS - DSA Course in Python Lecture 11

Let's return to Greg Hogg's video for a practical demonstration of DFS.

Watch the segment on Depth-First Search. The video does a great job of visualizing both the recursive (implicit stack) and iterative (explicit stack) versions and highlights the critical role of the seen set for handling cycles.


Implementation and Problem-Solving Patterns

Now that you have the conceptual foundation, let's look at how to implement these algorithms and, more importantly, when to use them.

On BFS and DFS - Jake Tae

This article provides clean Python implementations for both algorithms and shows how they can be applied to path-finding problems.

Please read the following sections: Breadth First Search: Review the Python code for bfs and note the discussion on using collections.deque for an efficient queue. Depth First Search: Examine both the iterative (dfs) and recursive (dfs_recursive) implementations. Path Finding: This is a key section. It demonstrates how to adapt both algorithms to find a path between two nodes and reinforces why BFS is the natural choice for the shortest path.

As an engineering manager, one of the most valuable skills is quickly recognizing which tool is right for a given job. The following resource provides an excellent mental checklist for deciding between DFS and BFS.

Depth-First Search (DFS)

This guide from DevWeekends focuses on pattern recognition, which is perfect for applying these concepts in practice.

Please review two key parts of this resource: The Pattern Recognition Checklist. This gives you a fast way to map problem requirements (e.g., "find shortest path," "detect cycles") to the correct algorithm. The DFS vs BFS Quick Reference table at the end. This is a perfect summary of the core trade-offs.


Complexity Analysis

Finally, let's briefly touch on the performance of these algorithms. Let be the number of vertices and be the number of edges in the graph.

  • Time Complexity: For both BFS and DFS, the time complexity is . This is because, in the worst case, we must visit every vertex () and traverse every edge () exactly once.

  • Space Complexity: This is where the two algorithms differ significantly.

    • BFS: The space complexity is proportional to the maximum width of the graph, . In a "bushy" graph that expands rapidly, the queue can hold many nodes at a single level. In the worst case of a complete graph, this can be .
    • DFS: The space complexity is proportional to the maximum depth of the graph, . For a "long and skinny" graph, the recursion stack can become very deep. In the worst case, this can also be .

The choice between them can sometimes be dictated by memory constraints if the graph has a known shape (e.g., very wide vs. very deep).


Conclusion

In this lesson, we've explored the two fundamental workhorses of graph traversal. You now have the knowledge to navigate connected data structures efficiently.

Here are the essential takeaways:

  • BFS (Breadth-First Search):
    • Strategy: Explores level by level, "wide" before "deep."
    • Data Structure: Queue (FIFO).
    • Best For: Finding the shortest path in an unweighted graph, level-order tree traversal.
  • DFS (Depth-First Search):
    • Strategy: Explores one path to its conclusion before backtracking.
    • Data Structure: Stack (LIFO), often implemented with recursion.
    • Best For: Checking for path existence, detecting cycles, exploring all possibilities (e.g., solving a maze), and as a foundation for backtracking.
  • Performance: Both have a time complexity of . Their space complexity depends on the graph's shape—BFS is sensitive to width (), while DFS is sensitive to height ().

In our next lesson, we will venture into a new and powerful algorithmic paradigm: dynamic programming. You will learn how to solve the classic 0/1 Knapsack problem, which will introduce you to techniques for breaking down complex optimization problems into simpler, overlapping subproblems.