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


 

目录

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

搜索二叉树的查找

搜索二叉树的插入

搜索二叉树的删除

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;//删除叶子节点
            }
        }
    }
}



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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode: 189.轮转数组

本篇目标了解&#xff0c;翻转数组的经典解法&#xff0c; 189. 轮转数组 - 力扣&#xff08;LeetCode&#xff09; 目录 基本方法概述&#xff1a; 1&#xff0c;翻转做法&#xff0c;推荐时O&#xff08;n&#xff09;&#xff0c;空&#xff08;1&#xff09; 2&#x…

喜讯 | 经纬恒润整车电子电气测试实验室通过一汽研发总院外部实验室资质认证!

近日&#xff0c;经纬恒润整车电子电气测试实验室成功通过中国一汽研发总院的资质评定&#xff0c;获得外部实验室认可证书。这是继经纬恒润测试实验室获得一汽智能网联开发院车载以太网测试资质认证之后的又一次认可&#xff0c;它将拓宽经纬恒润与红旗新能源及相关零部件供应…

windows pm2 执行 npm脚本或执行yarn脚本遇到的问题及解决方案

环境&#xff1a; 在windows上启动终端来运行一个项目&#xff1b;通过指令npm run start来启动&#xff0c;但是将终端一关&#xff0c;就无法访问了&#xff0c;所以想到用pm2来管理 1. 全局安装pm2 npm i pm2 -g2. 在项目根目录执行指令(大部分兄弟的错误使用方法) pm2 st…

docker私有库

