Quiz 2

21. BFS & DFS — Graph Traversals

876 words
4 min read
Python Week 1: the first filter for runtime behavior
Visual companion
Python
Type and operator map

Python Week 1: the first filter for runtime behavior

View
Revision summary

What this note is really saying

Short form

# 21. BFS & DFS — Graph Traversals > **What problem does this solve?** You have a graph.

21. BFS & DFS — Graph Traversals

What problem does this solve? You have a graph. How do you visit every reachable vertex? Two fundamental strategies exist: BFS (breadth-first, level by level, using a queue) and DFS (depth-first, go as deep as possible, using a stack/recursion).

1. Breadth-First Search (BFS)

Mental Model

BFS is like dropping a stone in a pond — the ripples spread outward one layer at a time. It finds the shortest path in unweighted graphs. (Diagram)

Implementation

python
# runnable
from collections import deque
def bfs(graph, start):
    """BFS traversal. Returns order of visitation.
    Time: O(V + E)
    Space: O(V) for queue + visited set
    """
    visited = {start}
    queue = deque([start])
    order = []
    while queue:
        vertex = queue.popleft()
        order.append(vertex)
        for neighbor in graph[vertex]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)
    return order
# Graph as adjacency list
graph = {
    'A': ['B', 'C'],
    'B': ['A', 'D', 'E'],
    'C': ['A', 'F'],
    'D': ['B'],
    'E': ['B', 'F'],
    'F': ['C', 'E']
}
print(f"BFS: {bfs(graph, 'A')}")  # ['A', 'B', 'C', 'D', 'E', 'F']

BFS Shortest Path

python
# runnable
def bfs_shortest_path(graph, start, target):
    """Return shortest path from start to target using BFS."""
    visited = {start}
    queue = deque([(start, [start])])  # (vertex, path so far)
    while queue:
        vertex, path = queue.popleft()
        if vertex == target:
            return path
        for neighbor in graph[vertex]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append((neighbor, path + [neighbor]))
    return None  # No path
print(f"Shortest path A→F: {bfs_shortest_path(graph, 'A', 'F')}")
# ['A', 'C', 'F']

2. Depth-First Search (DFS)

Mental Model

DFS is like exploring a maze — you go down one path until you hit a dead end, then backtrack and try another path. It's naturally recursive. (Diagram)

Implementation (Recursive)

python
# runnable
def dfs_recursive(graph, start):
    """DFS traversal using recursion."""
    visited = set()
    order = []
    def _dfs(v):
        visited.add(v)
        order.append(v)
        for neighbor in graph[v]:
            if neighbor not in visited:
                _dfs(neighbor)
    _dfs(start)
    return order
print(f"DFS rec: {dfs_recursive(graph, 'A')}")
# ['A', 'B', 'D', 'E', 'F', 'C']

Implementation (Iterative with Stack)

python
# runnable
def dfs_iterative(graph, start):
    """DFS traversal using explicit stack."""
    visited = set()
    stack = [start]
    order = []
    while stack:
        vertex = stack.pop()
        if vertex not in visited:
            visited.add(vertex)
            order.append(vertex)
            # Add in reverse order for consistent traversal
            for neighbor in reversed(graph[vertex]):
                if neighbor not in visited:
                    stack.append(neighbor)
    return order
print(f"DFS iter: {dfs_iterative(graph, 'A')}")

3. Preorder and Postorder Numbers (DFS)

DFS can assign preorder (when first discovered) and postorder (when all descendants processed) numbers to each vertex. This is crucial for topological sorting and cycle detection.
python
# runnable
def dfs_prepost(graph):
    """Assign preorder and postorder numbers to each vertex."""
    visited = set()
    pre = {}
    post = {}
    clock = [0]  # Mutable counter
    def _dfs(v):
        visited.add(v)
        clock[0] += 1
        pre[v] = clock[0]
        for neighbor in graph[v]:
            if neighbor not in visited:
                _dfs(neighbor)
        clock[0] += 1
        post[v] = clock[0]
    for v in graph:
        if v not in visited:
            _dfs(v)
    return pre, post
pre, post = dfs_prepost(graph)
print(f"Pre:  {pre}")   # {'A': 1, 'B': 2, 'D': 3, 'E': 4, 'F': 5, 'C': 6}
print(f"Post: {post}")  # {'D': 4, 'F': 7, 'E': 8, 'B': 9, 'C': 10, 'A': 12}

4. Connected Components

python
# runnable
def connected_components(graph):
    """Find all connected components in an undirected graph."""
    visited = set()
    components = []
    for v in graph:
        if v not in visited:
            # BFS/DFS to find all vertices in this component
            component = []
            queue = deque([v])
            visited.add(v)
            while queue:
                curr = queue.popleft()
                component.append(curr)
                for neighbor in graph[curr]:
                    if neighbor not in visited:
                        visited.add(neighbor)
                        queue.append(neighbor)
            components.append(component)
    return components
# Disconnected graph
disc = {
    'A': ['B'],
    'B': ['A'],
    'C': ['D'],
    'D': ['C'],
    'E': []
}
print(f"Components: {connected_components(disc)}")
# ['A', 'B'], ['C', 'D'], ['E'](/courses/bscs2002/notes/'A'%2C%20'B'%5D%2C%20%5B'C'%2C%20'D'%5D%2C%20%5B'E')

5. Applications

AlgorithmUse Case
BFSShortest path (unweighted), web crawling, social networks
DFSTopological sort, cycle detection, connected components, solving mazes
BFS + QueueFinding shortest path in unweighted graphs
DFS + Pre/PostDetecting cycles, topological ordering

Practice Questions

Q1. Trace BFS on a graph A→B→D, A→C→E. What's the visitation order? Q2. When would you use BFS over DFS, and vice versa? Q3. In DFS iterative, why do we add neighbors in reverse order? Q4. How can you detect a cycle in a directed graph using DFS? Q5. What's the time complexity of BFS/DFS? Why is it O(V+E) and not O(V×E)?
Answers
A1. BFS: A, B, C, D, E (level by level).
A2. BFS: shortest path, any problem where closer neighbors are better. DFS: topological sort, cycle detection, maze solving, exhaustive search.
A3. To maintain the same traversal order as the recursive version (which processes neighbors in list order).
A4. During DFS, if we encounter a back edge (an edge to an ancestor in the DFS tree — a vertex currently on the recursion stack), there's a cycle.
A5. Each vertex is visited once (V visits). For each vertex, we examine all its incident edges. Sum of degrees = 2E for undirected, E for directed. Total: O(V + E). Join Discord Previous20. Graph Representations — Adjacency Matrix & ListNext22. Topological Sort & DAG Longest Path
Document outline

Keep your place and jump directly to a heading.

Table of Contents
System Normal // Awaiting Context

Intelligence Hub

Navigate the knowledge graph to generate context. The Hub adapts dynamically to surface backlinks, related notes, and metadata insights.