Breadth First Search Operations
Node
Edge→
Run
Start
Target
100%
Ctrl + scroll to zoom
Watch BFS explore a graph level by level, visiting all neighbors before going deeper.
Time O(V + E) · Space O(V)
from collections import deque
from typing import Dict, Set, List, Any
def bfs(graph: Dict[Any, Set[Any]], start: Any) -> List[Any]:
"""
Perform BFS traversal starting from a given node.
Args:
graph: Adjacency list representation {node: {neighbors}}
start: Starting node for traversal
Returns:
List of nodes in BFS order (level by level)
Raises:
ValueError: If start node is not in graph
"""
if start not in graph:
raise ValueError(f"Start node '{start}' not in graph")
visited = {start}
queue = deque([start])
order = []
while queue:
node = queue.popleft() # O(1) - why we use deque instead of list
order.append(node)
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
return order
def bfs_with_levels(graph: Dict[Any, Set[Any]], start: Any) -> Dict[Any, int]:
"""
Perform BFS and return the level (distance) of each reachable node.
Args:
graph: Adjacency list representation
start: Starting node
Returns:
Dictionary mapping each reachable node to its level (distance from start)
"""
if start not in graph:
raise ValueError(f"Start node '{start}' not in graph")
levels = {start: 0}
queue = deque([start])
while queue:
node = queue.popleft()
for neighbor in graph[node]:
if neighbor not in levels:
levels[neighbor] = levels[node] + 1
queue.append(neighbor)
return levels
def bfs_shortest_path(graph: Dict[Any, Set[Any]], start: Any, target: Any) -> List[Any] | None:
"""
Find the shortest path between two nodes using BFS.
In an unweighted graph, BFS guarantees the shortest path
(minimum number of edges) because it explores level by level.
Args:
graph: Adjacency list representation
start: Starting node
target: Target node to reach
Returns:
List of nodes representing the shortest path, or None if no path exists
"""
if start not in graph:
raise ValueError(f"Start node '{start}' not in graph")
if target not in graph:
raise ValueError(f"Target node '{target}' not in graph")
if start == target:
return [start]
visited = {start}
queue = deque([(start, [start])]) # (node, path_to_node)
while queue:
node, path = queue.popleft()
for neighbor in graph[node]:
if neighbor not in visited:
new_path = path + [neighbor]
if neighbor == target:
return new_path
visited.add(neighbor)
queue.append((neighbor, new_path))
return None # No path found
# Example usage and demonstration
if __name__ == "__main__":
# Example graph (undirected, represented with bidirectional edges)
#
# A
# / \
# B C
# / \ \
# D E F
#
example_graph = {
"A": {"B", "C"},
"B": {"A", "D", "E"},
"C": {"A", "F"},
"D": {"B"},
"E": {"B"},
"F": {"C"},
}
print("BFS Traversal:", bfs(example_graph, "A"))
print("Node Levels:", bfs_with_levels(example_graph, "A"))
print("Shortest Path A->F:", bfs_shortest_path(example_graph, "A", "F"))
Breadth-first search (BFS) explores a graph by visiting all neighbors of a node before moving to the next level. It uses a queue to process nodes in the order they were discovered. BFS finds the shortest path in unweighted graphs.
Start at the source node, mark it as visited, and add it to a queue. While the queue is not empty, dequeue a node, process it, and enqueue all its unvisited neighbors. This guarantees level-by-level exploration.