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

我们以中序遍历为例,在二叉树:听说递归能做的,栈也能做! (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;
}
相关推荐
田里的水稻5 分钟前
C++_队列编码实例,从末端添加对象,同时把头部的对象剔除掉,中的队列长度为设置长度NUM_OBJ
java·c++·算法
ponnylv14 分钟前
深入剖析Spring Boot启动流程
java·开发语言·spring boot·spring
萧邀人21 分钟前
第一课、Cocos Creator 3.8 安装与配置
开发语言
前行的小黑炭31 分钟前
Android 协程的使用:结合一个环境噪音检查功能的例子来玩玩
android·java·kotlin
李少兄1 小时前
解决IntelliJ IDEA 提交代码时无复选框问题
java·ide·intellij-idea
Jayden_Ruan1 小时前
C++逆向输出一个字符串(三)
开发语言·c++·算法
不吃鱼的羊1 小时前
启动文件Startup_vle.c
c语言·开发语言
cyforkk1 小时前
Spring Boot @RestController 注解详解
java·spring boot·后端
VBA63372 小时前
VBA之Word应用第四章第二节:段落集合Paragraphs对象(二)
开发语言
叫我阿柒啊2 小时前
从Java全栈到前端框架:一次真实面试的深度复盘
java·spring boot·typescript·vue·database·testing·microservices