Algorithm Search⌘K

Proofs: Morris traversal

These are the human proofs of Morris traversal, at full length.

MORRIS-INORDER visits the nodes in inorder, terminates, and restores the tree full

This proof shows: correctness, termination.

Fix a binary tree with the original pointers left0\mathrm{left}_0 and right0\mathrm{right}_0. For a node xx, let T(x)T(x) be its subtree in the original tree, let ∣T(x)∣|T(x)| be its number of nodes, and let last(x)\mathrm{last}(x) be the last node of T(x)T(x) in inorder. The node last(x)\mathrm{last}(x) is reached from xx by following right0\mathrm{right}_0 pointers until one is NIL, so right0[last(x)]=NIL\mathrm{right}_0[\mathrm{last}(x)] = \mathrm{NIL}. The algorithm never writes left\mathrm{left}, so left=left0\mathrm{left} = \mathrm{left}_0 at all times.

Say that T(x)T(x) is clean with exit ss if every node yy of T(x)T(x) has right[y]=right0[y]\mathrm{right}[y] = \mathrm{right}_0[y], except that right[last(x)]=s\mathrm{right}[\mathrm{last}(x)] = s, where ss is NIL or a node outside T(x)T(x).

Lemma 1. Suppose that at the start of an iteration of line L2 the current node is xx, and T(x)T(x) is clean with exit ss. Then after finitely many iterations, the current node is ss, the visits made in between are the nodes of T(x)T(x) in inorder, T(x)T(x) is again clean with exit ss, and no right pointer outside T(x)T(x) was read or written in between.

Proof. By strong induction on ∣T(x)∣|T(x)|. Let L=left[x]L = \mathrm{left}[x] and R=right0[x]R = \mathrm{right}_0[x].

Case 1: L=NILL = \mathrm{NIL}. Line L4 visits xx and sets the current node to right[x]\mathrm{right}[x].

  • If R=NILR = \mathrm{NIL}, then T(x)={x}T(x) = \{x\}, last(x)=x\mathrm{last}(x) = x and right[x]=s\mathrm{right}[x] = s. So the current node is ss after one iteration, the only visit is xx, which is the inorder of T(x)T(x), and nothing was written.
  • If R≠NILR \ne \mathrm{NIL}, then x≠last(x)x \ne \mathrm{last}(x), so right[x]=R\mathrm{right}[x] = R and the current node becomes RR. The subtree T(R)T(R) has last(R)=last(x)\mathrm{last}(R) = \mathrm{last}(x), and it is clean with exit ss, because T(R)⊂T(x)T(R) \subset T(x) and s∉T(x)s \notin T(x). It is smaller than T(x)T(x), so by the induction hypothesis the walk visits T(R)T(R) in inorder, arrives at ss, and leaves T(R)T(R) clean with exit ss. The visits are xx followed by the inorder of T(R)T(R), which is the inorder of T(x)T(x) since T(x)T(x) has no left subtree.

Case 2: L≠NILL \ne \mathrm{NIL}. Let p∗=last(L)p^* = \mathrm{last}(L), the inorder predecessor of xx. It is in T(L)T(L), so p∗≠last(x)p^* \ne \mathrm{last}(x) (which is xx or in T(R)T(R)), and right[p∗]=right0[p∗]=NIL\mathrm{right}[p^*] = \mathrm{right}_0[p^*] = \mathrm{NIL}.

First visit. Line L5 starts at p=Lp = L and follows right pointers. Every node on the path from LL to p∗p^* along right0\mathrm{right}_0 is in T(L)T(L) and is not last(x)\mathrm{last}(x), so its right pointer is original. For each such node before p∗p^*, the right pointer is a node of T(L)T(L), so it is neither NIL nor xx, and the loop goes on. At p∗p^* the right pointer is NIL, and the loop stops with p=p∗p = p^*. Line L6 sets right[p∗]=x\mathrm{right}[p^*] = x and moves to LL. Now T(L)T(L) is clean with exit xx, and x∉T(L)x \notin T(L). Only right pointers in T(L)T(L) were read, and only right[p∗]\mathrm{right}[p^*] was written.

The left subtree. ∣T(L)∣<∣T(x)∣|T(L)| < |T(x)|, so by the induction hypothesis the walk visits T(L)T(L) in inorder and arrives at xx, with T(L)T(L) clean with exit xx, and touches no right pointer outside T(L)T(L). So the rest of T(x)T(x), in particular right[x]\mathrm{right}[x] and the pointers in T(R)T(R), is as it was before the first visit.

Second visit. Line L5 again walks from LL along the right pointers. As before, the nodes before p∗p^* have original pointers into T(L)T(L), so the loop reaches p∗p^*, and it stops there because right[p∗]=x\mathrm{right}[p^*] = x. Line L6 does not apply, and line L7 sets right[p∗]=NIL\mathrm{right}[p^*] = \mathrm{NIL}, visits xx and sets the current node to right[x]\mathrm{right}[x]. Now every right pointer in T(L)T(L) is original. The rest is as in Case 1: if R=NILR = \mathrm{NIL}, then x=last(x)x = \mathrm{last}(x) and right[x]=s\mathrm{right}[x] = s, so the walk arrives at ss; else right[x]=R\mathrm{right}[x] = R, and the induction hypothesis on T(R)T(R), which is clean with exit ss, gives the inorder of T(R)T(R) and the arrival at ss.

The visits are the inorder of T(L)T(L), then xx, then the inorder of T(R)T(R), which is the inorder of T(x)T(x). At the end, the pointers in T(L)T(L) and at xx are original and T(R)T(R) is clean with exit ss, so T(x)T(x) is clean with exit ss. Only right pointers in T(x)T(x) were read or written. Each part takes finitely many iterations, by the induction hypothesis. □\square

Theorem. MORRIS-INORDER terminates, visits every node of the tree exactly once and in inorder, and at the end every pointer has its original value.

Proof. If the root is NIL, the loop does not run, nothing is visited, and nothing is written. Otherwise, at the start, all pointers are original. The tree T(root)T(\mathrm{root}) is then clean with exit NIL, because right0[last(root)]=NIL\mathrm{right}_0[\mathrm{last}(\mathrm{root})] = \mathrm{NIL}. By Lemma 1, after finitely many iterations the current node is NIL, the visits are the inorder of the whole tree, and the tree is clean with exit NIL, which means that all right pointers are original. At that moment the test of line L2 fails, and the algorithm stops. ■\blacksquare

Back to Morris traversal