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

我们的心永远向前憧憬

尽管活在阴沉的现在        

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

而那逝去的将变为可爱

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

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树、红黑树)等数据结构来保持树的平衡性,以提高查找、插入和删除操作的性能。

结语:

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

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

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

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

相关文章

2023第十届GIAC全球互联网架构大会:洞察未来互联网架构的革新与突破(附大会核心PPT下载)

随着互联网的迅猛发展&#xff0c;其底层架构的演进与革新成为了推动全球数字化进程的关键力量。2023年第十届GIAC全球互联网架构大会如期而至&#xff0c;汇聚了全球互联网架构领域的顶尖专家、学者、企业领袖和创新者&#xff0c;共同探讨和展望互联网架构的未来发展趋势。本…

【Logback】Logback 中的 Appenders

目录 1、什么是 Appenders&#xff1f; 2、解说 AppenderBase.doAppend() 方法 3、logback-core 模块中的 Appenders &#xff08;1&#xff09;OutputStreamAppender &#xff08;2&#xff09;ConsoleAppender &#xff08;3&#xff09;FileAppender &#xff08;4&a…

devops-Maven【部署及配置】

1、准备maven工具包&#xff0c;Maven官网下载Maven的安装包 Maven – Download Apache Maven Index of /maven (apache.org) 选择后缀是.bin.tar.gz的文件下载&#xff0c;此处下载的版本是3.9.6。 2、安装maven的目录下&#xff0c;建一个Maven路径&#xff0c;然后把压缩…

GEE 底图加载——自定义底图样式加载案例分析(含免费引如多款底图)

在本教程中&#xff0c;您将学习如何更改地图对象的选项&#xff0c;以便为底层基础地图定义自己的样式。 地球引擎中的默认地图 地球引擎的基础地图是 Google Map API 中的地图。默认选项包括 roadmap&#xff0c;显示默认的路线图视图、卫星&#xff0c;显示谷歌地球卫星图…

洗衣洗鞋店小程序对接水洗唛打印,一键预约,支付无忧

随着社会的进步和科技的发展&#xff0c;我们的生活幸福感与日俱增。为了让我们从琐碎中解脱出来&#xff0c;干洗店洗鞋店行业也日新月异。今天&#xff0c;我为大家推荐这款优秀的干洗店小程序系统&#xff0c;让您的洗衣洗鞋服务体验更上一层楼。 干洗店管理系统是一款专为洗…

前端对于大图片与小图片的处理 转base64

对于小图片&#xff0c;可以转换为base64字节编码的字符串&#xff0c;减少一次资源请求&#xff0c;资源占用多了一丢丢而已 对于大图片&#xff0c;就算了&#xff0c;得不偿失 比如&#xff0c;webpack处理图片资源

掌握这几个技术点,你也能开发出爆款ARPG游戏!

在众多ARPG游戏的发售下&#xff0c;游戏市场温度迅速升高&#xff0c;今年很可能会成为一个“ARPG手游大年”&#xff0c;或许会再次出现“神仙打架”的情况。 ARPG作为一种非常经典且流行的游戏类型, 已经诞生过无数经典的作品,比如魂系,暗黑破坏神系列,塞尔达传说系列&#…

《计算机程序的构造和解释》

文章目录 写在末尾 &#x1f680; 个人简介&#xff1a;CSDN「博客新星」TOP 10 &#xff0c; C/C 领域新星创作者&#x1f49f; 作 者&#xff1a;锡兰_CC ❣️&#x1f4dd; 专 栏&#xff1a;【锡兰赠书】&#x1f308; 若有帮助&#xff0c;还请关注➕点赞➕收藏&…

一文搞懂电容两端电压为啥不能突变?

大家好&#xff0c;我是砖一。 我们工作学习过程中&#xff0c;经常会遇到的电容&#xff0c;对于电容的作用&#xff0c;可能大家一般去网上搜有很多&#xff0c;比如储能&#xff0c;滤波&#xff0c;旁路&#xff0c;去耦等等。 但是我要告诉大家的是&#xff0c;电容最重…

惊呼:腾讯云服务器99元一年,要不要来一台?

