回顧
問題陳述: 給定一棵二叉樹,實現中序遍歷並返回包含其中序序列的陣列
例如給定下列二叉樹:
我們按照左、根、右的順序遞迴遍歷二叉樹,得到以下遍歷:
最終中序遍歷結果可以輸出為: [3, 1, 9, 2, 4, 7, 5, 8, 6]
Morris trick
Morris 中序遍歷是一種樹遍歷演算法,旨在實現 O(1) 的空間複雜度,無需遞迴或外部資料結構。該演算法應高效地按中序順序訪問二叉樹中的每個節點,並在遍歷過程中列印或處理節點值,而無需使用堆疊或遞迴。
關鍵思想是在 current node 與其對應的 rightmost node 之間建立臨時連結
先來看下中序遍歷的過程:
做法討論
節點的中序前驅是左子樹中最右邊的節點。因此,當我們遍歷左子樹時,我們會遇到一個右子節點為空的節點,這是該子樹中的最後一個節點。因此,我們觀察到一種模式,每當我們處於子樹的最後一個節點時,如果右子節點指向空,我們就會移動到該子樹的父節點。
當我們當前處於某個節點時,可能會出現以下情況:
情況1:當前節點沒有左子樹
- 列印當前節點的值
- 然後到當前節點的右子節點
如果沒有左子樹,我們只需列印當前節點的值,因為左側沒有節點可遍歷。之後,我們移至右子節點繼續遍歷。
情況 2:存在一棵左子樹,並且該左子樹的最右邊的孩子指向空。
- 將左子樹的最右邊的子節點設定為指向當前節點。
- 移動到當前節點的左子節點。
在這種情況下,我們還沒有訪問左子樹。我們從左子樹的最右節點到當前節點建立一個臨時連結。此連結可幫助我們稍後確定何時完成左子樹的按序遍歷。設定連結後,我們移至左子節點以探索左子樹。
情況3:存在一棵左子樹,並且該左子樹的最右邊的孩子已經指向當前節點。
- 列印當前節點的值
- 恢復臨時連結(將其設定回空)
- 移動到當前節點的右子節點。
這種情況對於保持樹結構的完整性至關重要。如果左子樹的最右邊的子節點已經指向當前節點,則意味著我們已經完成了左子樹的按序遍歷。我們列印當前節點的值,然後恢復臨時連結以恢復原始樹結構。最後,我們移動到右子節點繼續遍歷。
演算法
步驟 1:初始化 current 來遍歷樹。將 current 設定為二叉樹的根。
步驟 2:當前節點不為空時:如果當前節點沒有左子節點,則列印當前節點的值並移動到右子節點,即將當前節點設定為其右子節點。
步驟 3: 當前節點有左孩子,我們找到當前節點的 in-order predecessor 。這個 in-order predecessor 是左子樹的最右節點。
- 如果 in-order predecessor 的右孩子節點為空:
- 將 in-order predecessor 右孩子節點設定為當前節點。
- 移動到 current 的左孩子
- 如果 in-order predecessor 的右孩子不為空:
- 透過in-order predecessor 的右孩子設定為空
- 列印當前節點的值。
- 透過先前 in-order predecessor 的右孩子拿到 current , 然後移動到 cuurent 的右孩子節點
重複步驟 2 和 3,直到到達樹的末尾。
程式碼實現
#include <iostream>
#include <sstream>
#include <unordered_map>
#include <vector>
#include <queue>
#include <map>
using namespace std;
// TreeNode structure
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution {
public:
// Function to perform iterative Morris
// inorder traversal of a binary tree
vector<int> getInorder(TreeNode* root) {
// Vector to store the
// inorder traversal result
vector<int> inorder;
// Pointer to the current node,
// starting from the root
TreeNode* cur = root;
// Loop until the current
// node is not NULL
while (cur != NULL) {
// If the current node's
// left child is NULL
if (cur->left == NULL) {
// Add the value of the current
// node to the inorder vector
inorder.push_back(cur->val);
// Move to the right child
cur = cur->right;
} else {
// If the left child is not NULL,
// find the predecessor (rightmost node
// in the left subtree)
TreeNode* prev = cur->left;
while (prev->right && prev->right != cur) {
prev = prev->right;
}
// If the predecessor's right child
// is NULL, establish a temporary link
// and move to the left child
if (prev->right == NULL) {
prev->right = cur;
cur = cur->left;
} else {
// If the predecessor's right child
// is already linked, remove the link,
// add current node to inorder vector,
// and move to the right child
prev->right = NULL;
inorder.push_back(cur->val);
cur = cur->right;
}
}
}
// Return the inorder
// traversal result
return inorder;
}
};
int main() {
TreeNode* root = new TreeNode(1);
root->left = new TreeNode(2);
root->right = new TreeNode(3);
root->left->left = new TreeNode(4);
root->left->right = new TreeNode(5);
root->left->right->right = new TreeNode(6);
Solution sol;
vector<int> inorder = sol.getInorder(root);
cout << "Binary Tree Morris Inorder Traversal: ";
for(int i = 0; i< inorder.size(); i++){
cout << inorder[i] << " ";
}
cout << endl;
return 0;
}