二叉樹中序遍歷,先序遍歷,后序遍歷(遞歸棧,非遞歸棧,Morris Traversal)


例題

遞歸棧

遞歸函數棧的方法很基礎,寫法也很簡單,三種遍歷方式之間只需要改變一行代碼的位置即可

中序遍歷

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    
    void inorder(TreeNode* root, vector<int>& v){
        if(root != nullptr) {
            inorder(root->left, v);
            v.push_back(root->val); // 改變位置的代碼
            inorder(root->right, v);
        }
    }
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> v;
        
        inorder(root, v);
        return v;
    }
};

先序遍歷

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    
    void inorder(TreeNode* root, vector<int>& v){
        if(root != nullptr) {
            v.push_back(root->val);
            inorder(root->left, v);
            inorder(root->right, v);
        }
    }
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> v;
        
        inorder(root, v);
        return v;
    }
};

后序遍歷

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    
    void inorder(TreeNode* root, vector<int>& v){
        if(root != nullptr) {
            inorder(root->left, v);
            inorder(root->right, v);
            v.push_back(root->val);
        }
    }
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> v;
        
        inorder(root, v);
        return v;
    }
};

非遞歸棧

當樹的深度過大時,函數棧可能會溢出,這時候需要我們使用數據結構中的棧,來模擬節點在棧中的壓入和彈出

中序遍歷

cur指針時刻指向需要處理的節點
如果當前節點不為空,則壓入棧中,並改變cur指針指向其左節點
如果當前節點為空,也代表空節點的中序遍歷自動完成,無需壓棧,這時候需要彈出棧頂的節點,保存棧頂節點的值,並更改cur指向其右子樹,以完成右子樹的中序遍歷

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> v;
        stack<TreeNode*> s;
        TreeNode* cur = root;

        while(cur || !s.empty()){
            if(cur){
                s.push(cur);
                cur = cur->left;
            } else {
                cur = s.top();
                s.pop();
                v.push_back(cur->val);
                cur = cur->right;
            }
        }
        
        return v;
    }
};

先序遍歷

先序遍歷與中序遍歷代碼相比只改變了保存節點的值的代碼的位置,當先訪問根的時候就記錄節點,而不是等左子樹遍歷完,彈出根節點的時候再記錄

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> v;
        stack<TreeNode*> s;
        TreeNode* cur = root;

        while(cur || !s.empty()){
            if(cur){
                v.push_back(cur->val);
                s.push(cur);
                cur = cur->left;
            } else {
                cur = s.top();
                s.pop();
                cur = cur->right;
            }
        }
        
        return v;
    }
};

后序遍歷

因為后序遍歷的壓棧順序是左-右-根,由於先遍歷完左子樹,然后遍歷完右子樹,然后才能處理當前節點,為了和之前的代碼的結構保持一致,我們可以反向處理,也就是按根-右-左的順序壓棧,
結果反向輸出即可

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> v;
        stack<TreeNode*> s;
        TreeNode* cur = root;

        while(cur || !s.empty()){
            if(cur){
                v.push_back(cur->val);
                s.push(cur);
                cur = cur->right;
            } else {
                cur = s.top();
                s.pop();
                cur = cur->left;
            }
        }
        
        reverse(v.begin(), v.end()); // 反向輸出結果
        return v;
    }
};

Morris Traversal

線索二叉樹,O(n)時間,常數空間
Morris Traversal方法遍歷二叉樹(非遞歸,不用棧,O(1)空間)
就是當前節點的中序遍歷前驅節點,如果此前驅節點的右指針為空,則將此前驅節點的右指針指向當前節點
當尋找當前節點的中序遍歷前驅節點時,發現能循環到自己,說明左子樹已經遍歷完,需要遍歷右子樹

中序遍歷

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        TreeNode* cur, *prev;
        vector<int> v;
        cur = root;
        prev = nullptr;
        
        while(cur){
            if(cur->left == nullptr){
                v.push_back(cur->val);
                cur = cur->right;
            } else {
                prev = cur->left;
                while(prev->right != nullptr && prev->right != cur) 
                    prev = prev->right;
                
                if(prev->right == nullptr){
                    prev->right = cur;
                    cur = cur->left;
                } else {
                    v.push_back(cur->val);
                    prev->right = nullptr;
                    cur = cur->right;
                }
            }
        }
        
        return v;
    }
};

先序遍歷

同樣先序遍歷與中序遍歷相比也只需要改變一行代碼的位置

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        TreeNode* cur, *prev;
        vector<int> v;
        cur = root;
        prev = nullptr;
        
        while(cur){
            if(cur->left == nullptr){
                v.push_back(cur->val);
                cur = cur->right;
            } else {
                prev = cur->left;
                while(prev->right != nullptr && prev->right != cur) 
                    prev = prev->right;
                
                if(prev->right == nullptr){
                    v.push_back(cur->val);
                    prev->right = cur;
                    cur = cur->left;
                } else {
                    prev->right = nullptr;
                    cur = cur->right;
                }
            }
        }
        
        return v;
    }
};

后序遍歷

后序遍歷需要反向輸出cur->left到cur的中序前驅結點之間的路徑

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:

    void reverse_traverse_reverse(TreeNode* cur, vector<int>& v){
        TreeNode* prev = nullptr;

        while(cur){
            auto right = cur->right;
            cur->right = prev;
            prev = cur;
            cur = right;
        }


        cur = prev;
        prev = nullptr;

        while(cur){
            auto right = cur->right;
            cur->right = prev;
            v.push_back(cur->val);
            prev = cur;
            cur = right;
        }
    }
    vector<int> postorderTraversal(TreeNode* root) {
        TreeNode* cur, *prev;
        vector<int> v;
        cur = new TreeNode(-1);
        prev = nullptr;
        cur->left = root;
        while(cur){
            if(cur->left == nullptr){
                cur = cur->right;
            } else {
                prev = cur->left;
                while(prev->right != nullptr && prev->right != cur)
                    prev = prev->right;
                
                if(prev->right == nullptr){
                    prev->right = cur;
                    cur = cur->left;
                } else {
                    prev->right = nullptr;
                    reverse_traverse_reverse(cur->left, v);
                    cur = cur->right;
                }
            }
        }
        
        return v;
    }
};

總結

三種遍歷方式中,后序遍歷的處理比較麻煩,但是無論是使用遞歸棧,非遞歸棧還是Morris Traversal,代碼的結構都是一樣的,中序和前序甚至只有一行代碼位置的差別
使用棧的版本中,最壞空間復雜度為O(n)鏈型,平均空間復雜度為O(lgn)
Morris Traversal空間復雜度為O(1)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM