【数据结构+算法】非递归遍历二叉树的理解

递归遍历二叉树较易理解,并可以利用函数进行回溯,时间复杂度和空间复杂度都是O(n)。而理解非递归遍历二叉树,关键在于手动模拟递归调用的过程,即,用一个栈(Stack)(深度优先遍历)。它不仅能深刻反映遍历的本质,也是解决递归可能导致"栈溢出"问题的关键。

一、核心原理:栈模拟递归过程

递归遍历二叉树时,系统会隐式使用调用栈保存函数上下文(如参数、返回地址)。非递归实现则需显式使用栈来模拟这一过程:

栈的作用:保存待访问的节点,通过"入栈"和"出栈"操作控制遍历顺序。

遍历逻辑:根据访问根节点的时机不同,分为前序 、中序 、后序三种遍历。

二、三种遍历的非递归实现

以二叉树节点结构为例:

cpp 复制代码
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
};

1. 前序遍历(根→左→右)

逻辑:访问根节点后,优先遍历左子树,再处理右子树。

步骤:

①将根节点压入栈。

②循环执行:弹出栈顶节点并访问,先将其右孩子入栈(若存在),再将其左孩子入栈(若存在)。(注意顺序:右子树先入栈,左子树后入栈,保证左子树先弹出)

preorder代码示例.cpp

cpp 复制代码
void preorder(TreeNode* root) {
    stack<TreeNode*> s;
    s.push(root);
    while (!s.empty()) {
        TreeNode* node = s.top(); s.pop();
        if (node) {
            cout << node->val;    // 访问节点
            s.push(node->right);  // 右孩子先入栈
            s.push(node->left);   // 左孩子后入栈
        }
    }
}

2. 中序遍历(左→根→右)

逻辑:沿左子树深入到底,回溯时访问节点,再转向右子树。

步骤:

①从根节点开始,沿左孩子路径将所有节点压入栈,直到左子节点为空。

②弹出栈顶节点并访问,转向其右子树,重复步骤1。

inorder代码示例.cpp

cpp 复制代码
void inorder(TreeNode* root) {
    stack<TreeNode*> s;
    TreeNode* cur = root;
    while (cur || !s.empty()) {
        while (cur) {       // 沿左子树入栈
            s.push(cur);
            cur = cur->left;
        }
        cur = s.top(); s.pop();
        cout << cur->val;   // 访问节点
        cur = cur->right;   // 转向右子树
    }
}

3. 后序遍历(左→右→根)(广度优先)

逻辑:需确保左右子树均遍历完成后再访问根节点,实现最复杂。

关键技巧:用栈存储节点,使用标记指针记录最近访问的节点(以下示例代码额外维护visited变量),判断当前节点的右子树是否已处理。

步骤:

①沿左子树入栈到底。

②查看栈顶节点:若其右孩子为空或已被访问,则访问该节点;否则转向右子树。

postorder代码示例.cpp

cpp 复制代码
void postorder(TreeNode* root) {
    stack<TreeNode*> s;
    TreeNode* cur = root;
    TreeNode* visited = nullptr; // 标记最近访问的节点
    while (cur || !s.empty()) {
        while (cur) {           // 沿左子树入栈
            s.push(cur);
            cur = cur->left;
        }
        cur = s.top();
        if (!cur->right || cur->right == visited) {
            cout << cur->val;   // 访问节点
            s.pop();
            visited = cur;      // 更新已访问节点
            cur = nullptr;      // 重置cur,避免重复入栈
        } else {
            cur = cur->right;   // 转向右子树
        }
    }
}

三、非递归遍历的优势与应用场景

非递归可避免递归调用栈的开销,空间复杂度稳定为 O(h)(h为树高),而递归可能因深度过大导致栈溢出。

性能更稳定,适用于:

大规模数据处理(如数据库索引遍历)。

嵌入式系统等栈空间受限的环境。

需要精确控制遍历过程的场景(如表达式树求值)。

需要跟踪调试状态的其他场景。

四、对比递归与非递归实现

特性 递归实现 非递归实现
代码复杂度 简洁直观 需手动管理栈,逻辑较复杂
空间开销 O(h)(隐式调用栈) O(h)(显式栈)
栈溢出风险 深度大时易发生 可控,风险低
执行效率 函数调用开销较大 迭代操作,效率更高
实现方式 依赖系统栈 需手动维护栈/队列
适用场景 简单逻辑、深度优先 广度优先或深度受限场景

五、总结

非递归遍历二叉树的本质是用栈显式模拟递归调用的轨迹。
理解三种遍历:

前序:入栈即访问。

中序:出栈时访问。

后序:需判断子树是否遍历完成。
选择建议:

优先考虑递归实现逻辑清晰,但需评估数据规模和栈深度。

对性能敏感或者深度不可测的场景,使用非递归更安全。

相关推荐
天河归来9 小时前
第三方接口不稳定,如何让核心任务继续完成
java·大数据
ZhengEnCi10 小时前
GPT-6 Astra 爆改 Qwen 权重真有那么神吗:权重空间编程 0.016% 参数的真相
算法·架构
罗西的思考10 小时前
【轻量化复刻大模型 / KIMI K3】KDA(Kimi Delta Attention):并行
人工智能·算法·机器学习
GlobalInfo10 小时前
2026年全球机器人用超平执行器市场研究报告:市场规模达7.51亿美元,人形机器人商业化与轻量化运动控制需求推动行业增长
大数据·人工智能·数码相机·microsoft·机器人
IT古董12 小时前
《Agent开发工程师成长指南》- 第4章 第2节:Prompt设计五要素——任务、角色、上下文、约束与输出格式
大数据·人工智能·prompt
夜不会漫长12 小时前
C++:红黑树
数据结构·算法
文丑颜不良啊12 小时前
LeetCode1661~1680题目解析
java·leetcode
AI备案指南-满满13 小时前
算法备案审核不通过,一般是什么原因?
java·人工智能·算法·大模型备案·算法备案·大模型登记
Ramble_Naylor13 小时前
Rust深度解读:Send 与 Sync 的本质与线程安全边界
python·算法·rust
秋天的怀念_David13 小时前
算法一千零一夜 · 第二十三夜 电力民生写成的曲线
java·程序人生·算法