Lesson 44 of 50 advanced

BFS and DFS — Exploring Graphs Step by Step

Master the two most important ways to explore a graph — level by level or deep into the unknown!

Open interactive version (quiz + challenge)

Real-world analogy

Imagine you drop a pebble into a calm pond. The ripples spread outward in perfect circles — first the water right around the pebble moves, then the next ring, then the next. That is BFS! It explores everything nearby before going further away. Now imagine you are in a giant maze. You pick one path and walk as far as you can until you hit a dead end. Then you backtrack to the last fork and try a different path. That is DFS! Both methods explore every reachable room in the maze, but in completely different orders. BFS is calm and organized like ripples, DFS is adventurous and bold like a maze explorer!

What is it?

BFS (Breadth-First Search) and DFS (Depth-First Search) are the two fundamental ways to explore every node in a graph. BFS uses a queue and explores level by level, making it ideal for shortest paths in unweighted graphs. DFS uses recursion or a stack and explores as deep as possible, making it great for finding connected components and exploring all paths.

Real-world relevance

GPS navigation apps use BFS to find the shortest route between two locations on a road map. Social media platforms use BFS to suggest 'People You May Know' by exploring friends-of-friends. DFS is used in puzzle solvers (like solving a Sudoku by trying each possibility deeply), in web crawlers that follow links as deep as they go, and in detecting cycles in dependency graphs (like making sure software packages do not depend on each other in a loop).

Key points

Code example

#include <bits/stdc++.h>
using namespace std;

// BFS — find shortest distances from a starting node
void bfs(int start, vector<vector<int>>& adj, int n) {
    vector<int> dist(n + 1, -1);   // -1 means not visited yet
    queue<int> q;

    dist[start] = 0;               // Start node is 0 steps away
    q.push(start);                 // Add start to the queue

    while (!q.empty()) {
        int node = q.front();      // Grab the next node to process
        q.pop();

        for (int nb : adj[node]) { // Look at each neighbor
            if (dist[nb] == -1) {  // If we have not visited it
                dist[nb] = dist[node] + 1;  // It is one step farther
                q.push(nb);        // Add it to the queue
            }
        }
    }

    // Show shortest distances from start to every node
    for (int i = 1; i <= n; i++) {
        cout << "Distance to " << i << ": " << dist[i] << "\n";
    }
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    // Graph with 7 nodes, two groups: {1,2,3,4} and {5,6,7}
    int n = 7;
    vector<vector<int>> adj(n + 1);  // Adjacency list (1-indexed)

    // Group 1 edges: 1-2, 2-3, 3-4, 1-4
    adj[1].push_back(2); adj[2].push_back(1);
    adj[2].push_back(3); adj[3].push_back(2);
    adj[3].push_back(4); adj[4].push_back(3);
    adj[1].push_back(4); adj[4].push_back(1);

    // Group 2 edges: 5-6, 6-7
    adj[5].push_back(6); adj[6].push_back(5);
    adj[6].push_back(7); adj[7].push_back(6);

    cout << "=== BFS Shortest Paths from Node 1 ===\n";
    bfs(1, adj, n);
    // Nodes 5, 6, 7 are not connected to node 1, so distance = -1

    return 0;
}

