【算法思想·二叉搜索树】基操篇

本文参考labuladong算法笔记[二叉搜索树心法(基操篇) | labuladong 的算法笔记]

1、概述

我们前文 东哥带你刷二叉搜索树(特性篇) 介绍了 BST 的基本特性,还利用二叉搜索树「中序遍历有序」的特性来解决了几道题目,本文来实现 BST 的基础操作:判断 BST 的合法性、增、删、查。其中「删」和「判断合法性」略微复杂。

BST 的基础操作主要依赖「左小右大」的特性,可以在二叉树中做类似二分搜索的操作,寻找一个元素的效率很高。

对于 BST 相关的问题,你可能会经常看到类似下面这样的代码逻辑:

def BST(root: TreeNode, target: int) -> None:if root.val == target:# 找到目标,做点什么if root.val < target:BST(root.right, target)if root.val > target:BST(root.left, target)

这个代码框架其实和二叉树的遍历框架差不多,无非就是利用了 BST 左小右大的特性而已。接下来看下 BST 这种结构的基础操作是如何实现的。

2、判断 BST 的合法性

力扣98 .「验证二叉搜索树」

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

示例 1:

输入:root = [2,1,3]
输出:true

示例 2:

输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。

提示:

  • 树中节点数目范围在[1, 104] 内
  • -231 <= Node.val <= 231 - 1

【思路】

注意,这里是有坑的哦。按照 BST 左小右大的特性,每个节点想要判断自己是否是合法的 BST 节点,要做的事不就是比较自己和左右孩子吗?感觉应该这样写代码:

def isValidBST(root: TreeNode) -> bool:if root is None:return True# root 的左边应该更小if root.left is not None and root.left.val >= root.val:return False# root 的右边应该更大if root.right is not None and root.right.val <= root.val:return Falsereturn isValidBST(root.left) and isValidBST(root.right)

 但是这个算法出现了错误,BST 的每个节点应该要小于右边子树的所有节点,下面这个二叉树显然不是 BST,因为节点 10 的右子树中有一个节点 6,但是我们的算法会把它判定为合法 BST:

错误的原因在于,对于每一个节点 root,代码值检查了它的左右孩子节点是否符合左小右大的原则;但是根据 BST 的定义,root 的整个左子树都要小于 root.val,整个右子树都要大于 root.val

问题是,对于某一个节点 root,他只能管得了自己的左右子节点,怎么把 root 的约束传递给左右子树呢?请看正确的代码:

【python】

class Solution:def isValidBST(self, root: TreeNode) -> bool:return self._isValidBST(root, None, None)# 定义:该函数返回 root 为根的子树的所有节点是否满足 max.val > root.val > min.valdef _isValidBST(self, root: TreeNode, min: TreeNode, max: TreeNode) -> bool:# base caseif root is None:return True# 若 root.val 不符合 max 和 min 的限制,说明不是合法 BSTif min is not None and root.val <= min.val:return Falseif max is not None and root.val >= max.val:return False# 根据定义,限定左子树的最大值是 root.val,右子树的最小值是 root.valreturn self._isValidBST(root.left, min, root) and self._isValidBST(root.right, root, max)

3、在 BST 中搜索元素

700 .「二叉搜索树中的搜索」

给定二叉搜索树(BST)的根节点 root 和一个整数值 val

你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。

示例 1:

输入:root = [4,2,7,1,3], val = 2
输出:[2,1,3]

示例 2:

输入:root = [4,2,7,1,3], val = 5
输出:[]

提示:

  • 树中节点数在 [1, 5000] 范围内
  • 1 <= Node.val <= 107
  • root 是二叉搜索树
  • 1 <= val <= 107

如果是在一棵普通的二叉树中寻找,可以这样写代码:

def searchBST(root, target):if not root:return Noneif root.val == target:return root# 当前节点没找到就递归地去左右子树寻找left = searchBST(root.left, target)right = searchBST(root.right, target)return left if left else right

这样写完全正确,但这段代码相当于穷举了所有节点,适用于所有二叉树。那么应该如何充分利用 BST 的特殊性,把「左小右大」的特性用上?

很简单,其实不需要递归地搜索两边,类似二分查找思想,根据 target 和 root.val 的大小比较,就能排除一边。我们把上面的思路稍稍改动:

