1. 程式人生 > >[LeetCode] Binary Tree Inorder Traversal 二叉樹的中序遍歷

[LeetCode] Binary Tree Inorder Traversal 二叉樹的中序遍歷

Given a binary tree, return the inorder traversal of its nodes' values.

For example:
Given binary tree {1,#,2,3},

   1
    \
     2
    /
   3

return [1,3,2].

Note: Recursive solution is trivial, could you do it iteratively?

二叉樹的中序遍歷順序為左-根-右,可以有遞迴和非遞迴來解,其中非遞迴解法又分為兩種,一種是使用棧來接,另一種不需要使用棧。我們先來看遞迴方法,十分直接,對左子結點呼叫遞迴函式,根節點訪問值,右子節點再呼叫遞迴函式,程式碼如下:

解法一:

// Recursion
class Solution {
public:
    vector<int> inorderTraversal(TreeNode *root) {
        vector<int> res;
        inorder(root, res);
        return res;
    }
    void inorder(TreeNode *root, vector<int> &res) {
        if (!root) return;
        if
(root->left) inorder(root->left, res); res.push_back(root->val); if (root->right) inorder(root->right, res); } };

下面我們再來看非遞迴使用棧的解法,也是符合本題要求使用的解法之一,需要用棧來做,思路是從根節點開始,先將根節點壓入棧,然後再將其所有左子結點壓入棧,然後取出棧頂節點,儲存節點值,再將當前指標移到其右子節點上,若存在右子節點,則在下次迴圈時又可將其所有左子結點壓入棧中。這樣就保證了訪問順序為左-根-右,程式碼如下:

解法二: 

// Non-recursion
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.push_back(p->val);
            p = p->right;
        }
        return res;
    }
};

下面這種解法跟Binary Tree Preorder Traversal中的解法二幾乎一樣,就是把結點值加入結果res的步驟從if中移動到了else中,因為中序遍歷的順序是左-根-右,參見程式碼如下:

解法三:

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> s;
        TreeNode *p = root;
        while (!s.empty() || p) {
            if (p) {
                s.push(p);
                p = p->left;
            } else {
                TreeNode *t = s.top(); s.pop();
                res.push_back(t->val);
                p = t->right;
            }
        }
        return res;
    }
};

下面我們來看另一種很巧妙的解法,這種方法不需要使用棧,所以空間複雜度為常量,這種非遞迴不用棧的遍歷方法有個專門的名字,叫Morris Traversal,在介紹這種方法之前,我們先來引入一種新型樹,叫 Threaded binary tree,這個還不太好翻譯,我第一眼看上去以為是叫執行緒二叉樹,但是感覺好像又跟執行緒沒啥關係,後來看到網上有人翻譯為螺紋二叉樹,但本人認為這翻譯也不太敢直視,很容易讓人聯想到為計劃生育做出突出貢獻的某世界著名品牌,但是苦於找不到更合理的翻譯方法,就暫且叫螺紋二叉樹吧。我們先來看看維基百科上關於它的英文定義:

A binary tree is threaded by making all right child pointers that would normally be null point to the inorder successor of the node (if it exists), and all left child pointers that would normally be null point to the inorder predecessor of the node.

就是說螺紋二叉樹實際上是把所有原本為空的右子節點指向了中序遍歷順序之後的那個節點,把所有原本為空的左子節點都指向了中序遍歷之前的那個節點,具體例子可以點選這裡。那麼這道題跟這個螺紋二叉樹又有啥關係呢?由於我們既不能用遞迴,又不能用棧,那我們如何保證訪問順序是中序遍歷的左-根-右呢。原來我們需要構建一個螺紋二叉樹,我們需要將所有為空的右子節點指向中序遍歷的下一個節點,這樣我們中序遍歷完左子結點後,就能順利的回到其根節點繼續遍歷了。具體演算法如下:

1. 初始化指標cur指向root

2. 當cur不為空時

  - 如果cur沒有左子結點

      a) 打印出cur的值

    b) 將cur指標指向其右子節點

  - 反之

     將pre指標指向cur的左子樹中的最右子節點 

     * 若pre不存在右子節點

          a) 將其右子節點指回cur

        b) cur指向其左子節點

     * 反之

      a) 將pre的右子節點置空

      b) 列印cur的值

      c) 將cur指標指向其右子節點

解法四:

// Non-recursion and no stack
class Solution {
public:
    vector<int> inorderTraversal(TreeNode *root) {
        vector<int> res;
        if (!root) return res;
        TreeNode *cur, *pre;
        cur = root;
        while (cur) {
            if (!cur->left) {
                res.push_back(cur->val);
                cur = cur->right;
            } else {
                pre = cur->left;
                while (pre->right && pre->right != cur) pre = pre->right;
                if (!pre->right) {
                    pre->right = cur;
                    cur = cur->left;
                } else {
                    pre->right = NULL;
                    res.push_back(cur->val);
                    cur = cur->right;
                }
            }
        }
        return res;
    }
};

其實Morris遍歷不僅僅對中序遍歷有用,對先序和後序同樣有用,具體可參見網友NOALGO部落格,和 Annie Kim's Blog的部落格。所以對二叉樹的三種常見遍歷順序(先序,中序,後序)就有三種解法(遞迴,非遞迴,Morris遍歷),總共有九段程式碼呀,熟練掌握這九種寫法才算初步掌握了樹的遍歷挖~~ 至於二叉樹的層序遍歷也有遞迴和非遞迴解法,至於有沒有Morris遍歷的解法還有待大神們的解答,若真有也請勞煩告知博主一聲~~

類似題目:

參考資料: