Skip to main content

Welcome to a Grasp lesson

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

Lesson 21

BFS for Shortest Paths in Unweighted Graphs

Recommended resources

Hello! Welcome back to our module on graph algorithms.

In our previous lesson, we established the two primary ways to represent a graph: the adjacency matrix and the adjacency list. We concluded that for many common graph algorithms that involve exploring a vertex's neighbors, the adjacency list is the preferred representation due to its space efficiency on sparse graphs and its fast neighbor lookups.

Today, we will put that representation to use with our first graph traversal algorithm: Breadth-First Search (BFS). This lesson directly addresses your learning outcome: to implement and analyze breadth-first search (BFS) to find shortest paths in unweighted graphs.

We will cover:

  • The core intuition behind BFS and why it's guaranteed to find the shortest path in an unweighted graph.
  • The step-by-step mechanics of the algorithm, focusing on the roles of a queue and a visited set.
  • The process of modifying BFS to not only find the shortest path length but also to reconstruct the path itself.
  • A formal analysis of the time and space complexity of BFS.

Let's begin our exploration.

1. The Core Idea: Exploring in Layers

At its heart, Breadth-First Search is a systematic way to explore a graph. Starting from a source vertex, it visits all its immediate neighbors first. Then, for each of those neighbors, it visits their unvisited neighbors, and so on. This creates a "layer-by-layer" or "ripples-in-a-pond" effect.

Breadth-First Search (BFS) Traversal Example
This diagram illustrates the core principle of BFS. Starting from node 'S' (Level 0), the algorithm explores all nodes at Level 1, then all nodes at Level 2, and so on. It explores "broadly" before going "deeper."

This layered approach is precisely why BFS is the perfect tool for finding the shortest path in an unweighted graph. In such a graph, the shortest path is simply the one with the fewest edges. Because BFS explores all paths of length 1 before any paths of length 2, and all paths of length 2 before any of length 3, the first time it reaches any destination node, it is guaranteed to have done so via a shortest path.

The following video provides a fantastic analogy that should resonate with your engineering background and solidifies this core concept.

Breadth First Search (BFS): Visualized and Explained

The video 'Breadth First Search (BFS): Visualized and Explained' from the channel Reducible offers a great intuitive start. Pay close attention to the 'pulling the strings' analogy, as it elegantly explains why BFS naturally finds shortest paths.

Please watch the first 2 minutes and 38 seconds of the video. Focus on how the level-by-level exploration guarantees that when you first encounter a vertex, you have found the shortest path to it from the source.


2. The Algorithm Mechanics

To implement this "layer-by-layer" strategy, BFS relies on a few key data structures. Given your software engineering experience, you'll find these very familiar.

  1. A Queue: To manage the order of vertices to visit. This enforces the First-In, First-Out (FIFO) discipline crucial for the level-by-level traversal. We add a vertex's neighbors to the back of the queue and always process the vertex at the front.
  2. A Visited Set (or boolean array): To keep track of vertices we have already enqueued. This is essential to prevent processing the same vertex multiple times and to avoid getting stuck in infinite loops if the graph contains cycles.
  3. A Parent Array/Map: To reconstruct the shortest path. For each vertex v we visit, we store the vertex u from which we discovered it. This creates a trail of "breadcrumbs" back to the source.

The following video walks through this process, showing how these data structures interact to produce a BFS traversal and find a path.

Breadth First Search Algorithm | Shortest Path | Graph Theory

The video 'Breadth First Search Algorithm' by WilliamFiset clearly demonstrates the mechanics. It does an excellent job of showing the queue in action and then explains the pseudocode, including the critical prev (parent) array for path reconstruction.

Watch from 01:19 to 06:00. This covers two key parts: A visual demonstration of how the queue manages the traversal order. A walkthrough of the pseudocode, explaining the setup of the queue, visited array, and the prev array for path finding.

