第六章 二叉树part01-上

递归遍历

题目描述

给你二叉树的根节点 root,分别返回它节点的 前序 中序 后序 遍历。

解题思路

总体思路

通过递归版本进行二叉树的节点遍历,需要关注以下三个部分:

  1. 确定函数的返回值和参数列表
  2. 确定递归的终止条件
  3. 确定单层递归的逻辑

二叉树的三种遍历方式实际上对应着遍历中点的顺序,例如:

  • 前序遍历:中、左、右
  • 中序遍历:左、中、右
  • 后序遍历:左、右、中

这里以前序遍历为例,介绍一下前序遍历的递归遍历流程:

  1. 通过分析题意可以发现最终我们返回的是一个vector数组,因此在递归函数定义时,我们需要一个vector类型的参数用于保存递归调用过程中的节点值,还需要一个Treenode参数用于递归调用每个节点,由于结果是保存在vector中,因此无需返回值。
  2. 递归调用的终止条件为当前元素为空节点时,说明此时的调用的是叶子节点的左右子树,直接终止即可
  3. 由于是先序遍历,因此递归的逻辑自然是从中心节点开始,然后依次调用左右子树。

时空分析

  • 时间复杂度:O(N)
  • 空间复杂度:O(N)

代码实现

测试地址

先序:https://leetcode.cn/problems/binary-tree-preorder-traversal/description/

class Solution {
public:
    void travelVal(TreeNode* cur, vector<int>& vec) {
        // 递归终止条件
        if (cur == nullptr) {
            return;
        }
        vec.push_back(cur->val);    // 中
        travelVal(cur->left, vec);  // 左
        travelVal(cur->right, vec); // 右
    }

    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        travelVal(root, result);
        return result;
    }
};

中序:https://leetcode.cn/problems/binary-tree-inorder-traversal/description/

class Solution {
public:
    void travelVal(TreeNode* cur, vector<int>& vec) {
        // 递归终止条件
        if (cur == nullptr) {
            return;
        }
        travelVal(cur->left, vec);  // 左
        vec.push_back(cur->val);    // 中
        travelVal(cur->right, vec); // 右
    }
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        travelVal(root, result);
        return result;
    }
};

后序:https://leetcode.cn/problems/binary-tree-postorder-traversal/description/

class Solution {
public:
    void travelVal(TreeNode* cur, vector<int>& vec) {
        // 递归终止条件
        if (cur == nullptr) {
            return;
        }
        travelVal(cur->left, vec);  // 左
        travelVal(cur->right, vec); // 右
        vec.push_back(cur->val);    // 中
    }
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> result;
        travelVal(root, result);
        return result;
    }
};

迭代遍历

题目描述

给你二叉树的根节点 root,分别返回它节点的 前序 中序 后序 遍历。

解题思路

前序遍历

思路

先序遍历的顺序为:中-左-右,因此在遍历完当前节后后,我们实际上需要先将当前节点的右子节点压入栈中,然后再压入左子节点,确保左子节点的优先级更高,更先弹出,具体流程如下:

  1. 定义一个栈,将根节点压入栈中。

  2. 当栈不为空时,执行以下步骤:

    • 弹出栈顶节点,并输出其值。
    • 如果当前节点有右子节点,将其压入栈中。
    • 如果当前节点有左子节点,将其压入栈中。
  3. 重复步骤2,直到栈为空。

中序遍历

思路

中序遍历的顺序为:左-中-右,因此需要先处理所有的左子节点,将它们依次压入栈中,直至遇到空节点后,此时已发现最左子节点,弹出当前左子节点的值,然后继续处理当前左子节点的右子节点,具体流程如下:

  1. 初始化一个栈。

  2. 当栈不为空或当前节点不为空时,执行以下步骤:

    • 如果当前节点不为空,将其压入栈中,并移动到其左子节点。
    • 如果当前节点为空,说明已经到达最左下角,弹出栈顶节点并输出其值,然后移动到其右子节点。
  3. 重复步骤2,直到栈为空且当前节点也为空。

后序遍历

方式一

思路

后序遍历的顺序为:左-右-中,从前面可以得知先序遍历的顺序为:中-左-右,我们将先序遍历经过调整左右循序可以得到:中-右-左,再进行整体翻转后得到的最后的顺序结果:左-右-中,和后序遍历的顺序一致,因此代码只需要在先序遍历的基础上进行修改即可,具体流程如下:

  1. 定义一个栈用于临时存储节点
  2. 将根节点压入栈中
  3. 按照根-右-左的顺序依次操作节点,将值加入结果集中
  4. 反转结果集输出即可
方式二

思路

