数据结构奇妙旅程之二叉树题型解法总结

news2024/11/15 16:00:30

꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱
ʕ̯•͡˔•̯᷅ʔ大家好,我是xiaoxie.希望你看完之后,有不足之处请多多谅解,让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客
本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如需转载还请通知˶⍤⃝˶
个人主页:xiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客
系列专栏:xiaoxie的JAVA系列专栏——CSDN博客●'ᴗ'σσணღ*
我的目标:"团团等我💪( ◡̀_◡́ ҂)" 

( ⸝⸝⸝›ᴥ‹⸝⸝⸝ )欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​+关注(互三必回)!

 一.关于二叉树的遍历的总结

1.使用递归来遍历二叉树

使用递归的方法来遍历二叉树我相信大家应该都没有什么大问题,在这里就不过多的赘述了,直接上代码

1.前序遍历(按照根 -> 左 -> 右)

public void preOrder(TreeNode root) {
        if(root == null) {
            return;
        }
        System.out.print(root.val + " ");
        preOrder(root.left);
        preOrder(root.right);
    }

2.中序遍历(按照左 -> 根 -> 右)

public void inOrder(TreeNode root) {
        if(root == null){
            return;
        }
        inOrder(root.left);
        System.out.print(root.val + " ");
        inOrder(root.right);
    }

3.后序遍历(按照左 -> 右 -> 根)

public void postOrder(TreeNode root) {
        if(root == null){
            return;
        }
        postOrder(root.left);
        postOrder(root.right);
        System.out.print(root.val + " ");
    }

2.迭代解法 (借助栈的思想)

迭代解法本质上是在模拟递归,因为在递归的过程中使用了系统栈,所以在迭代的解法中常用 Stack 来模拟系统栈。

1.前序遍历(按照根 -> 左 -> 右)

首先我们应该创建一个 Stack 用来存放节点,首先我们想要打印根节点的数据,此时 Stack 里面的内容为空,所以我们优先将头结点加入 Stack,然后打印。

之后我们应该先打印左子树,然后右子树。所以先加入 Stack 的就是右子树,然后左子树。
此时你能得到的流程如下:

 代码为:

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
         List<Integer> list = new ArrayList<>();//打印的容器
        if(root == null) return list;
        Stack<TreeNode> stack = new Stack<>(); //创建一个栈
        TreeNode cur = root;//用cur来遍历二叉树
        while (!stack.isEmpty() || cur != null) {
            while (cur != null) {
                list.add(cur.val);
                stack.push(cur);
                cur = cur.left;
            }
            TreeNode top = stack.pop();
            cur = top.right;
        }
        return list;
    }
}

OJ练习为144. 二叉树的前序遍历 - 力扣(LeetCode)

2.中序遍历 按照(左 ->根 -> 右)

尽可能的将这个节点的左子树压入 Stack,此时栈顶的元素是最左侧的元素,其目的是找到一个最小单位的子树(也就是最左侧的一个节点),并且在寻找的过程中记录了来源,才能返回上层,同时在返回上层的时候已经处理完毕左子树了。当处理完最小单位的子树时,返回到上层处理了中间节点。(如果把整个左中右的遍历都理解成子树的话,就是处理完 左子树->中间(就是一个节点)->右子树)同理创建一个 Stack,然后按 左 中 右的顺序输出节点,只是输出的顺序改变了这里直接上代码

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        TreeNode cur = root;
        Stack<TreeNode> stack = new Stack<>();
        while(cur != null || !stack.isEmpty()){
        while(cur != null) {
            stack.push(cur);
            cur = cur.left;
        }
        TreeNode top = stack.pop();
        list.add(top.val);
        cur = top.right;
      }
      return list;
    }
}

OJ练习为94. 二叉树的中序遍历 - 力扣(LeetCode)

3. 中序遍历 按照(左 ->右 -> 根 )

方法1

  1. 前序遍历的过程 是 根左右。
  2. 将其转化成 根右左。也就是压栈的过程中优先压入左子树,在压入右子树。
  3. 然后将这个结果返回来,这里是利用栈的先进后出倒序打印
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        Deque<TreeNode> stack = new LinkedList<>();
        LinkedList<Integer> ans = new LinkedList<>();
        if (null == root) return ans;
        stack.addFirst(root);
        while(!stack.isEmpty()) {
            TreeNode node = stack.removeFirst();
            ans.addFirst(node.val);
            if (null != node.left) {
                stack.addFirst(node.left);
            }
            if (null != node.right) {
                stack.addFirst(node.right);
            }
        }
        return ans;
    }
}

 方法2.

