深度优先遍历(DFS)

深度优先遍历(DFS)

  • 1. 计算布尔二叉树的值
  • 2. 求根节点到叶节点数字之和
  • 3.二叉树剪枝
  • 4.验证二叉搜索树
  • 5. 二叉搜索树中第 K 小的元素
  • 6. 二叉树的所有路径

深度优先遍历(DFS,全称为Depth First Traversal),是我们树或者图这样的数据结构中常⽤的⼀种遍历算法。这个算法会尽可能深的搜索树或者图的分⽀,直到⼀条路径上的所有节点都被遍历完毕,然后再回溯到上⼀层,继续找⼀条路遍历。
在⼆叉树中,常⻅的深度优先遍历为:前序遍历中序遍历以及后序遍历
因为树的定义本⾝就是递归定义,因此采⽤递归的⽅法去实现树的三种遍历不仅容易理解⽽且代码很
简洁。并且前中后序三种遍历的唯⼀区别就是访问根节点的时机不同,在做题的时候,选择⼀个适当的遍历顺序,对于算法的理解是⾮常有帮助的。

1. 计算布尔二叉树的值

题目链接:2331. 计算布尔二叉树的值

算法思路:
这道题需要采用DFS,先访问左边的叶子节点,再访问右边的叶子节点,最后再访问根节点,因此可以采用后序遍历

算法流程:

  1. 根据子问题设计出函数头:只需要一个参数root即可,返回值为boolean类型
  2. 只关心某一个子问题,设计函数体:首先得到左边叶子的值,再得到右边叶子的值,再根据跟节点的值,将左右叶子的值进行操作
  3. 递归的出口:当访问到叶子节点时,返回该叶子节点的值

实现代码:

class Solution {public boolean evaluateTree(TreeNode root) {if(root.left == null && root.right == null) return root.val == 0 ? false : true;boolean left = evaluateTree(root.left);boolean right = evaluateTree(root.right);return root.val == 2 ? left | right : left & right;}
}

2. 求根节点到叶节点数字之和

题目链接:129. 求根节点到叶节点数字之和

解题思路:

  1. 将父亲节点的值与当前节点的值整合起来向下传递,再将传递下来的值与下一个节点的值整合,并向下传递,直到遇到叶子节点时,将其与根节点的值整合后返回
  2. 每个非叶子节点将左子树和右子树返回的值相加,并返回

在这里插入图片描述

递归函数设计:

  1. 函数头:需要包含一个根节点参数,一个前序和参数,返回值为int
  2. 函数体:更新前序和,向下传递,直至遇到叶子节点返回更新后的前序和,非叶子节点将左右返回的前序和相加并返回
  3. 递归的出口:遇到叶子节点,返回前序和与叶子节点值的整合值

实现代码:

class Solution {public int sum;public int sumNumbers(TreeNode root) {return dfs(root, 0);}private int dfs(TreeNode root, int preSum) {preSum = 10 * preSum + root.val;if(root.left == null && root.right == null) return preSum;int ret = 0;if(root.left != null) ret += dfs(root.left, preSum);if(root.right != null) ret += dfs(root.right, preSum);return ret;}
}

3.二叉树剪枝

题目链接:814. 二叉树剪枝

自己的解题思路:
从宏观上看,如果一个节点的左边可以移除,右边也可以移除,并且该节点的值为0,那么返回true给上一个节点,表明以该节点为根节点的树都可以移除。因此,该题需要采用后续遍历

实现代码:

class Solution {public TreeNode pruneTree(TreeNode root) {boolean rootTree = dfs(root);if(rootTree) return null;return root;}private boolean dfs(TreeNode root) {if(root == null) return true;boolean leftTree = dfs(root.left);if(leftTree) root.left = null;boolean rightTree = dfs(root.right);if(rightTree) root.right = null;return leftTree && rightTree && root.val == 0;}
}

答案解题思路:

  1. 先处理掉左子树的剪枝,再处理右子树的剪枝,最后判断当前当前这棵树是否需要剪掉,因此需要采用后序遍历
  2. 如果需要剪掉,那么父亲节点的左(或右)节点就需要置为NULL,即需要向上返回一个NULL;而当不需要剪掉时,为了保证递归解决问题的统一性,向上返回当前节点
  3. 递归出口为当遍历到空节点时,向上返回NULL

实现代码:

class Solution {public TreeNode pruneTree(TreeNode root) {if (root == null)return null;root.left = pruneTree(root.left);root.right = pruneTree(root.right);if (root.left == null && root.right == null && root.val == 0)root = null;return root;}
}

4.验证二叉搜索树

题目链接: 98. 验证二叉搜索树

