Graph Traversal: BFS and DFS
[!NOTE] If a Graph is a maze, Traversal is the strategy to explore it. There are two main philosophies: Go Wide (BFS) or Go Deep (DFS).
In interviews and real-world system design, graphs model almost everything: networks of computers, social media connections, dependencies in a package manager, or cities on a map. Knowing how to search these graphs efficiently is foundational. Do you need to find the absolute shortest path between two people on LinkedIn? Or do you need to quickly figure out if two servers are connected at all? The answers lie in BFS and DFS.
1. Breadth-First Search (BFS)
Imagine dropping a stone in a pond. The ripples expand outward in perfect circles. That is BFS.
- Strategy: Explore all neighbors at the current depth before moving deeper.
- Data Structure: Queue (FIFO).
- Key Property: Finds the Shortest Path in unweighted graphs.
- Analogy: A contagious virus spreading person-to-person.
2. Depth-First Search (DFS)
Imagine solving a maze. You follow a path until you hit a dead end, then backtrack to the last junction and try a different path. That is DFS.
- Strategy: Go as deep as possible down one branch before backtracking.
- Data Structure: Stack (LIFO) or Recursion.
- Key Property: Good for puzzles, topological sorting, and detecting cycles.
- Analogy: Exploring a cave system.
3. Complexity & Comparison
Time & Space Complexity
| Algorithm | Time Complexity | Space Complexity | Why? |
|---|---|---|---|
| BFS | $O(V + E)$ | $O(V)$ | Every vertex $V$ and edge $E$ is visited once. Space is bounded by the widest level (up to $O(V)$). |
| DFS | $O(V + E)$ | $O(V)$ | Every vertex and edge is visited once. Space is bounded by the maximum recursion depth (up to $O(V)$ for a skewed graph). |
When to use which?
- BFS: Use when you want to find the shortest path on an unweighted graph, or when you know the target is close to the starting point. It requires more memory for wide graphs.
- DFS: Use for exploring all possibilities (like solving a maze, generating permutations), checking for cycles, or topological sorting. It uses less memory for wide graphs but more for deep graphs.
4. Interactive: Traversal Visualizer
Click the grid to place Obstacles (Walls). Then run BFS or DFS to see how they navigate from Start (Green) to fill the available space.
5. Code Implementation
Breadth-First Search (Queue)
Java
public void bfs(int startNode) {
boolean[] visited = new boolean[V];
Queue<Integer> queue = new LinkedList<>();
visited[startNode] = true;
queue.add(startNode);
while (!queue.isEmpty()) {
int u = queue.poll();
System.out.print(u + " ");
for (int v : adj.get(u)) {
if (!visited[v]) {
visited[v] = true;
queue.add(v);
}
}
}
}
Go
func (g *Graph) BFS(startNode int) {
visited := make(map[int]bool)
queue := []int{startNode}
visited[startNode] = true
for len(queue) > 0 {
u := queue[0]
queue = queue[1:] // Dequeue
fmt.Printf("%d ", u)
for _, v := range g.Adj[u] {
if !visited[v] {
visited[v] = true
queue = append(queue, v) // Enqueue
}
}
}
}
Depth-First Search (Stack / Recursion)
Java
public void dfs(int u, boolean[] visited) {
visited[u] = true;
System.out.print(u + " ");
for (int v : adj.get(u)) {
if (!visited[v]) {
dfs(v, visited);
}
}
}
// Wrapper
public void startDFS(int startNode) {
boolean[] visited = new boolean[V];
dfs(startNode, visited);
}
Go
func (g *Graph) DFS(u int, visited map[int]bool) {
visited[u] = true
fmt.Printf("%d ", u)
for _, v := range g.Adj[u] {
if !visited[v] {
g.DFS(v, visited)
}
}
}
// Usage: g.DFS(0, make(map[int]bool))