跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

二叉树中序遍历的递归与迭代实现

二叉树中序遍历要求按照左子树、根节点、右子树的顺序访问节点。基于第一性原理推导遍历逻辑,提供两种核心解法:递归利用函数调用栈自然实现,代码简洁;迭代通过手动维护显式栈模拟递归过程,避免系统栈溢出风险。详细解析了从定义拆解到面试回答的完整思路,并对比了空间复杂度及 Morris 遍历的进阶优化方案。

活在当下发布于 2026/3/16更新于 2026/9/878 浏览
二叉树中序遍历的递归与迭代实现

题目描述

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

示例 1:

文章配图

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

输出:[1,3,2]

示例 2:

输入:root = []

输出:[]

示例 3:

输入:root = [1]

输出:[1]


用'第一性原理'(First Principles Thinking)来解决问题,意味着我们要剥离所有表象和经验主义的惯性,回归到事物最基本的真理,并从那里开始重新推导。

对于'二叉树中序遍历',我们不直接去背诵代码,而是从最底层的物理逻辑开始。

回归本质——什么是二叉树和中序遍历?

1. 物理结构的基本事实:

一个二叉树节点(Node)只有三种状态或组成部分:

  • 它自己(根/Root)
  • 它的左边(左子树/Left)
  • 它的右边(右子树/Right)

2. 任务的基本事实:

'遍历'意味着我们要走遍每一个节点,且不重不漏。

'中序'(In-order)是一个人为定义的顺序约定:左子树 -> 根节点 -> 右子树。

3. 推导:

无论这棵树多大,它在任何一个局部都必须遵循这个顺序。如果一个节点有左子树,你必须先处理完左边的一切,才能处理自己,最后处理右边。


寻找最简单的解法

如果我们从第一性原理出发,既然二叉树的定义本身就是递归的(一个节点又是另一棵树的根),那么最自然的解法就是递归。

逻辑推导:

  1. 基本情况(Base Case): 如果节点是空的,我什么都不用做,直接返回。
  2. 原子操作:
    • 去处理左边:inorder(root->left)
    • 处理我自己(存入结果):res.push_back(root->val)
    • 去处理右边:inorder(root->right)
C++ 代码实现(递归版):
class Solution {
public:
    void traverse(TreeNode* root, vector<int>& res) {
        if (!root) return; // 基本事实:空节点无需处理
        traverse(root->left, res); 
        res.(root->val); 
        (root->right, res); 
    }
    
    {
        vector<> res;
        (root, res);
         res;
    }
};
// 先解决左边
push_back
// 解决中间
traverse
// 解决右边
vector<int> inorderTraversal(TreeNode* root)
int
traverse
return

如果不使用递归,计算机会怎么做?

第一性原理要求我们理解底层运作。递归在计算机底层是通过调用栈(Call Stack)实现的。

如果我们想用迭代(非递归)来实现,我们要问自己:

在处理左子树的过程中,我如何'记住'以后要回来处理当前的根节点和右子树?

我们需要一个物理容器来模拟这个记忆,这个容器就是栈(Stack)。栈的特性是'后进先出',这完美契合了我们的需求:最后被路过的根节点,要先被处理(在处理完它的左子树后)。

迭代逻辑的重新推导:

  1. 向左深钻: 只要有左孩子,我就一直往左走,并把路过的每一个'根节点'都扔进栈里。因为根据中序遍历,这些节点现在都不能处理,得等左边处理完。
  2. 触底反弹: 当我走到最左边(遇到 nullptr)时,栈顶就是当前最左的节点。
  3. 处理并转向: 弹出栈顶节点,记录它的值(这就是'根'),然后转向它的'右子树',重复上述过程。
C++ 代码实现(迭代版):
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> st;
        TreeNode* curr = root;
        // 只要当前节点不为空,或者栈里还有没回溯完的节点
        while (curr != nullptr || !st.empty()) {
            // 步骤 1: 尽可能向左走,把路上的节点都'暂存'起来
            while (curr != nullptr) {
                st.push(curr);
                curr = curr->left;
            }
            // 步骤 2: 此时 curr 是空,说明左边走到底了。弹出最近的一个'根'
            curr = st.top();
            st.pop();
            // 步骤 3: 处理当前节点(左 -> 根)
            res.push_back(curr->val);
            // 步骤 4: 转向右子树(根 -> 右)
            curr = curr->right;
        }
        return res;
    }
};

