牛客刷题之二叉树的遍历

 前言:

 与牛客的相知相遇:

一次偶然的机会我接触到了牛客网,从那次我就发现牛客网好像是一个全能型的网站,里面有各种语言的练习题、算法题、大厂的面试题、还有求职等各项功能。从那以后我就开始了我的牛客之旅。

链接我就放在这了需要的伙伴们自取👉注册即可免费刷题

二叉树的先序遍历

1、题目以及描述

 

 2、递归实现

1、二叉树的递归知识点

递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能讲原本的问题分解为更小的子问题,这是使用递归的关键。

而二叉树的递归,则是将某个节点的左子树、右子树看成一颗完整的树,那么对于子树的访问或者操作就是对于原树的访问或者操作的子问题,因此可以自我调用函数不断进入子树。

2、思路

什么是二叉树的前序遍历?简单来说就是“根左右”,展开来说就是对于一颗二叉树优先访问其根节点,然后访问它的左子树,等左子树全部访问完了再访问其右子树,而对于子树也按照之前的访问方式,直到到达叶子节点。

从上述前序遍历的解释中我们不难发现,它存在递归的子问题:每次访问一个节点之后,它的左子树是一个要前序遍历的子问题,它的右子树同样是一个要前序遍历的子问题。那我们可以用递归处理:

  • 终止条件: 当子问题到达叶子节点后,后一个不管左右都是空,因此遇到空节点就返回。
  • 返回值: 每次处理完子问题后,就是将子问题访问过的元素返回,依次存入了数组中。
  • 本级任务: 每个子问题优先访问这棵子树的根节点,然后递归进入左子树和右子树。

具体做法:

  • step 1:准备数组用来记录遍历到的节点值,Java可以用List,C++可以直接用vector。
  • step 2:从根节点开始进入递归,遇到空节点就返回,否则将该节点值加入数组。
  • step 3:依次进入左右子树进行递归。

3、代码实现

①  Java代码实现

import java.util.*;
public class Solution {
    public void preorder(List<Integer> list, TreeNode root){
        //遇到空节点则返回
        if(root == null) 
            return;
        //先遍历根节点
        list.add(root.val); 
        //再去左子树
        preorder(list, root.left); 
        //最后去右子树
        preorder(list, root.right); 
    }
    
    public int[] preorderTraversal (TreeNode root) {
        //添加遍历结果的数组
        List<Integer> list = new ArrayList(); 
        //递归前序遍历
        preorder(list, root); 
        //返回的结果
        int[] res = new int[list.size()]; 
        for(int i = 0; i < list.size(); i++)
            res[i] = list.get(i);
        return res;
    }
}

②C++代码实现

class Solution {
public:
    void preorder(vector<int> &res, TreeNode* root){
        //遇到空节点则返回
        if(root == NULL) 
            return;
        //先遍历根节点
        res.push_back(root->val); 
        //再去左子树
        preorder(res, root->left); 
        //最后去右子树
        preorder(res, root->right); 
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res; 
        //递归前序遍历
        preorder(res, root); 
        return res;
    }
};

③python代码实现

class Solution:
    def preorder(self, list: List[int], root: TreeNode):
        # 遇到空节点则返回
        if root == None: 
            return
        # 先遍历根节点
        list.append(root.val) 
        # 再去左子树
        self.preorder(list, root.left) 
        # 最后去右子树
        self.preorder(list, root.right) 
        
    def preorderTraversal(self , root: TreeNode) -> List[int]:
        # 添加遍历结果的数组
        list = [] 
        # 递归前序遍历
        self.preorder(list, root) 
        return list

复杂度分析:

  • 时间复杂度:O(n),其中nnn为二叉树的节点数,遍历二叉树所有节点
  • 空间复杂度:O(n),最坏情况下二叉树化为链表,递归栈深度为n

3、非递归(扩展思路)

1、栈的知识点

栈是一种仅支持在表尾进行插入和删除操作的线性表,这一端被称为栈顶,另一端被称为栈底。元素入栈指的是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;元素出栈指的是从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

2、思路