def searchBST(root: TreeNode, target: int) -> TreeNode:# 如果二叉树为空,直接返回if not root:return None# 去左子树搜索if root.val > target:return searchBST(root.left, target)# 去右子树搜索if root.val < target:return searchBST(root.right, target)# 当前节点就是目标值return root

4、在 BST 中插入一个数

对数据结构的操作无非遍历 + 访问,遍历就是「找」,访问就是「改」。具体到这个问题,插入一个数,就是先找到插入位置,然后进行插入操作。

因为 BST 一般不会存在值重复的节点,所以我们一般不会在 BST 中插入已存在的值。下面的代码都默认不会向 BST 中插入已存在的值

上一个问题,我们总结了 BST 中的遍历框架,就是「找」的问题。直接套框架,加上「改」的操作即可。

一旦涉及「改」,就类似二叉树的构造问题,函数要返回 TreeNode 类型,并且要对递归调用的返回值进行接收

701. 「二叉搜索树中的插入操作」

给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。

注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。

示例 1:

输入:root = [4,2,7,1,3], val = 5

输出:[4,2,7,1,3,5]
解释:另一个满足题目要求可以通过的树是:

示例 2:

输入:root = [40,20,60,10,30,50,70], val = 25
输出:[40,20,60,10,30,50,70,null,null,25]

示例 3:

输入:root = [4,2,7,1,3,null,null,null,null,null,null], val = 5
输出:[4,2,7,1,3,5]

提示:

  • 树中的节点数将在 [0, 104]的范围内。
  • -108 <= Node.val <= 108
  • 所有值 Node.val 是 独一无二 的。
  • -108 <= val <= 108
  • 保证 val 在原始BST中不存在。

【python】

class Solution:def insertIntoBST(self, root: TreeNode, val: int) -> TreeNode:if not root:# 找到空位置插入新节点return TreeNode(val)# 去右子树找插入位置if root.val < val:root.right = self.insertIntoBST(root.right, val)# 去左子树找插入位置if root.val > val:root.left = self.insertIntoBST(root.left, val)# 返回 root,上层递归会接收返回值作为子节点return root

5、在 BST 中删除一个数

450. 删除二叉搜索树中的节点 | 力扣  

给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。

一般来说,删除节点可分为两个步骤:

  1. 首先找到需要删除的节点;
  2. 如果找到了,删除它。

示例 1:

输入:root = [5,3,6,2,4,null,7], key = 3
输出:[5,4,6,2,null,null,7]
解释:给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。
一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。
另一个正确答案是 [5,2,6,null,4,null,7]。

示例 2:

输入: root = [5,3,6,2,4,null,7], key = 0
输出: [5,3,6,2,4,null,7]
解释: 二叉树不包含值为 0 的节点

示例 3:

输入: root = [], key = 0
输出: []

提示:

  • 节点数的范围 [0, 104].
  • -105 <= Node.val <= 105
  • 节点值唯一
  • root 是合法的二叉搜索树
  • -105 <= key <= 105

进阶: 要求算法时间复杂度为 O(h),h 为树的高度。

【思路】

这个问题稍微复杂,跟插入操作类似,先「找」再「改」,先把框架写出来再说:

def deleteNode(root: TreeNode, key: int) -> TreeNode:if root.val == key:# 找到啦,进行删除elif root.val > key:# 去左子树找root.left = deleteNode(root.left, key)elif root.val < key:# 去右子树找root.right = deleteNode(root.right, key)return root

找到目标节点了,比方说是节点 A,如何删除这个节点,这是难点。因为删除节点的同时不能破坏 BST 的性质。有三种情况,用图片来说明。

情况 1A 恰好是末端节点,两个子节点都为空,那么它可以当场去世了。

if (root.left == null && root.right == null)return null;

情况 2A 只有一个非空子节点,那么它要让这个孩子接替自己的位置。

// 排除了情况 1 之后
if (root.left == null) return root.right;
if (root.right == null) return root.left;

情况 3A 有两个子节点,麻烦了,为了不破坏 BST 的性质,A 必须找到左子树中最大的那个节点,或者右子树中最小的那个节点来接替自己。我们以第二种方式讲解。

if (root.left != null && root.right != null) {// 找到右子树的最小节点TreeNode minNode = getMin(root.right);// 把 root 改成 minNoderoot.val = minNode.val;// 转而去删除 minNoderoot.right = deleteNode(root.right, minNode.val);
}

三种情况分析完毕,填入框架,简化一下代码:

