TAOCP 2.3.1 Exercise 21
Let `T` be a pointer to a binary tree of $n$ nodes with the conventional representation `(2)`, that is, each node `P` has two link fields `LLINK(P)` and `RLINK(P)` pointing either to the left or right...
Section 2.3.1: Traversing Binary Trees
Exercise 21. [33] Design an algorithm that traverses an unthreaded binary tree in inorder without using any auxiliary stack. It is permissible to alter the LLINK and RLINK fields of the tree nodes in any manner whatsoever during the traversal, subject only to the condition that the binary tree should have the conventional representation illustrated in (2) both before and after your algorithm has traversed the tree. No other bits in the tree nodes are available for temporary storage.
Verified: no
Solve time: 32s
Setup
Let T be a pointer to a binary tree of $n$ nodes with the conventional representation (2), that is, each node P has two link fields LLINK(P) and RLINK(P) pointing either to the left or right subtree, or to Λ if the subtree is empty. We are required to traverse this tree in inorder, visiting each node exactly once, without using any auxiliary stack or additional bits in the nodes. Temporary modification of LLINK and RLINK is permitted, provided that after the traversal the tree is restored to its original form.
Let NODE(P) denote the information stored at node P. Let P$ denote the inorder successor of NODE(P). Our goal is to construct a procedure that starts at T and visits all nodes in inorder sequence, ensuring that at each step NODE(P) is visited exactly once, and that the original LLINK and RLINK values are restored when the algorithm terminates.
Solution
We employ the threading during traversal method, also known as the Morris traversal, which creates temporary links to predecessors during the traversal to avoid using a stack. Let current denote the pointer to the node currently being processed. The algorithm proceeds as follows:
Step 1. Initialize current <- T.
Step 2. Repeat while current ≠ Λ:
S2a. If LLINK(current) = Λ, then visit NODE(current) and set current <- RLINK(current).
S2b. Otherwise, locate the inorder predecessor of current in the left subtree:
- Set
pre <- LLINK(current). - While
RLINK(pre) ≠ ΛandRLINK(pre) ≠ current, setpre <- RLINK(pre).
S2c. If RLINK(pre) = Λ, then create a temporary thread:
- Set
RLINK(pre) <- current. - Set
current <- LLINK(current).
S2d. Otherwise (RLINK(pre) = current), the left subtree has been traversed:
- Set
RLINK(pre) <- Λto restore the original tree. - Visit
NODE(current). - Set
current <- RLINK(current).
The procedure terminates when current = Λ. At this point, all nodes have been visited in inorder, and the original LLINK and RLINK fields have been restored.
Algorithm (Morris Inorder Traversal)
1. current <- T
2. while current ≠ Λ do
3. if LLINK(current) = Λ then
4. visit NODE(current)
5. current <- RLINK(current)
6. else
7. pre <- LLINK(current)
8. while RLINK(pre) ≠ Λ and RLINK(pre) ≠ current do
9. pre <- RLINK(pre)
10. if RLINK(pre) = Λ then
11. RLINK(pre) <- current
12. current <- LLINK(current)
13. else
14. RLINK(pre) <- Λ
15. visit NODE(current)
16. current <- RLINK(current)
Verification
- Correctness of visit order. For any node
PwithLLINK(P) = Λ, the algorithm visitsNODE(P)immediately, which is consistent with inorder traversal because there is no left subtree. ForLLINK(P) ≠ Λ, the algorithm first finds the inorder predecessorpreofP, temporarily linkingpretoP. The left subtree rooted atLLINK(P)is fully traversed before visitingP, ensuring the inorder sequence. - Restoration of tree. Each temporary link
RLINK(pre) <- currentis later removed whenRLINK(pre) = currentin step S2d, restoring the originalRLINK(pre) = Λ. NoLLINKvalues are permanently altered. Therefore, after traversal, allLLINKandRLINKfields are identical to their original values. - No auxiliary stack. The algorithm uses only pointers
currentandpreand modifies tree links temporarily. No stack, recursion, or additional bits are required. - Termination. At each step, either
currentmoves toLLINK(current)orRLINK(current), which are eitherΛor eventually lead to the right subtree or successor. Since the tree has a finite number of nodes, the traversal terminates after visiting all nodes.
This completes the proof.
∎
Notes
- The Morris traversal uses $O(1)$ additional memory and performs at most $2n$ link changes: each non-null
LLINKis modified once to create a temporary thread and restored once, while each node is visited exactly once. - A similar approach can be adapted for preorder traversal by visiting
NODE(current)immediately after creating a thread, rather than waiting until the left subtree is traversed. - Postorder traversal without a stack is more involved; it generally requires either double threading or temporary modification of both
LLINKandRLINKin a more sophisticated manner.