我们都知道递归,是不断调用自己,计算内部实现递归的时候,是将之前的父问题存储在栈中,先去计算子问题,等到子问题返回给父问题后再从栈中将父问题弹出,继续运算父问题。因此能够递归解决的问题,我们似乎也可以用栈来试一试。

根据前序遍历“根左右”的顺序,首先要遍历肯定是根节点,然后先遍历左子树,再遍历右子树。递归中我们是先进入左子节点作为子问题,等左子树结束,再进入右子节点作为子问题。

1

2

3

4

5

6

//先遍历根节点

res.push_back(root->val);

//再去左子树

preorder(res, root->left);

//最后去右子树

preorder(res, root->right);

这里我们同样可以这样做,它无非相当于把父问题挂进了栈中,等子问题结束再从栈中弹出父问题,从父问题进入右子树,我们这里已经访问过了父问题,不妨直接将右子节点挂入栈中,然后下一轮先访问左子节点。要怎么优先访问左子节点呢?同样将它挂入栈中,依据栈的后进先出原则,下一轮循环必然它要先出来,如此循环,原先父问题的右子节点被不断推入栈深处,只有左子树全部访问完毕,才会弹出继续访问。

1

2

3

4

5

6

7

res.push_back(node->val);

//如果右边还有右子节点进栈

if(node->right)

    s.push(node->right);

//如果左边还有左子节点进栈

if(node->left)

    s.push(node->left);

具体做法:

  • step 1:优先判断树是否为空,空树不遍历。
  • step 2:准备辅助栈,首先记录根节点。
  • step 3:每次从栈中弹出一个元素,进行访问,然后验证该节点的左右子节点是否存在,存的话的加入栈中,优先加入右节点。

3、图示

 4、代码实现

①Java代码实现

import java.util.*;
public class Solution {
    public int[] preorderTraversal (TreeNode root) {
        //添加遍历结果的数组
        List<Integer> list = new ArrayList(); 
        Stack<TreeNode> s = new Stack<TreeNode>();
        //空树返回空数组
        if(root == null) 
            return new int[0];
        //根节点先进栈
        s.push(root); 
        while(!s.isEmpty()){
            //每次栈顶就是访问的元素
            TreeNode node = s.pop(); 
            list.add(node.val);
            //如果右边还有右子节点进栈
            if(node.right != null) 
                s.push(node.right);
            //如果左边还有左子节点进栈
            if(node.left != null) 
                s.push(node.left);
        }
        //返回的结果
        int[] res = new int[list.size()]; 
        for(int i = 0; i < list.size(); i++)
            res[i] = list.get(i);
        return res;
    }
}

②C++代码实现

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res; 
        if(root == NULL)
            return res;
        //辅助栈
        stack<TreeNode*> s; 
        //根节点先进栈
        s.push(root); 
        //直到栈中没有节点
        while(!s.empty()){ 
            //每次栈顶就是访问的元素
            TreeNode* node = s.top(); 
            s.pop();
            res.push_back(node->val);
            //如果右边还有右子节点进栈
            if(node->right) 
                s.push(node->right);
            //如果左边还有左子节点进栈
            if(node->left) 
                s.push(node->left);
        }
        return res;
    }
};

③python代码实现

class Solution:
    def preorderTraversal(self , root: TreeNode) -> List[int]:
        res = []
        if not root:
            return res
        # 辅助栈
        s = [] 
        # 根节点先进栈
        s.append(root) 
        # 直到栈中没有节点
        while s: 
            # 每次栈顶就是访问的元素
            node = s[-1] 
            s.pop()
            res.append(node.val)
            # 如果右边还有右子节点进栈
            if node.right: 
                s.append(node.right)
            # 如果左边还有左子节点进栈
            if node.left: 
                s.append(node.left)
        return res

复杂度分析:

  • 时间复杂度:O(n),其中n为二叉树的节点数,遍历二叉树所有节点
  • 空间复杂度:O(n),辅助栈空间最坏为链表所有节点数

若本文有任何不足之处欢迎各位指正

最后刷题网站我就放在这了,需要的伙伴们自取 👉注册即可免费刷题

  • 21
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 25
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

敲代码的小王!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值