Skip to content

Commit 646cb97

Browse files
committed
Add Morris Traversal algorithm - Fixes issue #1806
1 parent 69676c2 commit 646cb97

File tree

2 files changed

+132
-32
lines changed

2 files changed

+132
-32
lines changed

Trees/MorrisTraversal.js

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
* Wikipedia: https://en.wikipedia.org/wiki/Threaded_binary_tree#Morris_traversal
44
*
55
* WHAT IS MORRIS TRAVERSAL?
6-
* Morris Traversal is a clever technique to traverse a binary tree in inorder
7-
* (Left → Root → Right) using O(1) extra space - meaning it doesn't need recursion
6+
* Morris Traversal is a clever technique to traverse a binary tree in inorder
7+
* (Left → Root → Right) using O(1) extra space - meaning it doesn't need recursion
88
* or an explicit stack like traditional methods.
99
*
1010
* HOW DOES IT WORK?
@@ -32,7 +32,7 @@
3232
* 3 6
3333
* \
3434
* 9
35-
*
35+
*
3636
* Traversal order: 3 → 5 → 6 → 9 → 7 → 8
3737
*
3838
* TIME COMPLEXITY: O(n) - each edge is traversed at most twice
@@ -41,66 +41,70 @@
4141

4242
class TreeNode {
4343
constructor(val, left = null, right = null) {
44-
this.val = val;
45-
this.left = left;
46-
this.right = right;
44+
this.val = val
45+
this.left = left
46+
this.right = right
4747
}
4848
}
4949

5050
function morrisTraversal(root) {
51-
const result = []; // Array to store the inorder traversal result
52-
let curr = root; // Current node we're processing
53-
51+
const result = [] // Array to store the inorder traversal result
52+
53+
// Handle edge case: empty tree
54+
if (!root) {
55+
return result
56+
}
57+
58+
let curr = root // Current node we're processing
59+
5460
// Continue until we've processed all nodes
5561
while (curr) {
56-
5762
// CASE 1: Current node has no left child
5863
// This means we can safely visit this node (no left subtree to process first)
5964
if (!curr.left) {
60-
result.push(curr.val); // Visit the current node
61-
curr = curr.right; // Move to right subtree
62-
}
63-
65+
result.push(curr.val) // Visit the current node
66+
curr = curr.right // Move to right subtree
67+
}
68+
6469
// CASE 2: Current node has a left child
6570
// We need to find a way to come back to this node after processing left subtree
6671
else {
67-
6872
// STEP 1: Find the inorder predecessor of current node
6973
// (Rightmost node in the left subtree)
70-
let pred = curr.left;
71-
74+
let pred = curr.left
75+
7276
// Keep going right until we find the rightmost node
7377
// BUT stop if we find a node that already points back to curr (existing thread)
7478
while (pred.right && pred.right !== curr) {
75-
pred = pred.right;
79+
pred = pred.right
7680
}
77-
81+
7882
// STEP 2a: If predecessor's right is null, we need to create a thread
7983
// This thread will help us return to current node later
8084
if (!pred.right) {
8185
// Create the thread: make predecessor point to current node
82-
pred.right = curr;
83-
86+
pred.right = curr
87+
8488
// Now go left to process the left subtree first
85-
curr = curr.left;
86-
}
87-
89+
curr = curr.left
90+
}
91+
8892
// STEP 2b: If predecessor's right already points to current node
8993
// This means we've already processed the left subtree and are back via the thread
9094
else {
9195
// Remove the thread (restore original tree structure)
92-
pred.right = null;
93-
96+
pred.right = null
97+
9498
// Now we can safely visit the current node
95-
result.push(curr.val);
96-
99+
result.push(curr.val)
100+
97101
// Move to right subtree
98-
curr = curr.right;
102+
curr = curr.right
99103
}
100104
}
101105
}
102-
103-
return result;
106+
107+
return result
104108
}
105109

106-
module.exports = { TreeNode, morrisTraversal };
110+
export { TreeNode, morrisTraversal }

Trees/test/MorrisTraversal.test.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { TreeNode, morrisTraversal } from '../MorrisTraversal'
2+
3+
describe('Morris Traversal', () => {
4+
it('Empty tree case', () => {
5+
expect(morrisTraversal(null)).toStrictEqual([])
6+
})
7+
8+
it('Single node tree', () => {
9+
const root = new TreeNode(42)
10+
expect(morrisTraversal(root)).toStrictEqual([42])
11+
})
12+
13+
it('Simple inorder traversal', () => {
14+
// Tree structure:
15+
// 2
16+
// / \
17+
// 1 3
18+
const root = new TreeNode(2, new TreeNode(1), new TreeNode(3))
19+
expect(morrisTraversal(root)).toStrictEqual([1, 2, 3])
20+
})
21+
22+
it('Complex tree traversal - Example from documentation', () => {
23+
// Tree structure:
24+
// 7
25+
// / \
26+
// 5 8
27+
// / \
28+
// 3 6
29+
// \
30+
// 9
31+
const root = new TreeNode(
32+
7,
33+
new TreeNode(5, new TreeNode(3), new TreeNode(6, null, new TreeNode(9))),
34+
new TreeNode(8)
35+
)
36+
expect(morrisTraversal(root)).toStrictEqual([3, 5, 6, 9, 7, 8])
37+
})
38+
39+
it('Left skewed tree', () => {
40+
// Tree structure:
41+
// 3
42+
// /
43+
// 2
44+
// /
45+
// 1
46+
const root = new TreeNode(3, new TreeNode(2, new TreeNode(1)))
47+
expect(morrisTraversal(root)).toStrictEqual([1, 2, 3])
48+
})
49+
50+
it('Right skewed tree', () => {
51+
// Tree structure:
52+
// 1
53+
// \
54+
// 2
55+
// \
56+
// 3
57+
const root = new TreeNode(1, null, new TreeNode(2, null, new TreeNode(3)))
58+
expect(morrisTraversal(root)).toStrictEqual([1, 2, 3])
59+
})
60+
61+
it('Larger tree with mixed structure', () => {
62+
// Tree structure:
63+
// 10
64+
// / \
65+
// 5 15
66+
// / \ \
67+
// 3 7 18
68+
// / / \
69+
// 1 6 8
70+
const root = new TreeNode(
71+
10,
72+
new TreeNode(
73+
5,
74+
new TreeNode(3, new TreeNode(1)),
75+
new TreeNode(7, new TreeNode(6), new TreeNode(8))
76+
),
77+
new TreeNode(15, null, new TreeNode(18))
78+
)
79+
expect(morrisTraversal(root)).toStrictEqual([1, 3, 5, 6, 7, 8, 10, 15, 18])
80+
})
81+
82+
it('Tree with duplicate values', () => {
83+
// Tree structure:
84+
// 5
85+
// / \
86+
// 5 5
87+
// / \
88+
// 3 7
89+
const root = new TreeNode(
90+
5,
91+
new TreeNode(5, new TreeNode(3)),
92+
new TreeNode(5, null, new TreeNode(7))
93+
)
94+
expect(morrisTraversal(root)).toStrictEqual([3, 5, 5, 5, 7])
95+
})
96+
})

0 commit comments

Comments
 (0)