数据结构:图文详解 搜索二叉树(搜索二叉树的概念与性质,查找,插入,删除)


 

目录

搜索二叉树的相关概念和性质

搜索二叉树的查找

搜索二叉树的插入

搜索二叉树的删除

1.删除节点只有右子树,左子树为空

2.删除节点只有左子树,右子树为空

3.删除节点左右子树都不为空

搜索二叉树的完整代码实现

 


搜索二叉树的相关概念和性质

搜索二叉树(Binary Search Tree,简称BST)是一种树形数据结构,具有以下性质:

  1. 每个节点最多有两个子节点,分别称为左子节点和右子节点
  2. 左子节点的值小于父节点的值,右子节点的值大于父节点的值
  3. 中序遍历搜索二叉树得到的序列是有序的

搜索二叉树提供了快速的插入删除搜索操作,因为它能够通过比较节点值来减少搜索的范围。比如,要搜索一个值为x的节点,可以从根节点开始,如果x小于当前节点的值,则继续在左子树中搜索,如果x大于当前节点的值,则继续在右子树中搜索。重复这个过程,直到找到目标节点或者搜索到叶子节点。

插入和删除操作同样也是通过比较节点值来找到合适的位置进行操作的。具体的插入和删除操作可以根据需要进行扩展,保持BST的特性。

BST的时间复杂度取决于树的高度,在最坏情况下(树被构造成链表),时间复杂度为O(n),其中n是节点的数量。但是在平均情况下,BST的时间复杂度是O(log n)

搜索二叉树的应用非常广泛,比如数据库索引、缓存等。它提供了高效的数据访问操作,并且能够保持数据的有序性。

如下图就是一颗搜索二叉树,如果我们对这颗搜索二叉树进行遍历的话,我们就会得到 0 1 2 3 4 5 6 7 8 9 的有序队列,也就是我们上述提到的第三条性质。

我们随便拿到其中的一部分进行说明,对于任意一个节点他的左子节点一定小于该节点,而该节点一定小于该节点的右子节点


搜索二叉树的查找

搜索二叉树一般有三种操作:查找,插入,删除

而在其中查找是最为容易的,同时其他俩个操作也都是基于查找实现的。对于一颗搜索二叉树,因为他的规则是否的明确,节点之间是存在明确的大小关系的,所以我们就利用这个大小关系来进行查找操作,具体操作起来有点像二分查找,我们首先拿要查找的值与根节点的值进行比较,如果根节点的值就是我们要查找的元素,那我们就查找成功,然后返回。如果根节点的值<查找的值,那就说明我们要查找的值在根节点的右子树,如果根节点的值>查找的值,那就说明我们要查找的值在根节点的左侧。然后我们反复的将左右子树的根节点带入进行判断。最终我们就可以得出结果。

我们给出如下的图示举例:

对于这样的搜索过程,我们可以分析他的效率,第一次查找我们排除了左边5个节点,第二次查找我们排除了右边2个节点,我们仅仅进行了3次判断就得到了我们想要的结果。这相较于我们常规的数组二分查找,提高了效率和确定性。由此可见搜索二叉树的效率还是非常高的。

对于这样的搜索过程,我们可以给出相应的代码:

    //查找public boolean search(int val) {TreeNode cur = root;while (cur != null) {if (cur.val == val) {return true;} else if (cur.val < val) {cur = cur.right;} else {cur = cur.left;}}return false;}

搜索二叉树的插入

对于搜索二叉树的插入,我们需要解决俩个问题:

  1. 找到往哪个节点后插入
  2. 往这个节点后插入时选择左子节点还是右子节点

对于第一个问题,其实本质上就是对节点的查找,查找出合适的插入节点;对于第二个问题,我们需要判断插入节点和目标节点的值的大小关系,另外在代码层面实现时有一点需要注意,我们找到插入节点的位置后,在使用指针指向的时候要避免空指针的问题,我们用下图举例:

假如我们要将节点 “7” 插入到搜索二叉树中,原本节点 “6” 的左右子节点都为空,那我们在插入的时候就不能直接将新节点的值赋给这俩个空节点,不然就会出现 null = newNode 这样的空指针异常,所以我们必须要将插入节点的父节点记录下来,通过父节点的指向来进行插入

    //插入public void insert(int val) {TreeNode newNode = new TreeNode(val);if (root == null) {root = newNode;return;}TreeNode cur = root;//记录插入节点TreeNode parent = null;//插入节点的父亲节点while (cur != null) {if (cur.val < val) {parent = cur;cur = cur.right;} else if (cur.val > val) {parent = cur;cur = cur.left;}else {//重复元素不予插入return;}}//判断在父亲节点的左边还是右边进行插入if (parent.val < val) {parent.right = newNode;} else {parent.left = newNode;}}

搜索二叉树的删除

对于整个搜索二叉树的操作中,删除节点是最麻烦的,因为我们需要对很多种情况进行判断

因为BST中元素与元素之间是存在严谨的大小关系的,所以在我们删除元素之后也应该任然保持这样的关系,为此我们将删除节点可能出现的情况做出分类:

  1. 删除节点只有右子树,左子树为空
  2. 删除节点只有左子树,右子树为空
  3. 删除节点左右子树都不为空

尽管我们对删除节点的状态做出了分类,但这任然不能包含所有的情况,上述三种情况是对删除节点的子树情况做出的分类,事实上删除节点的位置,也就是和父节点的关系也会影响我们的删除过程。以下我们分别对上述三种情况做出进一步的分类:

1.删除节点只有右子树,左子树为空

如图,在这种情况下,我们根据删除节点的位置又可以分出三类:

情况一:当删除节点为根节点的时候,因为删除节点没有左子树,所以我们直接将删除节点的右子节点更新为新的根节点,也就是

root = cur.right;

情况二:当删除节点为父亲节点的左子节点的时候,因为删除节点没有左子树,所以我们直接让父亲节点的左指针指向删除节点的右子节点,也就是

parent.left = cur.right;

情况三:当删除节点为父亲节点的右子节点的时候,因为删除节点没有左子树,所以我们直接让父亲节点的右指针指向删除节点的右子节点,也就是

parent.right = cur.right;

以上便是删除节点左子树为空的情况 

2.删除节点只有左子树,右子树为空

如图,在这种情况下,我们根据删除节点的位置又可以分出三类:

情况一:当删除节点为根节点的时候,因为删除节点没有右子树,所以我们直接将删除节点的左子节点更新为新的根节点,也就是

root = cur.left;

情况二:当删除节点为父亲节点的左子节点的时候,因为删除节点没有右子树,所以我们直接让父亲节点的左指针指向删除节点的左子节点,也就是

parent.left = cur.left;

情况三:当删除节点为父亲节点的右子节点的时候,因为删除节点没有右子树,所以我们直接让父亲节点的右指针指向删除节点的左子节点,也就是

parent.right = cur.left;

3.删除节点左右子树都不为空

在这种情况下,我们往往需要在多个叶子节点中选取一个合适的叶子节点,并且将其替换掉删除节点,最后再将这个叶子节点删除,我们拿下图的情况一举例:

那么我们就需要解决一个问题,如何找到合适的叶子节点,在这里笔者给大家提供一个方法:

  • 找删除节点左子树中的最大值
  • 找删除节点右子树中的最小值

上述俩中方法选择一种即可,我们随便拿其中一种方法分析举例

假如我们找到了删除节点右子树中的最小值,那他就可以同时满足俩个条件, 他比删除节点中的所有右子树节点都小,又因为他在右子树,所以他也大于删除节点的左子节点,那么就同时满足了成为搜索二叉树节点的俩个条件,即该节点大于左子节点,小于右子节点。

在删除节点的时候,我们也需要使用父亲节点来避免出现空指针异常的情况,就如前文提到的那样。当我们找到删除节点后,其实我们还需要对删除节点的位置进行判断,就像上图的情况二和情况三一样,原因很简单,删除操作需要使用指针操作,如果删除节点在父亲节点的右边,那我们就需要更改父亲节点的右指针指向,反之则要更改父亲节点的左指针指向,因此我们对这种情况还得做出判断。

