DFS:解决二叉树问题
文章目录
- 了解DFS
- 1.计算布尔二叉树的值
- 思路
- 代码展示
- 2.求根节点到叶节点数字之和
- 思路
- 代码展示
- 3.二叉树剪枝
- 思路
- 代码展示
- 4.验证二叉搜索树
- 思路分析
- 代码展示
- 5.二叉搜索树中第k小元素
- 思路:
- 代码展示
- 6.二叉树的所有路径
- 思路分析
- 代码展示
- 总结
了解DFS
所谓DFS就是就是深度优先搜索,首先我们回到我们以前学习过的二叉树,对于二叉树我们讲过深度优先遍历,也就前序,后序,中序,这三种遍历方式,对于深度优先搜索,深度优先遍历是一个过程,在这个过程中我们加上搜索。
在一颗二叉树上,对于遍历来说,我们会一条路走到黑,直到走到空的节点为止,才会返回上一个节点,走另一个分支,但是对于DFS(深度优先搜索)来说,我们的目的是、搜索当中的值,而不是一味地遍历。
接下来我们通过几道题来深入理解这个算法
1.计算布尔二叉树的值
首先我们来理解题意,题意很简单就是在一颗二叉树中只有0,1,2,3
这几个值,他们分别代表的是false true || &&
,我们来看看实际的一颗树:
右边这颗二叉树就可以投影成左边这颗树的样子。
接下来我们来分析一下这个道题应该怎么做:
思路
首先这道题说了这颗树是完整的二叉树,意思就是所有节点要么一个节点都没有,要么就是有两个节点。我们再来看这颗树的特征:非叶子节点肯定是2或者3,叶子节点肯定是1或者0,所以这里划分就出来了,我们对叶子节点和非叶子节点做不同的处理,如果是叶子节点就直接返回当前节点的值,如果不是叶子节点就判断一下该节点的值,如果是2就对左子树和右子树进行||操作,反之则进行&&操作即可。
函数头
函数头:bool dfs(root)
函数体
遇到叶子节点返回叶子节点的值,遇到非叶子节点,对左子树和右子树进行递归操作。
递归出口
就是返回叶子节点的值
代码展示
class Solution {
public:bool evaluateTree(TreeNode* root) {//只用判断一边就可以if(root->right==nullptr){//叶子节点直接返回值return root->val;}//得到左子树的结果bool left=evaluateTree(root->left);//得到右子树的结果bool right=evaluateTree(root->right);//判断一下当前节点的值是2还是3,进行&&操作还是||操作return root->val==2?left||right:left&&right;}
};
2.求根节点到叶节点数字之和
题目解释:
首先我们先给出一棵树
对于这棵树并不是说所有节点的和就是把所有节点的值加起来,而是,我们先看第一个路径,4--9--5
对于这个路径来说,这个路径下对应的和就是495,第二个路对应的是491,第三个路径对应的是40.
从下面图应该可以看出:
思路
对于这道题,我们先来走一遍,当我们进入根节点4的时候,我们先递归左子树,我们肯定必须要知道前面的和是多少,因为我们要计算下一个节点的和,所以必须知道前面节点的和是多少,所以这里我们传递的参数就多了一个presum(前驱和)
函数头
函数头:int dfs(root,presum)
因为这道题要求返回所有路径的和,所以有一个返回值就是int
函数体
这里我们来想一下函数体是什么?
我们把presum传进行,当进入根节点的时候肯定不能带值,因为根节点的前驱和是0,所以这里我们传参的话,传presum进去先是0,进了函数之后我们先更新一下这个 presum,presum=presum*10+root->val
,更新了presum之后,判断一下这个节点是否是叶子节点,如果是叶子节点直接返回presum,因为如果是叶子节点的话就说明这个路径的和已经求完了,只需要求下一个路径的和就可以了,这里我们用一个ret来存放一下左子树和右子树的和,如果左子树不为空,则返回将左子树的和加在ret上,如果右子树不为空,则再将右子树的和加在ret上,最后返回ret。
代码展示
class Solution {
public:int dfs(TreeNode*root,int presum){//先将前驱和加上个presum=presum*10+root->val;//如果是末尾节点的话,直接返回前驱和if(root->left==nullptr&&root->right==nullptr){return presum;}int ret=0;//如果左节点不为空的话直接ret叠加上左节点的dfsif(root->left!=nullptr){ret+=dfs(root->left,presum);}//如果右节点不为空的话只额吉ret叠加右节点的dfsif(root->right!=nullptr){ret+=dfs(root->right,presum);}//返回两边树的总和return ret;}int sumNumbers(TreeNode* root) {return dfs(root,0);//刚传递进去的时候前驱和是0}
};
3.二叉树剪枝
首先我们来看看下面一颗二叉树,注意这颗二叉树上只有1或者0.
根据题目的意思,就是将只含有0的子树删除,对于上面这颗二叉树来说,只含有0的子树,首先我们看左子树,左子树全是零,直接删除,再看右子树,右子树的第一个节点是1,不满足,不能删除,右子树的左子树的节点,只有0,直接删除,右子树右子树只有1,不能删除,所以删除之后的二叉树就变成了下面的样子:
思路
对于这道题,我们要删除节点的话,肯定要知道左子树和右子树的信息,才能删除这个节点,由于这个特殊的性质,我们首先想到的则是后序遍历,因为只有后序遍历,才能将左子树和右子树的信息传递给节点,确定了该如何遍历之后,我们来讨论应该如何删除节点,首先我们肯定不能从非叶子节点开始删,因为我们根本不知道他的左子树和右子树的信息,所以应该从叶子节点开始删,所以这里删除的标志就是判断叶子节点的值是否为0,如果为0,则返回nullptr,证明将这个节点删除了,nullptr就是将删除的信息带给非叶子节点,如果叶子节点不是0,则返回当前节点,如果返回的是非空节点这个信息的话,就表示它的子树不是0,
函数头
函数头:dfs(root)
函数体
就是上面所讲的
递归出口
当遇到空节点的时候,直接返回空节点。
代码展示
class Solution {
public:TreeNode* pruneTree(TreeNode* root) {//空节点直接返回if(root==nullptr){return nullptr;}//递归左子树root->left=pruneTree(root->left);//递归右子树root->right=pruneTree(root->right);//判断叶子节点的值if(root->left==nullptr&&root->right==nullptr&&root->val==0){//delete root防止内存泄露return nullptr;}//如果是1,则不删除else{return root;}}
};
4.验证二叉搜索树
题目很简单,就是验证一棵树是否是二叉搜索树,如果是,则直接返回true,如果不是则返回false
思路分析
首先我们要知道一个二叉搜索树的一个很重要的性质,就是它的中序遍历是一个有序序列,这是一个这道题重要的突破口,我们只需要记录它中序遍历的前驱的节点,然后与当前节点进行比较即可,如果比当前节点大则当前情况来看的话是二叉搜索树,如果不满足的话,直接返回false,根本不需要进行判断了。注意,当返回结果的时候,我们要求左子树和右子树都满足还有根节点都满足二叉搜索树,才是二叉搜索树,否则不是二叉搜索树
函数头
函数头:dfs(root)
函数体
在定义函数体的时候,我们最好将prev(前驱)定义为全局变量,因为全局变量,随着地递归不会改变,我们只能手动改变
递归出口
当递归到空节点的时候,直接返回true,因为空节点就是二叉搜索树
代码展示
class Solution {long prev=LONG_MIN;
public:bool isValidBST(TreeNode* root) {if(root==nullptr){return true;}bool left=isValidBST(root->left);//左子树都不满足,则不用递归右子树了//剪枝if(left==false){return false;}//用cur表示当前节点是否满足bool cur=false;//如果满足则进入将cur置为trueif(root->val>prev)cur=true;//不满足直接返回falseelsereturn false;//更新prevvprev=root->val;//递归右子树bool right=isValidBST(root->right);//返回左子树和右子树和当前节点是否满足是否是二叉搜索树return left&&right&&cur;}
};
5.二叉搜索树中第k小元素
对于这道题还是和上一道题类似。
思路:
对于上面这个二叉搜索树,我们要求这个二叉搜索树的第k小的节点是不是应该用中序遍历,因为中序遍历是有序的,当我们中序遍历到叶子节点的时候,我们就可以开始数了,所以这里我们需要一个count来计数,计算这个是第几小,count我们最好选择全局变量,因为全局变量不会随着递归而改变,当我们中序遍历到叶子节点的时候,我们的count就应该–操作,每次–之后,我么都应该判断一下这个count是否已经==0了,如果等于0,我们用一个全局变量ret来接收一下这个值。
到3的时候直接用全局变量接收这个值。
代码展示
class Solution {int count;int ret;
public:int kthSmallest(TreeNode* root, int k) {count=k;dfs(root);return ret;}void dfs(TreeNode*root){//count==0是剪枝if(root==nullptr||count==0)return;dfs(root->left);count--;if(count==0){ret=root->val;}dfs(root->right);}
};
上面代码的递归出口的count==0,可不写,因为我们也可以继续递归,count为0只有一次,所以如果count等于0,我们可以直接不用递归了,直接返回。
6.二叉树的所有路径
这道题需要返回的是,一个路径的数组,类型是string类的
思路分析
这道题要返回string类的数组的话,为了不被递归影响到数组的值,所以我们最好还是创建一个全局变量数组,string的来记录这个每个路径,还需要一个局部变量,还需要一个string变量来记录当前路径。
函数头
函数头:dfs(root,path)
传递一个局部变量的路径
函数体
注意函数体部分,我们分析一下,我们要求出所有路径,我们先看看下面的输入和输出样例。
对于这个输出样例,我们可以看到这个string不仅需要路径的值还需要一个->将其串联起来,所以这里我们就分为了两种情况,一种是非叶子节点,一种是叶子节点,对于非叶子节点,我们不仅需要向string变量中加入当前值的字符,还需要向里加入两个符号“->”,但是对于叶子节点来说,我们只需要向里添加当前节点对应值的字符就可以了,注意:添加完之后,我们将string类的变量丢进string类的数组中,注意:这里我们不创建全局变量string的原因是因为当我们返回到上一节点的时候,因为全局变量不会改变,所以我们需要手动删除当前路径下的不需要的所有节点,才能进入下一个分支,就拿上面的图为例子,当我们要进入右子树的时候,我们必须将左子树中的2和3删完之后,只留下1才能进入右子树分支,但是对于局部变量,则不一样,注意:这里我们创建局部变量的时候,传参也要传拷贝构造,而不是引用,传引用的话和创建全局变量没有任何区别,传递拷贝构造的话,每次返回上一个分支都是一个新的string,不会存在什么删除不需要的情况。
代码展示
class Solution {//全局若string数组,用来存储字符串vector<string> ret;
public:vector<string> binaryTreePaths(TreeNode* root){//创建path记录路径string path;dfs(root,path);//返回字符数组return ret;}void dfs(TreeNode*root,string path){//叶子节点的处理方式if(root->left==nullptr&&root->right==nullptr){path+=to_string(root->val);ret.push_back(path);return;}//非叶子节点的处理方式path+=to_string(root->val)+"->";//左子树不为空才递归,为空直接剪枝if(root->left)dfs(root->left,path);//右子树也一样if(root->right)dfs(root->right,path);}
};
总结
通过本文的探讨,我们了解了深度优先搜索(DFS)在解决二叉树问题中的强大功能和广泛应用。DFS 通过其递归和迭代两种实现方式,为我们提供了处理二叉树的不同策略,使得问题的求解变得更加灵活。无论是前序遍历、中序遍历还是后序遍历,DFS 都能够有效地遍历二叉树的每一个节点,从而帮助我们解决各种实际问题,如路径求和、树的对称性检查以及节点间距离计算等。
希望通过本文的介绍,大家对 DFS 在二叉树问题中的应用有了更深入的理解,并能够在实际编程中灵活运用这些技巧来解决复杂的树结构问题。感谢阅读,期待在你们的代码中见到这些算法的身影!如果有任何疑问或进一步的讨论,欢迎在评论区留言,我们一起交流学习。
相关文章:

DFS:解决二叉树问题
文章目录 了解DFS1.计算布尔二叉树的值思路代码展示 2.求根节点到叶节点数字之和思路代码展示 3.二叉树剪枝思路代码展示 4.验证二叉搜索树思路分析代码展示 5.二叉搜索树中第k小元素思路:代码展示 6.二叉树的所有路径思路分析代码展示 总结 了解DFS 所谓DFS就是就…...
【相机开发问题总结】曝光补偿ExposureCompensation未生效异常分析及解决
问题描述 做一款相机应用时,用户反馈相机预览界面太暗了,由于我们是嵌入式设备,没有手机那么高大上得闪光灯补光,因此只能考虑从软件层面提高界面亮度,还好找到了曝光补偿,但是开发过程中发现曝光补偿未生…...
Flutter 中的 DateRangePickerDialog 小部件:全面指南
Flutter 中的 DateRangePickerDialog 小部件:全面指南 在 Flutter 应用开发中,日期和时间的选择是一项常见的用户交互需求。DateRangePickerDialog 是一个方便的小部件,它提供了一个对话框界面,允许用户选择日期范围。这个小部件…...

MCS-51伪指令
上篇我们讲了汇编指令格式,寻址方式和指令系统分类,这篇我们讲一下单片机伪指令。 伪指令是汇编程序中用于指示汇编程序如何对源程序进行汇编的指令。伪指令不同于指令,在汇编时并不翻译成机器代码,只是会汇编过程进行相应的控制…...

vue3 vant4实现抖音短视频功能
文章目录 1. 实现效果2. 精简版核心代码3. 完整功能点(本文章不写,只写核心代码) 1. 实现效果 2. 精简版核心代码 使用的 vue3 vant4组件使用van-swipe进行轮播图切换实现 <template><div :style"{width: width px,overflo…...
C#结合JS实现HtmlTable动态添加行并保存到数据库
目录 需求 效果视频演示 范例运行环境 准备数据源 数据表设计 UI及表结构Json配置 Json数据包提交配置 设计实现 前端UI Javascript 脚本 Jquery引用 C# 服务端操作 小结 需求 在 Web 应用项目中,实现一对多录入的数据管理功能是一项常见的应用。因此…...

Unity Render Streaming 云渲染 外网访问
初版: 日期:2024.5.20 前言:临时思路整理,后期会详细补充 环境: 1. 阿里云服务器 需要安装好nodejs 、npm 2. windows电脑,需安装好 nodejs 、npm 3.Unity 2021.3.15f1 4.Unity Render Streaming …...
美易官方:Copilot全面升级!
Copilot的全面升级,无疑在科技界掀起了一场革命性的浪潮!微软在一夜之间推出的这50余项AI更新,不仅彰显了其在人工智能领域的深厚底蕴,更是让全球用户见证了计算机理解人类能力的一次飞跃。 在微软2024年Build开发者大会的主题演…...
深入了解FreeRTOS:实时操作系统的核心概念和应用
前言: 在当今数字化世界中,嵌入式系统扮演着至关重要的角色,从工业自动化到智能设备,无所不在。而实时操作系统(RTOS)则是这些系统的核心引擎,它们负责管理任务、资源和时间,确保系统…...

Spring框架学习笔记(五):JdbcTemplate 和 声明式事务
基本介绍:通过 Spring 框架可以配置数据源,从而完成对数据表的操作。JdbcTemplate 是 Spring 提供的访问数据库的技术。将 JDBC 的常用操作封装为模板方法 1 JdbcTemplate 使用前需进行如下配置 1.1 在maven项目的pom文件加入以下依赖 <dependencies…...

考研计组chap1计算机系统概述
目录 一、计算机发展历程(不考了) 二、计算机硬件的基本组成 3 1.五个部分 (1)输入设备 (2)控制器 (3)运算器 (4)(主)存储器 (5࿰…...
如何使用Python中的生成器
如何使用Python中的生成器 在Python中,生成器是一种特殊的迭代器,它允许你逐个地生成值,而不是一次性地计算并存储所有的值。这对于处理大量数据或者无限序列特别有用,因为它能够节省内存并提高效率。 生成器通常是通过以下两种…...
C语言 读取 MIDI文件头部
在C语言中直接读取MIDI文件并不简单,因为MIDI文件是一种包含音乐事件(如音符的开始和结束、控制信号等)的二进制格式,而不是像文本文件那样容易解析。不过,你可以通过以下步骤来实现: 了解MIDI文件格式&am…...
C# Winform实现五子棋游戏(代完善)
实现了基本的玩法。 BoardController.cs using System;namespace GomokuGame {public class BoardController{private static BoardController instance;private readonly int[,] board;private const int boardSize 15;private BoardController(){board new int[boardSize…...

文档档案管理系统整体建设方案书(实际项目原件word2024)
1.系统概述 1.1.需求描述 1.2.需求分析 1.3.重难点分析 1.4.重难点解决措施 2.系统架构设计 2.1.系统架构图 2.2.关键技术 数据备份技术 3.系统功能设计 3.1.功能清单列表 3.2.基础数据管理 3.3.位置管理 3.4.文档使用 3.5.文档管理 软件全套资料包获取方式①:软件项…...
React与Vue的区别?
一、区别: 1. 语法 Vue采用自己特有的模板语法; React是单向的,采用jsx语法创建react元素。 2.监听数据变化的实现原理不同 Vue2.0 通过Object.defineproperty()方法的getter/setter属性, 实现数据劫持, 每次修改完数据会触发diff算法(双端对比) …...
leetcode 2115.从给定原材料中找到所有可以做出的菜
思路:拓补排序,哈希表 在思路上其实很好发现,我们需要有一个明确的做菜顺序,也就是说需要定下来我们根据原材料先做哪些菜,然后做完该做的菜之后,后来又能做哪些菜。 你也发现了,这个顺序其实…...

Opencompass模型评测教程
模型评测 模型评测非常关键,目前主流的方法主要可以概括为主观评测和客观评测,主观评测又可以分为两种形式:人工判断或者和模型竞技场。客观评测一般采用评测数据集的形式进行模型评测。本教程使用Opencompass工具进行对Internlm2-7b模型进行…...
什么是安全测试,如何进行安全测试?
什么是安全测试? 概述 安全测试是一种旨在识别系统、网络或应用程序中的安全漏洞的测试方法。其目标是确保系统能够抵御恶意攻击,保护数据的机密性、完整性和可用性。安全测试通常包括漏洞扫描、渗透测试、代码审计和安全评估等多个方面。 安全测试的…...
ros的pcl库中对于自己定义的消息,调用pcl库时总是报错 c++
首先定义自己的消息类型 struct CustomPoint { // 定义点类型结构PCL_ADD_POINT4D; // 该点类型有4个元素float intensity 0.0;uint32_t zone;uint32_t ring;uint32_t sector;EIGEN_MAKE_ALIGNED_OPERATOR_NEW // 确保new操作符对齐操作 } EIGEN_ALIGN16; // 强制SSE对齐POIN…...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...

EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...