Python Program to Implement BFS (Breadth-First Search)
from collections import deque; queue = deque([start]); while queue: node = queue.popleft(); for neighbor in graph[node]: if neighbor not in visited: queue.append(neighbor).Examples
How to Think About It
Algorithm
Code
from collections import deque def bfs(graph, start): visited = set([start]) queue = deque([start]) order = [] while queue: node = queue.popleft() order.append(node) for neighbor in graph.get(node, []): if neighbor not in visited: visited.add(neighbor) queue.append(neighbor) return order # Example usage graph = {'A': ['B', 'C'], 'B': ['D'], 'C': ['E'], 'D': [], 'E': []} print(bfs(graph, 'A'))
Dry Run
Let's trace BFS on graph {'A': ['B', 'C'], 'B': ['D'], 'C': ['E'], 'D': [], 'E': []} starting at 'A'.
Initialize
visited = {'A'}, queue = ['A'], order = []
Visit 'A'
queue = [], order = ['A'], neighbors = ['B', 'C']
Add neighbors 'B' and 'C'
visited = {'A', 'B', 'C'}, queue = ['B', 'C']
Visit 'B'
queue = ['C'], order = ['A', 'B'], neighbors = ['D']
Add neighbor 'D'
visited = {'A', 'B', 'C', 'D'}, queue = ['C', 'D']
Visit 'C'
queue = ['D'], order = ['A', 'B', 'C'], neighbors = ['E']
Add neighbor 'E'
visited = {'A', 'B', 'C', 'D', 'E'}, queue = ['D', 'E']
Visit 'D'
queue = ['E'], order = ['A', 'B', 'C', 'D'], neighbors = []
Visit 'E'
queue = [], order = ['A', 'B', 'C', 'D', 'E'], neighbors = []
| Queue | Visited | Order |
|---|---|---|
| ['A'] | {'A'} | [] |
| ['B', 'C'] | {'A', 'B', 'C'} | ['A'] |
| ['C', 'D'] | {'A', 'B', 'C', 'D'} | ['A', 'B'] |
| ['D', 'E'] | {'A', 'B', 'C', 'D', 'E'} | ['A', 'B', 'C'] |
| ['E'] | {'A', 'B', 'C', 'D', 'E'} | ['A', 'B', 'C', 'D'] |
| [] | {'A', 'B', 'C', 'D', 'E'} | ['A', 'B', 'C', 'D', 'E'] |
Why This Works
Step 1: Use a queue to explore nodes
The queue ensures nodes are visited in the order they are discovered, which is key to BFS exploring level by level.
Step 2: Track visited nodes
Keeping a set of visited nodes prevents revisiting and infinite loops in cyclic graphs.
Step 3: Add neighbors to queue
Neighbors of the current node are added to the queue to be visited later, ensuring all nodes at the current depth are processed first.
Alternative Approaches
from collections import deque def bfs_recursive(graph, queue, visited, order): if not queue: return order node = queue.popleft() order.append(node) for neighbor in graph.get(node, []): if neighbor not in visited: visited.add(neighbor) queue.append(neighbor) return bfs_recursive(graph, queue, visited, order) # Example usage graph = {'A': ['B', 'C'], 'B': ['D'], 'C': ['E'], 'D': [], 'E': []} print(bfs_recursive(graph, deque(['A']), {'A'}, []))
def bfs_list_queue(graph, start): visited = set([start]) queue = [start] order = [] while queue: node = queue.pop(0) order.append(node) for neighbor in graph.get(node, []): if neighbor not in visited: visited.add(neighbor) queue.append(neighbor) return order # Example usage graph = {'A': ['B', 'C'], 'B': ['D'], 'C': ['E'], 'D': [], 'E': []} print(bfs_list_queue(graph, 'A'))
Complexity: O(V + E) time, O(V) space
Time Complexity
BFS visits each vertex (V) and edge (E) once, so the time complexity is O(V + E).
Space Complexity
The queue and visited set store up to O(V) nodes, so space complexity is O(V).
Which Approach is Fastest?
Using deque is faster than list for queue operations. Recursive BFS is elegant but less practical for large graphs.
| Approach | Time | Space | Best For |
|---|---|---|---|
| Iterative BFS with deque | O(V + E) | O(V) | General use, efficient queue operations |
| Recursive BFS | O(V + E) | O(V) | Small graphs, elegant code but limited by recursion depth |
| List as queue BFS | O(V + E) | O(V) | Simple code but slower for large graphs due to list pop(0) |