力扣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;
          }
      };
相关推荐
玖釉-2 小时前
下一个排列:从字典序到原地算法的完整推导
数据结构·c++·windows·算法
IronMurphy2 小时前
【算法五十】62. 不同路径
算法
影寂ldy2 小时前
C#一维数组
算法
枕星而眠3 小时前
数据结构八大排序详解(一):四大简单排序
c语言·数据结构·c++·后端
过期动态3 小时前
【LeetCode 热题 100】移动零
java·数据结构·算法·leetcode·职场和发展·rabbitmq
努力努力再努力wz3 小时前
【Qt入门系列】:按钮组件全解析:从 QAbstractButton 到快捷键事件、单选与复选机制
c语言·开发语言·数据结构·c++·git·qt·github
Dlrb12114 小时前
数据结构-栈
数据结构··内核栈·满栈空栈·增栈减栈
计算机安禾4 小时前
【算法分析与设计】第10篇:下界理论与NP完全性初步
大数据·人工智能·算法
水木流年追梦5 小时前
大模型入门-大模型分布式训练2
开发语言·分布式·python·算法·正则表达式·prompt