栈遍历版本: 建议先做中序遍历,后序只是在中序上多了一些操作。

与中序的不同之处在于:

  • 中序遍历中,从栈中弹出的节点,其左子树是访问完了,可以直接访问该节点,然后接下来访问右子树。
  • 后序遍历中,从栈中弹出的节点,我们只能确定其左子树肯定访问完了,但是无法确定右子树是否访问过。

因此,我们在后序遍历中,引入了一个prev来记录历史访问记录。

  • 当访问完一棵子树的时候,我们用prev指向该节点。
  • 这样,在回溯到父节点的时候,我们可以依据prev是指向左子节点,还是右子节点,来判断父节点的访问情况。
class Solution{
    public List<Integer> method1(TreeNode root) {
        List<Integer> ans=new LinkedList<>();
        Stack<TreeNode> stack=new Stack<>();
        TreeNode prev=null;
        //主要思想:
        //由于在某颗子树访问完成以后,接着就要回溯到其父节点去
        //因此可以用prev来记录访问历史,在回溯到父节点时,可以由此来判断,上一个访问的节点是否为右子树
        while(root!=null||!stack.isEmpty()){
            while(root!=null){
                stack.push(root);
                root=root.left;
            }
            //从栈中弹出的元素,左子树一定是访问完了的
            root=stack.pop();
            //现在需要确定的是是否有右子树,或者右子树是否访问过
            //如果没有右子树,或者右子树访问完了,也就是上一个访问的节点是右子节点时
            //说明可以访问当前节点
            if(root.right==null||prev==root.right){
                ans.add(root.val);
                //更新历史访问记录,这样回溯的时候父节点可以由此判断右子树是否访问完成
                prev=root;
                root=null;
            }else{
            //如果右子树没有被访问,那么将当前节点压栈,访问右子树
                stack.push(root);
                root=root.right;
            }
        }
        return ans;
    }
}

  OJ练习为145. 二叉树的后序遍历 - 力扣(LeetCode)

二.关于二叉树子树的问题

1.100. 相同的树 - 力扣(LeetCode)

要想知道两棵树是否相同,首先我们需要比较结构是否相同,然后再比较值是否相同,同样也可以分为递归和迭代两种方法

1.递归方法

class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p == null && q == null) return true;
        if(p == null && q != null || p != null && q == null) return false;
        if(p.val != q.val) return false;
        return isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
    }

2.迭代的方法

class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p == null && q == null) return true;
        if (p == null || q == null) return false;
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(p);
        queue.offer(q);
        while(!queue.isEmpty()) {
            p = queue.poll();
            q = queue.poll();
            if(p == null && q == null) continue;
            if((p == null || q == null) || p.val != q.val)
                return false;  
            queue.offer(p.left);
            queue.offer(q.left);
            queue.offer(p.right);
            queue.offer(q.right);     
        }
        return true;
    }
}

 通过借助队列来判断两棵树的结构和节点的值是否相同,需要注意的是两种方法的时间复杂度都为O(n)。

2.572. 另一棵树的子树 - 力扣(LeetCode)

​主要思路:将是否为子树的问题转换成是否相等的问题

class Solution {
    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        if(root == null) return false;
        if(isSameTree(root,subRoot)) return true;//判断中是否相同
        if(isSubtree(root.left,subRoot.left)) return true;//判断左子树是否相同
        if(isSubtree(root.right,subRoot.right)) return true;//判断右子树是否相同
        return false;
    }
    private boolean isSameTree(TreeNode root, TreeNode subRoot) {
        if(root == null && subRoot == null) return true;
        if(root == null && subRoot != null ||root != null && subRoot == null) return false;
        if(root.val != subRoot.val) return false;
        return isSameTree(root.left,subRoot.left) && isSameTree(root.right,subRoot.right);
    }
}

其中需要注意的是

判断两个树是否相等的三个条件是的关系,

