Graph : Depth-First Search (DFS)

Graph : Depth-First Search (DFS)

·

5 min read

Explain the Problem

Implement the Depth-First Search (DFS) algorithm for graph traversal. Given a graph and a starting vertex, traverse the graph in depth-first order and print the vertices in the order they are visited.

Example:

Input:

Graph: 
0: [1, 2]
1: [0, 3, 4]
2: [0, 4]
3: [1, 5]
4: [1, 2, 5]
5: [3, 4]

Starting Vertex: 0

Output:

0 1 3 5 4 2

Imagine Climbing a Big Treehouse

You're in a big treehouse with many levels and rooms, and you want to explore every room to find hidden clues. The treehouse is built with lots of branches and rooms connected by ladders.

How DFS Works in the Treehouse Exploration

  1. Start at the Entrance: You start at the entrance of the treehouse, which we'll call Room A.

  2. Pick a Path and Dive Deep: Instead of exploring nearby rooms first, you pick one path and go as far as you can. For example, you find a ladder to Room B and climb up.

  3. Keep Going Until You Hit a Dead End: From Room B, you find another ladder to Room D and climb up. From Room D, you find a ladder to Room G and climb up. But Room G has no more ladders to climb; it's a dead end.

  4. Backtrack: Since Room G is a dead end, you go back down the ladder to Room D and look for other ladders. If there’s another ladder to Room H, you climb up. If Room H is also a dead end, you backtrack again.

  5. Continue Until All Paths Are Explored: You keep picking a path and diving deep until you hit dead ends, then backtrack and explore other paths you haven’t visited. You do this until you’ve explored all rooms in the treehouse.

Key Points

  • Stack: Instead of a queue, you use a stack (like a pile of books) to keep track of rooms to visit. You always explore the top room on the stack first.

  • Go Deep First: You go as deep as possible down one path before exploring other paths.

  • Mark Visited: You mark rooms as visited to avoid revisiting them.

Visualization

Stack: [A]
Visit: A
Stack: [B]
Visit: B
Stack: [D]
Visit: D
Stack: [G]
Visit: G (dead end)
Backtrack to: D
Stack: [H]
Visit: H (dead end)
Backtrack to: B
Explore other paths from B

Real-Life Example: Treasure Hunt in a Treehouse

  1. Start at Room A: You start at the entrance (Room A).

  2. Climb Ladder to Room B: You find a ladder to Room B and climb up.

  3. Keep Climbing to Room D: From Room B, you find a ladder to Room D and climb up.

  4. Go Deeper to Room G: From Room D, you find a ladder to Room G and climb up. Room G is a dead end.

  5. Backtrack to Room D: Since Room G is a dead end, you go back to Room D.

  6. Explore Room H: From Room D, you find another ladder to Room H and climb up. Room H is also a dead end.

  7. Backtrack to Room B: Since Room H is a dead end, you go back to Room B.

  8. Explore Other Paths: You explore other ladders from Room B and continue the process.

Why Use DFS?

DFS is great when you want to explore all possibilities and paths thoroughly, like finding all hidden clues in a treehouse. It’s like being super curious and diving deep into every path you find, making sure you don't miss anything hidden in the deepest corners.

Visualization of a Simple Treehouse:

         A
        / \
       B   C
      / \
     D   E
    / \
   G   H
  • Start at A.

  • Go deep through B -> D -> G (dead end).

  • Backtrack to D -> H (dead end).

  • Backtrack to B -> E (if unexplored).

  • Backtrack to A -> C (if unexplored).

In DFS, you explore one path deeply, then backtrack and explore other paths, ensuring you don’t miss any rooms or hidden clues!

Solution in JavaScript with Code Commenting

Here's the JavaScript solution with detailed comments:

function dfs(graph, start) {
  const visited = new Set(); // Set to keep track of visited vertices
  const stack = [start]; // Stack for DFS

  while (stack.length > 0) {
    const vertex = stack.pop(); // Pop a vertex from the stack
    if (!visited.has(vertex)) {
      console.log(vertex); // Visit the vertex
      visited.add(vertex); // Mark the vertex as visited

      // Push all unvisited neighbors to the stack
      for (const neighbor of graph[vertex]) {
        if (!visited.has(neighbor)) {
          stack.push(neighbor);
        }
      }
    }
  }
}

// Example usage:
const graph = {
  0: [1, 2],
  1: [0, 3, 4],
  2: [0, 4],
  3: [1, 5],
  4: [1, 2, 5],
  5: [3, 4]
};

dfs(graph, 0);
// Output: 0 2 4 5 3 1

Explanation of the Solution in Simple Terms

Imagine you have a group of friends, and you want to visit each friend one by one, starting from a specific friend. Instead of visiting friends in the order they were introduced to you, you follow each friend as far as you can before backtracking. You keep track of who you've already visited to avoid visiting the same person twice.

  • Initialize: You start with the first friend and put them in a stack.

  • Process Stack: While there are friends in the stack, you take the most recently added friend, visit them, and add all their unvisited friends to the stack.

Code Explanation in Pointers

  1. Initialize Visited Set: Create a set to keep track of visited vertices.

  2. Initialize Stack: Start the stack with the starting vertex.

  3. Process Stack: While the stack is not empty, do the following:

    • Pop a vertex from the stack.

    • If the vertex has not been visited, visit it and mark it as visited.

    • Push all unvisited neighbors of the vertex to the stack.

  4. Print Vertex: Print the vertex when visiting.

Complexity Analysis

  • Time Complexity: O(V + E), where V is the number of vertices and E is the number of edges. This is because each vertex and edge is processed once.

  • Space Complexity: O(V), due to the storage of the stack and the visited set.