二叉树中序遍历:递归与非递归实现详解

什么是二叉树的中序遍历?

中序遍历(Inorder Traversal)是二叉树遍历的一种经典方式,其遍历顺序遵循 "左子树 → 根节点 → 右子树" 的原则。

对于下面这个二叉树:

css 复制代码
        A
       / \
      B   C
     / \   \
    D   E   F

中序遍历的结果是:D → B → E → A → C → F

中序遍历的核心原理

递归思想(最直观的理解)

  1. 遍历左子树
  2. 访问根节点
  3. 遍历右子树

这种分治思想非常适合用递归实现,因为每个子树都可以看作是一个更小的二叉树。

非递归思想(手动维护栈)

模拟递归调用的过程,需要显式地使用栈来保存待访问的节点:

  1. 从根节点开始,将所有左子节点压入栈
  2. 弹出栈顶节点并访问
  3. 转向该节点的右子树,重复步骤1

完整C++实现

1. 二叉树节点定义

cpp 复制代码
#include <iostream>
#include <stack>
#include <vector>
using namespace std;

// 二叉树节点结构
struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

2. 递归实现

cpp 复制代码
class Solution {
public:
    // 递归中序遍历
    vector<int> inorderTraversalRecursive(TreeNode* root) {
        vector<int> result;
        inorderHelper(root, result);
        return result;
    }
    
private:
    void inorderHelper(TreeNode* node, vector<int>& result) {
        if (!node) return;  // 递归终止条件
        
        inorderHelper(node->left, result);  // 遍历左子树
        result.push_back(node->val);        // 访问根节点
        inorderHelper(node->right, result); // 遍历右子树
    }
};

递归流程分析:

scss 复制代码
调用 inorder(A)
├── 调用 inorder(B)
│   ├── 调用 inorder(D) → 访问 D
│   ├── 访问 B
│   └── 调用 inorder(E) → 访问 E
├── 访问 A
└── 调用 inorder(C)
    ├── 访问 C
    └── 调用 inorder(F) → 访问 F

3. 非递归实现(使用栈)

cpp 复制代码
class Solution {
public:
    // 非递归中序遍历(迭代法)
    vector<int> inorderTraversalIterative(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> nodeStack;
        TreeNode* current = root;
        
        // 当当前节点不为空或栈不为空时继续
        while (current != nullptr || !nodeStack.empty()) {
            // 深入左子树,将所有左子节点入栈
            while (current != nullptr) {
                nodeStack.push(current);
                current = current->left;
            }
            
            // 弹出栈顶节点并访问
            current = nodeStack.top();
            nodeStack.pop();
            result.push_back(current->val);
            
            // 转向右子树
            current = current->right;
        }
        
        return result;
    }
};

非递归流程分析(示例二叉树):

ini 复制代码
初始:current = A, stack = []
1. 将A、B、D依次入栈 → stack = [A, B, D], current = null
2. 弹出D访问 → result = [D], current = D.right = null
3. 弹出B访问 → result = [D, B], current = B.right = E
4. 将E入栈 → stack = [A, E], current = null
5. 弹出E访问 → result = [D, B, E], current = E.right = null
6. 弹出A访问 → result = [D, B, E, A], current = A.right = C
7. 将C入栈 → stack = [C], current = null
8. 弹出C访问 → result = [D, B, E, A, C], current = C.right = F
9. 将F入栈 → stack = [F], current = null
10. 弹出F访问 → result = [D, B, E, A, C, F], current = null
结束

对比分析

特性 递归实现 非递归实现
代码简洁性 ⭐⭐⭐⭐⭐ ⭐⭐⭐
理解难度 容易 较难
空间复杂度 O(h)(递归调用栈) O(h)(显式栈)
栈溢出风险 树深度大时可能溢出 更可控
执行效率 函数调用开销较大 直接操作栈,效率较高

测试示例

cpp 复制代码
// 创建测试二叉树
TreeNode* createTestTree() {
    TreeNode* root = new TreeNode(1);
    root->left = new TreeNode(2);
    root->right = new TreeNode(3);
    root->left->left = new TreeNode(4);
    root->left->right = new TreeNode(5);
    root->right->right = new TreeNode(6);
    return root;
}

int main() {
    TreeNode* root = createTestTree();
    Solution solution;
    
    cout << "递归中序遍历结果: ";
    vector<int> recursiveResult = solution.inorderTraversalRecursive(root);
    for (int val : recursiveResult) {
        cout << val << " ";
    }
    cout << endl;
    
    cout << "非递归中序遍历结果: ";
    vector<int> iterativeResult = solution.inorderTraversalIterative(root);
    for (int val : iterativeResult) {
        cout << val << " ";
    }
    cout << endl;
    
    return 0;
}

输出结果:

makefile 复制代码
递归中序遍历结果: 4 2 5 1 3 6 
非递归中序遍历结果: 4 2 5 1 3 6 

应用场景

  1. 二叉搜索树(BST)排序:中序遍历BST会得到有序序列
  2. 表达式树求值:中序遍历可以还原中缀表达式
  3. 复制二叉树:按中序顺序复制节点
  4. 调试和分析:查看树的结构

总结

中序遍历是二叉树算法的基础,掌握其递归和非递归实现非常重要:

  • 递归实现:直观易懂,适合快速实现和教学
  • 非递归实现:更高效,避免递归深度限制,是面试常考点

理解两种实现方式不仅有助于掌握二叉树遍历,还能加深对递归和栈的理解,为学习更复杂的树形结构算法打下坚实基础。

相关推荐
码兄科技3 小时前
实战:基于Spring Boot + UniApp的地理信息小程序开发
spring boot·后端·uni-app
腾讯云云开发5 小时前
腾讯云 CloudBase 登上 WAIC:我们为 Agent 重新设计了云的生产线
后端
星栈5 小时前
从装一堆工具到看懂 Node 工程化思维:我的项目复盘记录
后端·node.js
65岁退休Coder5 小时前
LangChain v1.3.4 笔记 - 05 Agent 上下文记忆
后端
颜酱6 小时前
04 | 召回前置准备:搭好召回所需的四个数据库
前端·人工智能·后端
JaneConan6 小时前
鸿蒙 韶非 UI 系列:能力调用 startAbilityForResult,跳能力拿回参,鸿蒙能力路由入门
后端·harmonyos
晴空了无痕6 小时前
从 Go 基础到 K8s:一条可落地的 Go 服务端成长路线
开发语言·后端·golang·kubernetes
用户8356290780516 小时前
如何使用 Python 在 Excel 中添加、编辑和删除超链接
后端·python
花椒技术6 小时前
原本要 2 天的服务端冒烟前置审查,为什么 3 分半就能出报告?|QA 质量交付实践(三)
后端·ai编程·测试
达达尼昂6 小时前
AI 编程的工程化实践:Flutter AI Harness 的设计与落地
人工智能·后端·全栈