• 深入理解后序遍历二叉树


    起因

    在看邓俊辉在学堂在线上的数据结构课的时候,发现前序/中序的二叉树迭代遍历讲的都非常好,偏偏不讲后序。看了书上的讲解后也觉得一头雾水,尤其是需要去找左侧最深可见叶子节点那里,我基本需要背诵代码的逻辑,非常痛苦。
    所以开始在网上找有没有更好理解的逻辑去写这个后序遍历。

    发现

    我发现,大多数讲解后序遍历的博客多提到两个关键点,第一个是要关注:对于一个节点node,我们什么时候才对其进行访问?答案是在左右子树都访问完后,对node进行访问;或是左右子树都空时,这就等同于左右子树都访问完了。
    第二个要关注的点是,后序遍历的访问次序是怎么样的?答案是,左|右|当前根节点。

    基于以上的两个理解,我们不难发现,每次遇到一个节点,我们需要首先进行一个判断,当前这个节点是否满足访问条件?如果满足,对其进行访问,并且出栈(表示访问完成了)。如果不满足,那么我们先不要访问当前这个节点,(不要出栈),而是将当前节点的右/左子树根节点依次入栈,并且进入下一次循环。

    思路

    还是延续前序和中序的循环思路,尝试使用stack非空检查。根据第二个关键问题写push stack顺序,根据第一个关键问题写判断条件,可以写出简单粗暴的代码。遇到一个节点时,有三种状态:1)第一次遇到 2)从左子树返回 3)从右子树返回。
    而对于一颗子树,有 [单左,单右,左右都有,左右都无],根据这四种情况写第一次遇到节点的判断条件。

    实现1

    vector<int> postorderTraversal(TreeNode* root) {
            vector<int> ret;
            if (!root)
                return ret;
            int left = 0, right = 0;
            vector<TreeNode *> stack = {root};
            TreeNode *prev = nullptr; // 记录上一个访问的节点,用于分辨从左子树返回还是右子树返回
            while (!stack.empty()) {
                auto node = stack.back();
                if (!node->left && !node->right) { // 第一次进入(叶子节点)
                    ret.push_back(node->val);
                    prev = node;
                    stack.pop_back();
                }
                else if (node->left && node->right && prev != node->left && prev != node->right  //左右都有,但既不是从左返回也不是从右返回
                    || node->left && !node->right && prev != node->left  //单左,但不是从左返回
                    || node->right && !node->left && prev != node->right) { // 单右,但不是从右返回
                    if (node->right)
                        stack.push_back(node->right);
                    if (node->left)
                        stack.push_back(node->left);
                    // continue;
                }
                else if (node->right && prev == node->right ){ //从右返回时,可直接访问
                    ret.push_back(node->val);
                    prev = node;
                    stack.pop_back();
                }
                else if (!node->right && node->left && prev == node->left) { //从左返回且没右子树时,可直接访问
                    ret.push_back(node->val);
                    prev = node;
                    stack.pop_back();
                }
                
            }
            return ret;
    
        }
    

    实现2

    vector<int> postorderTraversal(TreeNode* root) {
            if (!root) return vector<int> {};
            stack<TreeNode *> s;
            vector<int> res;
            
            s.push(root);
            TreeNode *prev = nullptr, *node;
            while (!s.empty()) {
                node = s.top();
                if (node->left == nullptr && node->right == nullptr || node->right && prev == node->right || node->left && node->left == prev && node->right == nullptr) {
                    res.push_back(node->val);
                    prev = node;
                    s.pop();
                } else {
                    if (node->right != nullptr) {
                        s.push(node->right);
                    }
                    if (node->left != nullptr)
                        s.push(node->left);
                }
            }
            return res;
        }
    
  • 相关阅读:
    HTML5 drag拖动事件
    echarts 实现立体柱子图
    团队管理(七)
    echarts环比图实现
    父组件调用图表组件根据按钮切换展示数据
    echarts 折柱图绘制图表标注
    团队管理(六)
    团队管理(五)
    css 绘制圆角三角形
    团队管理(四)
  • 原文地址:https://www.cnblogs.com/ijpq/p/15428290.html
Copyright © 2020-2023  润新知