二叉樹遍歷(前中后序遍歷,三種方式)


刷題中碰到二叉樹的遍歷,就查找了二叉樹遍歷的幾種思路,在此做個總結。對應的LeetCode題目如下:

144.二叉樹的前序遍歷94.二叉樹中序遍歷145.二叉樹的后續遍歷102.層次遍歷

接下來以前序遍歷來說明三種解法的思想,后面中序和后續直接給出代碼。

首先定義二叉樹的數據結構如下:

 //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) {
        if(root == NULL) return {};
        vector<int> res;
        helper(root,res);
        return res;
    }
    void helper(TreeNode *root, vector<int> &res){
        res.push_back(root->val);
        if(root->left) helper(root->left, res);
        if(root->right) helper(root->right, res);
    }
};

使用輔助棧迭代實現:

算法為:先把根節點push到輔助棧中,然后循環檢測棧是否為空,若不空,則取出棧頂元素,保存值到vector中,之后由於需要想訪問左子節點,所以我們在將根節點的子節點入棧時要先經右節點入棧,再將左節點入棧,這樣出棧時就會先判斷左子節點。

代碼如下:

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        if(root == NULL) return {};
        vector<int> res;
        stack<TreeNode*> st;
        st.push(root);
        while(!st.empty()){
            //將根節點出棧放入結果集中
            TreeNode *t = st.top();
            st.pop();
            res.push_back(t->val);
            //先入棧右節點,后左節點
            if(t->right) st.push(t->right);
            if(t->left) st.push(t->left);
        }
        return res;
    }
};

Morris Traversal方法

具體的詳細解釋可以參考如下鏈接:

http://www.cnblogs.com/AnnieKim/archive/2013/06/15/MorrisTraversal.html

這種解法可以實現O(N)的時間復雜度和O(1)的空間復雜度。

在O(1)空間進行遍歷,最大的難點在於遍歷到子節點的時候怎么重新返回父節點(並且節點中沒有返回父節點的指針)。為了解決這個難點,Morris方法用到了線索二叉樹(threaded binary tree)的概念,但是在這個方法中不需要額外分配指針指向其前驅或者后繼節點,而是利用葉子節點的左右空指針指向某種順序遍歷下的前驅節點或后繼節點就可以了。Morris給出了中序遍歷的方法,在中序遍歷的基礎上修改便可得到前序和后續遍歷。

算法步驟如下:

  1. 如果當前節點的左孩子為空,則輸出當前節點並將其右孩子作為當前節點。

  2. 如果當前節點的左孩子不為空,在當前節點的左子樹中找到當前節點在中序遍歷下的前驅節點。

    a. 如果前驅節點的右孩子為空,將它的右孩子設置為當前節點。輸出當前節點(在這里輸出,這是與中序遍歷唯一一點不同)。當前節點更新為當前節點的左孩子。

    b. 如果前驅節點的右孩子為當前節點,將它的右孩子重新設為空。當前節點更新為當前節點的右孩子。

  3. 重復以上 1、2 直到當前節點為空。

圖示可以更清楚的展示這個過程,(圖片來源於上面連接中的文章)。

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        TreeNode *cur = root, *prev = NULL;
        vector<int> res;
        while(cur!=NULL){ //第一步
            if(cur->left == NULL){
                res.push_back(cur->val);
                cur = cur->right;
            }else{
                //第二步,找到該節點的前驅節點
                prev = cur->left;
                while(prev->right != NULL && prev->right !=cur)
                    prev = prev ->right;
                if(prev->right == NULL){ //2.a步驟
                    res.push_back(cur->val);
                    prev->right = cur;
                    cur = cur->left;
                }else{
                    //2.b步驟
                    prev ->right = NULL;
                    cur = cur->right;
                }
            }
        }
        return res;
    }
};

中序遍歷:遍歷順序為“左-中-右”

遞歸:

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        if(root == NULL) return {};
        vector<int> res;
        helper(root,res);
        return res;
    }
    void helper(TreeNode *root, vector<int> &res){
        if(root->left) helper(root->left, res);
        res.push_back(root->val);
        if(root->right) helper(root->right, res);
    }
};

使用輔助棧:

算法步驟為先讓根節點入棧,因為要遍歷左節點,所以之后將其所有的左子節點壓入棧,然后取出棧頂節點,保存節點的值,再將當前指針指向該節點的右子節點上,若存在右子節點,則在下次循環中又可以將其所有左子節點壓入棧中。這樣就保證了順序為左-根-右。

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> s;
        TreeNode *p = root;
        while(p || !s.empty()){
            while(p){
                //先將所有左節點入棧
                s.push(p);
                p = p->left;
            }
            //然后取出棧頂元素
            p = s.top();
            s.pop();
            //將其放入結果res中
            res.push_back(p->val);
            //將指針指向其右子節點,如果有的話,則將右子節點的左子節點繼續入棧。
            p = p->right;
        }
        return res;
    }
};

Morris Traversal方法:

