94.二叉树的中序遍历

1.题目介绍

给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。
示例 1:

输入:root = [1,null,2,3]
输出:[1,3,2]

示例 2:
输入:root = []
输出:[]

示例 3:
输入:root = [1]
输出:[1]

提示:
树中节点数目在范围 [0, 100] 内
-100 <= Node.val <= 100

2.题解

2.1 递归

首先我们需要了解什么是二叉树的中序遍历:按照访问左子树——根节点——右子树的方式遍历这棵树,而在访问左子树或者右子树的时候我们按照同样的方式遍历,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质,我们可以直接用递归函数来模拟这一过程。递归终止的条件为碰到空节点。

代码

//
// Created by trmbh on 2023-10-26.
// 94.二叉树中序遍历

#include<iostream>
#include<vector>
#include<string>

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:
    std::vector<int> inorderTraversal(TreeNode *root) {
        using namespace std;
        if (root == nullptr) return arr;
        else {
            inorderTraversal(root->left);
            arr.push_back(root->val);
            inorderTraversal(root->right);
        }
        return arr;
    }

private:
    std::vector<int> arr;
};

int main(){
    Solution solution;
    TreeNode n1(3);
    TreeNode n2(2, &n1, nullptr);
    TreeNode n(1, nullptr,&n2);
    std::vector<int> arr = solution.inorderTraversal(&n);
    for (int num:arr){
        std::cout << num << ' ';
    }
}

2.2迭代

思路

在递归中,我们其实隐式地使用了栈来保存前面节点的信息;所以这里如果我们要使用迭代的方法的话,就应该使用显式栈来保存节点信息,并在返回时提供节点信息。

这里大循环的终止条件是节点遍历完毕且栈空(若栈非空,代表还有前置节点需要回退)

代码

class Solution {
public:
    std::vector<int> inorderTraversal(TreeNode *root) {
        std::stack<TreeNode *> stk;
        std::vector<int> arr;
        while(!stk.empty() || root != nullptr) {
            while (root != nullptr) {
                stk.push(root);
                root = root->left;
            }
            root = stk.top();
            stk.pop();
            arr.push_back(root->val);
            root = root->right;
        }
        return arr;
    }
};

复杂度分析

  • 时间复杂度:\(O(n)\),其中\(n\)为二叉树节点的个数。
    二叉树的遍历中每个节点会被访问一次且只会被访问一次。

  • 空间复杂度:\(O(n)\)
    空间复杂度取决于栈深度,而栈深度在二叉树为一条链的情况下会达到\(O(n)\)的级别。

2.3 Morris 中序遍历

思路

Morris 遍历算法是另一种遍历二叉树的方法,它能将非递归的中序遍历空间复杂度降为 O(1)。
就像我们用栈存储了节点之间的前后关系, 这里巧妙地利用了\(predecessor\)右子树设置的方法为我们保存了返回的节点信息

Morris 遍历算法整体步骤如下:(假设当前遍历到的节点为\(x\) ) :
1.如果\(x\)无左孩子,先将\(x\)的值加入答案数组,再访问\(x\)的右孩子,即\(x=x.right\)
2.如果\(x\)有左孩子,则找到\(x\)左子树上最右的节点(即左子树中序遍历的最后一个节点,\(x\)在中序遍历中的前驱节点),我们记为\(predecessor\)
根据\(predecessor\)的右孩子是否为空,进行如下操作。

  • 如果\(predecessor\)的右孩子为空,则将其右孩子指向\(x\) ,然后访问\(x\)的左孩子,即\(x=x.left\)
  • 如果\(predecessor\)的右孩子不为空,则此时其右孩子指向\(x\) ,说明我们已经遍历完\(x\)的左子树,
    我们将\(predecessor\)的右孩子置空,将\(x\)的值加入答案数组,然后访问\(x\)的右孩子,即\(x=x.right\)

3.重复上述操作,直至访问完整棵树。
其实整个过程我们就多做一步:假设当前遍历到的节点为 x,将 x 的左子树中最右边的节点的右孩子指向 x,
这样在左子树遍历完成后我们通过这个指向走回了 x,且能通过这个指向知晓我们已经遍历完成了左子树,而不用再通过栈来维护,省去了栈的空间复杂度。

代码

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        TreeNode *predecessor = nullptr;

        while (root != nullptr) {
            // 如果左子树不为空,我们就要遍历左子树,寻找pre节点; 
            // 1.若pre节点右子树为空,说明左子树尚未遍历完,链接pre节点和当前root, root = root -> left 继续遍历左子树 
            // 2.若pre节点右子树不为空,说明这里的root是通过上一个pre节点的右子树返回回来的(左子树已经遍历完),故存入该root到答案数组, 断开pre节点链接,遍历右子树
            if (root->left != nullptr) {
                // predecessor 节点就是当前 root 节点向左走一步,然后一直向右走至无法走为止
                predecessor = root->left;
                while (predecessor->right != nullptr && predecessor->right != root) {
                    predecessor = predecessor->right;
                }
                
                // 让 predecessor 的右指针指向 root,继续遍历左子树
                if (predecessor->right == nullptr) {
                    predecessor->right = root;
                    root = root->left;
                }
                // 说明左子树已经访问完了,我们需要断开链接, 继续访问右子树
                else {
                    res.push_back(root->val);
                    predecessor->right = nullptr;
                    root = root->right;
                }
            }
            // 如果没有左孩子,则直接访问右孩子(左子树访问完毕,开始通过right返回前置节点,相当于出栈操作)
            else {
                res.push_back(root->val);
                root = root->right;
            }
        }
        return res;
    }
};

复杂度分析

  • 时间复杂度:\(O(n)\),其中\(n\)为二叉树的节点个数。
    Morris 遍历中每个节点会被访问两次,因此总时间复杂度为\(O(2n)=O(n)\)

  • 空间复杂度:\(O(1)\)

posted @ 2023-10-26 18:45  DawnTraveler  阅读(10)  评论(0编辑  收藏  举报