Module 11: Graphs·Tutorial 18 of 23 (Step 11.1)
All Tutorials·DSA Roadmap·2 Problems · ~8 hrs

11.1 Graph Fundamentals, BFS & DFS

Understand what a Graph is (Vertices, Edges, Directed/Undirected), how to build an Adjacency List, and when to use Graph DFS vs. Multi-Source BFS.

01 Read the story: What & Why
02 Follow the drawing: Visual stepper
03 Practice code: 4 languages + problems

1. What Is a Graph?

A Graph is the most general way to model connections between anything:

  • Vertices (Nodes, V): The things themselves (cities on a map, users on a social network, courses in a degree, or cells (r, c) in a 2D grid).
  • Edges (E): The links connecting two nodes (u and v).
    • Undirected Edge (u <-> v): A two-way street (if A is friends with B, B is friends with A).
    • Directed Edge (u -> v): A one-way street (Course A must be taken before Course B).

How is a Graph different from a Tree? A Tree is just a restricted Graph with one root and no loops (cycles). A general Graph can have cycles, disconnected islands, and multiple paths between the same two nodes!

Visual: Graph Nodes & Adjacency List Representation
0123Adjacency List adj[u]:0: [1, 2] | 1: [0, 3]2: [0] | 3: [1]Memory: O(V + E)

2. Why Do We Need visited & How Do We Store a Graph in Code?

1. How to Store a Graph: Adjacency List (adj[u])

In interviews, graphs are usually given as a list of edges like edges = [[0, 1], [0, 2], [1, 3]]. Before doing anything else, convert that edge list into an Adjacency List (adj[u] = list of neighbors of u). That lets you look up all neighbors of any node u immediately using only O(V + E) memory.

RepresentationSpace UsedCheck if Edge (u, v) ExistsLoop Over All Neighbors of uBest Used When
Adjacency List (adj[u])O(V + E)O(degree(u))O(degree(u)) (Fast!)99% of Interview Graphs (sparse graphs where E << V^2)
Adjacency Matrix (mat[u][v])O(V^2)O(1)O(V) (Slow on sparse graphs)Small, dense graphs (V <= 500) or 2D Grids
Edge List ([[u, v], ...])O(E)O(E)O(E)Kruskal's MST / Union-Find problems

2. Why visited Is Mandatory

Because graphs can contain cycles (0 -> 1 -> 2 -> 0), walking to neighbors without remembering where you have already been will cause an infinite loop! Always mark a node in visited the moment you discover it.

3. When to Pick DFS vs. BFS

  • Graph DFS (Recursion): Best for exploring or counting connected islands (Number of Islands, Clone Graph).
  • Graph BFS / Multi-Source BFS (FIFO Queue): Best for shortest unweighted path / minimum minutes (Rotting Oranges, Word Ladder). Because BFS expands outward one ripple at a time, the first time it touches a node is guaranteed to be via the shortest number of edges!
The Hand-Drawn Step-by-Step Walkthrough
Step 1 / 2

Interactive Step-by-Step Trace: Multi-Source BFS Wavefront

Watch how pushing all starting sources at t = 0 spreads outward layer by layer:

Minute 0: Push ALL Initial Sources into the Queue Together
dist = 0Src At = 0
dist = 0Src Bt = 0
unvisitedCell C
unvisitedCell D
queue = deque([SrcA, SrcB]) // Mark visited immediately on enqueue!

Both sources start expanding simultaneously at t = 0.

✎ Remember this: Mark nodes visited WHEN PUSHING to the queue, not when popping!

3. Basic Operations & Walkthroughs (All 4 Languages)

from collections import deque

# 1. Build an Adjacency List from an undirected edge list -> O(V + E)
def build_graph(n: int, edges: list[list[int]]) -> list[list[int]]:
  adj = [[] for _ in range(n)]
  for u, v in edges:
      adj[u].append(v)
      adj[v].append(u)
  return adj

# 2. Graph DFS (Explore one connected component) -> O(V + E)
def dfs(u: int, adj: list[list[int]], visited: set[int]) -> None:
  visited.add(u)
  for v in adj[u]:
      if v not in visited:
          dfs(v, adj, visited)

# 3. Multi-Source Grid BFS (e.g., Rotting Oranges) -> O(R * C)
def multi_source_bfs(grid: list[list[int]]) -> int:
  rows, cols = len(grid), len(grid[0])
  q = deque()
  fresh = 0
  for r in range(rows):
      for c in range(cols):
          if grid[r][c] == 2: q.append((r, c))
          elif grid[r][c] == 1: fresh += 1
  minutes = 0
  while q and fresh > 0:
      minutes += 1
      for _ in range(len(q)):
          r, c = q.popleft()
          for dr, dc in ((1,0), (-1,0), (0,1), (0,-1)):
              nr, nc = r + dr, c + dc
              if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 1:
                  grid[nr][nc] = 2
                  fresh -= 1
                  q.append((nr, nc))
  return minutes if fresh == 0 else -1

4. Core Interview Patterns & Common Traps

Pattern: Multi-Source BFS & Border-Inward Flood Fill

Use Multi-Source BFS when multiple starting cells spread simultaneously (Rotting Oranges, 01 Matrix); use Border-Inward DFS when finding cells connected to the boundary of a grid (Pacific Atlantic Water Flow).

# Mark visited IMMEDIATELY when pushing to the BFS queue:
q = deque(initial_sources)
visited = set(initial_sources)
steps = 0
while q:
  for _ in range(len(q)):
      r, c = q.popleft()
      for dr, dc in ((1,0), (-1,0), (0,1), (0,-1)):
          nr, nc = r + dr, c + dc
          if 0 <= nr < R and 0 <= nc < C and (nr, nc) not in visited:
              visited.add((nr, nc))
              q.append((nr, nc))
  steps += 1
Operation / PatternTimeSpaceNotes
Build Adjacency ListO(V + E)O(V + E)Standard representation for sparse graphs
Graph / Grid BFS & DFSO(V + E) or O(R * C)O(V) or O(R * C)Marks each node visited once

5. Practice On Your Own (Curated Submodule Problems)

0 / 2 Solved
StatusProblemDifficultyComplexityHint?Links & Notes
Number of Islands
Matrix & Graph BFS / DFS
Medium
T: O(R * C)
S: O(R * C)
Solve
Rotting Oranges
Matrix & Graph BFS / DFS
Medium
T: O(R * C)
S: O(R * C)
Solve