Trees and graphs are everywhere in AI: decision trees classify data, knowledge graphs store relationships, and neural networks are directed acyclic graphs. Mastering traversal is non-negotiable.
Every tree problem boils down to: how do you visit every node?
from collections import deque
def level_order(root):
if not root:
return []
result, queue = [], deque([root])
while queue:
level = []
for _ in range(len(queue)): # process one level
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 preorder(node): # Root -> Left -> Right
if not node: return
visit(node)
preorder(node.left)
preorder(node.right)
def inorder(node): # Left -> Root -> Right (sorted for BST!)
if not node: return
inorder(node.left)
visit(node)
inorder(node.right)
def postorder(node): # Left -> Right -> Root (delete safely)
if not node: return
postorder(node.left)
postorder(node.right)
visit(node)
When to use which? BFS for shortest path / level-by-level processing. DFS for path finding, depth calculation, or when you need to process children before parents (postorder).
| Problem | Traversal | Key Insight |
|---------|-----------|-------------|
| Max depth | DFS | 1 + max(depth(left), depth(right)) |
| Level order traversal | BFS | Queue with level-size batching |
| Validate BST | Inorder DFS | Values must be strictly increasing |
| Lowest Common Ancestor | DFS | Recurse, check left/right returns |
| Serialize/Deserialize | BFS or Preorder | Encode nulls as markers |
Graphs are trees without the "no cycles" guarantee. The key addition: a visited set to avoid infinite loops.
from collections import deque
def bfs(graph, start):
visited = {start}
queue = deque([start])
while queue:
node = queue.popleft()
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
def dfs(graph, node, visited=None):
if visited is None:
visited = set()
visited.add(node)
for neighbor in graph[node]:
if neighbor not in visited:
dfs(graph, neighbor, visited)
Why is BFS guaranteed to find the shortest path in an unweighted graph? Because it explores all nodes at distance d before any node at distance d+1. DFS has no such guarantee — it might find a longer path first.
| Problem | Algorithm | Complexity | |---------|-----------|------------| | Shortest path (unweighted) | BFS | O(V + E) | | Detect cycle (undirected) | DFS with parent tracking | O(V + E) | | Detect cycle (directed) | DFS with 3-color marking | O(V + E) | | Number of islands | BFS/DFS flood fill | O(rows * cols) | | Topological sort | DFS postorder reverse | O(V + E) | | Connected components | BFS/DFS from each unvisited | O(V + E) |
Trees and graphs aren't just data structures — they're the backbone of modern AI:
| Structure | Traversal | Data Structure | Space | Best For | |-----------|-----------|----------------|-------|----------| | Tree BFS | Level order | Queue | O(w) | Level processing, shortest path | | Tree DFS | Pre/In/Post | Stack/Recursion | O(h) | Path problems, BST validation | | Graph BFS | Wavefront | Queue + Visited | O(V) | Shortest path, connected components | | Graph DFS | Deep first | Stack + Visited | O(V) | Cycle detection, topological sort |
w = max width, h = height, V = vertices