我们将删除节点总的代码给出如下:

    //删除public void remove(int val) {TreeNode cur = root;TreeNode parent = null;//先找到要删除的节点的位置while (cur != null) {if (cur.val < val) {parent = cur;cur = cur.right;} else if (cur.val > val) {parent = cur;cur = cur.left;}else {//找到要删除的节点了removeNode(parent,cur);//调用函数删除节点return;}}}private void removeNode(TreeNode parent,TreeNode cur) {//对节点的状态进行分类if (cur.left == null) {//要删除节点左子树为空树if (cur == root) {root = cur.right;}else if (cur == parent.left) {parent.left = cur.right;}else {parent.right = cur.right;}}else if (cur.right == null) {//要删除节点右子树为空树if (cur == root) {root = cur.left;}else if (cur == parent.left) {parent.left = cur.left;}else {parent.right = cur.left;}}else {//要删除节点左右都不为空TreeNode temp = cur.right;//用来指向叶子节点TreeNode tempParent = cur;//用来指向叶子节点的父节点while (temp != null) {tempParent = temp;temp = temp.left;}//找到了要替换的节点cur.val = temp.val;//替换完成后就删除叶子节点if (tempParent.left == temp) {tempParent.left = temp.right;//删除叶子节点}else {tempParent.right = temp.right;//删除叶子节点}}

前半部分是用来找到删除节点,后半部是根据我们的分析对删除节点的情况做出分类判断然后进行删除操作。

搜索二叉树的完整代码实现

为了方便读者使用,我们这里将整个搜索二叉树的完整代码给出:

public class BinarySearchTree {static class TreeNode {int val;TreeNode left;TreeNode right;public TreeNode(int val) {this.val = val;}}public TreeNode root;//查找public boolean search(int val) {TreeNode cur = root;while (cur != null) {if (cur.val == val) {return true;} else if (cur.val < val) {cur = cur.right;} else {cur = cur.left;}}return false;}//插入public void insert(int val) {TreeNode newNode = new TreeNode(val);if (root == null) {root = newNode;return;}TreeNode cur = root;//记录插入节点TreeNode parent = null;//插入节点的父亲节点while (cur != null) {if (cur.val < val) {parent = cur;cur = cur.right;} else if (cur.val > val) {parent = cur;cur = cur.left;}else {//重复元素不予插入return;}}//判断在父亲节点的左边还是右边进行插入if (parent.val < val) {parent.right = newNode;} else {parent.left = newNode;}}//删除public void remove(int val) {TreeNode cur = root;TreeNode parent = null;//先找到要删除的节点的位置while (cur != null) {if (cur.val < val) {parent = cur;cur = cur.right;} else if (cur.val > val) {parent = cur;cur = cur.left;}else {//找到要删除的节点了removeNode(parent,cur);//调用函数删除节点return;}}}private void removeNode(TreeNode parent,TreeNode cur) {//对节点的状态进行分类if (cur.left == null) {//要删除节点左子树为空树if (cur == root) {root = cur.right;}else if (cur == parent.left) {parent.left = cur.right;}else {parent.right = cur.right;}}else if (cur.right == null) {//要删除节点右子树为空树if (cur == root) {root = cur.left;}else if (cur == parent.left) {parent.left = cur.left;}else {parent.right = cur.left;}}else {//要删除节点左右都不为空TreeNode temp = cur.right;//用来指向叶子节点TreeNode tempParent = cur;//用来指向叶子节点的父节点while (temp != null) {tempParent = temp;temp = temp.left;}//找到了要替换的节点cur.val = temp.val;//替换完成后就删除叶子节点if (tempParent.left == temp) {tempParent.left = temp.right;//删除叶子节点}else {tempParent.right = temp.right;//删除叶子节点}}}
}



 本次的分享就到此为止了,希望我的分享能给您带来帮助,也欢迎大家三连支持,你们的点赞就是博主更新最大的动力!如有不同意见,欢迎评论区积极讨论交流,让我们一起学习进步!有相关问题也可以私信博主,评论区和私信都会认真查看的,我们下次再见

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

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

