二叉树基础OJ题

本文的练习为基础练习,适合刚学习数据结构的小伙伴练习,最好是已经学习过相关的知识再进行练习效果更好。推荐先做练习再看解析哦~

1. 二叉树的前序遍历

链接:leetcode_144

题目描述:给你二叉树的根节点root,返回它节点值的前序遍历。

【解析】

(做法一:递归)

【递归的本质理解】

三序遍历的递归写法的差异,其实是每个节点进入对应函数的次数的差异

以下面这颗二叉树为例,分析其递归的写法

二叉树基础OJ题_第1张图片

void f(TreeNode* root)
{
    if(root == nullptr)
       return;

    // 第一次进入该节点对应的函数
    printf("%d ", root->val);

    f(root->left);
    // 从左子树递归回来,第二次进入该节点对应的函数
    printf("%d ", root->val);

    f(root->right);
    // 从右子树递归回来,第三次进入该节点对应的函数
    printf("%d ", root->val);
}

如果每一次进入函数都打印一遍,那么打印出来的序列将会是下面这样:

1  2  3  3  3  2  2  1  4  5  5  5  4  6  6  6  4  1

而当我们仔细观察就会发现,如果保留第一次进函数的过程,就会输出:

1  2  3  4  5  6

也就是前序遍历的过程

同理,保留第二次和第三次进函数的过程,就输出:

3  2  1  5  4  6

3  2  5  4  6  1

也就是对应中序和后序的过程

所以打印不同次进入函数节点的过程,就是对应不同的遍历方式

根据上面的逻辑,就可以得出前中后序的递归写法了

这里以前序遍历为例,给出相应的代码

class Solution {
public:
    vector res;
    
    void preorder(TreeNode* root)
    {
        if(root == nullptr)
            return;
        res.push_back(root->val);
        preorder(root->left);
        preorder(root->right);
    }

    vector preorderTraversal(TreeNode* root) {
        preorder(root);
        return res;
    }
};

(做法二:栈/非递归做法)

因为栈是后进先出,所以实现先序遍历时进栈要先进右再进左。如果左子树也有左右,那么右子树的节点会一直保留在栈中,等左子树遍历完之后右子树才开始按先序遍历

class Solution {
public:
    vector preorderTraversal(TreeNode* root) {
        vector res;
        if(root)
        {
            stack st;
            st.push(root);
            while(!st.empty())
            {
                TreeNode* cur = st.top();
                st.pop();
                res.push_back(cur->val); 

                if(cur->right)
                    st.push(cur->right);
                if(cur->left)
                    st.push(cur->left);
            }
        }
        return res;
    }
};

2. 二叉树的中序遍历

链接:leetcode_94

【解析】

(做法一:递归)

与前序遍历类似,只需改变放入res数组和进入左子树的顺序就好了

(做法二:栈)

1)先把左子树干到栈里,直到为空,目的就是每次都是左子树优先打印;

2)然后栈弹出节点,打印此节点,再将右子树重复步骤1),也就是先输出根节点,再把右子树放最后;

3)没子树且栈为空,结束。

class Solution {
public:
    vector inorderTraversal(TreeNode* root) {
        vector res;
        stack st;
        if(root == nullptr)
            return res;
        while(!st.empty() || root != nullptr)
        //结束判断条件
        {
            if(root != nullptr)
            {
                st.push(root);
                root = root->left;
            } //对应步骤 1)
            else
            {
                root = st.top();
                st.pop();
                res.push_back(root->val);
                root = root->right;
            } //对应步骤 2)
        }
        return res;
    }
};

3. 二叉树的后序遍历

链接:leetcode_145​​​​​​​

【解析】

(做法一:递归)

与前序遍历类似,只需改变放入res数组的顺序就好了

(做法二:两个栈实现)

这个做法简单说就是将前序遍历的 中-左-右 过程换成 中-右-左 过程,再将其逆序成后序

实现方式就是在要输入结果时,改成输入collect栈中,最后再将collect转入res数组

不过这种做法并不推荐,因为需要额外开一个栈实现,所以空间复杂度是O(n)

class Solution {
public:
    vector postorderTraversal(TreeNode* root) {
        vector res;
        if(root == nullptr)
            return res;
        stack st;
        stack collect;
        st.push(root);
        while(!st.empty())
        {
            TreeNode* cur = st.top();
            st.pop();
            collect.push(cur);
            
            if(cur->left)
                st.push(cur->left);
            if(cur->right)
                st.push(cur->right);
        }

        while(!collect.empty())
        {
            res.push_back(collect.top()->val);
            collect.pop();
        }
        return res;
    }
};

(做法三:一个栈实现

代码如下(详解见注释):

class Solution {
public:
    vector postorderTraversal(TreeNode* root) {
        vector res;
        if(root == nullptr)
            return res;
        stack st;
        st.push(root);
        TreeNode* cur = root;

        //没有数据被存入res中,则cur一直为root
        //当有数据被处理后,cur就变成对应节点
        //每一次cur对应的含义就是上一个入res的值对应的节点
        while(!st.empty())
        {
            root = st.top();
            //有左子树且左子树还没被操作(cur对应的是被操作过的节点)
            if(root->left != nullptr
            && cur != root->left
            && cur != root->right)
                st.push(root->left);
            //有右子树且右子树还没被操作,
            //因为前面已经考虑过左子树,所以只用判断右子树即可
            else if(root->right != nullptr 
            && cur != root->right)
                st.push(root->right);
            else
            {
                cur = st.top();
                res.push_back(st.top()->val);
                st.pop();
            }
            //三个条件判断其实隐含着 左-右-中 的顺序
        }

        
        return res;
    }
};

4. 二叉树的层序遍历

链接:leetcode_102

【题目描述】给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

【解析】

方法:用队列来实现BFS

先将root放入队中,然后开始循环: 

每次循环为队中的元素个数次(该层元素的个数),在每个元素出队时,

将其左右子树放入队中(为下一层的元素遍历作准备)

class Solution {
public:
    vector> levelOrder(TreeNode* root) {
        vector> res;
        if(!root)
            return res;
        queue q;
        q.push(root);
        while(!q.empty())
        {
            int sz = q.size();
            vector ans;
            while(sz--)
            {
                TreeNode* cur = q.front();
                q.pop();
                ans.push_back(cur->val);
                if(cur->left)
                    q.push(cur->left);
                if(cur->right)
                    q.push(cur->right);
            }
            res.push_back(ans);
        }
        return res;
    }
};

5. 二叉树的锯齿形层序遍历

链接:leetcode_103

这题其实就是上一题的延伸,只需要加一个条件判断隔行逆置一下就可以了

class Solution {
public:
    vector> zigzagLevelOrder(TreeNode* root) {
        vector> res;
        if(!root)
            return res;
        queue q;
        q.push(root);
        while(!q.empty())
        {
            int sz = q.size();
            vector ans;
            while(sz--)
            {
                TreeNode* cur = q.front();
                q.pop();
                ans.push_back(cur->val);
                if(cur->left)
                    q.push(cur->left);
                if(cur->right)
                    q.push(cur->right);
            }
            if(res.size() % 2 == 1) reverse(ans.begin(), ans.end());
            res.push_back(ans);
        }
        return res;
    }
};

你学会了吗?

你可能感兴趣的:(数据结构)