196 lines
5.6 KiB
Markdown
196 lines
5.6 KiB
Markdown
# Lecture 21 --- Trees, Part IV
|
||
|
||
## Today’s Lecture
|
||
|
||
- Morris Traversal
|
||
|
||
## 21.1 Morris Traversal
|
||
|
||
Morris Traversal is a tree traversal algorithm that allows in-order, pre-order, and post-order traversal of a binary tree without using recursion or a stack/queue, achieving O(1) space complexity. It modifies the tree temporarily but restores it afterward.
|
||
|
||
Instead of using extra memory (like recursion stack or an explicit stack), Morris Traversal utilizes threaded binary trees by:
|
||
|
||
- Finding the inorder predecessor of the current node.
|
||
|
||
- Temporarily modifying the tree structure by creating threads (links) to the current node.
|
||
|
||
- Using these links to traverse back instead of a recursive call.
|
||
|
||
## 21.2 Morris Traversal - In Order
|
||
|
||
- Start from the root.
|
||
|
||
- If the left subtree is NULL, print the node and move to the right.
|
||
|
||
- If the left subtree exists, find the inorder predecessor (rightmost node in the left subtree):
|
||
|
||
- If the predecessor’s right child is NULL, set it to the current node (threading) and move left.
|
||
|
||
- If the predecessor’s right child points to the current node (thread already exists), remove the thread, print the current node, and move right.
|
||
|
||
- Repeat until you traverse the entire tree.
|
||
|
||
```cpp
|
||
void inorderTraversal(TreeNode* root) {
|
||
TreeNode *current=root;
|
||
TreeNode *rightmost;
|
||
while(current!=NULL){
|
||
if(current->left!=NULL){
|
||
rightmost=current->left;
|
||
while(rightmost->right!=NULL && rightmost->right!=current){
|
||
rightmost=rightmost->right;
|
||
}
|
||
if(rightmost->right==NULL){ /* first time */
|
||
rightmost->right=current;
|
||
current=current->left;
|
||
}else{ /* second time */
|
||
std::cout << current->val << " ";
|
||
rightmost->right=NULL;
|
||
current=current->right;
|
||
}
|
||
}else{ /* nodes which do not have left child */
|
||
std::cout << current->val << " ";
|
||
current=current->right;
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
```
|
||
|
||
You can test the above function using this program: [inorder_main.cpp](inorder_main.cpp).
|
||
|
||
For this test case,
|
||
|
||

|
||
|
||
The testing program prints:
|
||
|
||
```console
|
||
$ g++ inorder_main.cpp
|
||
$ ./a.out
|
||
Inorder Traversal using Morris Traversal:
|
||
4 2 6 5 7 1 3 9 8
|
||
```
|
||
|
||
## 21.3 Morris Traversal - Pre Order
|
||
|
||
To perform preorder traversal:
|
||
|
||
Print the node before going left instead of after restoring links.
|
||
|
||
```cpp
|
||
void preorderTraversal(TreeNode* root) {
|
||
TreeNode *current=root;
|
||
TreeNode *rightmost;
|
||
while(current != nullptr){
|
||
if(current->left != nullptr){
|
||
rightmost=current->left;
|
||
while(rightmost->right!=nullptr && rightmost->right!=current){
|
||
rightmost=rightmost->right;
|
||
}
|
||
if(rightmost->right==nullptr){ /* visiting the right most node for the first time */
|
||
std::cout << current->val << " ";
|
||
rightmost->right=current;
|
||
current=current->left;
|
||
}else{ /* visiting the right most node for the second time */
|
||
rightmost->right=nullptr;
|
||
current=current->right;
|
||
}
|
||
}else{ /* nodes which do not have left child */
|
||
std::cout << current->val << " ";
|
||
current=current->right;
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
```
|
||
|
||
You can test the above function using this program: [preorder_main.cpp](preorder_main.cpp).
|
||
|
||
For above test case, the testing program prints:
|
||
|
||
```console
|
||
$ g++ preorder_main.cpp
|
||
$ ./a.out
|
||
Preorder Traversal using Morris Traversal:
|
||
1 2 4 5 6 7 3 8 9
|
||
```
|
||
|
||
## 21.4 Morris Traversal - Post Order
|
||
|
||
Post order is different, and we need to write some helper functions here.
|
||
|
||
```cpp
|
||
// function to reverse the right-edge path of a subtree
|
||
TreeNode* reverse(TreeNode* head) {
|
||
TreeNode* prev = nullptr;
|
||
TreeNode* next = nullptr;
|
||
|
||
while (head != nullptr) {
|
||
next = head->right;
|
||
head->right = prev;
|
||
prev = head;
|
||
head = next;
|
||
}
|
||
return prev;
|
||
}
|
||
|
||
// function to traverse and collect nodes along a reversed right edge
|
||
void reverseTraverseRightEdge(TreeNode* head) {
|
||
TreeNode* tail = reverse(head);
|
||
TreeNode* current = tail;
|
||
|
||
while (current != nullptr) {
|
||
std::cout << current->val << " ";
|
||
current = current->right;
|
||
}
|
||
reverse(tail); // restore the original tree structure
|
||
}
|
||
|
||
// Morris Postorder Traversal
|
||
void postorderTraversal(TreeNode* root) {
|
||
TreeNode* current = root;
|
||
TreeNode* rightmost;
|
||
|
||
while (current != nullptr) {
|
||
if (current->left != nullptr) {
|
||
rightmost = current->left;
|
||
while (rightmost->right != nullptr && rightmost->right != current) {
|
||
rightmost = rightmost->right;
|
||
}
|
||
|
||
if (rightmost->right == nullptr) {
|
||
rightmost->right = current;
|
||
current = current->left;
|
||
} else {
|
||
rightmost->right = nullptr;
|
||
reverseTraverseRightEdge(current->left);
|
||
current = current->right;
|
||
}
|
||
} else {
|
||
current = current->right;
|
||
}
|
||
}
|
||
|
||
reverseTraverseRightEdge(root); // traverse the final right edge
|
||
return;
|
||
}
|
||
```
|
||
|
||
You can test the above function using this program: [postorder_main.cpp](postorder_main.cpp).
|
||
|
||
For above test case, the testing program prints:
|
||
|
||
```console
|
||
$ g++ postorder_main.cpp
|
||
$ ./a.out
|
||
Postorder Traversal using Morris Traversal:
|
||
4 6 7 5 2 9 8 3 1
|
||
```
|
||
|
||
## Time and Space Complexity in Morris Traversal (in-order, pre-order, post-order)
|
||
|
||
- Time Complexity: O(N) (each node is visited at most twice)
|
||
|
||
- Space Complexity: O(1) (no extra space used except for modifying pointers)
|