腾讯云服务器99元一年是真的吗&#xff1f;真的&#xff0c;99元优惠购买入口 txybk.com/go/99 折合每天8元1个月&#xff0c;腾讯云99元服务器配置为2核2G3M带宽&#xff0c;2024年99元服务器配置最新报价为61元一年&#xff0c;如下图&#xff1a; 腾讯云服务器99元一年 腾讯…

机器学习流程—数据预处理 清洗

机器学习流程—数据预处理 清洗 数据清洗因为它涉及识别和删除任何丢失、重复或不相关的数据。数据清理的目标是确保数据准确、一致且无错误,因为不正确或不一致的数据会对 ML 模型的性能产生负面影响。专业数据科学家通常会在这一步投入大量时间,因为他们相信Better data b…

【Docker】若依ruoyi项目部署

一 搭建局域网 1 # 搭建net-ry局域网&#xff0c;用于部署若依项目docker network create net-ry --subnet172.68.0.0/16 --gateway172.68.0.1 # 注意1&#xff1a;关闭宿主机的防火墙&#xff0c;否者容器内部的MySQL、redis等服务&#xff0c;外部访问不了&#xff1b;开放…

智慧油气场站:油气行业实现数字化转型的关键一步

智慧油气场站&#xff1a;油气行业实现数字化转型的关键一步 在现代社会&#xff0c;能源供应是国家经济发展和人民生活的重要保障。而油气场站作为能源的重要供应和储存基地&#xff0c;扮演着至关重要的角色。此外&#xff0c;油气场站还可以为石油和天然气的生产提供支持。…

分享MDN前端结构化技能、实践指南、学习资源

前言 MDN课程为成为一名成功的前端开发人员提供了一个结构化的基本技能和实践指南&#xff0c;以及推荐的学习资源。 先看下让人不得不服的书《宝宝的网页设计》&#xff08;套装共3册&#xff09; 宝宝的HTML、宝宝的CSS、宝宝的JavaScript 全球首套中英文宝宝编程启蒙书&a…

云计算项目十:ES集群安装|部署kibana

ES集群安装 部署ES集群&#xff0c;用于ELK日志分析平台的构建 es-0001 主机更改 /etc/hosts [rootes-0001 ~]# vim /etc/hosts 192.168.1.71 es-0001 192.168.1.72 es-0002 192.168.1.73 es-0003 192.168.1.74 kibana 192.168.1.75 logstash # 将最新的/etc/hosts配置文件更…

javascript正则深入

文章目录 一、前言二、高级`API`2.1、模式匹配的用法`(x)`2.2、非捕获括号的模式匹配`(?:x)`2.3、先行断言`x(?=y)`2.4、后行断言`(?<=y)x`2.5、正向否定查找`x(?!y)`2.6、反向否定查找`(?<!y)x`2.7、字符集合和反向字符集合的用法 `[xyz] / [^xyz]`2.8、词边界和非…

【C++】设计模式:观察者、策略、模板

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍设计模式&#xff1a;观察者、策略、模板。 学其所用&#xff0c;用其所学。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xf…

STM32CubeMX学习笔记17--- FSMC

1.1 TFTLCD简介 TFT-LCD&#xff08;thin film transistor-liquid crystal display&#xff09;即薄膜晶体管液晶显示器。液晶显示屏的每一个像素上都设置有一个薄膜晶体管&#xff08;TFT&#xff09;&#xff0c;每个像素都可以通过点脉冲直接控制&#xff0c;因而每个节点都…

JavaScript 二分查找(迭代与递归)

二分搜索被定义为一种在排序数组中使用的搜索算法&#xff0c;通过重复将搜索间隔一分为二。二分查找的思想是利用数组已排序的信息&#xff0c;将时间复杂度降低到O(log N)。 二分查找算法示例 何时在数据结构中应用二分查找的条件&#xff1a; 应用二分查找算法&#xff1a…

Langchain-Chatchat本地搭建ChatGLM3模型和提取PDF内容

文章目录 1、软件要求2、安装CUDA2.1、安装gcc2.2、安装CUDA 3、安装Anaconda33.1、下载Anaconda33.2、创建python虚拟环境 4、部署系统4.1、下载源码4.2、安装依赖4.3、下载模型4.4、初始化配置和知识库4.4.1、初始化配置4.4.2、初始化知识库 4.5、运行4.6、运行4.6.1、启动4.…