即当前两个树的根节点值相等
并且,s的左子树和 r 的左子树相等;
并且,s 的右子树和 r 的右子树相等 

判断s 是否为 r的子树的三个条件是的关系

即当前两棵树相等
或者,s 是 r 的左子树
或者,s 是 r 的右子树

总的来说以上这些方法都是比较简单的暴力或者是递归解法,可能还达不到面试的高度,但学习就是循序渐进的,学会了这些比较基础的解法我相信你在后面学习更优的解法时,肯定更加得心应手

三.二叉树的广度优先搜索的总结(BFS)

1.层序遍历

二叉树的层序遍历问题借助队列可以使用广度优先搜索(BFS)算法来实现这种方法可以保证按层遍历二叉树,先遍历完当前层的节点,再遍历下一层的节点,直到所有节点都被遍历完成 

1.基础的二叉树的层序遍历

                                                                                                                                                            题目要求的二叉树的 从上至下 打印(即按层打印),又称为二叉树的 广度优先搜索(BFS)。BFS 通常借助 队列 的先入先出特性来实现。接下来我将一步步的详细讲解

我们先将根节点放到队列中,然后不断遍历队列。

再取出根节点,如果左子树或者右子树不为空,就将他们放入队列中。第一遍处理完后,根节点已经从队列中拿走了,而根节点的两个孩子已放入队列中了,现在队列中就有两个节点即为B和C

同理再取出B和C,如果B的左右孩子不为空,就加入队列,C同理,这样就完成了层层遍历了

 根据上图的解释我们就可以得到如下的代码

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        if(root == null) return res;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);//首先先把根节点加入到队列中去
        while(!queue.isEmpty()) {
            List<Integer> list = new ArrayList<>();
            int size = queue.size();//根据队列的长度来判断是否为同一层的节点
            while(size >0) {
             TreeNode cur = queue.poll();
             list.add(cur.val);
             size--;
             if(cur.left != null) {//左孩子不为空,就加入到队列中
                 queue.offer(cur.left);
             }
             if(cur.right != null) {//右孩子不为空,就加入到队列中
                 queue.offer(cur.right);
             } 
        }
        res.add(list);
        }
        return res;
    }
}

   

复杂度分析

记树上所有节点的个数为 n。

时间复杂度:每个结点进队出队各一次,故时间复杂度为 O(n)。
空间复杂度:队列中元素的个数不超过 n 个,故空间复杂度为 O(n)。

OJ链接为:102. 二叉树的层序遍历 - 力扣(LeetCode)

2.层序遍历的应用

这题就看成和上一题一样使用广度优先搜索(BFS),借助队列只不过是这题需要稍微改变一些,输出的是每一层的最后一个。所以可以得到以下的代码

class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if(root == null) return list;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while(!queue.isEmpty()) {
            int size = queue.size();//根据队列的长度判断是每一层有多少个节点
            while(size > 0) {
                TreeNode cur = queue.poll();
                if(size == 1) {//如果是每一层的最后一个节点就输出它
                    list.add(cur.val);
                }
                size--;
                if(cur.left != null) {
                    queue.offer(cur.left);
                }
                if(cur.right != null) {
                    queue.offer(cur.right);
                }
            }
        }
        return list;
    }
}

复杂度分析

时间复杂度 : O(n)。 每个节点最多进队列一次,出队列一次,因此广度优先搜索的复杂度为线性。

空间复杂度 : O(n)。每个节点最多进队列一次,所以队列长度最大不不超过n,所以这里的空间代价为 O(n)。

 当然这题也可以用深度优先搜索(DFS),根据题意我们每次都要输出每一层最右边的那一个,然后根据 根 -> 右 -> 左 的顺序这样的话每次第一个搜索的就是最右边的那个节点即每一层的最后一个节点 所以我们可以得到以下代码

class Solution {
    List<Integer> list = new ArrayList<>();
    public List<Integer> rightSideView(TreeNode root) {
        dfs(root,0);
        return list;
    }
    private void dfs(TreeNode root,int depth) {
        if(root == null) return;
         //先访问 当前节点,再递归地访问 右子树 和 左子树。
        if(depth == list.size()) {//如果当前节点所在深度还没有出现在res里,说明在该深度下当前节点是第一个被访问的节点,因此将当前节点加入res中。
            list.add(root.val);
        }
        depth++;
        dfs(root.right,depth);
        dfs(root.left,depth);
    } 
}

