BFS & DFS

BFS(Breadth-First-Search) and DFS(Depth-First-Search) are 2 common ways to search a path from A to B in the graph.

This article will focus on UndirectedGraph Graph.

BFS

BFS will traverse all the adjacent vertices before moving to the next vertex.

It's similar to Level traversing of Tree. So we need a queue to traverse the vertices.

Here, we use BFS to find a path in a graph.

def bfs(graph: UndirectedGraph, start: int, end: int) -> None:
    if start == end:
        print("The start is equal to the end.")
        return
    if start not in range(0, graph.v) or end not in range(0, graph.v):
        print("The start or end is out of the range.")
        return

    visited = [False for _ in range(0, graph.v)]
    visited[start] = True
    prev_vertices = [-1 for _ in range(0, graph.v)]
    queue = [start]

    while len(queue) != 0:
        vertex = queue.pop(0)
        p = graph.adj[vertex].next
        is_found = False
        
        while p is not None:
            adj_vertex = p.val
            
            if not visited[adj_vertex]:
                prev_vertices[adj_vertex] = vertex
                
                if adj_vertex == end:
                    show_path(prev_vertices, start, end)
                    is_found = True
                    break
                
                visited[adj_vertex] = True
                queue.append(adj_vertex)
            
            p = p.next
        
        if is_found:
            break

We suppose that V is the number of the vertices and E is the number of the edges.

The worst case is that all the edges and vertrices are visited. The time complexity is O(V+E). If the graph is connected (all the vertices are connected), then E > V - 1. So, the time complexity can be simplified as O(E).

The space complexity is O(V) because the length of both 2 assist array, visited and prev_vertices, is V.

DFS

BFS will traverse all the descendant vertices before moving to the next branch.

It's similar to InOrder traversing, (left -> mid -> right), of Binary Tree. So we need a stack to traverse the vertices. (Recursion also use a stack behind.)

So, we replace the queue in BFS by the stack, (queue.pop(0) is replaced by stack.pop(), too), then we have DFS.

Here, we use DFS to find a path in a graph in the recursive way.

Attention: The place to updated visited array is different.

is_found = False 

def dfs_recursive(graph: UndirectedGraph, start: int, end: int) -> None:
    if start not in range(0, graph.v) or end not in range(0, graph.v):
        print("The start or end is out of the range.")
        return

    visited = [False for _ in range(0, graph.v)]
    prev_vertices = [-1 for _ in range(0, graph.v)]

    dfs_worker(graph, start, end, visited, prev_vertices)
    show_path(prev_vertices, start, end)


def dfs_worker(graph: UndirectedGraph, start: int, end: int, visited: List[bool], prev_vertices: List[int]) -> None:
    global is_found

    if is_found:
        return
    if start == end:
        is_found = True
        return
    
    # The following line can't be placed inside of while loop.
    # Because the last function in recursion stack is first executed.
    # Some function can be executed before the vertex is marked visited 
    # if the following line is in the while loop.
    visited[start] = True
    
    p = graph.adj[start].next
    while p is not None:
        adj_vertex = p.val

        if not visited[adj_vertex]:
            prev_vertices[adj_vertex] = start
            dfs_worker(graph, adj_vertex, end, visited, prev_vertices)
        
        p = p.next

The worst case is that each vertex is visited once, and each edge is visited twice (forward and backward).

  • Time complexity: O(E)
  • Space complexity: O(V)