方式二和方式一的大体思路一致,都是对前序遍历(中-左-右)的变体,通过调整遍历顺序和使用栈来翻转最终的输出顺序,方式二使用了两个栈,第一个栈用来模拟递归的过程,第二个栈用来收集节点,并在最后通过第二个栈来输出结果,具体流程如下:

  1. 定义两个栈。

  2. 将根节点压入第一个栈中。

  3. 当第一个栈不为空时,执行以下步骤:

    • 弹出第一个栈的栈顶节点,并将其压入第二个栈中。
    • 如果当前节点有左子节点,将其压入第一个栈中。
    • 如果当前节点有右子节点,将其压入第一个栈中。
  4. 当第一个栈为空时,从第二个栈中依次弹出节点并输出其值。

方式三

思路

方式三使用一个栈来追踪未处理的节点,并通过一个循环结构,结合辅助变量来标记前一个访问的节点,从而实现了对二叉树的后序遍历,即先访问左子树,再访问右子树,最后访问根节点,并在遍历过程中,确保每个节点只在其左右子节点都已被处理后才被加入到遍历结果中,具体流程如下:

  1. 定义一个栈,将根节点压入栈中。
  2. 初始化一个指针lastPrinted用于记录上一次输出的节点。
  3. 从根节点开始,尽可能地向左深入,并将途径的每个节点都压入栈中。
  4. 无法继续向左时,从栈中取出节点。如该节点右子树为空或已处理,则将其值添加到结果向量中,并标记此节点为已处理。否则,将此节点重新压回栈中,并转向处理其右子树。
  5. 重复以上步骤,直到所有节点都被处理。

代码实现

前序遍历实现

测试地址:https://leetcode.cn/problems/binary-tree-preorder-traversal/

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* head) {
        // 定义vector数组,用于存储遍历后的节点值
        vector<int> result;
        // 如果当前二叉树不是空树,开始遍历
        if (head != nullptr) {
            // 定义一个栈存放将要访问的节点
            stack<TreeNode*> stack;
            // 将根节点压入栈中
            stack.push(head);
            // 当栈不为空时处理节点
            while (!stack.empty()) {
                // 取出栈顶元素
                head = stack.top();
                // 弹出栈顶元素
                stack.pop();
                // 将节点值添加到结果列表中
                result.push_back(head->val);
                // 如果存在右子节点,将右子节点压入栈中(由于栈的LIFO特性,先压右子节点)
                if (head->right)
                    stack.push(head->right);
                // 如果存在左子节点,将左子节点压入栈中
                if (head->left)
                    stack.push(head->left);
            }
        }
        // 返回遍历结果
        return result;
    }
};

中序遍历实现

测试:https://leetcode.cn/problems/binary-tree-inorder-traversal/

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* head) {
        // 结果数组,用来存储中序遍历的结果
        vector<int> result;
        // 如果头节点不为空,开始遍历
        if (head != nullptr) {
            // 使用栈来管理遍历过程中的节点
            stack<TreeNode*> stack;
            // 当当前节点不为空或栈不为空时,继续处理
            while (head != nullptr || !stack.empty()) {
                // 如果当前节点不为空,将其压入栈中,并前往左子节点
                if (head != nullptr) {
                    stack.push(head);
                    head = head->left;
                } else {
                    // 如果当前节点为空,说明左侧已经处理完毕,处理栈顶节点
                    head = stack.top();
                    stack.pop();
                    // 将栈顶节点值添加到结果数组中
                    result.push_back(head->val);
                    // 移动到右子节点
                    head = head->right;
                }
            }
        }
        // 返回中序遍历的结果
        return result;
    }
};

后序遍历实现

测试地址:https://leetcode.cn/problems/binary-tree-postorder-traversal/

方式一
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        // 创建一个用于存储后序遍历结果的向量
        vector<int> result;
      
        // 创建一个栈,用于临时存储节点
        stack<TreeNode*> st;
      
        // 如果根节点为空,则返回空的结果向量
        if (root == nullptr)
            return result;
      
        // 将根节点推入栈中
        st.push(root);
      
        // 当栈不为空时,循环继续
        while (!st.empty()) {
            // 取栈顶元素即当前节点
            root = st.top();
            // 弹出栈顶元素
            st.pop();
            // 将当前节点的值加入到结果向量中
            result.push_back(root->val);
          
            // 如果当前节点有左子节点,则将左子节点推入栈中
            if (root->left)
                st.push(root->left);
            // 如果当前节点有右子节点,则将右子节点推入栈中
            if (root->right)
                st.push(root->right);
        }
        // 反转结果向量,因为我们是以根-右-左的顺序加入结果的,后序遍历应为左-右-根
        reverse(result.begin(), result.end());
      
        // 返回后序遍历的结果
        return result;
    }
};

