top of page
Writer's pictureHarini Mallawaarachchi

BFS & DFS


BFS and DFS are two popular graph traversal algorithms used to visit and explore all the vertices in a graph or tree. Both algorithms have their own strengths and weaknesses and can be used in different scenarios depending on the specific requirements of the problem.


In summary, BFS and DFS are two algorithms used for searching and traversing graphs or trees. BFS visits all the nodes at a certain level before moving on to the nodes at the next level, while DFS visits all the nodes along a path as far as possible before backtracking.


Here is a table comparing BFS and DFS:

Property

BFS

DFS

Traversal Order

Level-Order (breadth-first)

​Depth-First

Data Structure Used

Queue

Stack

Memory Usage

More (stores all nodes at a level)

Less (only stores path to a node)

​Shortest Path

Yes (if weights are equal)

Not guaranteed

Time Complexity

O(V+E)

O(V+E)

BFS:


BFS (Breadth-First Search) is a graph traversal algorithm that explores all the vertices at the same level before moving on to the next level. BFS is often used to find the shortest path between two nodes in an unweighted graph, since it guarantees that the first time we visit the destination node, we have found the shortest path to it. BFS is also useful for finding all the vertices in a connected component, detecting cycles in a graph, performing a topological sort on a DAG, and for web crawling.

def bfs(graph, start):
    visited = set()
    queue = [start]

    while queue:
        node = queue.pop(0)
        if node not in visited:
            visited.add(node)
            neighbors = graph[node]
            queue.extend(neighbors - visited)

    return visited

In this code snippet, `graph` is a dictionary where each key represents a node in the graph, and the corresponding value is a set of its neighbors. `start` is the starting node for the BFS. The function returns a set of all the visited nodes.



DFS:


DFS (Depth-First Search), on the other hand, explores as far as possible along each branch before backtracking. DFS can be implemented recursively or iteratively using a stack. DFS is often used to find all the vertices in a connected component, detect cycles in a graph, perform a topological sort on a DAG, and for maze solving.

def dfs(graph, start, visited=None):
    if visited is None:
        visited = set()

    visited.add(start)

    for neighbor in graph[start] - visited:
        dfs(graph, neighbor, visited)

    return visited


In this code snippet, `graph` is the same as in the BFS example, and `start` is the starting node for the DFS. The function uses recursion to traverse the graph and returns a set of all the visited nodes. The `visited` parameter is optional and is used to keep track of visited nodes between recursive calls.



Note that these code snippets are for undirected graphs. For directed graphs, the neighbors of a node should only include its outgoing edges. Also, in practice, it's common to use a `deque` instead of a list as the queue data structure in BFS for better performance.


How to identify when to use BFS and DFS?


To decide whether to use BFS or DFS, consider the specific requirements of the problem and the properties of the graph or tree. Here are some general guidelines:


  • If the problem requires finding the shortest path in an unweighted graph, use BFS.

  • If the problem requires finding all the vertices in a connected component, either BFS or DFS can be used, but BFS is faster and easier to implement.

  • If the problem requires detecting cycles in a graph, use DFS.

  • If the problem requires performing a topological sort on a DAG, use DFS.

  • If the problem requires solving a maze, use DFS.

  • If the problem requires traversing a tree, either BFS or DFS can be used, but BFS is more appropriate for wide and shallow trees, while DFS is more appropriate for deep and narrow trees.


In summary, choosing between BFS and DFS depends on the specific requirements of the problem and the properties of the graph or tree being analyzed.



BFS and DFS can be used to traverse a tree.


BFS and DFS can be used to traverse a tree as well as a graph. In fact, the concept of BFS and DFS originates from tree traversal.


For tree traversal, BFS and DFS work similarly as they do for graph traversal. In BFS, we visit all the nodes at the same level before moving to the next level, while in DFS, we explore as far as possible along each branch before backtracking.


However, there is one major difference between using BFS and DFS for tree traversal compared to graph traversal: trees have no cycles, so there is no need to keep track of visited nodes during traversal. As a result, the implementation of BFS and DFS for tree traversal can be simpler and more efficient than for graph traversal.


In summary, BFS and DFS can be used to traverse both trees and graphs, but the implementation details may differ due to the lack of cycles in trees.


def bfs_tree(root):
    queue = [root]

    while queue:
        node = queue.pop(0)
        for child in root.get(node, []):
            queue.append(child)
    return

def dfs_tree(root):
    stack = [root]

    while stack:
        node = stack.pop()
        for child in root.get(node, []):
            stack.append(child)
    return


Conclusion


In conclusion, BFS and DFS are both useful graph traversal algorithms with their own unique characteristics. BFS is typically used when we want to find the shortest path between two nodes in an unweighted graph, while DFS is useful when we want to explore as far as possible along each branch of a tree or graph.


BFS guarantees that the shortest path is found when traversing an unweighted graph, but can be slower and require more memory than DFS. On the other hand, DFS may not necessarily find the shortest path but can be faster and require less memory than BFS.


The choice between BFS and DFS ultimately depends on the specific problem and requirements of the application.

3 views0 comments

Recent Posts

See All

Greedy Algorithms

Greedy algorithms are a class of algorithms that make locally optimal choices at each step in hopes of achieving a globally optimal...

Divide and Conquer Algorithm

Divide and conquer is a popular algorithmic technique used to solve a wide range of problems. It involves breaking down a problem into...

Commentaires


bottom of page