<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. DSA
  3. Graphs
All lessons in this course

Lesson 33 of 50 14 minAdvanced

Depth-first search

Explore as deep as possible before backing up, recursively or with a stack, and use DFS to find connected components, detect cycles and reason about dependencies.

Depth-first search (DFS) explores like someone walking a maze with one hand on the wall: keep going forward to a new place as long as you can; when you hit a dead end, back up to the last junction with an unexplored way and continue from there. It visits the same vertices as BFS, in a very different order, and the order itself turns out to be useful.

Interactive

Depth-first search, step by step

Green roads were used to discover a new place (tree edges). Red dashed roads lead back to a place still on the current path (back edges: proof of a cycle).

ABCDEFG
Call stack (current path)
empty
Visit order
none yet
Depth-first search from A. Go to the first unvisited neighbour, and keep going deeper. Only when a place has no unvisited neighbours left, back up.

Recursive DFS

The call stack does the backing up for you: when the recursive call for a neighbour returns, you are back at the previous vertex and continue with its next neighbour.

Dfs.cs
var adj = new Dictionary<char, char[]>{    ['A'] = new[] { 'B', 'D' },    ['B'] = new[] { 'A', 'C', 'E' },    ['C'] = new[] { 'B', 'E', 'F' },    ['D'] = new[] { 'A', 'E', 'G' },    ['E'] = new[] { 'B', 'C', 'D', 'F', 'G' },    ['F'] = new[] { 'C', 'E', 'G' },    ['G'] = new[] { 'D', 'E', 'F' }}; var visited = new HashSet<char>();var order = new List<char>();Visit('A');Console.WriteLine(string.Join(" ", order)); void Visit(char u){    visited.Add(u);    order.Add(u);    foreach (char v in adj[u])        if (!visited.Contains(v))            Visit(v);                    // go deeper; when it returns, we are back at u}
Output
A B C E D G F

Compare with BFS on the same graph (A B D C E G F): DFS dives down one long path A → B → C → E → D → G → F before it ever looks at A’s second neighbour. Time is the same O(V + E); memory is O(depth) for the recursion.

Iterative DFS with a stack

On a very large graph the recursion can be thousands of calls deep and overflow the stack. Swap the queue in BFS for a Stack<T> and you get a depth-first order without recursion:

DfsIterative.cs
var adj = new Dictionary<char, char[]>{    ['A'] = new[] { 'B', 'D' },    ['B'] = new[] { 'A', 'C', 'E' },    ['C'] = new[] { 'B', 'E', 'F' },    ['D'] = new[] { 'A', 'E', 'G' },    ['E'] = new[] { 'B', 'C', 'D', 'F', 'G' },    ['F'] = new[] { 'C', 'E', 'G' },    ['G'] = new[] { 'D', 'E', 'F' }}; var visited = new HashSet<char>();var order = new List<char>();var stack = new Stack<char>();stack.Push('A');while (stack.Count > 0){    char u = stack.Pop();    if (!visited.Add(u)) continue;              // already visited via another route    order.Add(u);    foreach (char v in adj[u].Reverse())         // reversed, so the first neighbour is popped first        if (!visited.Contains(v)) stack.Push(v);}Console.WriteLine(string.Join(" ", order));
Output
A B C E D G F

Connected components

Is the site network in one piece, or are some places cut off? Start a DFS from every vertex that is not visited yet. Each start discovers one whole connected component. The number of starts is the number of components.

Components.cs
int n = 8;int[][] links = { new[] { 0, 1 }, new[] { 1, 2 }, new[] { 3, 4 }, new[] { 5, 6 }, new[] { 6, 7 }, new[] { 5, 7 } }; var adj = new List<int>[n];for (int i = 0; i < n; i++) adj[i] = new List<int>();foreach (var e in links) { adj[e[0]].Add(e[1]); adj[e[1]].Add(e[0]); } var group = new int[n];Array.Fill(group, -1);int groups = 0;for (int s = 0; s < n; s++){    if (group[s] != -1) continue;    Mark(s, groups++);                       // a new component starts here}Console.WriteLine($"{groups} components: {string.Join(" ", group)}"); void Mark(int u, int id){    group[u] = id;    foreach (int v in adj[u])        if (group[v] == -1) Mark(v, id);}
Output
3 components: 0 0 0 1 1 2 2 2

Finding cycles in dependencies

In a directed graph of dependencies, a cycle means an impossible plan: A needs B, B needs C, C needs A. DFS finds it by remembering which vertices are on the current path. If DFS reaches a vertex that is still on the current path, it has walked in a circle. (Visited-but-finished vertices are fine: that is just two routes to the same task.)

CycleCheck.cs
var deps = new Dictionary<string, string[]>{    ["Excavate"] = new[] { "Footings" },    ["Footings"] = new[] { "Columns" },    ["Columns"] = new[] { "Beams" },    ["Beams"] = new[] { "Deck" },    ["Deck"] = new[] { "Roof" },    ["Roof"] = Array.Empty<string>()};Console.WriteLine(HasCycle(deps)); deps["Roof"] = new[] { "Excavate" };            // a wrong dependencyConsole.WriteLine(HasCycle(deps)); static bool HasCycle(Dictionary<string, string[]> g){    var state = new Dictionary<string, int>();  // missing = new, 1 = on the current path, 2 = finished    foreach (var node in g.Keys)        if (Visit(node)) return true;    return false;     bool Visit(string u)    {        int s = state.GetValueOrDefault(u);        if (s == 1) return true;                // back on the current path: a cycle        if (s == 2) return false;               // already fully explored        state[u] = 1;        foreach (var v in g[u])            if (Visit(v)) return true;        state[u] = 2;                           // leaving u: no longer on the path        return false;    }}
Output
False
True

BFS or DFS?

NeedUse
Shortest path by number of stepsBFS
Nearest somethingBFS
Just “can I reach it?” / visit everythingEither (DFS is often simpler to write)
Connected components, flood fillEither
Cycle detection, topological order, dependency checksDFS
Explore all solutions of a puzzle (backtracking)DFS
Quick check

In the CycleCheck code, why is reaching a vertex with state 2 (finished) not a cycle?

Quick check

What replaces the queue of BFS in an iterative DFS?

Practice

Write CountComponents(int n, int[][] links) using DFS with an explicit Stack<int> (no recursion). For n = 7 and links (0,1), (1,2), (3,4), (5,5) the answer is 4: {0,1,2}, {3,4}, {5} and {6}.

Show a hint

Build the adjacency list. For each unvisited vertex, add 1 to the count, push it, and pop/push until the stack is empty, marking vertices as visited.

Show the solution
Solution.cs
Console.WriteLine(CountComponents(7, new[] { new[] { 0, 1 }, new[] { 1, 2 }, new[] { 3, 4 }, new[] { 5, 5 } })); static int CountComponents(int n, int[][] links){    var adj = new List<int>[n];    for (int i = 0; i < n; i++) adj[i] = new List<int>();    foreach (var e in links) { adj[e[0]].Add(e[1]); adj[e[1]].Add(e[0]); }     var seen = new bool[n];    int count = 0;    for (int s = 0; s < n; s++)    {        if (seen[s]) continue;        count++;        var stack = new Stack<int>();        stack.Push(s);        seen[s] = true;        while (stack.Count > 0)        {            int u = stack.Pop();            foreach (int v in adj[u])                if (!seen[v]) { seen[v] = true; stack.Push(v); }        }    }    return count;}
Output
4

Remember

  • DFS goes as deep as possible, then backs up; recursion or an explicit stack, O(V + E).
  • One DFS per unvisited vertex finds the connected components.
  • In directed graphs, reaching a vertex still on the current path means a cycle.
  • Use BFS for shortest steps; DFS for cycles, ordering, dependencies and backtracking.
0 of 50 lessons completed