相关文章

一、冯诺依曼计算机

图灵与冯诺依曼两位计算机发展重要人物。冯诺依曼简介&#xff1a;被誉为现代计算机之父。 世界上第一台通用计算机Eniac&#xff0c;就有冯诺依曼的参与。他提出的思想&#xff0c;将数据和程序分离开了&#xff0c;程序是程序&#xff0c;数据是数据&#xff0c;数据可以由程…

大数据时代效率革新:数字化管理助力企业迈向全新高度-亿发

在大数据时代的浪潮中&#xff0c;数字化管理成为企业不可或缺的发展趋势。以信息技术为支撑&#xff0c;数字化管理为企业带来了前所未有的机遇和挑战。让我们一起探讨&#xff0c;在这个数字时代&#xff0c;数字化管理如何引领企业走向更加高效、智能的未来。 1. 数据驱动决…

更新!3D开发工具HOOPS发布2024版本:增强Navisworks、Revit等新版本支持,性能进一步提高!

3D工程软件开发工具包的领先提供商Tech Soft 3D今天宣布推出HOOPS Exchange 2024&#xff08;支持30多种文件格式的领先CAD数据转换SDK&#xff09;和HOOPS Publish 2024&#xff0c;用于发布交互式3D PDF、3D HTML和3D CAD数据的领先工具包。 HOOPS Exchange现在支持Naviswor…

微信公众号在线客服源码系统,开发组合PHP+MySQL 带完整的安装代码包以及搭建教程

移动互联网的快速发展&#xff0c;微信公众号成为了企业与用户之间的重要沟通桥梁。为了满足企业对微信公众号在线客服的需求&#xff0c;小编给大家分享一款基于PHP和MySQL的微信公众号在线客服源码系统。这套系统能够帮助企业快速搭建自己的微信公众号在线客服平台&#xff0…

FPGA高端项目:Xilinx Artix7系列FPGA 多路视频缩放拼接 工程解决方案 提供4套工程源码+技术支持

目录 1、前言版本更新说明给读者的一封信FPGA就业高端项目培训计划免责声明 2、相关方案推荐我这里已有的FPGA图像缩放方案我已有的FPGA视频拼接叠加融合方案本方案的Xilinx Kintex7系列FPGA上的ov5640版本本方案的Xilinx Kintex7系列FPGA上的HDMI版本 3、设计思路框架设计框图…

实际项目中的SpringAOP实现日志打印

目录 一、AOP实现日志 1.1 需求分析&#xff1a; 1.2 定义切面类和切点&#xff1a; 扩展&#xff1a;finally中的代码块一定会执行吗&#xff1f; 扩展 总结 1.3 定义环绕通知 1.4 handleBefore 的具体实现 1.4.1 获取url 1.4.2 获取接口描述信息 1.4.3 后续获取 1.5…

全球网络加速的5种方法,云桥通力推SDWAN企业组网

全球网络加速旨在通过采用多种技术和服务&#xff0c;提高全球范围内的网络连接速度和性能。在全球化发展的趋势下&#xff0c;跨国企业、云服务提供商和全球用户之间的网络通信变得日益关键。其目标是克服跨国网络连接中的延迟、带宽限制和数据包丢失等问题&#xff0c;以提供…

【C++】 C++入门—内联函数

C入门 1 内联函数1.1 定义1.2 查看方式1.3 注意 Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读下一篇文章见&#xff01;&#xff01;&#xff01; 1 内联函数 1.1 定义 程序在执行一个函数前需要做准备工作&#xff1a;要将实参、局部变量、返回地址以及若干寄存…

小米商城服务治理之客户端熔断器(Google SRE客户端熔断器)

目录 前言 一、什么是Google SRE熔断器 二、Google SRE 熔断器的工作流程&#xff1a; 三、客户端熔断器 (google SRE 熔断器) golang GRPC 实现 四、客户端熔断器 (google SRE 熔断器) golang GRPC单元测试 大家可以关注个人博客&#xff1a;xingxing – Web Developer …