面试回答

在面试中,面试官不仅看你的代码是否正确,更看重你解决问题的逻辑严密性和对计算机底层原理的理解。

你可以按照'定义解析 -> 递归推导 -> 迭代优化'的顺序,用一种类似'演化'的方式来讲授你的思路。以下是一个推荐的叙述话术:

第一步:拆解定义

'首先,我会从二叉树的物理结构出发。中序遍历的定义是'左子树 -> 根节点 -> 右子树'。

这意味着对于任何一个局部子树,我必须保证左侧的所有节点都被访问完,才能处理当前节点。这种局部与整体逻辑一致的特性,自然而然地让我联想到递归。'

第二步:由浅入深——递归解法

'在最底层逻辑上,我只需要定义一个递归函数:

  1. 边界条件:如果节点为空,说明到底了,直接返回。
  2. 递归单元:先递归调用左孩子,再记录当前节点的值,最后递归调用右孩子。

这是最符合直觉的解法,代码极其简洁,时间复杂度是 O(n)。'

第三步:硬核推演——迭代与栈

'如果面试官您希望探讨非递归的实现,那么我们需要思考计算机是如何处理递归的。

递归的本质是利用了系统的调用栈(Call Stack)。为了手动实现迭代,我们需要自己维护一个辅助栈 std::stack:

  • 核心矛盾:我们一路向左走的时候,经过的'根节点'还不能处理,必须先存起来。
  • 模拟过程:
    1. 我用一个 curr 指针模拟探索路径。只要 curr 不为空,我就把它压入栈中,然后拼命向左走。
    2. 当 curr 为空时,说明左边已经走到底了。此时,栈顶元素就是我们需要处理的'最左根节点'。
    3. 弹出栈顶,记录数值,然后将指针移向该节点的右孩子,重复这个循环。

这种方法在本质上是手动管理回溯路径,避免了递归带来的系统栈溢出风险。'

第四步:进阶对比

'最后,我会简单对比一下:

  • 空间复杂度:在树极度不平衡(像个链表)时,递归和迭代的栈空间都是 O(n)。
  • 面试亮点:如果对空间有极致要求,其实还有一种 Morris 遍历算法,利用叶子节点的空指针建立线索,可以将空间复杂度降到 O(1),但它会暂时改动树的结构。'

目录

  1. 题目描述
  2. 回归本质——什么是二叉树和中序遍历?
  3. 寻找最简单的解法
  4. C++ 代码实现(递归版):
  5. 如果不使用递归,计算机会怎么做?
  6. C++ 代码实现(迭代版):
  7. 面试回答
  8. 第一步:拆解定义
  9. 第二步:由浅入深——递归解法
  10. 第三步:硬核推演——迭代与栈
  11. 第四步:进阶对比

更多推荐文章

查看全部
  • 基于 Leaflet 与百度天气接口的空气质量 WebGIS 可视化实践
  • OpenClaw 跨平台卸载指南:Windows、macOS、Linux 及包管理器清理
  • 单链表与双向循环链表应用示例
  • 企业为何需要私有化专属大模型:从 ChatGPT 到私有化部署
  • YOLO26-Pose 零样本姿态估计技术解析
  • 自然语言处理在医疗健康领域的应用与实战
  • 网络安全挖洞实战指南:工具准备与漏洞提交流程
  • 基于 Rust 与 DeepSeek V3.2 构建高性能插件化 LLM 应用框架
  • Python Flask 企业合同管理系统技术要点与选型对比
  • MBA 培训管理系统低代码开发实战指南
  • 大模型学习路线:掌握核心技术能力与关键技能
  • Qwen3-32B 显存不足的低成本 GPU 优化部署方案
  • Gitea 轻量级私有化部署指南与常用 Git 命令
  • Jimi:打造 Java 程序员专属的开源 AI 编程代理
  • bilibili-danmaku: 自动抓取弹幕、生成词云与情感分析报告的开源工具
  • 机器人轨迹规划基础与常用算法
  • 对比 OpenClaw 的 nanobot QQ AI 机器人搭建与搜索优化实践
  • Java 面向对象入门:类、对象与封装核心详解
  • 宇树 Unitree 机器人 ROS 2 环境部署指南 (Humble + 真实硬件)
  • C++ std::stringstream 核心用法与实战解析

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online