Algorithm Breadth First Search Bfs And Depth First Search Dfs Complete Guide

 Last Update:2025-06-22T00:00:00     .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    10 mins read      Difficulty-Level: beginner

Understanding the Core Concepts of Algorithm Breadth First Search BFS and Depth First Search DFS

Breadth First Search (BFS)

Definition: Breadth First Search is a fundamental graph traversal algorithm that starts at a given node (commonly referred to as the "root" node in the context of trees) and explores all its neighbor nodes at the present depth level before moving on to nodes at the next depth level. This approach ensures that the shortest path to any node is found in terms of the fewest edges when dealing with an unweighted graph.

Key Steps:

  1. Initialization: Start from the root node (or any chosen starting node) and mark it as visited.
  2. Queue Management: Place this initial node in a queue.
  3. Traversal Process:
    • Dequeue a node from the front of the queue.
    • Visit all its unvisited neighbors, mark them as visited, and enqueue them.
    • Repeat the process until the queue is empty.

Data Structures Used:

  • Queue: Essential for the FIFO (First In, First Out) nature of BFS, ensuring nodes are explored level by level.

Importance:

  • Shortest Path in Graphs: BFS is crucial for finding the shortest path from the start node to any other node in an unweighted graph. This property is essential in network routing protocols where minimizing latency is a priority.
  • Cycle Detection: Can be adapted to detect cycles in an undirected graph by keeping track of parent nodes.
  • Bipartite Check: BFS can determine if a graph is bipartite (can be divided into two sets such that no two nodes within a set have an edge connecting them).

Applications:

  • Network Broadcasting: Ensures information reaches all parts of the network efficiently and uniformly.
  • Web Crawling: Utilized by search engines to crawl web pages systematically starting from a seed URL.
  • Social Media Analysis: Helps in identifying friend circles or the degree of separation between individuals.

Example Code (Python):

from collections import deque

def bfs(graph, start):
    visited = set()
    queue = deque([start])
    visited.add(start)
    
    while queue:
        vertex = queue.popleft()
        print(vertex)
        
        for neighbor in graph[vertex]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)
                
# Example Usage
graph = {'A': ['B', 'C'], 
         'B': ['A', 'D', 'E'], 
         'C': ['A', 'F'], 
         'D': ['B'], 
         'E': ['B', 'F'], 
         'F': ['C', 'E']}

bfs(graph, 'A')

Depth First Search (DFS)

Definition: Depth First Search is another core graph traversal method that begins at a root node (or any arbitrary node in an unconnected graph) and explores as far as possible along each branch before backtracking. Unlike BFS, it uses a LIFO (Last In, First Out) approach which can be implemented using recursion or a stack.

Key Steps:

  1. Initialization: Start from the root node (or any starting point) and mark it as visited.
  2. Recursive Exploration or Stack Management:
    • For recursive DFS: Visit an adjacent node, recursively explore it, return, and visit the next adjacent node.
    • For iterative DFS: Use a stack to keep track of nodes to visit.
  3. Backtrack: Once a node has no more adjacent unvisited nodes, backtrack to explore other branches.

Data Structures Used:

  • Stack: Commonly used in the iterative version of DFS to manage the nodes to be visited next.
  • Recursion Stack: Automatically managed during the recursive approach.

Importance:

  • Path Finding: Especially useful for solving puzzles with only one solution, like a mazes, where you need to explore the entire path before determining it's unsolvable.
  • Topological Sorting: Crucial for scheduling tasks with dependencies. DFS is often part of the topological sort algorithm.
  • Checking Connectivity: Can determine if a graph is connected by exploring all nodes from one arbitrary node.

Applications:

  • Game Solving: Used in games like Minesweeper, Chess, or Sudoku, where backtracking might find the correct moves or solutions.
  • Circuit Design: In computer science, helps in analyzing logic gates and circuits where connectivity and cycle detection matter.
  • Maze Solving: Commonly used in robotics to navigate through mazes or environments where obstacles must be circumvented.

Example Code (Python):