方式二
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* head) {
        // 创建一个用于存储后序遍历结果的向量
        vector<int> ans;

        // 检查根节点是否为空
        if (head != nullptr) {
            // 创建一个栈,用于临时存储节点
            stack<TreeNode*> stack;
            // 创建一个栈,用于收集节点值
            stack<TreeNode*> collect;
          
            // 将根节点推入栈中
            stack.push(head);
          
            // 当栈不为空时,循环继续
            while (!stack.empty()) {
                // 取栈顶元素作为当前节点
                head = stack.top();
                // 弹出栈顶元素
                stack.pop();
              
                // 将当前节点推入收集栈中
                collect.push(head);
              
                // 如果当前节点有左子节点,则将左子节点推入栈中
                if (head->left != nullptr) {
                    stack.push(head->left);
                }
                // 如果当前节点有右子节点,则将右子节点推入栈中
                if (head->right != nullptr) {
                    stack.push(head->right);
                }
            }
            // 收集栈中的元素(即节点值),并添加到最终结果向量中
            while (!collect.empty()) {
                ans.push_back(collect.top()->val);
                collect.pop();
            }
        }
        // 返回后序遍历的结果
        return ans;
    }
};
方式三
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        // 创建一个向量来存储后序遍历的结果
        vector<int> ans;

        // 如果根节点为空,则直接返回空的向量
        if (root == nullptr) return ans;

        // 创建一个栈来辅助后序遍历
        stack<TreeNode*> stack;
        // 定义一个指针prev,用于记录上一个访问的节点
        TreeNode* prev = nullptr;
        // 当根节点不为空或栈不为空时,循环继续
        while (root != nullptr || !stack.empty()) {
            // 遍历到最左边的节点
            while (root != nullptr) {
                stack.push(root); // 将当前节点压入栈中
                root = root->left; // 移动到左子节点
            }
            // 回溯到最近的未处理节点
            root = stack.top(); // 获取栈顶元素,即当前需处理的节点
            stack.pop(); // 弹出栈顶元素

            // 如果当前节点的右子节点为空,或者右子节点已经被访问
            // 则可以处理当前节点
            if (root->right == nullptr || root->right == prev) {
                ans.push_back(root->val); // 将当前节点的值添加到结果向量中
                prev = root; // 更新prev为当前节点
                root = nullptr; // 重置root为nullptr,准备下一次循环
            } else {
                // 如果当前节点的右子节点还没被访问,则
                // 将当前节点再次压入栈中,并移动到右子节点
                stack.push(root);
                root = root->right;
            }
        }
        // 返回后序遍历的结果
        return ans;
    }
};

统一迭代

思路

这里以中序遍历为例进行说明:

在使用栈进行二叉树的中序遍历时,通常会遇到访问节点(遍历节点)和处理节点(将元素放进结果集)的时机不一致的问题。这是因为中序遍历的顺序是“左-根-右”,在遍历过程中,我们需要先访问(即移动到)最左边的节点,然后才能开始处理节点。

如果我们只使用栈来访问节点,而不使用额外的标记或机制,我们无法知道何时应该开始处理节点。例如,当我们到达最左边的节点时,我们可能已经将许多节点压入了栈中,但我们无法确定何时应该开始从栈中弹出节点并处理它们。

为了解决这个问题,我们可以使用使用NULL标记 在每个节点前压入一个NULL标记到栈中。当栈顶元素为NULL时,我们知道下一个节点是一个待处理的节点。这种方法允许我们在正确的时机处理节点,即在访问了所有左子节点之后。

代码实现

中序遍历

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        // 创建一个向量来存储中序遍历的结果
        vector<int> result;

        // 创建一个栈来辅助中序遍历
        stack<TreeNode*> st;

        // 如果根节点不为空,将其压入栈中
        if (root != nullptr)
            st.push(root);

        // 当栈不为空时,循环继续
        while (!st.empty()) {
            // 获取栈顶元素作为当前节点
            TreeNode* node = st.top();

            // 如果当前节点不为空
            if (node != NULL) {
                // 弹出栈顶元素
                st.pop();

                // 如果当前节点有右子节点,将其压入栈中
                if (node->right) //右
                    st.push(node->right);

                // 将当前节点再次压入栈中,并在其前压入一个NULL标记
                st.push(node); //中
                st.push(NULL);

                // 如果当前节点有左子节点,将其压入栈中
                if (node->left) //左
                    st.push(node->left);
            } else {
                // 如果栈顶元素为NULL,说明当前节点应该被处理
                // 弹出NULL标记
                st.pop();

                // 弹出当前节点
                node = st.top();
                st.pop();

                // 将当前节点的值添加到结果向量中
                result.push_back(node->val);
            }
        }
        // 返回中序遍历的结果
        return result;
    }
};

前序遍历

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if (root != nullptr)
            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 != nullptr)
            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;
    }
}
  • 35
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值