programmingconcepts

§ 3.8 · Algorithms

Graph traversal

Visit every node you can reach from a start node, using a queue to go wide first or a stack to go deep first.

Graph traversal visits every node reachable from a starting node by following edges. Breadth-first search (BFS) uses a queue and explores in rings of increasing distance, so it finds shortest paths in unweighted graphs. Depth-first search (DFS) uses a stack and follows one path as deep as it can before backtracking. Both take O(V + E) time.

Time
O(V + E)
Space
O(V)
BFS finds
Fewest-edge paths
DFS uses
A stack (or recursion)
Requires
Stacks and queues
from collections import deque


def bfs(graph, start):
    seen = {start}
    queue = deque([start])
    order = []
    while queue:
        node = queue.popleft()
        order.append(node)
        for nxt in graph[node]:
            if nxt not in seen:
                seen.add(nxt)
                queue.append(nxt)
    return order


def dfs(graph, start):
    seen = set()
    stack = [start]
    order = []
    while stack:
        node = stack.pop()
        if node in seen:
            continue
        seen.add(node)
        order.append(node)
        for nxt in reversed(graph[node]):
            if nxt not in seen:
                stack.append(nxt)
    return order

Edit the graph

In the figure: click an empty spot to add a node, click one node and then another to connect or disconnect them, and select a node then press Delete to remove it. Up to 10 nodes.

step 0 / 0waitingcurrentdiscoveredvisited

Fig. 3.8. Outlined nodes are waiting, orange is the node being visited, blue was just discovered, and black is done. Switch to depth-first search and compare the order.

Intuition

A graph is a set of nodes joined by edges: cities and roads, people and friendships, web pages and links. Traversal answers “what can I reach from here?”

The two classic strategies differ in what they do next:

  • Breadth-first search (BFS) is a ripple in a pond. It visits everything one edge away, then everything two edges away, and so on.
  • Depth-first search (DFS) is exploring a maze with one hand on the wall. It follows one path as far as it goes, then backs up to the last fork.

How it works

Both keep a list of nodes waiting to be visited and a set of nodes already seen:

  1. Put the start node in the waiting list.
  2. Take a node out and visit it.
  3. Add each of its unseen neighbours to the waiting list.
  4. Repeat until the list is empty.

The only difference is which node comes out next. BFS uses a queue (first in, first out), so the oldest discovery is visited next. DFS uses a stack (last in, first out), so the newest discovery is visited next. That one choice produces completely different orders.

Complexity

Each node enters the waiting list a bounded number of times and each edge is checked from both ends, so both searches take O(V + E) time for V nodes and E edges. Both need O(V) memory for the seen set and the waiting list.

When to use which

Need Use
Fewest-edge path in an unweighted graph BFS
Everything within k steps (e.g. friends of friends) BFS
Detecting cycles, topological order, maze solving DFS
Shortest path when edges have weights Dijkstra

Common mistakes

  1. No seen set. Without it, any cycle makes the search loop forever.
  2. Marking seen too late in BFS. Mark a node when you add it to the queue, not when you visit it, or it can be queued many times.
  3. Assuming DFS finds shortest paths. It finds a path, often a long one.
  4. Recursing too deep. Recursive DFS on a long chain can overflow the call stack; an explicit stack, as in the code here, can’t.

Check yourself

Check yourself

Which search finds the path with the fewest edges in an unweighted graph?

Check yourself

Which pairing is right?

Sources

  • Cormen, Leiserson, Rivest and Stein, Introduction to Algorithms, sections 22.2 and 22.3 (3rd edition).
  • Robert Sedgewick and Kevin Wayne, Algorithms, 4th edition, section 4.1.

Newsletter

One new concept each week, by email.

Signup opens soon.