1.registry私有仓库 拉取registry镜像 docker pull registry 修改docker配置文件并重启 vim /etc/docker/daemon.json {"insecure-registries": ["172.16.23.23:5000"], #添加&#xff0c;注意用逗号结尾"registry-mirrors": ["ht…

阿赵UE学习笔记——14、LOD

阿赵UE学习笔记目录   大家好&#xff0c;我是阿赵。   继续学习虚幻引擎的用法。这次看看虚幻引擎的Level Of Detail(LOD)的用法。 一、测试场景准备 用植物系统&#xff0c;在地形上面刷了好多草&#xff1a; 这个时候看一下网格&#xff0c;会发现网格比较多和密集。 …

免费AI写作网站,AI人工智能写作gpt+在线AI绘画midjourney国内版

大家可以通过收藏网页www.woka.chat 直接进行访问&#xff0c;也可通过关注新公众号实现微信端使用~ 注册赠送大量额度&#xff0c;可用于网站全部功能&#xff08;问答和绘画&#xff09;&#xff01;每天签到也可领取充足使用额度&#xff01; 废话不多说&#xff0c;我们现…

【android】 android->profile 查看内存泄露

目录 实例讲解 各字段解释 实例讲解 各字段解释 在 Android Studio 的 Profile 视图中&#xff0c;Arrange by Stack 用于对内存分配和释放事件进行堆栈排列&#xff0c;以便更好地了解内存使用情况。以下是表上各列的一般含义&#xff1a; 1. **Call Chart (调用图)**: …

Web中的转发与重定向

转发与重定向 一、转发和重定向的概念1.转发2.重定向 二、JavaWeb 中的转发和重定向三、SpringMVC 中的转发和重定向1.转发(1) 默认的方式(2) 完整的方式 2.重定向 四、总结 一、转发和重定向的概念 在 Web 应用中&#xff0c;转发和重定向都是用于将请求从一个页面传递到另一…

MIMIC-IV官方视图解析 - cardiac_marker心脏标记表

今天在学习官方衍生表mimiciv_derived.cardiac_marker心脏标记表时候发现了一些问题&#xff1a; 该表中troponin_t &#xff08;肌钙蛋白t&#xff09;的值结果都是空值null 或者 ___ &#xff08;由于去标识化&#xff09;&#xff0c; 这明显是不合理的 小编查看了该表的官…

提升小波的理解

本文简要介绍一下提升小波的计算过程和基本原理: 1、划分 假设有序列X, 将其奇数索引上的元素构成,Xo将其偶数索引上的元素构成,Xe之所以能文献中都用Xo和Xe划分,是因为 o来源于odd,奇数;e来源于even,偶数;举个例子: 有序列:X=[3,5,22,33,12,34,56,77,99,29] Xo…

乐鑫与 Elektor 杂志合作推出特刊,聚焦 AIoT 创新

在新一年的起始之际&#xff0c;我们很荣幸地与 Elektor 合作推出由乐鑫领衔编辑的杂志特刊。欢迎点此阅读电子版本。 Elektor 杂志作为国际电子工程和科技创新的重要平台&#xff0c;自 20 世纪 60 年代起&#xff0c;就引领着电子制造的发展潮流。如今&#xff0c;它已经发展…

服务端渲染

SSR简单来说就是页面上的内容是通过服务端渲染生成的&#xff0c;浏览器直接显示服务端返回的html就可以了。相比之前常用的SPA来说有很多的优点&#xff0c;如下图&#xff0c;但也有一些实际存在的问题&#xff0c;在实际应用中需要多方面权衡利弊。 SSR优势 SSR缺点&#xf…

THREE.JS动态场景开发实战【赛博朋克】

在本教程中&#xff0c;我们将探索如何创建类似 Three.js 的赛博朋克场景&#xff0c;灵感来自 Pipe 网站上的背景动画。 我们将指导你完成使用 Three.js 编码动态场景的过程&#xff0c;包括后处理效果和动态光照&#xff0c;所有这些都不需要任何着色器专业知识。 我用这个场…

【网络】:网络套接字(TCP)

网络套接字&#xff08;TCP&#xff09; 一.编写TCP服务器二.编写Tcp客户端三.多进程四.多线程版本五.线程池版完整源代码六.使用示例 一.编写TCP服务器 1.先搭一个架子 2.创建sockfd domain参数依然是AF_INET(因为是IPV4) type方式选择SOCK_STREAM&#xff08;提供可靠的连接…

优思学院|如何评价质量经理这个角色?

简单来说&#xff0c;公司的成败已经取决于质量的水准。质量是任何公司的重要组成部分&#xff0c;无法保证商品质量的公司将很快失去信誉与消费者的认可&#xff0c;最终导致销售额直线下降。 所以&#xff0c;质量经理的意义首先体现在他们对于质量控制体系的建立和维护上。…

辽宁链家新房数据采集与可视化实现

摘 要 网络爬虫也叫做网络机器人&#xff0c;是一种按照一定的规则&#xff0c;自动地抓取网络信息&#xff0c;进行数据信息的采集与整理的程序或者脚本。随着海量数据的出现&#xff0c;如何快速有效的获取到我们想要的数据成为难题。以房源信息为例&#xff0c;该文使用Pyt…

MySQL之索引分类,语法以及SQL性能分析(慢日志,profile,explain)

索引分类 分类含义特定关键字主键索引针对于表中主键创建的索引默认自动创建&#xff0c;只能有一个PRIMARY唯一索引避免同一个表中某数据列中的值重复可以有多个UNIQUE常规索引快速定位特定数据可以有多个全文索引全文索引查找的文本中的关键字&#xff0c;而不是比较索引中的…

Java正则表达式之Pattern和Matcher

目录 前言一、Pattern和Matcher的简单使用二、Pattern详解2.1 Pattern 常用方法2.1.1 compile(String regex)2.1.2 matches(String regex, CharSequence input)2.1.3 split(CharSequence input)2.1.4 pattern()2.1.5 matcher(CharSequence input) 三、Matcher详解3.1 Matcher 常…

猫头虎博主第10期赠书活动:《写给大家看的Midjourney设计书》

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通Golang》…

基于Spark+Springboot的电商用户行为分析系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行交流合作✌ 主要内容&#xff1a;SpringBoot、Vue、SSM、HLM…