feat(patterns): graph/tree traversal tutorials

This commit is contained in:
2025-08-18 22:00:08 +01:00
parent 2360727e11
commit 7fd9e2a632
4 changed files with 1128 additions and 0 deletions

View File

@@ -0,0 +1,255 @@
name: BFS (Breadth-First Search)
slug: bfs
difficulty_level: 3
description: >
Level-by-level traversal using a queue, exploring all neighbors at the current
depth before moving deeper. This guarantees the shortest path in unweighted
graphs and is ideal for problems requiring layer-by-layer processing.
when_to_use: |
- Shortest path in unweighted graphs
- Level-order tree traversal
- Finding all nodes at distance K
- Word ladder / transformation problems
- Spreading simulations (rotting oranges, infection)
metaphor: |
Imagine dropping a stone into a still pond. Ripples spread outward in concentric
circles—first touching everything 1 meter away, then 2 meters, then 3 meters.
BFS works the same way: it explores all nodes at distance 1 before any node at
distance 2.
Another analogy: searching for someone in a building. You check everyone on your
current floor before taking the stairs to the next floor. You never go upstairs
until you've searched every room on the current level.
core_concept: |
BFS uses a **queue** (FIFO) to process nodes in the order they were discovered.
This guarantees that when you first reach a node, you've taken the shortest path
to get there (in terms of number of edges).
The key insight is that the queue naturally separates nodes by their distance
from the source. Everything added in round 1 is at distance 1. Everything added
in round 2 is at distance 2. By the time you dequeue a node, all closer nodes
have already been processed.
**Why it finds shortest paths**: If there were a shorter path to node X, you
would have discovered X earlier (through that shorter path) and already
processed it. The first time you reach X is always via the shortest route.
visualization: |
**Example: Shortest path from A to F**
```
Graph:
A --- B --- E
| |
C --- D --- F
BFS from A:
Queue: [A] Visited: {A} Level 0
Process A → add B, C
Queue: [B, C] Visited: {A,B,C} Level 1
Process B → add E, D
Process C → add D (skip, visited)
Queue: [E, D] Visited: {A,B,C,E,D} Level 2
Process E → no new neighbors
Process D → add F
Queue: [F] Visited: {A,B,C,E,D,F} Level 3
Process F → Found! Distance = 3
```
**Level-order tree traversal:**
```
1
/ \
2 3
/ \ \
4 5 6
Level 0: [1]
Level 1: [2, 3]
Level 2: [4, 5, 6]
Result: [[1], [2,3], [4,5,6]]
```
code_template: |
from collections import deque
def bfs_shortest_path(graph: dict, start: str, end: str) -> int:
"""Find shortest path distance in unweighted graph."""
if start == end:
return 0
queue = deque([start])
visited = {start}
distance = 0
while queue:
distance += 1
# Process all nodes at current level
for _ in range(len(queue)):
node = queue.popleft()
for neighbor in graph[node]:
if neighbor == end:
return distance
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
return -1 # No path found
def bfs_level_order(root) -> list[list]:
"""Level-order traversal of binary tree."""
if not root:
return []
result = []
queue = deque([root])
while queue:
level = []
for _ in range(len(queue)):
node = queue.popleft()
level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(level)
return result
def bfs_matrix(grid: list[list[int]], start: tuple) -> int:
"""BFS on a 2D grid."""
rows, cols = len(grid), len(grid[0])
queue = deque([start])
visited = {start}
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
distance = 0
while queue:
for _ in range(len(queue)):
r, c = queue.popleft()
for dr, dc in directions:
nr, nc = r + dr, c + dc
if (0 <= nr < rows and 0 <= nc < cols
and (nr, nc) not in visited
and grid[nr][nc] != 0): # 0 = wall
visited.add((nr, nc))
queue.append((nr, nc))
distance += 1
return distance
recognition_signals:
- "shortest path"
- "minimum steps"
- "level order"
- "nearest"
- "unweighted graph"
- "distance"
- "spreading"
- "layer by layer"
- "all nodes at distance K"
- "word ladder"
- "rotting oranges"
common_mistakes:
- title: Adding to visited after dequeuing instead of when enqueuing
description: |
Marking nodes as visited when you dequeue them (instead of when you
enqueue them) causes the same node to be added multiple times, leading
to incorrect distances and wasted processing.
fix: |
Always mark nodes as visited immediately when adding to the queue:
```python
if neighbor not in visited:
visited.add(neighbor) # Mark NOW
queue.append(neighbor)
```
- title: Not tracking levels correctly
description: |
Processing the entire queue without tracking level boundaries makes it
impossible to know the distance or handle level-by-level logic.
fix: |
Use the "process current level size" pattern:
```python
for _ in range(len(queue)): # Fixed size at level start
node = queue.popleft()
# Process node
distance += 1 # Increment after level completes
```
- title: Using a list instead of deque
description: |
Using `list.pop(0)` is O(n) because all elements must shift. This turns
O(V+E) BFS into O(V²).
fix: |
Use `collections.deque` which has O(1) popleft:
```python
from collections import deque
queue = deque([start])
```
- title: Forgetting to check bounds in grid BFS
description: |
Moving to invalid coordinates (negative or beyond grid dimensions) causes
index errors.
fix: |
Always validate coordinates before accessing the grid:
```python
if 0 <= nr < rows and 0 <= nc < cols:
# Safe to access grid[nr][nc]
```
variations:
- name: Shortest path (unweighted)
description: |
Classic BFS to find minimum number of edges between two nodes.
example: "Word Ladder, Minimum Genetic Mutation, Open the Lock"
- name: Level-order traversal
description: |
Process tree nodes level by level, returning grouped results.
example: "Binary Tree Level Order Traversal, Zigzag Level Order"
- name: Multi-source BFS
description: |
Start BFS from multiple sources simultaneously. Useful for "spreading"
problems where multiple starting points expand in parallel.
example: "Rotting Oranges, Walls and Gates, 01 Matrix"
- name: Bidirectional BFS
description: |
Search from both start and end simultaneously, meeting in the middle.
Reduces search space from O(b^d) to O(b^(d/2)).
example: "Word Ladder (optimized), Shortest Path in large graphs"
- name: BFS with state
description: |
Track additional state beyond position (e.g., keys collected, walls broken).
State becomes part of the "node" being visited.
example: "Shortest Path in Grid with Obstacles Elimination"
related_patterns:
- dfs
- tree-traversal
- matrix-traversal
prerequisite_patterns: []