Traversals

In a previous course, we saw that we can traverse trees in a variety of ways, including depth-first traversals (including pre-order, post-order, and in-order traversals), and breadth-first traversals (which is the same as a level-order traversal).

With graphs, we need similar traversal algorithms. Given a starting point, we need to define ways of traversing the graph to visit all of the nodes -- at least, the ones reachable from the starting point.

Let's talk about the two main traversal techniques: depth-first and breadth-first traversals.

Depth-first traversal

Watch the following video to see how a depth-first traversal works on graphs, and to see what kinds of algorithms you can write with a depth-first traversal:

To summarize:

  • A depth-first traversal proceeds as far as possible along a given path before backing up
  • A DF traversal is most naturally implemented recursively
  • Since graphs can have cycles (unlike trees), we need to mark nodes as "visited" in order to know when the recursion down a particular path should stop

For your reference, here's some pseudocode for a depth-first traveral (assuming an adjacency list graph):

def df_trav(v, parent):
    # Visit v.
    print(v.id)

    # Mark this vertex as visited.
    v.done = True

    # Mark this vertex's parent in the DF traversal
    v.parent = parent

    # Iterate over the vertex's adjacency list.
    e = v.edges
    while e is not None:
        w = e.end # Consider a neighbor w
        if not w.done:
            df_trav(w, v)
        e = e.next

Breadth-first traversal

Watch the following video to see how a breadth-first traversal works on graphs, and to see what kinds of algorithms you can write with a breadth-first traverasl:

To summarize:

  • A breadth-first traversal uses the following algorithm:
    1. visit a vertex
    2. visit all of its neighbors
    3. visit all unvisited vertices 2 edges away
    4. visit all unvisited vertices 3 edges away, etc
  • In order to visit one level at a time, the BF traversal algorithm uses an auxiliary queue
  • Nodes still need to be marked as visited during the algorithm, since nodes can be seen in multiple "levels" of the traversal (since there can be cycles)

For your reference, here's some pseudocode to implement the BF traversal (assuming an adjacency list implementation):

def bf_trav(origin):
    origin.encountered = true
    origin.parent = null

    q = Queue()
    q.insert(origin)
    while len(q) > 0:
        # Get next vertex in the traversal.
        v = q.remove()

        # Visit v.
        print(v.id)

        # Add v's unencountered neighbors to the queue.
        e = v.edges
        while e is not None:
            w = e.end
            if not w.encountered:
                w.encoutered = True
                w.parent = v
                q.insert(w)
            e = e.next