Quick Info

Category: Graph/Searching
Time Complexity: O(V + E)
Space Complexity: O(V)
Input Type: Graph

Depth First Search (DFS)

Description

Depth First Search (DFS) is a graph traversal algorithm that explores as far as possible along each branch before backtracking. Starting from a chosen source vertex, it explores the graph by going deeper through adjacent nodes before visiting sibling nodes. DFS is particularly useful for:

  • Detecting cycles in a graph
  • Finding connected components
  • Topological sorting
  • Solving puzzles with only one solution (like mazes)
  • Finding strongly connected components

Visualization

Click on a node to start DFS traversal

How It Works

DFS works by following these steps:

  1. Start at a chosen vertex (root node)
  2. Mark the current vertex as visited
  3. Recursively visit any unvisited adjacent vertices
  4. Backtrack when no unvisited adjacent vertices remain

Key Characteristics

  • Uses a stack (or recursion) for implementation
  • Memory efficient for deep graphs
  • Can get stuck in infinite paths without cycle detection
  • Not guaranteed to find shortest paths

Implementation


def dfs_recursive(graph, vertex, visited=None):
    if visited is None:
        visited = set()
    
    visited.add(vertex)
    print(vertex, end=' ')  # Process current vertex
    
    for neighbor in graph[vertex]:
        if neighbor not in visited:
            dfs_recursive(graph, neighbor, visited)

def dfs_iterative(graph, start):
    visited = set()
    stack = [start]
    
    while stack:
        vertex = stack.pop()
        if vertex not in visited:
            visited.add(vertex)
            print(vertex, end=' ')  # Process current vertex
            
            # Add unvisited neighbors to stack
            for neighbor in reversed(graph[vertex]):
                if neighbor not in visited:
                    stack.append(neighbor)
#include <stack>
#include <vector>
#include <unordered_set>

void dfs_recursive(const vector<vector<int>>& graph, int vertex,
                  unordered_set<int>& visited) {
    visited.insert(vertex);
    cout << vertex << " ";  // Process current vertex
    
    for (int neighbor : graph[vertex]) {
        if (visited.find(neighbor) == visited.end()) {
            dfs_recursive(graph, neighbor, visited);
        }
    }
}

void dfs_iterative(const vector<vector<int>>& graph, int start) {
    unordered_set<int> visited;
    stack<int> s;
    s.push(start);
    
    while (!s.empty()) {
        int vertex = s.top();
        s.pop();
        
        if (visited.find(vertex) == visited.end()) {
            visited.insert(vertex);
            cout << vertex << " ";  // Process current vertex
            
            // Add unvisited neighbors to stack
            for (int i = graph[vertex].size() - 1; i >= 0; --i) {
                int neighbor = graph[vertex][i];
                if (visited.find(neighbor) == visited.end()) {
                    s.push(neighbor);
                }
            }
        }
    }
}
public class DFS {
    public void DfsRecursive(Dictionary<int, List<int>> graph, 
                            int vertex, HashSet<int> visited = null)
    {
        visited ??= new HashSet<int>();
        
        visited.Add(vertex);
        Console.Write($"{vertex} ");  // Process current vertex
        
        foreach (int neighbor in graph[vertex])
        {
            if (!visited.Contains(neighbor))
            {
                DfsRecursive(graph, neighbor, visited);
            }
        }
    }
    
    public void DfsIterative(Dictionary<int, List<int>> graph, int start)
    {
        var visited = new HashSet<int>();
        var stack = new Stack<int>();
        stack.Push(start);
        
        while (stack.Count > 0)
        {
            int vertex = stack.Pop();
            if (!visited.Contains(vertex))
            {
                visited.Add(vertex);
                Console.Write($"{vertex} ");  // Process current vertex
                
                // Add unvisited neighbors to stack
                foreach (int neighbor in graph[vertex].Reverse())
                {
                    if (!visited.Contains(neighbor))
                    {
                        stack.Push(neighbor);
                    }
                }
            }
        }
    }
}

Complexity Analysis

Operation Time Complexity Space Complexity
Worst Case O(V + E) O(V)
Average Case O(V + E) O(V)
Best Case O(1) O(1)

Where V is the number of vertices and E is the number of edges in the graph.

Advantages and Disadvantages

Advantages

  • Memory efficient for deep graphs
  • Naturally recursive implementation
  • Useful for finding paths and cycles
  • Good for topological sorting
  • Simple to implement

Disadvantages

  • Can get stuck in infinite paths
  • Not guaranteed to find shortest path
  • May use more stack space in recursive implementation
  • Less suitable for finding shortest paths
  • Can be slower for shallow, wide graphs

Applications

  • Solving mazes and puzzles
  • Topological sorting in DAGs
  • Finding connected components
  • Cycle detection in graphs
  • Path finding in game trees
  • Web crawling