BFS Programming Tutorial

Understanding Queue Operations & Implementation

From Riyadh to Makkah: Programming Breadth-First Search

Learning Objectives

🎯 Programming Concepts:
  • Queue data structure (FIFO)
  • Enqueue and Dequeue operations
  • BFS algorithm implementation
  • Graph representation in code
πŸ› οΈ Practical Skills:
  • Reading and writing BFS code
  • Debugging search algorithms
  • Understanding step-by-step execution
  • Visualizing algorithm progression
  • Implementing in Python
Our 5-City Network
Riyadh ↔ Qassim (408km) Riyadh ↔ Jeddah (849km) Qassim ↔ Medina (320km) Medina ↔ Jeddah (420km) Jeddah ↔ Makkah (80km)
BFS will find: Riyadh β†’ Jeddah β†’ Makkah (fewest hops)
Interactive City Map
Geographic layout shows Saudi Arabia cities
Start Cities Goal

BFS Queue Visualization

Queue is empty - Click "Start BFS" to begin!
Queue follows FIFO (First In, First Out) principle
Processed items shown grayed with checkmark
Ready to start | Use Previous/Next to navigate step-by-step
πŸ”‘ Key Programming Concepts
Queue Operations:
  • deque([start]) - Initialize queue
  • queue.popleft() - Dequeue (FIFO)
  • queue.append() - Enqueue (add to back)
  • len(queue) - Check if empty
Data Structures:
  • deque - Efficient double-ended queue
  • set - Fast lookup for visited cities
  • dict - Parent mapping for path reconstruction
BFS Properties:
  • Explores level by level (breadth-first)
  • Guarantees shortest path (fewest hops)
  • Time: O(V + E), Space: O(V)
  • Uses more memory than DFS
  • Visual: processed items stay visible
Common Pitfalls:
  • Forgetting to mark as visited before enqueue
  • Using list instead of deque (inefficient)
  • Not handling disconnected graphs
Complete Python Implementation
from collections import deque

def bfs_shortest_path(graph, start, goal):
    """
    Find shortest path using BFS (Breadth-First Search)
    
    Args:
        graph: dict of city connections {city: [neighbors]}
        start: starting city name (string)
        goal: destination city name (string)
        
    Returns:
        list: path from start to goal (shortest in terms of hops)
        None: if no path exists
        
    Time Complexity: O(V + E) where V=vertices, E=edges
    Space Complexity: O(V) for queue and visited set
    """
    # Step 1: Initialize data structures
    queue = deque([start])        # FIFO queue for BFS
    visited = set([start])        # Track visited cities
    parent = {start: None}        # Track parent for path reconstruction
    
    print(f"πŸš€ Starting BFS from {start} to {goal}")
    print(f"πŸ“‹ Initial queue: {list(queue)}")
    
    # Step 2: BFS main loop
    while queue:
        # Dequeue: Remove first city from queue (FIFO)
        current = queue.popleft()
        print(f"\nπŸ” Processing: {current}")
        print(f"πŸ“‹ Queue after dequeue: {list(queue)}")
        
        # Step 3: Check if goal reached
        if current == goal:
            print(f"🎯 Goal {goal} found!")
            # Reconstruct path by following parent pointers
            path = []
            temp = current
            while temp is not None:
                path.append(temp)
                temp = parent[temp]
            return path[::-1]  # Reverse to get start→goal order
        
        # Step 4: Explore all neighbors of current city
        neighbors = graph.get(current, [])
        print(f"πŸ‘€ Exploring neighbors of {current}: {neighbors}")
        
        for neighbor in neighbors:
            if neighbor not in visited:
                # Mark as visited to avoid cycles
                visited.add(neighbor)
                # Set parent for path reconstruction
                parent[neighbor] = current
                # Enqueue: Add to back of queue
                queue.append(neighbor)
                print(f"  βž• Added {neighbor} to queue (via {current})")
        
        print(f"πŸ“‹ Queue after exploring {current}: {list(queue)}")
        print(f"βœ… Visited cities: {visited}")
    
    print(f"❌ No path found from {start} to {goal}")
    return None  # No path exists

def print_bfs_analysis(graph, start, goal):
    """Run BFS with detailed analysis"""
    print("="*60)
    print("🧠 BFS ALGORITHM ANALYSIS")
    print("="*60)
    
    path = bfs_shortest_path(graph, start, goal)
    
    if path:
        print(f"\nπŸ† SUCCESS! Path found: {' β†’ '.join(path)}")
        print(f"πŸ“ Path length: {len(path) - 1} hops")
        print(f"πŸ™οΈ  Cities in path: {len(path)}")
    else:
        print(f"\nπŸ’” No path exists from {start} to {goal}")