Line-by-line walkthrough

  1. 1. We include bits/stdc++.h for all standard library features. Our graph is stored as vector> adj — an adjacency list where adj[i] contains all neighbors of node i.
  2. 2. In bfs(), we create a dist[] array initialized to -1 (meaning unvisited). We set dist[start] = 0 and push the start node into a queue. The queue ensures we process nodes in the order they were discovered.
  3. 3. The BFS while loop takes the front node from the queue, then checks each neighbor. The critical check 'if (dist[neighbor] == -1)' ensures we only visit each node once. Without this, cycles would cause infinite loops!
  4. 4. When we find an unvisited neighbor, we set dist[neighbor] = dist[node] + 1. This works because BFS explores level by level — every neighbor is exactly one step farther than the current node.
  5. 5. In countComponents(), we loop through all nodes 1 to n. Each time we find an unvisited node, we increment the component counter and run DFS from that node to mark all reachable nodes as visited.
  6. 6. The DFS uses a stack instead of recursion to avoid stack overflow on large graphs. We push the start, then repeatedly pop a node, check its neighbors, and push any unvisited ones onto the stack.
  7. 7. In main(), we build a graph with 7 nodes forming two components: {1,2,3,4} connected in a square, and {5,6,7} connected in a line. This lets us test both BFS distances and component counting.
  8. 8. BFS from node 1 will find distances 0, 1, 2, 1, -1, -1, -1 for nodes 1 through 7. Nodes 5, 6, 7 are unreachable from node 1, so their distance stays -1.
  9. 9. The component counter will find exactly 2 components. First DFS starts at node 1 and marks {1,2,3,4}. Then the loop reaches node 5 (unvisited), starts a second DFS marking {5,6,7}.

Spot the bug

#include <bits/stdc++.h>
using namespace std;

void bfs(int start, vector<vector<int>>& adj, int n) {
    vector<int> dist(n + 1, -1);
    queue<int> q;

    dist[start] = 0;
    q.push(start);

    while (!q.empty()) {
        int node = q.front();
        q.pop();

        for (int neighbor : adj[node]) {
            dist[neighbor] = dist[node] + 1;
            q.push(neighbor);
        }
    }

    for (int i = 1; i <= n; i++) {
        cout << "Distance to " << i << ": " << dist[i] << "\n";
    }
}

int main() {
    int n = 3;
    vector<vector<int>> adj(n + 1);
    adj[1].push_back(2); adj[2].push_back(1);
    adj[2].push_back(3); adj[3].push_back(2);
    adj[1].push_back(3); adj[3].push_back(1);

    bfs(1, adj, n);
    return 0;
}
Need a hint?
Look at the BFS loop — when we visit a neighbor, do we check if it has already been visited? What happens with cycles in the graph?
Show answer
The bug is that BFS does not check if a neighbor was already visited before processing it! Without the check 'if (dist[neighbor] == -1)', every neighbor gets pushed into the queue even if it was already visited. In a graph with cycles, this creates an infinite loop — nodes keep getting re-added to the queue forever. The fix is to wrap the two lines inside the for loop with 'if (dist[neighbor] == -1)'. This ensures each node is visited exactly once and BFS terminates correctly.

Explain like I'm 5

Imagine your school has lots of classrooms connected by hallways. BFS is like a fire alarm — the sound spreads to nearby rooms first, then to rooms farther away, like circles getting bigger and bigger. Every room hears the alarm, and nearby rooms hear it first. DFS is like you exploring the school by always opening the next door you see and going through it, going deeper and deeper until you reach a dead end. Then you come back and try a different door. Both ways let you visit every single room — BFS goes wide first, DFS goes deep first!

Fun fact

The BFS algorithm was first invented by Konrad Zuse in 1945 — before most computers even existed! He described it in his PhD thesis but it was not published until 1972. Meanwhile, DFS was formally studied by French mathematician Charles Pierre Tremaux in the 1800s as a strategy for solving mazes. These algorithms are over 100 years old and we still use them every single day in modern technology!

Hands-on challenge

Build a graph with 8 nodes and create two separate connected components. Run BFS from node 1 and verify that unreachable nodes show distance -1. Then modify BFS to also track the actual path (not just distance) using a parent array — when BFS discovers a neighbor, store parent[neighbor] = current_node. Then trace back from any target to the start using the parent array to print the full shortest path. Finally, try solving this: given a grid where '.' is open and '#' is a wall, use BFS to find the shortest path from the top-left corner to the bottom-right corner!

More resources

Open interactive version (quiz + challenge) ← Back to course: CP Zero to Hero