【C++干货基地】C++引用与指针的区别:深入理解两者特性及选择正确应用场景

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引入 哈喽各位铁汁们好啊&#xff0c;我是博主鸽芷咕《C干货基地》是由我的襄阳家乡零食基地有感而发&#xff0c;不知道各位的…

Log4j2-11-log4j2 Layout 布局入门介绍

Layout 布局 Appender使用Layout将LogEvent格式化为一种表单&#xff0c;以满足将要消费日志事件的任何需求。 在Log4j中。x和Logback布局被期望将事件转换为字符串。 在Log4j 2布局返回一个字节数组。这使得Layout的结果可以在更多类型的appender中使用。然而&#xff0c;这…

[机器学习]简单线性回归——最小二乘法

一.线性回归及最小二乘法概念 2.代码实现 # 0.引入依赖 import numpy as np import matplotlib.pyplot as plt# 1.导入数据 points np.genfromtxt(data.csv, delimiter,) # points[0,0]# 提取points中的两列数据&#xff0c;分别作为x&#xff0c;y x points[:, 0] y poi…

Netty源码三:NioEventLoop创建与run方法

1.入口 会调用到父类SingleThreadEventLoop的构造方法 2.SingleThreadEventLoop 继续调用父类SingleThreadEventExecutor的构造方法 3.SingleThreadEventExecutor 到这里完整的总结一下&#xff1a; 将线程执行器保存到每一个SingleThreadEventExcutor里面去创建了MpscQu…

安卓滚动视图ScrollView

<?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:orientatio…

docker镜像命令

docker images 列表本机上的镜像 - REPOSITORY&#xff1a;表示镜像的仓库源 - TAG&#xff1a;镜像的标签 - IMAGE ID&#xff1a;镜像 - ID CREATED&#xff1a;镜像创建时间 - SIZE&#xff1a;镜像大小 同一仓库源可以有多个 TAG&#xff0c;代表这个仓库源的不同个版本&am…

大洋钻探系列之七中国大洋钻探船梦想号

中国大洋钻探梦想号2021年11月30日开工建造&#xff0c;2023年12月27日在珠江口海域完成首航&#xff0c;预计2024年正式交付使用&#xff0c;从而实现了2011年中国IODP专家咨询委员会提出的我国大洋钻探发展“三步走”战略的第三步建造中国的大洋钻探船。 恰逢IODP新旧计划交替…

vue3 + vite:打包部署后,动态组件渲染404问题解决

问题描述: 当需要渲染动态组件,动态的组件路径配置在数据库中时,如下图,本地运行能正常访问,用vite打包部署后,生产上改路径为404. 起初认为是,vite打包后的文件都是.js, 当页面加载后从数据库拿来的路径是.vue, 并且是src/xxx/xxx.vue 这种绝对路径形式的,所以就找不…

【每日一题】 2024年1月汇编

&#x1f525;博客主页&#xff1a; A_SHOWY&#x1f3a5;系列专栏&#xff1a;力扣刷题总结录 数据结构 云计算 数字图像处理 力扣每日一题_ 【1.4】2397.被列覆盖的最多行数 2397. 被列覆盖的最多行数https://leetcode.cn/problems/maximum-rows-covered-by-columns/ 这…

哪个牌子的头戴式耳机好?推荐性价比高的头戴式耳机品牌

随着科技的不断发展&#xff0c;耳机市场也呈现出百花齐放的态势&#xff0c;从高端的奢侈品牌到亲民的平价品牌&#xff0c;各种款式、功能的耳机层出不穷&#xff0c;而头戴式耳机作为其中的一员&#xff0c;凭借其优秀的音质和降噪功能&#xff0c;受到了广大用户的喜爱&…

ArrayList在添加元素时报错java.lang.ArrayIndexOutOfBoundException

一、添加单个元素数组越界分析 add源码如下 public boolean add(E e) {ensureCapacityInternal(size 1); // Increments modCount!!elementData[size] e;return true; } size字段的定义 The size of the ArrayList (the number of elements it contains). ArrayList的大…