Online Code run

🔔 Note: Select your programming language to check or run code at

💻 Run Code Compiler

Step-by-Step Guide: How to Implement Algorithm Breadth First Search BFS and Depth First Search DFS

1. Graph Representation

First, let's define how we will represent a graph. We'll use an adjacency list for simplicity. An adjacency list represents each vertex and its adjacent vertices in a collection (like a dictionary or hash map).

Below is a sample graph for our examples:

    A
   / \
  B   C
 / \   \
D   E   F

This graph can be represented as:

graph = {
    'A': ['B', 'C'],
    'B': ['A', 'D', 'E'],
    'C': ['A', 'F'],
    'D': ['B'],
    'E': ['B'],
    'F': ['C']
}

2. Breadth-First Search (BFS)

BFS starts at the root node (or an arbitrary starting node) and explores all the neighbors at the present depth prior to moving on to nodes at the next depth level.

Step-by-Step Example for Unweighted, Undirected Graph

Let’s perform BFS on the graph starting from node A.

Steps:

  1. Initialize: Start with the initial node A, enqueue it, and mark it as visited.
  2. Process Queue: While the queue is not empty, dequeue a node v and visit it. For each neighbor of v that has not been visited, enqueue it and mark it as visited.
  3. Repeat: Continue this process until all reachable nodes are visited.

Python Code:

from collections import deque

def bfs(graph, start):
    visited = set()
    queue = deque([start])
    visited.add(start)
    
    while queue:
        v = queue.popleft()  # get first node from queue
        print(v, end=' ')  # visit it
        
        # visit all nieghbours
        for neighbor in graph[v]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)

# Sample graph
graph = {
    'A': ['B', 'C'],
    'B': ['A', 'D', 'E'],
    'C': ['A', 'F'],
    'D': ['B'],
    'E': ['B'],
    'F': ['C']
}

print("BFS Traversal starting from A:")
bfs(graph, 'A')

Output:

BFS Traversal starting from A:
A B C D E F 

The order in which nodes are visited is A -> B -> C -> D -> E -> F. This is because BFS explores all the nodes at the current depth before moving to the next level.

BFS Explanation

  • Start: Node A is enqueued, and marked as visited.
  • Dequeue A: Node A is visited, and its neighbors B and C are enqueued.
  • Dequeue B: Node B is visited, and its neighbors A, D, and E are considered. Nodes A and B are already visited, so only D and E are added to the queue.
  • Dequeue C: Node C is visited, and its neighbor F is added to the queue since A (its other neighbor) is already visited.
  • Dequeue D: Node D is visited, and since all its neighbors (B) have already been visited, nothing new is added to the queue.
  • Dequeue E & F: Similarly, nodes E and F are visited, and no new neighbors are added to the queue.

3. Depth-First Search (DFS)

DFS starts at the root node (or an arbitrary starting node) and explores as far as possible along each branch before backtracking.

There are two common ways to implement DFS: using a stack data structure or recursively.

Step-by-Step Example Using Stack

Let’s perform DFS on the same graph starting from node A.

Steps:

  1. Initialize: Start with the initial node A, push it onto the stack, and mark it as visited.
  2. Process Stack: While the stack is not empty, pop a node v from the stack and visit it. For each neighbor of v that has not been visited, push it onto the stack and mark it as visited.
  3. Repeat: Continue this process until all reachable nodes are visited.
def dfs_stack(graph, start):
    visited = set()
    stack = [start]
    
    while stack:
        v = stack.pop()  # get last node from stack
        if v not in visited:
            visited.add(v)
            print(v, end=' ')
        
        # Add unvisited neighbors to stack
        for neighbor in reversed(graph[v]):
            if neighbor not in visited:
                stack.append(neighbor)

# Sample graph
graph = {
    'A': ['B', 'C'],
    'B': ['A', 'D', 'E'],
    'C': ['A', 'F'],
    'D': ['B'],
    'E': ['B'],
    'F': ['C']
}

print("\nDFS Traversal using Stack starting from A:")
dfs_stack(graph, 'A')