算法思路:
二叉搜索树的特点是:采用中序遍历得到的是一个严格递增的序列。因此,这道题采用中序遍历来解题。我们需要初始化一个无穷小(要比二叉搜索树里的最小值还要小,而题意-231 <= Node.val <= 231 - 1,比int类型的最小值还要小,可以用long类型的最小值)的全局变量prev用来记录root的前驱节点值,比较prev的值与当前节点的值,然后prev修改为当前节点的值

算法流程:

  1. 定义一个全局变量,初始化为无穷小,用来记录前驱节点的值
  2. 递归的出口:当root == null时,返回true
  3. 先检查左子树是否是二叉搜索树,再判断当前节点是否满足二叉搜索树的性质(即前驱节点的值小于当前节点的值),再继续修改prev的值为当前节点的值,再去判断右子树是否是二叉搜索树
  4. 只有当左子树为二叉搜索树,右子树为二叉搜索树,当前的节点也满足二叉搜索树的性质时,这棵树才是二叉搜索树

实现代码:

class Solution {public long prev = Long.MIN_VALUE;public boolean isValidBST(TreeNode root) {if(root == null) return true;boolean leftTree = isValidBST(root.left);//剪枝if(!leftTree) return false;boolean cur = true;if(root.val <= prev) cur = false;//剪枝if(!cur) return false;prev = root.val;boolean rightTree = isValidBST(root.right);return leftTree && rightTree && cur;}
}

当左子树不是二叉搜索树,就表明已经这棵树不是二叉搜索树了,也就没有必要进行后续的操作了,直接向上返回false即可;同理,当当前节点不满足二叉搜索树的性质时,也直接向上返回false。剪枝操作是为了加快搜索过程

5. 二叉搜索树中第 K 小的元素

题目链接:230. 二叉搜索树中第 K 小的元素

解题思路: 利用中序遍历,当访问到第k个“根节点”时,该节点对应的val值就是我们要找的第 K 小的元素

算法流程:

  1. 递归的出口:当根节点为空时,返回-1,表明没有找到
  2. 递归去左子树上去找,判断当前根节点是否是所需要找的节点(即判断k是否等于1),将k–,再递归去右子树上找
class Solution {public int K;public int kthSmallest(TreeNode root, int k) {K = k;return kthSmallest(root);}private int kthSmallest(TreeNode root) {if(root == null) return -1;int leftFind = kthSmallest(root.left, K);//剪枝if(leftFind != -1) return leftFind;if(K == 1) return root.val;K--;int rightFind = kthSmallest(root.right, K);return rightFind;}
}

注意: 这里需要定义一个全局变量K,而不是使用k作为递归的参数。因为当回溯时,k的值同样会回溯,这不符合算法的流程

6. 二叉树的所有路径

题目链接:257. 二叉树的所有路径

解题思路:

  1. 除了根节点,其他节点前面都需要添加一个“->”
  2. 需要一个字符串来添加路径,因此函数头需要一个字符串
  3. 采用前序遍历,将根节点放入字符串中,再去遍历左子树、右子树
  4. 当遍历到根节点时,将这时的字符串放入字符数组中
  5. 递归的出口是:root == null

按照思路,我们能写出如下的代码

class Solution {private List<String> lists;public List<String> binaryTreePaths(TreeNode root) {lists = new LinkedList<String>();StringBuffer strBuff = new StringBuffer();dfs(root, strBuff);return lists;}private void dfs(TreeNode root, StringBuffer path) {if(root == null) return;if(!path.isEmpty()) path.append("->");path.append(root.val);if(root.left == null && root.right == null) {lists.add(path.toString());return;}dfs(root.left, path);dfs(root.right, path);}
}

实际上,这个代码是存在问题的!因为我们在递归完成后,没有对当前添加到 path的节点值进行删除操作,这就会导致在处理完这个路径后,后续的路径会附加在这个路径的字符串后面,从而导致最终的结果不正确
在这里插入图片描述
这是为什么呢?当函数返回时,path的值不是会回退到上一个函数的值吗?

答:当一个函数执行完毕返回时,函数内部的局部变量(比如基本数据类型的变量等)所占用的栈空间会被自动回收,其状态确实会 “撤销”,也就是这些局部变量会随着函数栈帧的销毁而消失。

例如,如果函数中有一个简单的 int 类型局部变量 count,每次进入函数它有不同的值,函数返回后,这个 count 变量所占用的内存空间会被释放,下次再进入该函数时,它可以重新被初始化并使用,从这个角度看是有自动 “撤销” 的过程。

递归确实会返回到上一个状态,但现在我们现在创建的字符串是一个对象,由于可变对象的状态管理不同于基本数据类型(如整数或字符),我们需要手动管理这种状态,以确保路径的正确记录

因此,我们需要在原代码的基础上,对字符串进行回溯(“恢复现场”)。另外,当我们解题时,如果使用了全局变量,也要去考虑该变量的回溯

修改后的代码:

class Solution {private List<String> lists;public List<String> binaryTreePaths(TreeNode root) {lists = new LinkedList<String>();StringBuffer strBuff = new StringBuffer();dfs(root, strBuff);return lists;}private void dfs(TreeNode root, StringBuffer path) {if(root == null) return;if(path.length() > 0) path.append("->");path.append(root.val);if(root.left == null && root.right == null) {lists.add(path.toString());} else {dfs(root.left, path);dfs(root.right, path);}//回溯int lastIndex = path.lastIndexOf(String.valueOf(root.val)) - 2;if(lastIndex >= 0) {path.delete(lastIndex, path.length());}}
}

另外,我们还可以通过创建一个局部变量——path的副本。在每次递归调用时,使用传入路径的副本。这允许每次递归都维护独立的路径,避免修改其他层级的路径。通过将 path 的内容复制到新的 StringBuffer _path 中,确保上下文保持准确,不会影响其他节点的路径。

代码如下:

class Solution {private List<String> lists;public List<String> binaryTreePaths(TreeNode root) {lists = new LinkedList<String>();StringBuffer strBuff = new StringBuffer();dfs(root, strBuff);return lists;}private void dfs(TreeNode root, StringBuffer path) {if(root == null) return;StringBuffer _path = new StringBuffer(path);if(_path.length() > 0) _path.append("->");_path.append(root.val);if(root.left == null && root.right == null) {lists.add(_path.toString());return;}dfs(root.left, _path);dfs(root.right, _path);}
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

免费下载 | 2024算网基础设施成熟度研究报告

《2024算网基础设施成熟度研究报告&#xff08;2023年&#xff09;》的核心内容概括如下&#xff1a; 算网基础设施总体发展态势&#xff1a; 算网基础设施成为数字化转型的坚实底座&#xff0c;推动算力与网络的深度融合。 算网基础设施已上升为各国信息战略的重要抓手。 算…

ITK-腐蚀

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 腐蚀原理 ‌‌图像形态学腐蚀是图像处理中的一种基本操作&#xff0c;主要用于图像细化、目标提取、去除小的干扰物体以及在特定…

MySQL多表查询时有哪些连接方式?

大家好&#xff0c;我是锋哥。今天分享关于【MySQL多表查询时有哪些连接方式?】面试题。希望对大家有帮助&#xff1b; MySQL多表查询时有哪些连接方式? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在 MySQL 中进行多表查询时&#xff0c;常见的连接方式有以下…

Ollama管理本地开源大模型,用Open WebUI访问Ollama接口

现在开源大模型一个接一个的,而且各个都说自己的性能非常厉害,但是对于我们这些使用者,用起来就比较尴尬了。因为一个模型一个调用的方式,先得下载模型,下完模型,写加载代码,麻烦得很。 对于程序的规范来说,只要东西一多,我们就需要一个集中管理的平台,如管理python…

Docker 安装 sentinel

Docker 安装系列 1、拉取 [rootTseng ~]# docker pull bladex/sentinel-dashboard Using default tag: latest latest: Pulling from bladex/sentinel-dashboard 4abcf2066143: Pull complete 1ec1e81da383: Pull complete 56bccb36a894: Pull complete 7cc80011dc6f: Pull…

Python实现中国象棋

探索中国象棋 Python 代码实现&#xff1a;从规则逻辑到游戏呈现 中国象棋&#xff0c;这款源远流长的棋类游戏&#xff0c;承载着深厚的文化底蕴与策略智慧。如今&#xff0c;借助 Python 与 Pygame 库&#xff0c;我们能够在数字世界中复刻其魅力&#xff0c;深入探究代码背后…

Spring--07-01---@Transactional注解失效的8大场景

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Transactiona1.默认回滚&#xff1a;RuntimeException 1.Transactional注解失效的8大场景1.数据库引擎是否支持事务3.方法不是public的4.自身调用5.数据源没有配置事…

SMMU软件指南SMMU编程之寄存器

安全之安全(security)博客目录导读 本博客介绍了SMMUv3的编程接口&#xff1a; • SMMU寄存器 • 流表&#xff08;Stream table&#xff09; • CD&#xff08;Context Descriptor&#xff09; • 事件队列&#xff08;Event queue&#xff09; • 命令队列&#xff08;…

负载均衡oj项目:介绍

目录 项目介绍 项目演示 项目介绍 负载均衡oj是一个基于bs模式的项目。 用户使用浏览器向oj模块提交代码&#xff0c;oj模块会在所有在线的后端主机中选择一个负载情况最低的主机&#xff0c;将用户的代码提交给该主机&#xff0c;该主机进行编译运行&#xff0c;将结果返回…

python 基于 docx 文件模板生成 docx 或 PDF 文件

需求背景 提供一个Word文档模板&#xff0c;使用python程序替换里边的占位符&#xff0c;替换内容包括文本和图片&#xff0c;然后输出docx或者PDF文件。 功能演示 输入示例 输出示例 实现程序 import os import shutil import subprocess import timefrom docx import Doc…

使用 Ansys Fluent 对气体泄漏检测进行建模

了解使用 Ansys Fluent 仿真气体泄漏和确保安全的前沿技术。 挑战 气体泄漏对人类安全和环境构成重大风险。及早检测气体泄漏可以防止潜在的灾难&#xff0c;包括爆炸、火灾和有毒物质暴露。有效的气体泄漏检测系统对于石油和天然气、化学加工和住宅基础设施等行业至关重要。…

Mac软件推荐

Mac软件推荐 截图SnipasteXnipBob 快捷启动Raycast 系统检测Stats 解压缩The UnarchiverKeka&#xff08;付费&#xff09; 视频播放IINA 视频下载Downie&#xff08;付费&#xff09; 屏幕刘海TopNotchMediaMate&#xff08;付费&#xff09;NotchDrop&#xff08;付费&#x…

在 Kibana 中为 Vega Sankey 可视化添加过滤功能

作者&#xff1a;来自 Elastic Tim Bosman 及 Miloš Mandić 有兴趣在 Kibana 中为 Vega 可视化添加交互式过滤器吗&#xff1f;了解如何利用 “kibanaAddFilter” 函数轻松创建动态且响应迅速的 Sankey 可视化。 在这篇博客中&#xff0c;我们将了解如何启用 Vega Sankey 可视…

阿里云数据库MongoDB版助力极致游戏高效开发

客户简介 成立于2010年的厦门极致互动网络技术股份有限公司&#xff08;以下简称“公司”或“极致游戏”&#xff09;&#xff0c;是一家集网络游戏产品研发与运营为一体的重点软件企业&#xff0c;公司专注于面向全球用户的网络游戏研发与运营。在整个产业链中&#xff0c;公…

数据地图怎么做?推荐这款数据可视化地图生成器

在数字化与信息化高速发展的今天&#xff0c;企业迎来了前所未有的发展机遇&#xff0c;规模迅速扩张&#xff0c;市场版图不断延伸。然而&#xff0c;伴随着这种快速的发展&#xff0c;一个不容忽视的问题逐渐浮出水面——如何精准高效地掌握分布在各地的分公司、业务点乃至整…

MongoDB存储照片和文件存储照片的区别在那里?

一、维度对比 比较维度MongoDB存储照片文件系统存储照片数据模型使用文档存储数据&#xff0c;可以存储不同结构的照片。以文件的形式存储照片&#xff0c;每个文件独立存在。性能高效的数据检索&#xff0c;适用于大规模应用程序中的高效检索和访问。但在处理大量高分辨率图片…

爬虫基础知识点

最近看了看爬虫相关知识点&#xff0c;做了记录&#xff0c;具体代码放到了仓库&#xff0c;本文仅学习使用&#xff0c;如有违规请联系博主删除。 这个流程图是我使用在线AI工具infography生成的&#xff0c;这个网站可以根据url或者文本等数据自动生成流程图&#xff0c;挺…

PCL点云库入门——PCL库可视化之CloudViewer类简单点云信息显示

1、前言 可视化&#xff08;visualization&#xff09;涉及运用计算机图形学和图像处理技术&#xff0c;将数据转换成图像并在屏幕上展示&#xff0c;同时支持交互式处理。在PCL库中&#xff0c;一系列强大的可视化工具可供使用&#xff0c;其中较为流行的包括CloudViewer和PCL…

GUNS搭建

一、准备工作 源码下载&#xff1a; 链接: https://pan.baidu.com/s/1bJZzAzGJRt-NxtIQ82KlBw 提取码: criq 官方文档 二、导入代码 1、导入后端IDE 导入完成需要&#xff0c;需要修改yml文件中的数据库配置&#xff0c;改成自己的。 2、导入前端IDE 我是用npm安装的yarn npm…

Keil5添加stc的库到keil5中

1打开STC-ISP软件 在上图中找到keil仿真设置 再点击 点击之后会弹出选择文件 必须找到c51选中确定即可 之后在keil5中可以看到创建项目选择芯片是可以看到图中的选项