To summarize the algorithm for finding the shortest path from a source to a destination:

  1. Initialization:

    • Create a queue and add the source vertex to it.
    • Create a visited array/set and mark the source as visited.
    • Create a parent array/map, and initialize all entries to a null value (e.g., -1).
  2. Traversal Loop: While the queue is not empty:

    • Dequeue a vertex, let's call it current.
    • If current is the destination, the shortest path has been found. We can stop and proceed to reconstruction.
    • For each neighbor of current (this is where our adjacency list from the last lesson comes in handy):
      • If neighbor has not been visited:
        • Mark neighbor as visited.
        • Set parent[neighbor] = current.
        • Enqueue neighbor.
  3. Path Reconstruction:

    • If the destination was reached, backtrack from destination to source using the parent array.
    • Start with a path list containing destination.
    • While the current node in the path is not the source, prepend its parent (parent[current_node]) to the path list.

This image provides a great all-in-one summary of the process, showing the graph, data structures, and the step-by-step trace.

Breadth-First Search (BFS) Algorithm Illustration
A complete visual breakdown of BFS on a sample graph. It shows the adjacency list, the pseudocode, and a step-by-step trace of the queue's state, the visited nodes, and the final traversal order.

3. Implementation and Complexity Analysis

Now, let's look at how this translates into code and analyze its performance.

Implementation

The following resource provides a concise, high-quality implementation that demonstrates how to maintain arrays for both distance and parent pointers.

Breadth-first search

The article 'Breadth-first search' from CP-Algorithms provides a clean and direct implementation. It clearly shows how to integrate distance (d) and parent (p) tracking into the main BFS loop.

Please read the sections 'Description of the algorithm' and 'Implementation'. Focus on how the code uses three core data structures: the queue q, the boolean array used, and the parent array p. Also, review the short snippet that shows how to use the parent array p to reconstruct the path.

As you saw, the implementation is a direct translation of the algorithm we described. The parent array (named p in the code) is the key to fulfilling our learning objective. By tracing it backward from the destination, we can rebuild the shortest path. If you're interested, the "Shortest path in an unweighted graph" article on GeeksforGeeks provides complete, runnable examples in several languages including Java, Python, and C++.

Complexity Analysis

Analyzing the performance of BFS is straightforward and is a direct consequence of how it operates.

  • Time Complexity:

    • Every vertex is enqueued and dequeued exactly once. This gives us a cost of for the queue operations.
    • When we process a vertex, we iterate through its adjacency list to find its neighbors. Over the entire run of the algorithm, we will look at every edge in the graph's adjacency lists once (for a directed graph) or twice (for an undirected graph, once from each endpoint). The total work done here is therefore proportional to the number of edges, .
    • Combining these, the total time complexity is .
  • Space Complexity:

    • The visited array and parent array each require space proportional to the number of vertices, so .
    • The queue's maximum size depends on the graph's structure. In the worst-case scenario (a very "wide" graph, like a star graph where the source is the center), the queue might have to hold nearly all vertices at once. Therefore, the space for the queue is also .
    • The dominant factor is , making the auxiliary space complexity .

This efficiency, combined with its ability to find shortest paths, makes BFS a fundamental and widely used algorithm.


Conclusion

In this lesson, we took a deep dive into Breadth-First Search. We started with the core principle of its layered traversal and understood why this guarantees finding the shortest path in terms of edge count.

Key Takeaways:

  • BFS explores a graph in layers, starting from a source vertex and moving outward.
  • It relies on a queue to maintain a FIFO order of nodes to visit, ensuring the "breadth-first" property.
  • It finds the shortest path in an unweighted graph because it discovers all paths of length k before any of length k+1.
  • By using a parent array to track how each vertex was discovered, we can easily reconstruct the shortest path by backtracking from the destination.
  • The algorithm is highly efficient, with a time complexity of and space complexity of , making it practical for large graphs.

Preview of the Next Lesson:

Now that you've mastered BFS, the natural next step is to explore its counterpart: Depth-First Search (DFS). While BFS explores broadly, DFS explores deeply. In the next lesson, we will see how DFS uses a different data structure (a stack, often implicitly via recursion) to traverse as far as possible down one path before backtracking. We will implement it and analyze how its different traversal strategy makes it suitable for other types of graph problems.