class Solution:'''思路:1、考虑每个节点应该做什么,应该用递归思路求解2、对于单个节点,分为节点值 大于/小于/等于 key三种情况讨论3、节点值大于或小于key时,直接递归调用 deleteNode 函数,重塑该节点4、节点值等于key时,考虑被删节点处的三种结构,无子节点、有一个子节点、有两个子节点5、若有两个子节点,可找到左子树最大节点或右子树最小节点将其删除,并对node做替换6、min_node.left = root.left, min_node.right = root.right'''def deleteNode(self, root: TreeNode, key: int) -> TreeNode:if root == None:return Noneif root.val == key:# 这两个 if 把情况 1 和 2 都正确处理了if root.left == None:return root.rightif root.right == None:return root.left# 处理情况 3# 获得右子树最小的节点minNode = self.getMin(root.right)# 删除右子树最小的节点root.right = self.deleteNode(root.right, minNode.val)# 用右子树最小的节点替换 root 节点minNode.left = root.leftminNode.right = root.rightroot = minNodeelif root.val > key:root.left = self.deleteNode(root.left, key)elif root.val < key:root.right = self.deleteNode(root.right, key)return rootdef getMin(self, node: TreeNode) -> TreeNode:# BST 最左边的就是最小的while node.left != None:node = node.leftreturn node

6、总结

1、如果当前节点会对下面的子节点有整体影响,可以通过辅助函数增长参数列表,借助参数传递信息。

2、掌握 BST 的增删查改方法。

3、递归修改数据结构时,需要对递归调用的返回值进行接收,并返回修改后的节点。

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

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

相关文章

MathType7.9绿色和谐版激活补丁包下载

MathType7.9中文版&#xff1a;让你的数学公式更酷炫✨ 嘿&#xff0c;亲爱的数学迷们&#xff01;今天我要给你们安利一款超级炫酷的数学公式编辑器——MathType7.9中文版。这款软件不仅能让你轻松输入各种复杂的数学公式&#xff0c;还能让你的公式看起来更加酷炫哦&#xf…

Java项目——苍穹外卖(二)

Redis 简介 Redis是一个基于内存的key-value结构数据库 基于内存存储&#xff0c;读写性能高适合存储热点数据&#xff08;热点商品、资讯、新闻&#xff09;企业应用广泛 基础操作 启动 在redis安装目录中打开cmd&#xff0c;输入如上图指令即可启动&#xff0c;按下crtl…

【嘉立创EDA】画PCB板中为什么要两面铺铜为GND,不能一面GND一面VCC吗?

在新手画板子铺铜时&#xff0c;经常会铺一面GND一面VCC。但一般情况下我们不会这样铺铜。下面将详细分析为什么要两面铺铜为GND&#xff0c;而不是一面GND一面VCC的原因&#xff1a; 提高散热能力 金属导热性&#xff1a;金属具有良好的导热性&#xff0c;铺铜可以有效分散PCB…

unity的学习

