
方法一:递归法(最基础,面试先写这个)
核心思路
中序遍历天然具有递归性质:
- 对于任意一个节点,先遍历它的左子树
- 然后访问这个节点本身
- 最后遍历它的右子树
- 遇到空节点就直接返回(递归终止条件)
递归的本质是系统帮我们维护了一个调用栈,自动记录我们走到了哪里,处理完子树后自动回到父节点
cpp
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
void inorder(TreeNode* root,vector<int>&res){
if(!root){return;}
inorder(root->left,res);
//因为是中序 所以如果有左先存左
res.push_back(root->val);
inorder(root->right,res);
}
vector<int> inorderTraversal(TreeNode* root) {
//法一 递归法 写一个函数
vector<int> res;
inorder(root,res);
return res;
}
};
方法二:迭代法(面试进阶要求,必须掌握)
1. 核心思路
递归和迭代是完全等价的,区别只是:
- 递归:系统隐式地帮我们维护了一个调用栈
- 迭代:我们自己显式地用一个栈来模拟这个过程
迭代法的核心逻辑(和递归完全一致):
- 先一路往左走,把所有经过的节点都压入栈(相当于递归里的 "先处理左子树")
- 走到头(左孩子为空)的时候,弹出栈顶节点(这个就是当前最左的节点,也就是中序的第一个)
- 访问这个节点(把值加入结果)
- 然后去处理这个节点的右子树(相当于递归里的 "最后处理右子树")
- 重复上面的步骤,直到栈为空且当前节点为空
cpp/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} * }; */ class Solution { public: vector<int> inorderTraversal(TreeNode* root) { //法二 迭代法 利用栈 vector<int> res; stack<TreeNode*> stk;//定义一个栈 while(root!=nullptr || !stk.empty()){ //先找左边 把左边的都给找到头 while(root!=nullptr){ //遍历一个压一个入栈 stk.push(root); root = root->left; } //root是空 说明到头了 此刻栈顶就是我们要第一个输出的 所以栈输出 root = stk.top(); stk.pop();;//清空栈 res.push_back(root->val); root = root->right; } return res; } };
方法三:Morris 中序遍历(面试加分项,O (1) 空间)
1. 核心思路
前面两种方法都需要 O (n) 的空间(递归栈或手动栈),Morris 遍历的牛逼之处在于:它利用了二叉树中大量空闲的右指针(叶子节点的右指针都是 nullptr),用这些指针来记录 "后继节点",从而不需要额外的栈空间,空间复杂度降到 O (1)。
核心思想: 对于当前节点 x:
- 如果 x 没有左孩子:直接访问 x,然后去 x 的右孩子
- 如果 x 有左孩子:
-
找到 x 左子树中最右边的节点(这个节点是左子树中序遍历的最后一个节点,也就是 x 在中序遍历中的前驱节点),记为 predecessor
-
如果 predecessor 的右指针是空:把它指向 x(相当于 "留个记号,等会儿遍历完左子树,从这里回到 x"),然后去 x 的左孩子
-
如果 predecessor 的右指针已经指向 x 了:说明左子树已经遍历完了,把 predecessor 的右指针恢复为空(不能修改原树的结构),然后访问 x,再去 x 的右孩子
cpp/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} * }; */ class Solution { public: vector<int> inorderTraversal(TreeNode* root) { //法二 Morris 法 利用当前节点的前驱节点的右指针当做栈的作用 记录根节点 vector<int> res; TreeNode *prede = nullptr; while(root!= nullptr){ if(root->left != nullptr){ //当前节点有左孩子 那么 predecessor 节点就是当前 root 节点向左走一步,然后一直向右走至无法走为止 prede = root->left; while(prede->right!=nullptr&&prede->right != root){ //直到 这个前驱节点的右指针为空并且右指针不等于root prede = prede->right; } //如果为空 那么就让这个指针指向根节点 if(prede->right == nullptr){ prede->right = root; root = root->left; } else{ //否则的话 说明程序已经不是第一次来了 这一次又遍历到头了 //那就重新清空prede 让它等于新的根节点。 res.push_back(root->val); prede->right = nullptr; root = root->right; } } else{ res.push_back(root->val); root = root->right; } } return res; } };
-