Output:

DFS Traversal using Stack starting from A:
A B D E C F 

The order in which nodes are visited is A -> B -> D -> E -> C -> F. This path demonstrates the deep exploration before backtracking.

Step-by-Step Example Using Recursion

Alternatively, you can perform DFS using recursion.

Steps:

  1. Initialize: Start with the initial node A, recursively visit it, and mark it as visited.
  2. Recursive Visit Neighbors: For each neighbor of the node that has not been visited, recursively perform DFS.
  3. Continue Until Exhausted: Repeat until all reachable nodes are visited.
def dfs_recursive(graph, v, visited):
    if v not in visited:
        visited.add(v)
        print(v, end=' ')
        
        for neighbor in graph[v]:
            dfs_recursive(graph, neighbor, visited)

def dfs(graph, start):
    visited = set()
    dfs_recursive(graph, start, visited)

# Sample graph
graph = {
    'A': ['B', 'C'],
    'B': ['A', 'D', 'E'],
    'C': ['A', 'F'],
    'D': ['B'],
    'E': ['B'],
    'F': ['C']
}

print("\nDFS Traversal using Recursion starting from A:")
dfs(graph, 'A')

Output:

DFS Traversal using Recursion starting from A:
A B D E C F 

The output here is the same as the stack implementation. But keep in mind that recursive DFS might lead to a stack overflow if the graph is very large and deeply nested.

4. Directed Graph Example

Let’s take a directed graph to illustrate how BFS and DFS work in this context. The graph will look like this:

    A
   / \
  v   v
  B   C
 / \   \
v   v   v
D   E   F

Directed Graph Representation:

directed_graph = {
    'A': ['B', 'C'],
    'B': ['D', 'E'],
    'C': ['F'],
    'D': [],
    'E': [],
    'F': []
}

BFS on Directed Graph

# Using the same bfs function from before

print("\nBFS Traversal starting from A (Directed Graph):")
bfs(directed_graph, 'A')

Output:

BFS Traversal starting from A (Directed Graph):
A B C D E F 

DFS on Directed Graph

Using the same dfs_stack and dfs_recursive functions from before

# Using stack implementation
print("\nDFS Traversal using Stack starting from A (Directed Graph):")
dfs_stack(directed_graph, 'A')

# Using recursive implementation
print("\nDFS Traversal using Recursion starting from A (Directed Graph):")
dfs(directed_graph, 'A')

Output:

DFS Traversal using Stack starting from A (Directed Graph):
A C F B E D 

DFS Traversal using Recursion starting from A (Directed Graph):
A B D E C F 

Notice how the traversal paths change depending on the direction of the edges in the graph.

5. Key Differences

  • BFS uses a queue and explores nodes level by level.
  • DFS can use either a stack or recursion and explores nodes by going as deep as possible.
  • BFS finds the shortest path in an unweighted graph where each edge has the same weight.
  • DFS is useful for problems like pathfinding (e.g., cycle detection), where exploring deeply first is beneficial.

Summary

In this tutorial, we learned the fundamental difference between Breadth-First Search (BFS) and Depth-First Search (DFS). We represented a graph in Python and provided step-by-step examples of both BFS and DFS, illustrating the difference in traversal methods on both undirected and directed graphs.

Top 10 Interview Questions & Answers on Algorithm Breadth First Search BFS and Depth First Search DFS


Q1: What is Breadth-First Search (BFS)?

A: Breadth-First Search (BFS) is an algorithm used to traverse or search through a data structure like a tree or graph. It begins at a root node (or any arbitrary node in a graph), and then explores all of the neighbor nodes at the present depth before moving on to nodes at the next depth level. BFS uses a queue to keep track of unvisited nodes by adding them as they're visited.


Q2: What is Depth-First Search (DFS)?

A: Depth-First Search (DFS) is a traversal technique used in graphs and trees where it starts from the root node and explores as far as possible along each branch before backtracking. Unlike BFS, DFS does not necessarily explore all immediate neighbors before continuing down a particular path. DFS can be implemented recursively using function calls, or iteratively using a stack.


