二叉樹的 Morris 中序遍歷——O(1)空間複雜度

sparkyen發表於2024-09-15

回顧

問題陳述: 給定一棵二叉樹,實現中序遍歷並返回包含其中序序列的陣列
例如給定下列二叉樹:
image
我們按照左、根、右的順序遞迴遍歷二叉樹,得到以下遍歷:
image
最終中序遍歷結果可以輸出為: [3, 1, 9, 2, 4, 7, 5, 8, 6]

Morris trick

Morris 中序遍歷是一種樹遍歷演算法,旨在實現 O(1) 的空間複雜度,無需遞迴或外部資料結構。該演算法應高效地按中序順序訪問二叉樹中的每個節點,並在遍歷過程中列印或處理節點值,而無需使用堆疊或遞迴。
關鍵思想是在 current node 與其對應的 rightmost node 之間建立臨時連結
先來看下中序遍歷的過程:
image

做法討論

節點的中序前驅是左子樹中最右邊的節點。因此,當我們遍歷左子樹時,我們會遇到一個右子節點為空的節點,這是該子樹中的最後一個節點。因此,我們觀察到一種模式,每當我們處於子樹的最後一個節點時,如果右子節點指向空,我們就會移動到該子樹的父節點
image

當我們當前處於某個節點時,可能會出現以下情況:

情況1:當前節點沒有左子樹

  • 列印當前節點的值
  • 然後到當前節點的右子節點
    image
    如果沒有左子樹,我們只需列印當前節點的值,因為左側沒有節點可遍歷。之後,我們移至右子節點繼續遍歷。

情況 2:存在一棵左子樹,並且該左子樹的最右邊的孩子指向空。

  • 將左子樹的最右邊的子節點設定為指向當前節點。
  • 移動到當前節點的左子節點。
    image
    在這種情況下,我們還沒有訪問左子樹。我們從左子樹的最右節點到當前節點建立一個臨時連結。此連結可幫助我們稍後確定何時完成左子樹的按序遍歷。設定連結後,我們移至左子節點以探索左子樹。

情況3:存在一棵左子樹,並且該左子樹的最右邊的孩子已經指向當前節點。

  • 列印當前節點的值
  • 恢復臨時連結(將其設定回空)
  • 移動到當前節點的右子節點
    image
    這種情況對於保持樹結構的完整性至關重要。如果左子樹的最右邊的子節點已經指向當前節點,則意味著我們已經完成了左子樹的按序遍歷。我們列印當前節點的值,然後恢復臨時連結以恢復原始樹結構。最後,我們移動到右子節點繼續遍歷。

演算法

image
步驟 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;
}

相關文章