LC94. 二叉树的中序遍历

后序遍历题解

题目

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

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

进阶: 递归算法很简单,你可以通过迭代算法完成吗?

LC94. 二叉树的中序遍历
LC144. 二叉树的前序遍历
LC145. 二叉树的后序遍历

注:主要掌握迭代方式遍历

思路

中序遍历:按照左节点->根节点->右节点的顺序进行遍历。二叉树中每个节点都采用这种方式。

递归

采用递归函数进行编写,属于基本操作,这里不进行讲解。
设N为二叉树的节点数:
时间复杂度:O(n)
空间复杂度:O(n)

迭代——左节点存栈(最主要方式)

递归本质上是系统通过栈来实现的,因此采用遍历时我们可以进行模拟一种栈结构。

  1. 建立一个stack数据结构存储二叉树节点指针,建立一个容器存储遍历内容。
  2. 从根节点开始访问,以下内容while迭代进行。
  3. 根据中序遍历顺序,首先要访问左节点。因此对于访问到的每一个节点root,首先通过while循环到root左子树的最左侧的节点的左侧,并依次将中间经历的节点压入到栈内,用于后续访问(可以发现,如果一个节点有左子节点,则在栈内它的左子节点一定在它上面)。
  4. 此时将一个节点出栈,这个节点可能是它的左节点为空的节点,也可能是它的左节点已经出栈并记录过了。不管是哪种情况,它都不需要考虑左子节点的问题,因此只需将改节点存储的值记录在容器内。
  5. 将节点指针指向3中出栈节点的右节点(左节点、根节点访问过,此时只需要访问右节点)

时间复杂度:O(n)
空间复杂度:O(n)

迭代——右节点存栈

此方法较复杂,相对上一方法有许多冗余之处,不建议采用。

  1. 建立一个stack数据结构存储二叉树节点指针,建立一个容器存储遍历内容。
  2. 从根节点开始访问,以下内容while迭代进行。
  3. 如果当前访问的节点左子节点为空,则记录当前节点。并将节点数值标记为数值以外的数,是为了说明该节点已经记录完毕,这里记为-101。最后将指针指向该节点的右子节点。
  4. 如果当前访问节点的左节点的数值为标记数值,说明已经遍历过了,则将该节点记录。最后将指针指向该节点的右子节点。
  5. 如果没有以上情况,首先压栈当前节点的右节点,再压栈当前节点,然后访问左节点。

时间复杂度:O(n)
空间复杂度:O(n)
该方法的缺点是有许多冗余步骤,同时二叉树内部数值被改变,在并发程序中可能会造成一些问题。

morris遍历

该方法也是一个迭代方式,核心是找到当前节点在中序遍历所处位置前一个节点——左子树的最右侧一个节点。与前面迭代方式有所不同的是,该方法不需要采用栈来记录节点,而是利用二叉树中空闲的指针,节省了空间的开销。

思路: 对于每一个当前访问节点,想要对该节点的数值进行记录,那么说明它的在中序遍历中的前驱节点predecessor已经记录完毕了。有两种方式可以表明它的前驱节点记录完毕:一种是对前驱节点原地数值标记,但是会对二叉树进行破坏;第二种是将这个前驱节点predecessor的右子树指向当前访问节点,因为这是当前节点在中序遍历上的前一个节点,说明这个前驱节点原本是没有右子树的,所以其右子树指针属于空闲状态。当某次迭代时发现这个前驱节点右子树就是当前节点时,说明这个前驱节点记录完毕。

  1. 建立一个容器存储遍历内容。
  2. 从根节点开始访问,以下内容while迭代进行。
  3. 如果当前访问节点的左子树不为空,找到该左子树的最右侧的一个节点predecessor,判断依据如下:若为空,将predecessor的右子节点指向当前访问节点。然后将访问指针指向当前访问节点的子节点。若predecessor右子节点指向目前访问的节点,说明当前访问节点的左子树已经全部遍历完成,则把当前节点数值记录,然后将访问指针指向它的右子节点(记得再把这个predecessor的链接断开,一方面是不破坏二叉树结构,另一方面是有些二叉树结构会形成一个闭环导致死循环出现)。
  4. 如果当前访问节点的左子树为空,记录当前节点的数值,然后将访问指针指向它的右子节点。

这里判断predecessor右子节点指向目前访问的节点可能有点难理解,我们借助图以根节点root的判断为例:

  • root首先访问它的左子树,从中找到左子树中最右侧的节点,图中为2。此时将2这个节点的右子节点指向根节点
  • 然后开始遍历root的左子节点,也就是4。4也按照上述办法进行。
  • 。。。。。。
  • 当1、7、4三个节点都遍历完成之后,指针指向2这个节点。
  • 2因为没有左子节点,2这个数值被记录进容器,然后开始指针指向2的右子节点,即为根节点。但是我们没有标记根节点,所以并不知道他是根节点root。
  • 于是根节点又重复了一遍刚才的操作,root首先访问它的左子树,从中找到左子树中最右侧的节点,此时它发现中间有个节点(这里是2)指向的是自己,他就知道这个操作已经做了一遍,根节点root的左子树全部遍历,便可记录自己的数值。
    在这里插入图片描述时间复杂度:O(n)
    空间复杂度:O(n),不需要栈,但是需要一个容器存遍历结果,所以还是O(n)

代码

递归
/**
 * 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> ans;
    vector<int> inorderTraversal(TreeNode* root) {
        get_data(root);
        return ans;
    }
    void get_data(TreeNode* root){
        if(root==nullptr)
            return;
        get_data(root->left);
        ans.push_back(root->val);
        get_data(root->right);
    }
};
迭代——左节点存储
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> ans;
        while(1){
            while(root!=nullptr){
                st.push(root);
                root = root->left;
            }
            if(st.empty()) break;
            root = st.top();
            st.pop();
            ans.push_back(root->val);
            root = root->right;
        }
        return ans;
    }
};
迭代——右节点存储
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ans;
        if(root == nullptr) return ans;
        stack<TreeNode*> st;
        if(root->right!=nullptr)
            st.push(root->right);
        st.push(root);
        root = root->left;
        while(1){
            if(root == nullptr){
                if(st.empty()) break;
                root = st.top();
                st.pop();
            }
            if(root->left==nullptr||(root->left!=nullptr&&root->left->val==-101)){
                ans.push_back(root->val);
                root->val = -101;
                if(root->right!=nullptr&&(st.empty()||!st.empty()&&root->right!=st.top()))
                    root = root->right;
                else{
                    if(st.empty()) break;
                    root = st.top();
                    st.pop();
                }
            }
            else{
                if(root->right!=nullptr)
                    st.push(root->right);
                st.push(root);
                root = root->left;
            }
        }
        return ans;
    }
};
morris遍历
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ans;
        while(root!=nullptr){
            if(root->left!=nullptr){
                TreeNode* predecessor = root->left;
                while(predecessor->right!=nullptr&&predecessor->right!=root)
                    predecessor = predecessor->right;
                if(predecessor->right==nullptr){
                    predecessor->right = root;
                    root = root->left;
                }
                else if(predecessor->right==root){
                    ans.push_back(root->val);
                    predecessor->right = nullptr;   //如果不及时断掉,有可能会陷到一个环里出不来
                    root = root->right;
                }
            }
            else{
                ans.push_back(root->val);
                root = root->right;
            }
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值