gh_mirrors/leet/leetcode项目:二叉树的中序遍历题解
你是否在面对二叉树中序遍历时感到困惑?是否还在用递归方法却担心栈溢出问题?本文将带你深入理解二叉树的中序遍历(Inorder Traversal),从基础概念到两种高效实现方法,让你轻松掌握这一必备算法技能。读完本文后,你将能够:
- 清晰理解中序遍历的访问顺序和应用场景
- 掌握栈实现的迭代解法,避免递归限制
- 学会O(1)空间复杂度的Morris遍历算法
- 能够独立解决LeetCode相关题目
什么是二叉树的中序遍历
二叉树的中序遍历(Inorder Traversal)是按照"左子树→根节点→右子树"的顺序访问树中所有节点的过程。对于二叉搜索树(BST)而言,中序遍历的结果是一个有序序列,这一特性使其在解决BST相关问题时具有重要应用。
树的节点定义在C++/chapTree.tex中给出:
// 树的节点
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) { }
};
以下面的二叉树为例:
1
\
2
/
3
中序遍历的结果是[1,3,2],即先访问左子树(为空),再访问根节点1,然后访问右子树的左节点3,最后访问右子树的根节点2。
栈实现的迭代解法
递归实现虽然简洁,但在处理大型树时可能会遇到栈溢出问题。使用显式栈的迭代方法可以有效避免这一问题,时间复杂度O(n),空间复杂度O(n)。
C++/chapTree.tex中提供了完整实现:
// LeetCode, Binary Tree Inorder Traversal
// 使用栈,时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
vector<int> inorderTraversal(TreeNode *root) {
vector<int> result;
stack<const TreeNode *> s;
const TreeNode *p = root;
while (!s.empty() || p != nullptr) {
if (p != nullptr) {
s.push(p);
p = p->left;
} else {
p = s.top();
s.pop();
result.push_back(p->val);
p = p->right;
}
}
return result;
}
};
算法步骤解析
- 初始化一个空栈和结果向量
- 从根节点开始,将所有左孩子依次入栈,直到左子树为空
- 弹出栈顶节点,将其值加入结果向量
- 转向该节点的右子树,重复步骤2-3
- 直到栈为空且当前节点也为空时结束
下面的流程图展示了这一过程:
Morris中序遍历:O(1)空间复杂度解法
Morris遍历算法通过利用树的空闲指针(线索化),实现了不需要栈的中序遍历,将空间复杂度降至O(1)。这一算法在C++/chapTree.tex中有详细实现:
// LeetCode, Binary Tree Inorder Traversal
// Morris中序遍历,时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
vector<int> inorderTraversal(TreeNode *root) {
vector<int> result;
TreeNode *cur = root, *prev = nullptr;
while (cur != nullptr) {
if (cur->left == nullptr) {
result.push_back(cur->val);
prev = cur;
cur = cur->right;
} else {
/* 查找前驱 */
TreeNode *node = cur->left;
while (node->right != nullptr && node->right != cur)
node = node->right;
if (node->right == nullptr) { /* 还没线索化,则建立线索 */
node->right = cur;
/* prev = cur; 不能有这句,cur还没有被访问 */
cur = cur->left;
} else { /* 已经线索化,则访问节点,并删除线索 */
result.push_back(cur->val);
node->right = nullptr;
prev = cur;
cur = cur->right;
}
}
}
return result;
}
};
算法核心思想
Morris算法的关键在于为每个节点寻找其前驱节点,并利用前驱节点的右指针暂时存储后继节点信息,避免使用栈。主要步骤包括:
- 如果当前节点左子树为空,访问该节点并转向右子树
- 否则,找到当前节点在中序遍历中的前驱节点(左子树的最右节点)
- 如果前驱节点右指针为空,将其指向当前节点,然后转向当前节点的左子树
- 如果前驱节点右指针已指向当前节点,说明左子树已遍历完成,访问当前节点并将前驱右指针复位,然后转向当前节点的右子树
两种方法的对比分析
| 实现方法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 栈迭代法 | O(n) | O(n) | 实现直观,易于理解 | 需要额外栈空间,可能栈溢出 |
| Morris遍历 | O(n) | O(1) | 空间效率极高 | 实现复杂,修改树结构(临时) |
实际应用与相关题目
中序遍历在二叉搜索树(BST)中有重要应用,例如验证BST的合法性、寻找BST中的第K小元素等。在本项目中,相关题目可以在C++/chapTree.tex中找到:
- Binary Tree Preorder Traversal
- Binary Tree Postorder Traversal
- Recover Binary Search Tree
以"恢复二叉搜索树"题目为例,中序遍历可以帮助我们找到被错误交换的节点,这展示了中序遍历在实际问题中的重要性。
总结与展望
二叉树的中序遍历是数据结构中的基础算法,本文详细介绍了两种主流实现方法:栈迭代法和Morris遍历法。栈迭代法简单直观,适合大多数情况;而Morris法则在空间受限场景下提供了优秀的解决方案。
建议初学者先掌握栈迭代法,理解中序遍历的本质;进阶学习者则应掌握Morris算法,理解其线索化思想,这对解决复杂树问题大有裨益。
更多二叉树相关题解,请参考项目中的C++/chapTree.tex文件,其中包含了先序、后序、层序等多种遍历方法的实现。
最后,推荐使用项目描述中提到的刷题网站进行练习,巩固所学知识:https://www.lintcode.com/?utm_source=soulmachine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



