二叉树的统一迭代法 标记法

我们以中序遍历为例,在二叉树:听说递归能做的,栈也能做! (opens new window)中提到说使用栈的话,无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况

那我们就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。

方法一:就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。 这种方法可以叫做空指针标记法

  1. 前序遍历(根 -> 左 -> 右):

    • 压栈顺序:右子节点 -> 左子节点 -> 当前节点 -> NULL
cpp 复制代码
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
                vector<int> result;
        stack<TreeNode*> st;
        if(root) st.push(root);
        while(!st.empty()){
            TreeNode* node = st.top( );
            if(node){
                st.pop();
                if(node->right) st.push(node->right);
                if(node->left) st.push(node->left);
                st.push(node);
                st.push(nullptr);
            }else{
                st.pop();
                node = st.top();
                st.pop();
                result.push_back(node->val);
            }
        }
        return result;
    }
};
  1. 中序遍历(左 -> 根 -> 右):

    • 压栈顺序:右子节点 -> 当前节点 -> NULL -> 左子节点。
cpp 复制代码
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if(root) st.push(root);
        while(!st.empty()){
            TreeNode* node = st.top( );
            if(node){
                st.pop();
                if(node->right) st.push(node->right);
                st.push(node);
                st.push(nullptr);
                if(node->left) st.push(node->left);
            }else{
                st.pop();
                node = st.top();
                st.pop();
                result.push_back(node->val);
            }
        }
        return result;
    }
};
  1. 后序遍历(左 -> 右 -> 根):

    • 压栈顺序:当前节点 -> NULL -> 右子节点 -> 左子节点。
cpp 复制代码
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if(root) st.push(root);
        while(!st.empty()){
            TreeNode* node = st.top( );
            if(node){
                st.pop();
                st.push(node);
                st.push(nullptr);
                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;
    }
};

方法二:加一个 boolean 值跟随每个节点,false (默认值) 表示需要为该节点和它的左右儿子安排在栈中的位次,true 表示该节点的位次之前已经安排过了,可以收割节点了。 这种方法可以叫做boolean 标记法,样例代码见下文C++ 和 Python 的 boolean 标记法。 这种方法更容易理解,在面试中更容易写出来。

Boolean 标记法的核心思想

  1. 标记的作用

    • false:表示该节点需要被"安排"(即需要处理其子节点)。

    • true:表示该节点已经被"安排"过,可以直接收割(加入结果集)。

  2. 栈的使用

    • 栈中存储的是 pair<TreeNode*, bool>,其中 bool 表示节点的状态。
  3. 遍历顺序

    • 根据前序、中序、后序遍历的要求,调整节点的压栈顺序。

visited 是一个布尔值(bool),用于标记当前节点是否已经被"处理"过。它的作用是区分节点的两种状态:

  1. visited = false

    • 表示该节点 需要被安排,即需要处理其子节点。

    • 此时,节点会被重新压入栈中,并按照遍历顺序(前序、中序、后序)调整其子节点的压栈顺序。

  2. visited = true

    • 表示该节点 已经被安排过,可以直接"收割"(将其值加入结果集)。

    • 此时,节点不再需要处理其子节点。

cpp 复制代码
vector<int> traversal(TreeNode* root) {
    vector<int> result;
    if (!root) return result;

    stack<pair<TreeNode*, bool>> st;
    st.push({root, false}); // 初始状态为 false,表示需要安排子节点

    while (!st.empty()) {
        auto [node, visited] = st.top();
        st.pop();

        if (visited) {
            // 如果节点已经被访问过,直接收割
            result.push_back(node->val);
        } else {
            // 根据遍历顺序调整压栈顺序
            // 前序:根 -> 左 -> 右
            // 中序:左 -> 根 -> 右
            // 后序:左 -> 右 -> 根
            if (node->right) st.push({node->right, false}); // 右子节点
            st.push({node, true}); // 当前节点标记为已访问
            if (node->left) st.push({node->left, false});   // 左子节点
        }
    }

    return result;
}
相关推荐
STY_fish_201213 分钟前
手拆STL
java·c++·算法
翻滚吧键盘13 分钟前
Spring Boot,两种配置文件
java·spring boot·后端
pumpkin845143 小时前
Rust Mock 工具
开发语言·rust
love530love3 小时前
【笔记】在 MSYS2(MINGW64)中安装 python-maturin 的记录
运维·开发语言·人工智能·windows·笔记·python
阿卡蒂奥4 小时前
C# 结合PaddleOCRSharp搭建Http网络服务
开发语言·http·c#
fanged5 小时前
构建系统maven
java·maven
沙滩小岛小木屋5 小时前
maven编译时跳过test过程
java·maven
江沉晚呤时6 小时前
SQL Server 事务详解:概念、特性、隔离级别与实践
java·数据库·oracle·c#·.netcore
泉飒6 小时前
lua注意事项
开发语言·笔记·lua
hao_wujing6 小时前
使用逆强化学习对网络攻击者的行为偏好进行建模
开发语言·网络·php