复杂度分析

时间复杂度 : O(n)

空间复杂度 : O(n)

当然这一题无论是DFS 还是 BFS 都可以解这题,时间复杂度和空间复杂度都差不多,只不过DFS更快一点,BFS更容易理解。

OJ链接为:199. 二叉树的右视图 - 力扣(LeetCode)                                                                                                                                                                                                                                  以上就是博主最近学习二叉树总结的所有内容呢,可能总结的并不是那么全面,不过后续博主学习的更加清楚明白一点,会再将这个总结补全完整,感谢大家的观看。                                         

                                                                                                                                                                                                                        

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1410114.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【推荐100个unity插件之16】3D物品描边效果——Quick Outline免费插件

文章目录 前言地址介绍使用例子完结 前言 关于3D描边&#xff0c;其实之前有用shader弄过一个&#xff1a;【实现100个unity特效】shader实现3D物品闪光和描边效果 但是很遗憾的是他不支持URP项目&#xff0c;所以现在推荐这款插件&#xff0c;他能很好的支持URP&#xff0c;…

每日一题 2859. 计算 K 置位下标对应元素的和(简单)

每次有空做每日一题&#xff0c;都碰到简单题。。。。。。 class Solution:def sumIndicesWithKSetBits(self, nums: List[int], k: int) -> int:ans 0for i in range(len(nums)):cnt 0t iwhile t > 0:cnt 1 if t & 1 1 else 0t >> 1ans nums[i] if cnt…

嵌入式工程师如何写好技术文档

嵌入式方案设计文档该怎么写&#xff1f;你是不是从来没有想过这个问题&#xff1f;今天就来分享一篇优秀的文章&#xff1a; 很多技术人自己非常轻视技术文档的书写&#xff0c;然而又时常抱怨文档不完善、质量差、更新不及时…… 这种在程序猿间普遍存在的矛盾甚至已经演变成…

【LeetCode】222. 完全二叉树的节点个数(简单)——代码随想录算法训练营Day16

题目链接&#xff1a;222. 完全二叉树的节点个数 题目描述 给你一棵 完全二叉树 的根节点 root &#xff0c;求出该树的节点个数。 完全二叉树 的定义如下&#xff1a;在完全二叉树中&#xff0c;除了最底层节点可能没填满外&#xff0c;其余每层节点数都达到最大值&#xf…

电工必备:故障电弧探测器安装方式

故障电弧探测器可以对故障电弧&#xff08;包括故障并联电弧、故障串联电弧&#xff09;进行有效的检测&#xff0c; 当检测到线路中存在引起火灾的故障电弧时&#xff0c;可以进行现场的声光报警&#xff0c;并将报警信息传输 给电气火灾监控设备。 故障电弧探测器适用于工…

Python连接MQTT服务器订阅和发布主题-Python物联网开发

一、前言 在物联网开发中&#xff0c;掌握MQTT可以说是一项必备的技能&#xff0c;要使用Python连接MQTT服务器、订阅和发布主题&#xff0c;我们需要导入paho-mqtt库。 二、实现代码 在之前的文章中&#xff0c;我们也介绍了JAVA是如何连接MQTT服务器实现发布和订阅主题的功能…

详细介绍node中动态数据内容的压缩应用

未使用 compression 压缩文件时 代码演示&#xff1a; const express require("express"); // 在node express框架当中&#xff0c;对node express框架进行一个引入const app express(); // 利用express()调用&#xff0c;对实例化对象进行获取app.get("/&q…

外汇天眼:美国证券交易委员会(SEC)采纳了一系列规定,以加强与特殊目的收购公司(SPACs)相关的投资者保护

美国证券交易委员会&#xff08;SEC&#xff09;今天通过了一系列新规和修订&#xff0c;以增强特殊目的收购公司&#xff08;SPACs&#xff09;的首次公开募股&#xff08;IPOs&#xff09;中的披露&#xff0c;并在SPACs与目标公司之间的后续业务合并交易&#xff08;de-SPAC…

FinBert模型:金融领域的预训练模型

