Algorithm Search⌘K

Proofs: Nonrecursive depth-first search

These are the human proofs of Nonrecursive depth-first search, at full length.

Nonrecursive DFS produces the same times and parents as recursive DFS full

This proof shows: correctness, termination.

Fix a graph GG, an order of VV and an order of each list. Let RR be the recursive procedure DFS of the depth-first search page and II the procedure ITERATIVE-DFS. We show that they compute the same dd, ff and π\pi.

Steps. Cut the run of RR into steps. A root step is one test of line L4 of RR (with the call it may start, up to the discovery of its root). Inside a call DFS-VISIT(x)(x), an edge step is one test of a list entry vv by the loop, with the discovery of vv if vv is white, and an end step is the end of the call, which sets f[x]f[x]. Cut the run of II in the same way: a root step is one test of line L4, an edge step is one execution of lines L9 to L11, and an end step is one execution of line L12.

Claim. Consider the two runs after they have done the same number of steps of the same kinds since the same root step. Then:

  1. both have the same colours, times dd and ff, parents and clock;
  2. the stack SS of II lists, from bottom to top, exactly the vertices whose calls in RR are active, from the oldest to the newest;
  3. for each xx on SS, the call of xx in RR has tested exactly the first next[x]−1\mathrm{next}[x] - 1 entries of Adj[x]\mathrm{Adj}[x].

Proof. By induction on the number of steps. At the start, nothing has happened in either run, both clocks are 0, SS is empty and no call is active.

Root step. Both runs are in their outer loops with no active call and an empty stack, by part 2, and they test the same vertex uu, which has the same colour in both, by part 1. If uu is white, RR calls DFS-VISIT(u)(u), which makes uu gray and sets d[u]d[u] to the next clock value, and II does the same at line L5 and pushes uu, with next[u]=1\mathrm{next}[u] = 1 and no entry tested. All three parts hold.

Step inside a tree. Let xx be the newest active call of RR, which is the call that runs. By part 2, xx is the top of SS, so line L7 gets the same xx. By part 3, RR's loop in the call of xx has tested next[x]−1\mathrm{next}[x] - 1 entries.

  • If next[x]≤∣Adj[x]∣\mathrm{next}[x] \le |\mathrm{Adj}[x]|, the loop of RR tests the entry v=Adj[x][next[x]]v = \mathrm{Adj}[x][\mathrm{next}[x]] next, and line L9 reads the same vv and increases next[x]\mathrm{next}[x]. If vv is white (the same in both runs), RR sets π[v]=x\pi[v] = x and calls DFS-VISIT(v)(v), which makes vv gray and sets d[v]d[v], and II does the same at line L11 and pushes vv on top of xx. The new call is the newest, with no entry tested, and next[v]=1\mathrm{next}[v] = 1 because vv was white and is pushed for the first time. If vv is not white, neither run changes anything else.
  • If next[x]>∣Adj[x]∣\mathrm{next}[x] > |\mathrm{Adj}[x]|, the call of xx in RR has tested its whole list, so it makes xx black, sets f[x]f[x] and returns, and the next active call is the one below it. Line L12 does the same and pops xx.

In each case, the two runs make the same change, and all three parts hold again. □\square

Termination. Each edge step increases some next[x]\mathrm{next}[x], which never exceeds ∣Adj[x]∣+1|\mathrm{Adj}[x]| + 1 and is never reset, because a vertex is pushed only while it is white and is then gray or black for ever. Each end step pops a vertex, and each vertex is pushed at most once. So the loop of line L6 runs at most m+nm + n times in total, where mm is the total length of the lists, and II terminates.

Theorem. ITERATIVE-DFS(G)(G) returns the same dd, ff and π\pi as the recursive DFS(G)(G) with the same orders.

Proof. The runs of RR and II have the same sequence of steps by the claim: whenever one of them can do an edge step or an end step, so can the other, and when the stack is empty, both are in the outer loop at the same vertex. When II terminates, all of its steps are matched by steps of RR, both outer loops have tested all vertices, and part 1 gives the same dd, ff and π\pi. ■\blacksquare

Back to Nonrecursive depth-first search