力扣刷题#25-0094-二叉树的中序遍历

力扣刷题#25-0094-二叉树的中序遍历

题目

给定一个二叉树的根节点 root,返回它的中序遍历。

输入: root = [1,null,2,3]
    1
     \
      2
     /
    3
输出: [1,3,2]

前置知识:三种 DFS 遍历的唯一区别

递归遍历二叉树的框架完全相同,只差一行 cout(或 push_back)的位置:

void dfs(TreeNode* root, vector<int>& res) {
    if (root == nullptr) return;

    // ① 前序:res.push_back(root->val) 放这里  → "中左右"

    dfs(root->left, res);

    // ② 中序:res.push_back(root->val) 放这里  → "左中右"

    dfs(root->right, res);

    // ③ 后序:res.push_back(root->val) 放这里  → "左右中"
}

同一棵树,跑三套代码的唯一区别就是把那行 push_back 挪到不同位置。


我的递归版代码

class Solution {
public:
    void dfs(TreeNode* root, vector<int>& res) {
        if (root == nullptr) return;
        dfs(root->left, res);
        res.push_back(root->val);
        dfs(root->right, res);
    }

    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        dfs(root, res);
        return res;
    }
};

套中序模板:先跑左子树 → 输出当前节点 → 再跑右子树。


从递归到迭代的思考

递归版写起来很爽——dfs(root->left) 一行代码,函数调用栈自动帮我记"返回时该回到哪一层"。

迭代版没了函数调用栈,我需要自己管理这个"回到哪一层"的信息。工具就是 stack<TreeNode*>

中序迭代的核心逻辑

1. 从当前节点出发,一路向左,边跑边把经过的节点全压栈
2. 左边走到底(cur == nullptr)→ 弹栈顶 → 这个节点就是"当前最左未处理"的 → 输出它
3. 转向这个节点的右子树,重复上述过程

用一棵具体树走一遍

        1
       / \
      2   3
     / \   \
    4   5   6
       /
      7
步骤 cur 操作
从 1 出发 [1] 2 1 压栈,去左
[1,2] 4 2 压栈,去左
[1,2,4] null 4 压栈,去左→空了
左边没路 [1,2] 弹 4,输出 4,转向 4 的右→null
[1] 弹 2,输出 2,转向 5
[1,5] 7 5 压栈,去左
[1,5,7] null 7 压栈,去左→空
[1,5] 弹 7,输出 7
[1] 弹 5,输出 5
[] 弹 1,输出 1,转向 3
[3] null 3 压栈,左空
[] 弹 3,输出 3,转向 6
[6] null 6 压栈,左空
[] 弹 6,输出 6
全空 [] null 结束

输出:4, 2, 7, 5, 1, 3, 6,和递归版一致。


我的迭代版代码

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> st;
        TreeNode* cur = root;

        while (cur != nullptr || !st.empty()) {
            while (cur != nullptr) {
                st.push(cur);
                cur = cur->left;
            }
            cur = st.top();
            st.pop();
            res.push_back(cur->val);
            cur = cur->right;
        }
        return res;
    }
};

代码逐段解析

第 5-6 行:初始化

stack<TreeNode*> st;
TreeNode* cur = root;

st 模拟递归调用栈,cur 表示"当前正在处理的节点指针"。递归版这个指针藏在函数参数里,迭代版我手动维护它。

第 8 行:外层循环条件

while (cur != nullptr || !st.empty()) {

这是整段代码最容易困惑的一行。两个条件是 ||(或),缺一不可。

为什么需要 cur != nullptr

弹完 1、转向右子树 3 的瞬间:

stk = []     ← 栈空了
cur = 3      ← 但 3 还没处理

如果只有 !st.empty() → false → 循环退出 → 3 和 6 全丢了。cur 还有活、栈没活,靠 cur != nullptr 续命。

为什么需要 !st.empty()

一路向左走到 4 之后:

cur = nullptr   ← 指针空了
stk = [1,2,4]   ← 但栈里还有三个等着

如果只有 cur != nullptr → false → 循环退出 → 2, 5, 1, 3, 6 全丢了。栈有活、cur 没活,靠 !st.empty() 续命。

状态 cur stk 还跑? 续命条件
向左走到底 null 有数据 !stk.empty()
弹完转右子树 指向新节点 可能空 cur != null
全处理完 null [] ❌ 结束 两个都 false

第 9-12 行:内层 while —— 一路向左

while (cur != nullptr) {
    st.push(cur);
    cur = cur->left;
}

和递归 dfs(root->left) 对应。把沿途每个节点都压栈——它们都是"还没处理的中序根节点",等着左子树跑完再回来输出。

第 13-16 行:弹栈 → 输出 → 转右

cur = st.top();
st.pop();
res.push_back(cur->val);
cur = cur->right;
操作 对应递归的
cur = st.top(); st.pop() 函数调用返回(自动回到上层)
res.push_back(cur->val) 中序的 cout
cur = cur->right dfs(root->right)

弹出来的节点左子树已经全处理完了,所以立即输出自身,然后转向右子树。


递归 vs 迭代对照

递归 迭代
dfs(root->left) st.push(cur); cur = cur->left
函数自动返回上层 cur = st.top(); st.pop()
res.push_back(root->val) res.push_back(cur->val)
dfs(root->right) cur = cur->right

复杂度分析

维度 递归 迭代
时间复杂度 O(n) O(n)
空间复杂度 O(h),递归栈 O(h),显式栈
优点 代码最短 不依赖递归,可避栈溢出
缺点 深层树可能爆栈 代码稍长

本文档由 AI 辅助生成,作者提供问题,思路和代码,AI仅负责文本修饰,综合获得以上内容。

posted @ 2026-08-03 15:16  STA_running  阅读(7)  评论(0)    收藏  举报