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

我们以中序遍历为例,在二叉树:听说递归能做的,栈也能做! (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;
}
相关推荐
带刺的坐椅2 分钟前
Solon Flow v3.4.0 轻量级流程编排框架
java·solon·工作流·审批流·solon-flow·计算流
lishaoan7710 分钟前
用Python实现神经网络(二)
开发语言·python·神经网络
阿华的代码王国14 分钟前
【Android】CheckBox实现和监听
android·xml·java
Honesty86102415 分钟前
深入排查:@Scope(“prototype“)与@RequestScope字段篡改问题全链路分析
java·spring boot·spring·原型模式
速易达网络18 分钟前
MyUI按钮VcButton 组件文档
java·服务器·前端
Rita的程序bug20 分钟前
R语言基础| 基本图形绘制(条形图、堆积图、分组图、填充条形图、均值条形图)
开发语言·信息可视化·r语言
Honesty86102421 分钟前
Spring 作用域冲突深度解析:@Scope(“prototype“)与@RequestScope的冲突与解决方案
java·spring·原型模式
前端橙一陈30 分钟前
原型继承(prototypal inheritance)的工作原理
开发语言·javascript·原型模式
柠檬味的薄荷心32 分钟前
【C#补全计划:枚举】
开发语言·c#
你不是我我36 分钟前
【Java开发日记】我们来说说 LockSupport 的 park 和 unpark
java·开发语言