syntax_ninja's blog

By syntax_ninja, history, 2 months ago, In English

1. Introduction to BFS

BFS, or Breadth-First Search, is one of the primary techniques for traversing graphs and trees. It’s commonly used in scenarios where you want to: - Explore all nodes connected to a starting node before moving deeper. - Find the shortest path in an unweighted graph (more on this soon). - Work with level-order traversal in tree structures.

If you’re familiar with Depth-First Search (DFS), you’ll notice BFS works differently. While DFS “dives deep” and backtracks, BFS explores breadth-wise, visiting nodes layer by layer, radiating out from the starting node.

Visual Example

Consider the following graph:

     A
    / \
   B   C
  /|   |\
 D E   F G

Starting from A, BFS visits nodes in this order: A, B, C, D, E, F, G.

Why BFS is Layer-by-Layer

BFS is designed to visit nodes in waves or “levels” around the start node. This property makes it perfect for finding the shortest path in unweighted graphs. Let’s break down how this level-wise processing works.

2. The Core Idea Behind BFS

In BFS, we use a queue to manage the traversal order. A queue is a First-In-First-Out (FIFO) data structure, which means elements added first are processed first. This ordering is crucial for BFS to maintain its layer-by-layer exploration.

Here’s how BFS processes nodes:

  1. Enqueue the starting node.
  2. While the queue is not empty:
  • Dequeue (process) a node.
  • Add all unvisited neighbors of the dequeued node to the queue.
  • Mark nodes as visited to avoid cycles.

Key Point:

Since BFS doesn’t go deep into one path before switching to the next, it ends up finding the shortest path to each reachable node in an unweighted graph. That’s because it explores all nodes at distance d from the start before exploring nodes at distance d + 1.

3. Code Walkthrough

Here’s a BFS implementation in Python with some comments to guide you through:

from collections import deque

def bfs(graph, start):
    visited = set()  # To track visited nodes
    queue = deque([start])  # Queue to manage the order of exploration

    while queue:
        node = queue.popleft()  # Dequeue from the front of the queue
        if node not in visited:
            print(node, end=" ")  # Process the node
            visited.add(node)  # Mark node as visited
            
            for neighbor in graph[node]:  # Explore each neighbor
                if neighbor not in visited:
                    queue.append(neighbor)  # Enqueue unvisited neighbors

# Example graph represented as an adjacency list
graph = {
    'A': ['B', 'C'],
    'B': ['A', 'D', 'E'],
    'C': ['A', 'F'],
    'D': ['B'],
    'E': ['B', 'F'],
    'F': ['C', 'E']
}

bfs(graph, 'A')

Explanation:

  • We initialize a queue with the start node (e.g., 'A').
  • While the queue has nodes:
  • We dequeue the front node.
  • If this node hasn’t been visited, we:
    • Print or process the node.
    • Add it to the visited set.
    • Enqueue each unvisited neighbor of the node.

Expected Output: A B C D E F

In this example, BFS explores A, then goes on to explore B and C, followed by D, E, and finally F. All nodes are visited in a layered order, which is perfect for discovering the shortest path if we add any distance or unweighted cost.

4. When and Why to Use BFS?

4.1 Shortest Path in an Unweighted Graph

In unweighted graphs (where all edges have the same cost), BFS can find the shortest path between nodes. Here’s why: BFS covers each layer in the minimum number of steps to reach it, so the first time we reach a node, it’s through the shortest possible path.

Example: Shortest Path in a Maze

Consider a maze represented by a grid where you can move up, down, left, or right. If you want to find the shortest path from a starting point to the exit, BFS is ideal since each move in the grid is “unweighted” (each step costs the same).

4.2 Level-Order Traversal of Trees

For binary trees or N-ary trees, BFS allows us to traverse level by level. Each “layer” corresponds to a level in the tree, which is great for problems where you need to process nodes by depth.

5. Common BFS Problem Patterns

  • Finding the Shortest Path: Problems where you need to find the shortest route from one node to another.
  • Connected Components: Problems involving counting or exploring groups of connected nodes in a graph.
  • Flood Fill: Used in matrix/graph problems where you need to explore all reachable nodes of a particular type (e.g., marking all cells of a specific color in an image).
  • Minimum Moves: BFS is often the key when you’re looking for the smallest number of steps to reach a destination in grid-based puzzles or knight’s moves on a chessboard.

6. Additional Resources and Visual Tools

  1. Visualizing BFS with Visualgo: A great tool for seeing how BFS works in real-time with animations.
  1. BFS Explained with Examples: GeeksforGeeks
  2. BFS on YouTube: A beginner-friendly video explaining BFS in simple terms.

Full text and comments »

  • Vote: I like it
  • +2
  • Vote: I do not like it