二叉樹:前中後序迭代方式統一寫法

程式碼隨想錄發表於2020-09-29

此時我們在二叉樹:一入遞迴深似海,從此offer是路人中用遞迴的方式,實現了二叉樹前中後序的遍歷。

二叉樹:聽說遞迴能做的,棧也能做!中用棧實現了二叉樹前後中序的迭代遍歷(非遞迴)。

之後我們發現「迭代法實現的先中後序,其實風格也不是那麼統一,除了先序和後序,有關聯,中序完全就是另一個風格了,一會用棧遍歷,一會又用指標來遍歷。」

實踐過的同學,也會發現使用迭代法實現先中後序遍歷,很難寫出統一的程式碼,不像是遞迴法,實現了其中的一種遍歷方式,其他兩種只要稍稍改一下節點順序就可以了。

其實「針對三種遍歷方式,使用迭代法是可以寫出統一風格的程式碼!」

「重頭戲來了,接下來介紹一下統一寫法。」

我們以中序遍歷為例,在二叉樹:聽說遞迴能做的,棧也能做!中提到說使用棧的話,「無法同時解決訪問節點(遍歷節點)和處理節點(將元素放進結果集)不一致的情況」

「那我們就將訪問的節點放入棧中,把要處理的節點也放入棧中但是要做標記。」

如何標記呢,「就是要處理的節點放入棧之後,緊接著放入一個空指標作為標記。」 這種方法也可以叫做標記法。

迭代法中序遍歷

中序遍歷程式碼如下:(詳細註釋)

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop(); // 將該節點彈出,避免重複操作,下面再將右中左節點新增到棧中
                if (node->right) st.push(node->right);  // 新增右節點(空節點不入棧)

                st.push(node);                          // 新增中節點
                st.push(NULL); // 中節點訪問過,但是還沒有處理,加入空節點做為標記。

                if (node->left) st.push(node->left);    // 新增左節點(空節點不入棧)
            } else { // 只有遇到空節點的時候,才將下一個節點放進結果集
                st.pop();           // 將空節點彈出
                node = st.top();    // 重新取出棧中元素
                st.pop();
                result.push_back(node->val); // 加入到結果集
            }
        }
        return result;
    }
};

看程式碼有點抽象我們來看一下動畫(中序遍歷):

中序遍歷迭代(統一寫法)

動畫中,result陣列就是最終結果集。

可以看出我們將訪問的節點直接加入到棧中,但如果是處理的節點則後面放入一個空節點, 這樣只有空節點彈出的時候,才將下一個節點放進結果集。

此時我們再來看前序遍歷程式碼。

迭代法前序遍歷

迭代法前序遍歷程式碼如下:(「注意此時我們和中序遍歷相比僅僅改變了兩行程式碼的順序」)

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop();
                if (node->right) st.push(node->right);  // 右
                if (node->left) st.push(node->left);    // 左
                st.push(node);                          // 中
                st.push(NULL);
            } else {
                st.pop();
                node = st.top();
                st.pop();
                result.push_back(node->val);
            }
        }
        return result;
    }
};

迭代法後序遍歷

後續遍歷程式碼如下:(「注意此時我們和中序遍歷相比僅僅改變了兩行程式碼的順序」)

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop();
                st.push(node);                          // 中
                st.push(NULL);

                if (node->right) st.push(node->right);  // 右
                if (node->left) st.push(node->left);    // 左

            } else {
                st.pop();
                node = st.top();
                st.pop();
                result.push_back(node->val);
            }
        }
        return result;
    }
};

總結

此時我們寫出了統一風格的迭代法,不用在糾結於前序寫出來了,中序寫不出來的情況了。

但是統一風格的迭代法並不好理解,而且想在面試直接寫出來還有難度的。

所以大家根據自己的個人喜好,對於二叉樹的前中後序遍歷,選擇一種自己容易理解的遞迴和迭代法。

本文:https://github.com/youngyangyang04/leetcode-master​已經收錄,裡面還有leetcode刷題攻略、各個型別經典題目刷題順序、思維導圖,可以fork到自己倉庫,有空看一看一定會有所收穫,如果對你有幫助也給一個star支援一下吧!

我的B站(裡面有我講解的演算法視訊以及程式設計相關知識)https://space.bilibili.com/525438321

我是程式設計師Carl,哈工大師兄,先後在騰訊和百度從事技術研發多年,利用工作之餘重刷leetcode,更多    精彩演算法文章盡在:  程式碼隨想錄,關注後,回覆「Java」「C++」「python」「簡歷模板」等等,有我整理多年的學習資料,可以加我   微信,備註「個人簡介」+「組隊刷題」,拉你進入刷題群(無任何廣告,純個人分享),每天一道經典題目分析,我選的每一道題目都不是孤立的,而是由淺入深一脈相承的,如果跟住節奏每篇連續著看,定會融會貫通。

 

相關文章