从根到叶:深入理解二叉搜索树

news2024/12/26 11:39:55

我们的心永远向前憧憬

尽管活在阴沉的现在        

一切都是暂时的,转瞬即逝,

而那逝去的将变为可爱

                                                                                   🌝(俄) 普希金 <假如生活欺骗了你>

1.二叉搜索树的概念

概念:搜索树(Search Tree)是一种有序的数据结构,用于存储和组织一组元素。它提供高效的搜索、插入和删除操作。

组成:搜索树是由节点(Node)组成的树状结构,每个节点包含一个关键字(Key)和相关的数据(Data)。通过比较节点的关键字,可以确定元素在搜索树中的位置。

常见的搜索树包括二叉搜索树(Binary Search Tree)和平衡二叉搜索树(Balanced Binary Search Tree),如红黑树(Red-Black Tree)、AVL树等。

本篇文章主要讲的是二叉搜索树

在二叉搜索树中,每个节点最多有两个子节点,且左子节点的关键字小于父节点的关键字,右子节点的关键字大于父节点的关键字。这种有序性质使得在搜索树中进行搜索操作时,可以通过比较关键字的大小来决定搜索方向,从而快速地找到目标元素,简而言之如下:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

 比如有一组数组:

int array[] = {{5,3,4,1,7,8,2,6,0,9}

则它的二叉搜索树为:


2.二叉搜索树的定义类

public class BinarySearchTree {
    static class TreeNode{
        public int val;//元素
        public TreeNode left;//左子树
        public TreeNode right;//右子树

        public TreeNode(int val){
            this.val = val;
        }
    }
    public TreeNode root;
}

3.实现二叉搜索树的查找

执行步骤:

  1. 从root根节点开始,将要查找的关键字与当前节点的关键字进行比较。
  2. 如果要查找的key关键字等于当前节点的关键字,则找到了目标元素,查找成功。
  3. 如果要查找的key关键字小于当前节点的关键字,则继续在当前节点的左子树中进行查找。
  4. 如果要查找的key关键字大于当前节点的关键字,则继续在当前节点的右子树中进行查找。
  5. 如果当前节点的左子树或右子树为空,表示查找失败,目标元素不存在于树中。
  6. 重复步骤1-5,直到找到目标元素或确定目标元素不存在。

例子解释:

现在有一组数据:查找关键字8

int array[] = {{5,3,4,1,7,8,2,6,0,9}

逻辑思路:

  1. 初始化一个指针,将其指向根节点,例如使用cur指针,并将其初始值设置为根节点。
  2. 进入一个循环,循环条件是当前节点不为空,即cur不为null。
  3. 在循环中,比较当前节点的关键字与目标关键字的大小关系。
  4. 如果当前节点的关键字小于目标关键字,说明目标元素应该在当前节点的右子树中,将当前节点指针cur移动到右子节点,即cur = cur.right
  5. 如果当前节点的关键字大于目标关键字,说明目标元素应该在当前节点的左子树中,将当前节点指针cur移动到左子节点,即cur = cur.left
  6. 如果当前节点的关键字等于目标关键字,表示已经找到了目标元素,可以返回true或执行其他相应的操作。
  7. 如果循环结束仍然没有找到目标元素,即当前节点为空,表示查找失败,可以返回false或执行其他相应的操作。

如视频展示:

二叉树搜索树-寻找

代码如下:




public boolean search(int key){
    TreeNode cur = root; // 初始化当前节点指针cur为根节点root
    while(cur != null){ // 循环条件:当前节点cur不为空
        if(cur.val < key){ // 如果当前节点的值小于目标关键字key
            cur = cur.right; // 移动当前节点指针cur到右子节点
        }else if(cur.val > key){ // 如果当前节点的值大于目标关键字key
            cur = cur.left; // 移动当前节点指针cur到左子节点
        }else {
            return true; // 当前节点的值等于目标关键字key,找到了目标元素,返回true
        }
    }
    return false; // 循环结束仍然没有找到目标元素,返回false,表示查找失败
}

时间复杂度:

  • 平均情况下,二叉搜索树的查找操作时间复杂度为O(log n),其中n是二叉搜索树中的节点数。每次比较都可以将搜索范围缩小一半。
  • 最坏情况下(只有左子树或右子树),如果二叉搜索树是非平衡树,查找操作的时间复杂度可能达到O(n),其中n是二叉搜索树中的节点数。树的结构类似于链表,需要遍历从根节点到叶子节点的路径。

空间复杂度:

  • 在迭代方式的二叉搜索树查找中,只使用了常数级别的额外空间,即只有一个额外的指针用于保存当前节点的引用,因此空间复杂度为O(1)

4.实现二叉搜索树的插入

执行步骤:

  1. 首先,检查根节点root是否为空。如果为空,表示该二叉搜索树为空树,将新节点TreeNode(val)作为根节点插入,并返回true表示插入成功。
  2. 如果根节点不为空,初始化当前节点指针cur为根节点root,父节点指针parent为null。
  3. 进入循环,条件为当前节点cur不为空。
  4. 在循环中,比较当前节点cur的值与待插入值val的大小关系:
  5. 循环结束后,表明找到了合适的插入位置。创建新节点TreeNode(val)
  6. 判断父节点parent的值与待插入值val的大小关系:
  7. 返回true,表示插入成功。

视频展示如下:

二叉树搜索树-插入

代码如下:

public boolean insert(int val){
    if(root == null){ // 如果根节点为空,将新节点作为根节点插入
        root = new TreeNode(val);
        return true;
    }
    TreeNode cur = root; // 初始化当前节点指针cur为根节点root
    TreeNode parent = null; // 初始化父节点指针parent为空
    while(cur != null){ // 循环条件:当前节点cur不为空
        if(cur.val < val){ // 如果当前节点的值小于待插入值val
            parent = cur; // 更新父节点指针为当前节点
            cur = cur.right; // 移动当前节点指针cur到右子节点
        } else if (cur.val > val) { // 如果当前节点的值大于待插入值val
            parent = cur; // 更新父节点指针为当前节点
            cur = cur.left; // 移动当前节点指针cur到左子节点
        } else{ // 如果当前节点的值等于待插入值val,即已存在相同值的节点
            return false; // 返回false,表示插入失败(不允许插入重复值)
        }
    }
    TreeNode node = new TreeNode(val); // 创建新节点
    if(parent.val > val){ // 如果父节点的值大于待插入值val
        parent.left = node; // 将新节点插入为父节点的左子节点
    }else {
        parent.right = node; // 将新节点插入为父节点的右子节点
    }
    return true; // 返回true,表示插入成功
}

时间复杂度:
在最坏情况下,即二叉搜索树是一个非平衡树的情况下,插入操作的时间复杂度为O(n),其中n是二叉搜索树中的节点数。这种情况下,树的结构类似于链表,每次插入都需要遍历从根节点到叶子节点的路径。

在平均情况下,二叉搜索树的插入操作的时间复杂度为O(log n),其中n是二叉搜索树中的节点数。每次插入操作都可以将搜索范围减半,因此插入操作的时间复杂度是对数级别的。

空间复杂度:
在二叉搜索树的插入操作中,只需要使用常数级别的额外空间,即只有几个指针变量用于保存当前节点和父节点的引用。因此,插入操作的空间复杂度为O(1)。


5.实现二叉搜索树的删除

具体删除操作分三种情况:

第一种情况:待删除节点cur没有左子节点。

  • 如果cur是根节点,直接将根节点指向其右子节点cur.right
  • 如果cur是父节点parent的左子节点,将父节点的左子节点指向cur.right
  • 如果cur是父节点parent的右子节点,将父节点的右子节点指向cur.right

视频展示:

二叉搜索树-删除-1

第二种情况:待删除节点cur没有右子节点。

  • 如果cur是根节点,直接将根节点指向其左子节点cur.left
  • 如果cur是父节点parent的左子节点,将父节点的左子节点指向cur.left
  • 如果cur是父节点parent的右子节点,将父节点的右子节点指向cur.left

视频展示

二叉树搜索树-删除-2

第三种情况:待删除节点cur既有左子节点又有右子节点。

注意:待删除结点的数据将来放的数据一定是比左边都大,比右边都小的数据
如何寻找数据?
要么在左树里面找到最大的数据[即左树最右边的数据]

要么在右树里面找到最小的数据[即右数最左边的数据]

下面我使用的是在右数找最小值

执行步骤:

  • 找到待删除节点cur的右子树中的最小节点target,即右子树中最左侧的节点。
  • 将最小节点target的值赋给待删除节点cur,相当于将cur节点的值替换target节点的值。
  • 删除最小节点target,即对最小节点target执行第一种或第二种情况的删除操作。

视频展示: 

二叉搜索树-删除-3

注意:

当target没有左孩子时,应当时targetParent.right == target.right

代码如下:

public void remove(int key){
    TreeNode cur = root; // 初始化当前节点指针cur为根节点root
    TreeNode parent = null; // 初始化父节点指针parent为空
    while(cur != null){ // 循环条件:当前节点cur不为空
        if(cur.val < key){ // 如果当前节点的值cur.val小于待删除值key
            parent = cur; // 更新父节点指针parent为当前节点cur
            cur = cur.right; // 将当前节点指针cur移动到右子节点cur.right
        } else if (cur.val > key) { // 如果当前节点的值cur.val大于待删除值key
            parent = cur; // 更新父节点指针parent为当前节点cur
            cur = cur.left; // 将当前节点指针cur移动到左子节点cur.left
        } else { // 当前节点的值cur.val等于待删除值key,找到待删除节点
            removeNode(cur, parent); // 调用removeNode函数执行删除操作
        }
    }
}

private void removeNode(TreeNode cur, TreeNode parent) {
    // 第一种情况:待删除节点cur没有左子节点
    if(cur.left == null){
        if(cur == root){ // 如果待删除节点cur是根节点
            root = cur.right; // 直接将根节点指向其右子节点cur.right
        } else if (cur == parent.left) { // 如果待删除节点cur是父节点parent的左子节点
            parent.left = cur.right; // 将父节点的左子节点指向cur.right
        } else { // 如果待删除节点cur是父节点parent的右子节点
            parent.right = cur.right; // 将父节点的右子节点指向cur.right
        }
    }
    // 第二种情况:待删除节点cur没有右子节点
    else if(cur.right == null){
        if(cur == root){ // 如果待删除节点cur是根节点
            root = cur.left; // 直接将根节点指向其左子节点cur.left
        } else if(cur == parent.left){ // 如果待删除节点cur是父节点parent的左子节点
            parent.left = cur.left; // 将父节点的左子节点指向cur.left
        } else { // 如果待删除节点cur是父节点parent的右子节点
            parent.right = cur.left; // 将父节点的右子节点指向cur.left
        }
    }
    // 第三种情况:待删除节点cur既有左子节点又有右子节点
    else {
        TreeNode targetParent = cur; // 初始化目标节点的父节点指针为cur
        TreeNode target = cur.right; // 初始化目标节点指针为cur的右子节点
        while(target.left != null){ // 寻找cur右子树中的最小节点
            targetParent = target; // 更新目标节点的父节点指针为target
            target = target.left; // 将目标节点指针移动到左子节点target.left
        }
        cur.val = target.val; // 将目标节点的值赋给待删除节点cur,相当于替换值
        if(targetParent.left == target){ // 如果目标节点是其父节点的左子节点
            targetParent.left = target.right; // 将目标节点的右子节点连接到目标节点的父节点的左子节点上
        } else { // 如果目标节点是其父节点的右子节点
            targetParent.right = target.right; // 将目标节点的右子节点连接到目标节点的父节点的右子节点上
        }
    }
}

时间复杂度:

  • 在平均情况下,二叉搜索树的高度为O(log N),其中N是树中节点的总数。在删除节点的过程中,需要遍历树以找到待删除节点的位置,这需要沿着树的高度移动。因此,平均情况下删除节点的时间复杂度为O(log N)。
  • 在最坏情况下,如果二叉搜索树是一个不平衡的树,即所有节点都只有一个子节点,删除节点的时间复杂度可以达到O(N),其中N是树中节点的总数。这种情况发生在树没有进行平衡操作或者插入和删除操作导致树失去平衡的情况下。

空间复杂度:

  • 删除节点的过程中使用了常数级别的额外空间,主要是用于存储当前节点指针cur和父节点指针parent。因此,删除节点的空间复杂度为O(1)。

 6.总结

  • 二叉搜索树的查找、插入和删除操作都是基于节点值的比较来进行的。
  • 查找操作的时间复杂度为O(log N),其中N是树中节点的总数。插入和删除操作的时间复杂度也是O(log N),但在最坏情况下(树不平衡),时间复杂度可能达到O(N)。
  • 二叉搜索树的插入和删除操作可以保持树的有序性,但如果插入和删除操作频繁且不平衡,可能会导致树的高度增加,降低操作效率。
  • 为了解决不平衡问题,可以使用平衡二叉搜索树(如AVL树、红黑树)等数据结构来保持树的平衡性,以提高查找、插入和删除操作的性能。

结语:

二叉搜索树提供了一种简洁而强大的数据结构,它不仅仅是一棵树,更是一种思想。通过理解和应用二叉搜索树的原理,我们可以解决各种问题,如数据的排序、查找最小/最大值、范围查询等。

在结束之际,让我们怀着对二叉搜索树的敬意,继续探索和学习更多的数据结构和算法,为解决复杂的计算问题开辟新的道路。无论是在计算机科学的领域中,还是在生活的各个方面,二叉搜索树的智慧将继续指引我们前行。

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

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

相关文章

基于R语言lavaan结构方程模型(SEM)技术应用

结构方程模型&#xff08;Sructural Equation Modeling&#xff0c;SEM&#xff09;是分析系统内变量间的相互关系的利器&#xff0c;可通过图形化方式清晰展示系统中多变量因果关系网&#xff0c;具有强大的数据分析功能和广泛的适用性&#xff0c;是近年来生态、进化、环境、…

视频监控平台EasyCVR+4G/5G应急布控球远程视频监控方案

随着科技的不断发展&#xff0c;应急布控球远程视频监控方案在公共安全、交通管理、城市管理等领域的应用越来越广泛。这种方案通过在现场部署应急布控球&#xff0c;实现对特定区域的实时监控&#xff0c;有助于及时发现问题、快速响应&#xff0c;提高管理效率。 智慧安防视…

Linux 7系统安装单机版 11.2.0.4 的两个bug处理

第1个bug&#xff1a;pdksh-5.2.14 is missing when Installing 11.2.0.4 on RHEL 7.6 结局办法&#xff1a;./runInstaller -ignorePrereq 或者 关注下面公众号 回复pdksh 获取pdksh下载链接安装 第2个bug&#xff1a;安装的时候报错输出 Error in invoking target agent nm…

uniapp封装文字提示气泡框toolTip组件

uniapp封装文字提示气泡框toolTip组件 文字提示气泡框&#xff1a;toolTip 因为uniapp 中小程序中没有window对象&#xff0c;需手动调用 关闭 第一种办法关闭&#xff1a;this.$refs.tooltip.close() 第二种办法关闭&#xff1a;visible.sync false 移动端没有现成的toolTip组…

SpringBoot项目没有启动按键

问题一&#xff1a; pom文件正常&#xff0c;但是springboot包报红&#xff0c;同时Plugin ‘org.springframework.boot:spring-boot-maven-plugin:‘ not found报红 解决办法&#xff1a; 无法识别使用哪个版本的 spring-boot-maven-plugin 包 <build><plugins>&…

Unity类银河恶魔城学习记录8-5 p81 Blackhole duration源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码、 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili Blackhole_Skill_Controller.cs using System.Collections; using Syste…

CSS布局——Flexbox布局中的对齐方式

你好&#xff0c;我是小白Coding日志&#xff0c;一个热爱技术的程序员。在这里&#xff0c;我分享自己在编程和技术世界中的学习心得和体会。希望我的文章能够给你带来一些灵感和帮助。欢迎来到我的博客&#xff0c;一起在技术的世界里探索前行吧&#xff01; justify-conten…

飞桨AI框架安装和使用示例

飞桨AI框架安装和使用示例 飞桨PaddlePaddle是非常流行的国产AI框架&#xff0c;让我们一起来动手实践吧&#xff01; 安装 飞桨安装参考页面&#xff1a;https://www.paddlepaddle.org.cn/install/quick?docurl/documentation/docs/zh/install/pip/linux-pip.html 在这个安…

【QT】绘画事件/刷新界面

绘图事件 QPaintEngine提供了画笔(QPainter)在不同的设备上进行绘制的统一的接口。QPaintEngine类应用于QPainter和QPaintDevice之间&#xff0c;通常对开发人员是透明的。除非你需要自定义一个设备&#xff0c;否则你是不需要关心QPaintEngine这个类的。我们可以把QPainter理…

如何解决Firefox提示“此网站可能不支持TLS1.2协议”的问题 错误代码:SSL_ERROR_UNSUPPORTED_VERSION

1.问题描述 当你在Firefox浏览器中访问一个网站时&#xff0c;可能会遇到这样的提示&#xff1a;   之后&#xff0c;不停地刷新或 重新输入&#xff0c;怎么也访问不进去&#xff1f;&#xff1f; 2.解决步骤 按照以下步骤操作&#xff0c;可以降低Firefox对TLS版本的要求…

【深度学习笔记】优化算法——动量法

动量法 &#x1f3f7;sec_momentum 在 :numref:sec_sgd一节中&#xff0c;我们详述了如何执行随机梯度下降&#xff0c;即在只有嘈杂的梯度可用的情况下执行优化时会发生什么。 对于嘈杂的梯度&#xff0c;我们在选择学习率需要格外谨慎。 如果衰减速度太快&#xff0c;收敛就…

【ETCD】简介安装常用操作---图文并茂详细讲解

目录 一 简介 1.1 etcd是什么 1.2. 特点 1.3. 使用场景 1.4 关键字 1.5 工作原理 二 安装 2.1 etcd安装前介绍 2.2 安装 2.3 启动 2.4 创建一个etcd服务 三 常用操作 一 简介 1.1 etcd是什么 etcd是CoreOS团队于2013年6月发起的开源项目&#xff0c;它的目标是构建…

爬虫实战——巴黎圣母院新闻【内附超详细教程,你上你也行】

文章目录 发现宝藏一、 目标二、简单分析网页1. 寻找所有新闻2. 分析模块、版面和文章 三、爬取新闻1. 爬取模块2. 爬取版面3. 爬取文章 四、完整代码五、效果展示 发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不…

【开源】SpringBoot框架开发河南软件客服系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统管理人员2.2 业务操作人员 三、系统展示四、核心代码4.1 查询客户4.2 新增客户跟进情况4.3 查询客户历史4.4 新增服务派单4.5 新增客户服务费 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的河…

10、Linux项目部署-WAR包、JAR包

一、WAR包 第一步&#xff0c;把War包解压&#xff0c;再重新打包成Zip。 第二步&#xff0c;在Linux里创建一个项目文件夹&#xff0c;将Zip的内容解压在这个文件夹内。 例如&#xff0c;创建的项目文件夹是/usr/local/software/project1 第三步&#xff0c;修改Tomcat配置…

Linux基础IO【 详 解 】

文章目录 C语言文件IOC语言文件接口汇总默认打开的三个流 系统文件IOopenclosewriteread 文件描述符fd文件描述符的分配规则重定向重定向的本质dup2 FILEFILE当中的文件描述符FILE当中的缓冲区 理解文件系统初识inode磁盘分区与格式化介绍 软硬链接软链接硬链接软硬链接的区别 …

混合专家系统(MoE)概述

MoE概述 神经网络的学习能力受限于它的参数规模&#xff0c;因此寻找更有效的方法来增加模型的参数已成为深度学习研究的趋势。混合专家系统 (MoE) 可以大幅增加模型参数规模且不会等比例地增加模型计算量&#xff0c;对于单个样本&#xff0c;神经网络只有某些部分被激活。在混…

基于JAVA+ springboot实现的抗疫物质信息管理系统

基于JAVA springboot实现的抗疫物质信息管理系统设计和实现 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 …

手机ip地址获取失败是什么原因

在移动互联网时代&#xff0c;手机的IP地址不仅是设备在网络中的唯一标识&#xff0c;还关系到我们的网络体验与数据安全。然而&#xff0c;有时我们在使用手机时可能会遇到IP地址获取失败的情况。这种情况不仅会影响我们的在线活动&#xff0c;还可能引发一系列问题。本文将探…

【动态规划】二维费用的背包问题

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;【LeetCode】winter vacation training 目录 &#x1f449;&#x1f3fb;一和零 &#x1f449;&#x1f3fb;一…