Morris Traversal Inorder Without Stack in DSA Go - Time & Space Complexity
We want to understand how the Morris Traversal algorithm performs when it visits nodes in a binary tree without using extra memory for a stack.
How does the number of steps grow as the tree gets bigger?
Analyze the time complexity of the following code snippet.
func morrisInorder(root *Node) {
current := root
for current != nil {
if current.Left == nil {
// Visit current node
current = current.Right
} else {
predecessor := current.Left
for predecessor.Right != nil && predecessor.Right != current {
predecessor = predecessor.Right
}
if predecessor.Right == nil {
predecessor.Right = current
current = current.Left
} else {
predecessor.Right = nil
// Visit current node
current = current.Right
}
}
}
}
This code visits all nodes of a binary tree in order without using a stack or recursion by temporarily modifying the tree links.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The outer loop visits each node, and the inner loop finds the rightmost node in the left subtree (predecessor).
- How many times: Each edge in the tree is visited at most twice: once to create a temporary link and once to remove it.
As the number of nodes (n) increases, the algorithm visits each node and its edges a limited number of times.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 20 visits (nodes and edges) |
| 100 | About 200 visits |
| 1000 | About 2000 visits |
Pattern observation: The total steps grow roughly twice the number of nodes, so the growth is linear.
Time Complexity: O(n)
This means the algorithm visits each node a small fixed number of times, so the total work grows directly with the number of nodes.
[X] Wrong: "Because of the inner loop, the algorithm must be O(n²) because it looks like nested loops."
[OK] Correct: Each edge is visited at most twice, so the inner loop does not run fully for every node. This keeps the total work linear, not quadratic.
Understanding Morris Traversal shows your skill in clever tree algorithms that save memory while keeping speed. This is a great example of thinking beyond usual methods like stacks or recursion.
"What if we changed the tree to be a linked list (all nodes only have right children)? How would the time complexity change?"
Practice
Solution
Step 1: Understand Morris Traversal space usage
Morris Traversal modifies the tree temporarily to avoid using stack or recursion, so it uses O(1) extra space.Step 2: Compare with other methods
Traditional inorder traversal uses recursion or stack which requires O(h) space, where h is tree height.Final Answer:
It uses no extra space for stack or recursion -> Option DQuick Check:
Morris Traversal = O(1) space [OK]
- Thinking it is faster than all methods
- Assuming it only works on BSTs
- Believing it needs balanced trees
Solution
Step 1: Understand pointer initialization
For traversal, current should start at root node, so current := root is correct.Step 2: Check other options
var current *Node = nil sets current to nil, which stops traversal immediately. current := &root takes address of root pointer, incorrect. current := root.left starts at left child, skipping root.Final Answer:
current := root -> Option AQuick Check:
Start traversal at root = current := root [OK]
- Initializing current as nil
- Taking address of root pointer
- Starting at root.left instead of root
2
/ \
1 3
func morrisInorder(root *Node) {
current := root
for current != nil {
if current.Left == nil {
fmt.Print(current.Val, " ")
current = current.Right
} else {
pre := current.Left
for pre.Right != nil && pre.Right != current {
pre = pre.Right
}
if pre.Right == nil {
pre.Right = current
current = current.Left
} else {
pre.Right = nil
fmt.Print(current.Val, " ")
current = current.Right
}
}
}
}Solution
Step 1: Understand inorder traversal order
Inorder traversal visits left subtree, root, then right subtree. For tree 2 with left 1 and right 3, order is 1 2 3.Step 2: Trace Morris traversal output
Morris traversal prints nodes in inorder without stack. The code prints 1, then 2, then 3 with spaces.Final Answer:
1 2 3 -> Option BQuick Check:
Inorder of given tree = 1 2 3 [OK]
- Confusing preorder or postorder output
- Misreading the tree structure
- Ignoring Morris threading logic
current := root
for current != nil {
if current.Left != nil {
pre := current.Left
for pre.Right != nil {
pre = pre.Right
}
pre.Right = current
current = current.Left
} else {
fmt.Print(current.Val, " ")
current = current.Right
}
}Solution
Step 1: Analyze inner loop for threading
The inner loop finds rightmost node of left subtree. Without checking if pre.Right == current, it keeps looping infinitely.Step 2: Identify missing condition causing infinite loop
Correct Morris traversal checks if pre.Right is nil or points to current to avoid infinite loop. Missing this causes infinite loop.Final Answer:
Missing check if pre.Right == current before setting pre.Right = current -> Option CQuick Check:
Check pre.Right == current to avoid loops [OK]
- Skipping pre.Right == current check
- Wrong pointer updates
- Printing too early
Solution
Step 1: Identify when nodes are visited in Morris traversal
Nodes are printed exactly once: when current.Left == nil or when removing the thread (pre.Right != nil).Step 2: Choose correct increment point for counting
Incrementing the counter each time a node's value is printed ensures every node is counted exactly once.Final Answer:
Add a counter variable and increment it each time you print a node's value during traversal -> Option AQuick Check:
Increment on every print operation [OK]
- Incrementing only when current.Left is nil (misses nodes with left subtrees)
- Incrementing when creating threads (counts only non-leaf nodes)
- Incrementing when removing threads (counts only non-leaf nodes)
Ready to prove it?
Pass the test and this pattern moves unknown
