Theory

Two ways to store it, two searches

First, how to store a graph. There are two ways and they are not equivalent.

An adjacency list

For each node, a list of its neighbours:

adj[0] = [1, 47]
adj[1] = [0, 2]
adj[2] = [1]
...

Memory: O(V + E). Visiting the neighbours: as many steps as there are neighbours.

An adjacency matrix

One bit for every pair of nodes, edge or no edge:

     0  1  2  3
  0  .  1  .  .
  1  1  .  1  .
  2  .  1  .  .
  3  .  .  .  .

Memory: O(V²). The question "is there an edge A–B?" takes one step, with no searching. But visiting the neighbours takes V steps, even when there are none.

In lesson 2 you worked out what slice headers would cost transit. That was an argument, not a measurement, and that lesson's gotcha said so plainly. Step 7 of this lesson measures the same question.

BFS — breadth-first

BFS(from, to):
    prev[*] ← −1;  seen[from] ← true
    queue ← [from]
    while queue is not empty:
        v ← FRONT of queue           // ← a queue
        if v = to: return the path
        for each neighbour w:
            c.Hit()
            if seen[w]: skip
            seen[w] ← true; prev[w] ← v
            queue ← queue + w

A queue visits nodes in layers: first everything one hop away, then everything two hops away, and so on. So the first time to is reached, it must be by the shortest path in hops — there is no shorter layer left.

DFS — depth-first

DFS(from, to):
    prev[*] ← −1
    stack ← [from]
    while stack is not empty:
        v ← BACK of stack            // ← a stack; the ONLY difference
        if seen[v]: skip
        seen[v] ← true
        if v = to: return the path
        for each neighbour w:
            c.Hit()
            if seen[w]: skip
            prev[w] ← v; stack ← stack + w

Compare the two. One line differs — FRONT of queue against BACK of stack — plus one detail: DFS checks seen when it takes, not when it puts, because the same node can be pushed several times before its turn comes.

A stack goes as deep as it can, hits a dead end, and backs up. There are no layers, so there is no promise about length.

And a third version

DFSRecursive(v):
    seen[v] ← true
    if v = to: found
    for each neighbour w:
        if not seen[w]: prev[w] ← v; DFSRecursive(w)

There is no stack here — because there is one: the call stack. Lesson 5 already showed and measured that; step 6 shows where the equivalence has a limit.

The cost

Both searches visit every node at most once and every edge at most twice — once from each end. So:

O(V + E) — for both.

The complexity is the same. What differs is not the cost but the answer.