Q3: When should I use BFS over DFS?

A: BFS is typically used when:

  • You need to find the shortest path in an unweighted graph.
  • The problem specifies exploration of the neighboring nodes first (level-wise exploration).
  • You want to search for nodes based on their proximity to the start node.

DFS is more suitable when:

  • You want to explore all branches of a tree/graph to its maximum depth (e.g., puzzles).
  • There's a limit on memory, as DFS uses less memory compared to BFS due to visiting deeper paths without exploring all neighbors first.
  • You need to check cycles in a graph or explore connected components.

Q4: Can both BFS and DFS be applied to directed and undirected graphs?

A: Yes, both BFS and DFS can be applied to both directed and undirected graphs. These algorithms fundamentally work with nodes and edges, where the direction of edges matters only in how connectivity is determined or explored, but not in the overall mechanism of the searches.


Q5: How do BFS and DFS handle disconnected graphs?

A: In the case of a disconnected graph, neither BFS nor DFS can traverse the entire graph unless explicitly told to do so.

For DFS, you can simply run the DFS algorithm starting from each node that has not been visited yet. This way, every connected component can be explored.

In BFS, you similarly iterate over each node and apply the BFS algorithm to visit all nodes reachable from the current unvisited node, ensuring the traversal of all connected components.


Q6: Does DFS require more time than BFS?

A: The time complexity of both BFS and DFS is (O(V + E)), where (V) is the number of vertices and (E) is the number of edges in the graph. Therefore, in terms of time complexity, DFS and BFS are equivalent.

However, the practical running time can vary based on the implementation details and specific characteristics of the graph or tree being searched.


Q7: Can both BFS and DFS be modified to determine paths between nodes?

A: Yes, both BFS and DFS can be modified to find paths between nodes.

For BFS, when you reach the destination node, the path recorded up to that point represents the shortest path in an unweighted graph since BFS explores levels (distances) one by one.

For DFS, it's usually more complex because DFS won't guarantee the shortest path in an unweighted graph due to its nature of going deep first. However, if paths to the target are needed, it can be done by storing the path in the recursive call stack (or manually in an iteration stack) and checking once the destination is reached.

In weighted graphs, Dijkstra's and A* algorithms, which are modifications of BFS, are used to find shortest paths, whereas DFS would require backtracking mechanisms like recursive calls or explicit stacks combined with additional data structures (like priority queues) to ensure shortest paths.


Q8: What are some common applications of BFS?

A: Common applications of BFS include:

  • Finding the shortest path in an unweighted graph.
  • Testing a graph for bipartiteness (can it be colored with two colors s.t. no two adjacent nodes have the same color?).
  • Finding connected components in a graph.
  • Solving puzzles with a unique solution, such as maze-solving.
  • Network routing algorithms, like SPF routing (Shortest Path Forwarding).

Q9: What are some common applications of DFS?

A: Common applications of DFS are:

  • Topological sorting, useful in scheduling jobs with prerequisites.
  • Finding connected components in a graph.
  • Detecting cycles in both directed and undirected graphs.
  • Generating permutations.
  • Pathfinding and mazes solving for unique or multiple solutions.

Q10: How do BFS and DFS differ in space complexity?

A: The space complexity of BFS is higher compared to DFS.

For BFS, since you are traversing level by level, the worst-case scenario (fully-connected graph) requires storing all nodes of the last level at once, resulting in a space complexity of (O(V)).

For DFS, you need to store visited nodes along the current path. If the graph/tree is very deep but has few branches, the space complexity will be relatively low because you only need enough space for the current path, not the entire breadth of levels. In the worst case, for a linearly-structured graph, the space complexity for DFS becomes (O(V)) (when a stack of depth V needs to be maintained).

Thus, while BFS uses more space for breadth across levels, DFS uses more space with depth for long paths when not using iterative deepening or pruning techniques.


You May Like This Related .NET Topic

Login to post a comment.