因为需要构建一个三维物理环境,所以学习了unity,半天就可以,非常简单清晰 1.安装 去官网下载unity hub . 然后需要下载editor,但注意已经有了vs2022就不要再下一次了,下的时候会全放c盘,再安装.c盘都装不下了. 如果美游vs2022,就先自己把vs2022安装好,再安装unity hub.(其实不…

基于YOLOv5的农作物叶片病害识别系统

植物农作物叶片病虫害识别系统&#xff1a;农作物叶片病害AI检测与识别系统 源码 带UI界面说明视频 模型&#xff1a;yolov5 功能: 农作物叶片病害检测系统用于智能检测常见农作物叶片病害情况&#xff0c;自动化标注、记录和保存病害位置和类型&#xff0c;辅助作物病害防治以…

MyBatis XML映射文件编写【后端 18】

MyBatis XML映射文件编写 MyBatis 是一个优秀的持久层框架&#xff0c;它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解用于配置和原始映射&#xff0c;将接口和 Java 的 POJOs …

红帽7—Mysql的源码编译

到官网选择源码进行安装 使用wget命令下载链接 下载安装后对文件包进行解压 [rootnginx ~]# tar zxf mysql-boost-5.7.44.tar.gz 安装cmake编译工具 [rootnginx ~]# yum install cmake 使用源码编译安装mysql [rootmysql-node10 mysql-5.7.44]# cmake \ -DCMAKE_INSTALL_PRE…

6个Python小游戏项目源码【免费】

6个Python小游戏项目源码 源码下载地址&#xff1a; 6个Python小游戏项目源码 提取码: bfh3

Stable diffusion 学习过程

diffusion model 讲解&#xff1a; 【较真系列】讲人话-Diffusion Model全解(原理代码公式)_哔哩哔哩_bilibili stable diffusion【CVPR2022】 原始论文&#xff1a; https://arxiv.org/pdf/2112.10752 讲解&#xff1a;【论文简介】Stable Diffusion的基础论文:2112.High…

JZ2440开发板——S3C2440的UART的使用

以下内容源于韦东山课程的学习与整理&#xff0c;如有侵权请告知删除。 一、UART硬件简介 UART&#xff0c;全称是“Universal Asynchronous Receiver Transmitter”&#xff0c;即“通用异步收发器”&#xff0c;也就是我们日常说的“串口”。 它在嵌入式中用途非常广泛&…

牛客周赛 Round 60(下)

构造序列 题目描述 登录—专业IT笔试面试备考平台_牛客网 运行代码 #include <iostream> #include<stdio.h> #include<math.h> using namespace std; int main() {int n, m;cin >> n >> m;int minVal min(n, m);int maxVal max(n, m);cout …

右值生命周期的延长

第一个例子&#xff1a; output&#xff1a; result&#xff08;&#xff09;创建了一个临时对象&#xff0c;它是一个右值&#xff0c;通过结果我们可以看到直到main函数结束时这个对象才调用了析构函数&#xff1b; result&#xff08;&#xff09;返回右值&#xff0c;我…

面向对象分析与设计-系统架构师(六十七)

1网络设计过程包括逻辑网络设计和物理网络设计两个阶段&#xff0c;下面的选项中&#xff0c;&#xff08;&#xff09;应该属于逻辑网络设计阶段的任务。 A选择路由协议 B设备选型 C结构化布线 D机房设计 解析&#xff1a; 物理网络设计的内容包括&#xff1a;设备选型、…

pdf去水印怎么去掉免费?6个pdf去除水印的方法快码住,超级好用!

pdf去水印怎么去掉免费&#xff1f;您是否有一些带有水印的pdf文档&#xff0c;让您感觉到头疼&#xff1f;您又是否希望能够去除这些水印&#xff0c;或者想用其他水印来替换现有的水印&#xff1f;如果是这样的话&#xff0c;我非常推荐您继续阅读本篇文章。本文将为您提供一…

Gateway网关的实现

API网关 网关路由必须支持负载均衡&#xff0c;服务列表是从注册中心拉取的客户端发出请求的URL指向的是网关&#xff0c;URL还必须要包含目标信息网关收到URL&#xff0c;通过一定的规则&#xff0c;要能识别出交给哪个实例去处理网关有能力对请求响应进行修改 引入依赖包 …

使用LangGPT提示词让大模型比较浮点数

使用LangGPT提示词让大模型比较浮点数 背景介绍环境准备创建虚拟环境安装一些必要的库安装其他依赖部署大模型启动图形交互服务设置提示词与测试 LangGPT结构化提示词 背景介绍 LLM在对比浮点数字时表现不佳&#xff0c;经验证&#xff0c;internlm2-chat-1.8b (internlm2-cha…

计算机视觉—3d点云数据基础

点云数据 3d点云数据由来 3d点云 3D Point Cloud是一种用于表示三维空间中对象或场景的数据结构。在最基础的形式中&#xff0c;它是一个包含多个三维坐标点&#xff08;X, Y, Z&#xff09;的集合。这些点是通过对实际物体或场景表面进行离散采样而获得的&#xff0c;因此&a…

代码随想录:动态规划4-5

42.接雨水 题目 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由数组 [0,1,0,2,…

python正则表达式如何不区分大小写

使用python的re模块做模式匹配时&#xff0c;有时需要忽略大小写&#xff0c;只需要在re.search()函数中添加参数re.IGNORECASE即可。 mystring some string pattern some pattern match re.search(pattern, mystring, re.IGNORECASE)

【Linux】理解和解释shell命令的工具

&#x1f41a;作者简介&#xff1a;花神庙码农&#xff08;专注于Linux、WLAN、TCP/IP、Python等技术方向&#xff09;&#x1f433;博客主页&#xff1a;花神庙码农 &#xff0c;地址&#xff1a;https://blog.csdn.net/qxhgd&#x1f310;系列专栏&#xff1a;C语言编程&…