# Our Saudi cities network (same as visual demo)
cities_graph = {
    'Riyadh': ['Qassim', 'Jeddah'],      # Capital city: 2 connections
    'Qassim': ['Riyadh', 'Medina'],      # Central region: 2 connections  
    'Medina': ['Qassim', 'Jeddah'],      # Holy city: 2 connections
    'Jeddah': ['Riyadh', 'Medina', 'Makkah'],  # Port city: 3 connections
    'Makkah': ['Jeddah']                 # Holy city: 1 connection
}

# Example usage
if __name__ == "__main__":
    print_bfs_analysis(cities_graph, 'Riyadh', 'Makkah')
    
    # Try different start/goal combinations
    print("\n" + "="*40)
    print("πŸ”„ TESTING OTHER ROUTES")
    print("="*40)
    
    test_cases = [
        ('Riyadh', 'Medina'),
        ('Qassim', 'Makkah'),  
        ('Medina', 'Riyadh')
    ]
    
    for start, goal in test_cases:
        path = bfs_shortest_path(cities_graph, start, goal)
        result = f"{' β†’ '.join(path)}" if path else "No path"
        print(f"πŸ“ {start} to {goal}: {result}")
Code Execution Output

This shows exactly what you'll see when running the Python code:

$ python bfs_search.py

============================================================
🧠 BFS ALGORITHM ANALYSIS  
============================================================
πŸš€ Starting BFS from Riyadh to Makkah
πŸ“‹ Initial queue: ['Riyadh']

πŸ” Processing: Riyadh
πŸ“‹ Queue after dequeue: []
πŸ‘€ Exploring neighbors of Riyadh: ['Qassim', 'Jeddah']
  βž• Added Qassim to queue (via Riyadh)
  βž• Added Jeddah to queue (via Riyadh)
πŸ“‹ Queue after exploring Riyadh: ['Qassim', 'Jeddah']
βœ… Visited cities: {'Riyadh', 'Qassim', 'Jeddah'}

πŸ” Processing: Qassim
πŸ“‹ Queue after dequeue: ['Jeddah']
πŸ‘€ Exploring neighbors of Qassim: ['Riyadh', 'Medina']
  βž• Added Medina to queue (via Qassim)
πŸ“‹ Queue after exploring Qassim: ['Jeddah', 'Medina']
βœ… Visited cities: {'Riyadh', 'Qassim', 'Jeddah', 'Medina'}

πŸ” Processing: Jeddah
πŸ“‹ Queue after dequeue: ['Medina']
πŸ‘€ Exploring neighbors of Jeddah: ['Riyadh', 'Medina', 'Makkah']
  βž• Added Makkah to queue (via Jeddah)
πŸ“‹ Queue after exploring Jeddah: ['Medina', 'Makkah']
βœ… Visited cities: {'Riyadh', 'Qassim', 'Jeddah', 'Medina', 'Makkah'}

πŸ” Processing: Medina
πŸ“‹ Queue after dequeue: ['Makkah']
πŸ‘€ Exploring neighbors of Medina: ['Qassim', 'Jeddah']
πŸ“‹ Queue after exploring Medina: ['Makkah']
βœ… Visited cities: {'Riyadh', 'Qassim', 'Jeddah', 'Medina', 'Makkah'}

πŸ” Processing: Makkah
πŸ“‹ Queue after dequeue: []
🎯 Goal Makkah found!

πŸ† SUCCESS! Path found: Riyadh β†’ Jeddah β†’ Makkah
πŸ“ Path length: 2 hops
πŸ™οΈ  Cities in path: 3

========================================
πŸ”„ TESTING OTHER ROUTES
========================================
πŸ“ Riyadh to Medina: Riyadh β†’ Qassim β†’ Medina
πŸ“ Qassim to Makkah: Qassim β†’ Riyadh β†’ Jeddah β†’ Makkah  
πŸ“ Medina to Riyadh: Medina β†’ Qassim β†’ Riyadh
Key Observations
  • FIFO Order: Qassim processed before Jeddah (added first)
  • Level-by-level: All neighbors of Riyadh before their neighbors
  • Shortest Path: Found 2-hop path instead of 3-hop path
  • No Revisits: Already visited cities are skipped
Programming Insights
  • Queue Operations: Clear dequeue/enqueue pattern
  • Data Tracking: Visited set prevents infinite loops
  • Path Reconstruction: Parent mapping builds final path
  • Debug Output: Print statements show algorithm state
Practice Exercise
🎯 Challenge: Modify the Code

Try these modifications to deepen your understanding:

  1. Add a city counter to track how many cities BFS explores
  2. Print the queue contents at each step
  3. Modify to return the distance as well as the path
  1. Add error handling for invalid start/goal cities
  2. Create a function to visualize the search tree
  3. Compare performance with different graph sizes