【代码随想录37期】Day14 二叉树的前序遍历、二叉树的中序遍历、二叉树的后序遍历

#打卡记录

二叉树的统一迭代法

对于树的遍历,我们通常关注三个动作:

  1. 访问节点(处理节点,如打印节点值)。
  2. 遍历左子树。
  3. 遍历右子树。

这三个动作的不同排列顺序形成了不同的遍历方式:

  • 前序遍历:访问节点 -> 遍历左子树 -> 遍历右子树
  • 中序遍历:遍历左子树 -> 访问节点 -> 遍历右子树
  • 后序遍历:遍历左子树 -> 遍历右子树 -> 访问节点

使用栈和标记方法(无论是颜色标记还是 nullptr 标记)来实现二叉树的前序、中序和后序遍历的代码可以很容易地进行调整,主要因为这些方法允许我们显式地控制每个节点访问的顺序。这种控制是通过决定何时将节点推回栈中来实现的,以及决定何时读取或输出节点的值。

栈的作用

栈是一种后进先出(LIFO)的数据结构,它允许我们推入元素并在稍后取出。这对于树的遍历非常有用,因为我们可以保持对需要访问的节点的显式控制:

  • 存储节点:在遍历过程中,栈用来存储待访问的节点。
  • 控制顺序:通过调整节点和标记的推入顺序,我们可以控制节点访问的先后顺序。例如,如果希望一个节点在其子节点之前处理(如前序遍历),我们可以先将节点本身推入栈,然后是其子节点。

标记的作用

标记(可以是特定的对象、特殊值或nullptr)用于指示特定节点的访问状态或行为:

  • 区分处理步骤:标记帮助我们区分一个节点是否应该直接输出其值,或者是否应该先处理其子节点。
  • 延迟处理:通过在节点和其子节点之间插入标记,我们可以延迟节点的处理。例如,在中序遍历中,我们可能希望在处理完左子树之后再处理节点本身,因此可以在节点和其左子节点之间插入一个标记。这里的核心矛盾是无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况。所以采用延迟处理当前访问节点。

延迟处理解释:

在中序遍历(左-节点-右)中,节点的处理(访问或输出)需要在完全处理完左子树之后,但在开始处理右子树之前。这里的“处理”指的是对节点的访问,例如输出节点的值。使用延迟处理的步骤如下:

  1. 推入节点和左子树

    • 访问一个节点时,先推入右子节点(如果存在),然后推入一个标记(用来标示此节点稍后需要处理),最后推入左子节点。
    • 这样,栈顶始终是当前节点应该处理的左子树的部分,符合中序遍历的需求。
  2. 使用标记标识处理时机

    • 当从栈中弹出一个节点并发现其紧随一个标记时,这表明其左子树已经完全处理完毕。
    • 然后弹出标记,再次弹出节点,此时可以安全地处理(访问或输出)节点,因为按照中序遍历的顺序,它的左子树处理已完成。
  3. 继续处理右子树

    • 节点处理后,遍历继续对其右子树进行相同的操作。

遍历动作的控制

根据不同的遍历需求(前序、中序、后序),我们可以通过改变入栈顺序来控制遍历动作:

  1. 前序遍历

    • 访问节点 -> 遍历左子树 -> 遍历右子树
    • 入栈顺序:右子节点、左子节点、节点本身(标记后)
  2. 中序遍历

    • 遍历左子树 -> 访问节点 -> 遍历右子树
    • 入栈顺序:右子节点、节点本身(标记后)、左子节点
  3. 后序遍历

    • 遍历左子树 -> 遍历右子树 -> 访问节点
    • 入栈顺序:节点本身(标记后)、右子节点、左子节点

在每种情况下,节点和其子节点的入栈顺序决定了它们被处理的顺序。标记则确保了在节点数据被访问(输出)前,其子树已经被适当地处理。

最后输出的一个“周期”是一个nullptr,一个输出的节点,例如{6,5,null,2,4,null,1,null}。只有是叶子节点,没有跟着null,与其他值相邻,如6,5,6是叶子节点的值,该方法碰到叶子节点会重新再入一次栈,由于没有左右子树,所以当前栈内是一个nullptr,一个输出的节点,又符合了输出的“周期”。

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;
    }
};

  • 49
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值