步驟:

  1. 如果當前節點的左孩子為空,則輸出當前節點並將其右孩子作為當前節點。

  2. 如果當前節點的左孩子不為空,在當前節點的左子樹中找到當前節點在中序遍歷下的前驅節點。

    a. 如果前驅節點的右孩子為空,將它的右孩子設置為當前節點。當前節點更新為當前節點的左孩子。

    b. 如果前驅節點的右孩子為當前節點,將它的右孩子重新設為空(恢復樹的形狀)。輸出當前節點。當前節點更新為當前節點的右孩子。

  3. 重復以上 1、2 直到當前節點為空。

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        TreeNode *cur = root, *prev = NULL;
        while(cur){
            if(cur->left == NULL){ // 1.
                res.push_back(cur->val);
                cur= cur->right;
            }else{
                //找到前驅節點
                prev = cur->left;
                while(prev->right!=NULL && prev->right !=cur){
                    prev = prev->right;
                }
                if(prev->right==NULL){//2.a
                    prev->right = cur;
                    cur = cur->left;
                }else{
                    //2.b
                    prev->right= NULL;
                    res.push_back(cur->val);
                    cur = cur->right;
                } 
            }
        }
        return res;
    }
};

后續遍歷:遍歷順序為左-右-根

遞歸方法:

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        if(root == NULL) return {};
        vector<int> res;
        helper(root,res);
        return res;
    }
    void helper(TreeNode *root, vector<int> &res){
        if(root->left) helper(root->left, res);
        if(root->right) helper(root->right, res);
        res.push_back(root->val);
    }
};

使用輔助棧:

由於后續遍歷的順序是左-右-根,而先序遍歷的順序是根-左-右,二者其實是相反的。可以在先序遍歷的方法上做一些小改動,使其遍歷順序變為根-右-左,然后翻轉一下,就是左-右-根。翻轉的方法可以使用反向加入結果res,每次在res的開頭加入根節點,而改變先序遍歷就只需要改變一下入棧順序,先左后右這樣子。

代碼如下:

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        if(root==NULL) return{};
        vector<int> res;
        stack<TreeNode*> st;
        st.push(root);
        while(!st.empty()){
            //訪問棧頂元素
            TreeNode *t = st.top();
            st.pop();
            //將結果依次插入res的頭部
            res.insert(res.begin(), t->val);
            //先將左節點入棧
            if(t->left) st.push(t->left);
            if(t->right) st.push(t->right);
        }
        return res;
    }
};

Morris Traversal方法:

后續遍歷稍顯復雜,需要建立一個臨時節點 dump,令其左孩子是 root。並且還需要一個子過程,就是倒序輸出某兩個節點之間路徑上的各個節點。

步驟:

當前節點設置為臨時節點 dump。

  1. 如果當前節點的左孩子為空,則將其右孩子作為當前節點。

  2. 如果當前節點的左孩子不為空,在當前節點的左子樹中找到當前節點在中序遍歷下的前驅節點。

    a. 如果前驅節點的右孩子為空,將它的右孩子設置為當前節點。當前節點更新為當前節點的左孩子。

    b. 如果前驅節點的右孩子為當前節點,將它的右孩子重新設為空。倒序輸出從當前節點的左孩子到該前驅節點這條路徑上的所有節點。當前節點更新為當前節點的右孩子。

  3. 重復以上 1、2 直到當前節點為空。


代碼如下:

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        TreeNode *dump = new TreeNode (0);
        dump->left = root;
        TreeNode *cur = dump, *prev = NULL;
        vector<int> res;
        while(cur){
            if(cur->left == NULL){
                cur = cur->right;
            }else{
                prev = cur->left;
                while(prev ->right!=NULL && prev->right!=cur){
                    prev = prev->right;
                }
                if(prev->right == NULL){
                    prev->right = cur;
                    cur = cur->left;
                }else{
                    printReverse(cur->left, prev,res);
                    prev->right=NULL;
                    cur = cur->right;
                }
            }
        }
        return res;
    }
    void reverse(TreeNode* from, TreeNode *to){
        //reverse the tree nodes 'from'->'to'
        if(from == to){
            return;
        }
        TreeNode *x = from, *y = from->right, *z;
        while(true){
            //swap指針指向的不同節點
            z = y->right;
            y->right = x;
            x = y;
            y = z;
            if(x == to)
                break;
        }
    }
    void printReverse(TreeNode *from, TreeNode *to, vector<int> &res){
        //將節點逆序
        reverse(from,to);
        TreeNode *p = to;
        //將其放入結果數組
        while(true){
            res.push_back(p->val);
            if(p == from)
                break;
            p = p->right;
        }
        //恢復原來的順序
        reverse(to,from);
    }
};

層次遍歷:

樹的層次遍歷的思想就是使用一個隊列依次存放每層的值。代碼如下:

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if(!root) return vector<vector<int>> (0,vector<int>()); 
        vector<vector<int>> res;
        queue<TreeNode*> q{{root}}; //賦初始值
        while(!q.empty()){
            vector<int> p; //存放當前層的值
            for(int i=q.size();i>0;i--){ //將當前隊列中的所有值放入數組
                TreeNode *t = q.front();
                p.push_back(t->val);
                q.pop();
                if(t->left) q.push(t->left); //繼續向隊列中添加下一層的值。
                if(t->right) q.push(t->right);
            }
            res.push_back(p);
            
        }
        return res;
    }
};

結束了!


免責聲明!

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



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