文章目录 模型及预训练方式模型结构训练语料预训练方式 下游任务实验结果实验一&#xff1a;金融短讯类型分类实验任务数据集实验结果 实验二&#xff1a;金融短讯行业分类实验任务数据集实验结果 实验三&#xff1a;金融情绪分类实验任务数据集实验结果 实验四&#xff1a;金融…

蓝桥杯省赛无忧 课件42 插入排序

01 插入排序的思想 02 插入排序的实现 03 例题讲解 #include <iostream> #include <vector> using namespace std; void insertionSort(vector<int>& arr) {int n arr.size();for (int i 1; i < n; i) {// 选择arr[i]作为要插入的元素int key arr…

linux系统配置本地yum源

配置本地yum源原因&#xff1a;没有网络&#xff0c;无法使用yum更新下载软件&#xff0c;所以验旧配置一个本地yum源 第一&#xff1a;挂载镜像或者拷贝一份目录下有repodate文件的源 1&#xff09;虚拟机cd/dvd需要绑定iso镜像文件如上图 2&#xff09;备份/etc/yum.repo.d目…

【SVD生成视频+可本地部署】ComfyUI使用(二)——使用Stable Video Diffusion生成视频 (2023.11开源)

SVD官方主页 &#xff1a; Huggingface | | Stability.ai || 论文地址 huggingface在线运行demo : https://huggingface.co/spaces/multimodalart/stable-video-diffusion SVD开源代码&#xff1a;Github&#xff08;含其他项目&#xff09; || Huggingface 在Comfyui使用&…

【Midjourney】绘画风格关键词

1.松散素描(Loose Sketch) "Loose sketch"&#xff08;松散素描&#xff09;通常指的是一种艺术或设计中的手绘风格&#xff0c;其特点是线条和形状的表现相对宽松、自由&#xff0c;没有过多的细节和精确度。这样的素描通常用于表达创意、捕捉概念或者作为设计的初步…

清华大学学生一行赴麒麟信安调研交流

1月24日&#xff0c;清华大学信息科学技术学院电子工程系学子组成的社会实践支队一行到访麒麟信安&#xff0c;调研交流长沙市先进计算产业发展情况和未来规划。 在公司展厅&#xff0c;清华大学学子详细了解了麒麟信安的发展历程、国产操作系统产业现状&#xff0c;以及麒麟信…

第三季《乐队风暴》全国总决赛圆满落幕

2024年1月21日&#xff0c;由广东珠江、盛娱星汇海选联合主办的第三季《乐队风暴》全国海选歌手赛道全国总决赛在广州罗格镇MUSIC LIVE&#xff08;太古仓店&#xff09;正式打响&#xff0c;第三季《乐队风暴》全国海选开启以来共有超8000人报名渴望登上绚丽舞台&#xff0c;从…

1.25 day2 C++

自己封装一个矩形类(Rect)&#xff0c;拥有私有属性:宽度(width)、高度(height)&#xff0c; 定义公有成员函数: 初始化函数:void init(int w, int h) 更改宽度的函数:set_w(int w) 更改高度的函数:set_h(int h) 输出该矩形的周长和面积函数:void show()

草原超声波气象站

TH-CQX9在广袤无垠的草原上&#xff0c;有一种神秘而重要的设施正在默默地守护着这片美丽的土地&#xff0c;它就是草原超声波气象站。这不仅是一个高科技的气象观测平台&#xff0c;更是草原生态保护的重要一环。那么&#xff0c;草原超声波气象站究竟是什么&#xff1f;它又是…

Java 字符串03 String构造方式代码实现和内存分析 (黑马)

第一种方式&#xff1a; 第二种方式&#xff1a; 有参的字符串&#xff1a; 传递字符串 传递字符数组 应用场景&#xff1a;将abc字符串改为Qbc&#xff0c;那么可以将其转换为数组&#xff0c;然后进行修改&#xff0c;最后传入即可获得Qbc&#xff1b; 字节数组&#xff1a;…

python自动化测试面试题

1、自动化代码中,用到了哪些设计模式? 单例设计模式工厂模式PO设计模式数据驱动模式面向接口编程设计模式 2、什么是断言( Assert) ? 断言Assert用于在代码中验证实际结果是不是符合预期结果&#xff0c;如果测试用例执行失败会抛出异常并提供断言日志 3、什么是web自动化…