力扣HOT100(32)二叉树的中序遍历

方法一:递归法(最基础,面试先写这个)

核心思路

中序遍历天然具有递归性质:

  • 对于任意一个节点,先遍历它的左子树
  • 然后访问这个节点本身
  • 最后遍历它的右子树
  • 遇到空节点就直接返回(递归终止条件)

递归的本质是系统帮我们维护了一个调用栈,自动记录我们走到了哪里,处理完子树后自动回到父节点

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. 核心思路

递归和迭代是完全等价的,区别只是:

  • 递归:系统隐式地帮我们维护了一个调用栈
  • 迭代:我们自己显式地用一个栈来模拟这个过程

迭代法的核心逻辑(和递归完全一致):

  1. 先一路往左走,把所有经过的节点都压入栈(相当于递归里的 "先处理左子树")
  2. 走到头(左孩子为空)的时候,弹出栈顶节点(这个就是当前最左的节点,也就是中序的第一个)
  3. 访问这个节点(把值加入结果)
  4. 然后去处理这个节点的右子树(相当于递归里的 "最后处理右子树")
  5. 重复上面的步骤,直到栈为空且当前节点为空
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:

  1. 如果 x 没有左孩子:直接访问 x,然后去 x 的右孩子
  2. 如果 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;
          }
      };
相关推荐
aramae7 小时前
MySQL复合查询(8)
java·c语言·开发语言·后端·算法
荆棘鸟智能7 小时前
城市感知设备怎么统一接入?从多协议网关到设备模型的中间件架构设计
人工智能·算法·边缘计算
淡海水8 小时前
13-04-面试-源码级深度追问链
数据结构·unity·面试·c#·游戏引擎·源码·il2cpp
Logic1018 小时前
C语言/数据结构位运算题解:异或XOR找出时尚聚会中的“独特颜色“——只出现一次的数字
c语言·数据结构·数组·位运算·时间复杂度·算法题·异或性质
INGNIGHT11 小时前
270 · 电话号码的字母组合II(Trie)
linux·算法
2401_8390805411 小时前
C++常见八股
数据结构·c++·算法
青山是哪个青山12 小时前
LeetCode 188:买卖股票的最佳时机 IV
算法
暮雨封夕12 小时前
跳表(Skip List)详解:原理、实现、使用场景与实际应用
数据结构
mikuyyds12 小时前
geo-toolbox 插件算法解析:RUSLE 与 MUSLE 的工程化落地
算法·rust·gis
Tisfy12 小时前
LeetCode 3498.字符串的反转度:一次遍历
leetcode·字符串·题解·遍历