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

我们的心永远向前憧憬

尽管活在阴沉的现在        

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

而那逝去的将变为可爱

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

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为根节点rootwhile(cur != null){ // 循环条件:当前节点cur不为空if(cur.val < key){ // 如果当前节点的值小于目标关键字keycur = cur.right; // 移动当前节点指针cur到右子节点}else if(cur.val > key){ // 如果当前节点的值大于目标关键字keycur = 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为根节点rootTreeNode parent = null; // 初始化父节点指针parent为空while(cur != null){ // 循环条件:当前节点cur不为空if(cur.val < val){ // 如果当前节点的值小于待插入值valparent = cur; // 更新父节点指针为当前节点cur = cur.right; // 移动当前节点指针cur到右子节点} else if (cur.val > val) { // 如果当前节点的值大于待插入值valparent = cur; // 更新父节点指针为当前节点cur = cur.left; // 移动当前节点指针cur到左子节点} else{ // 如果当前节点的值等于待插入值val,即已存在相同值的节点return false; // 返回false,表示插入失败(不允许插入重复值)}}TreeNode node = new TreeNode(val); // 创建新节点if(parent.val > val){ // 如果父节点的值大于待插入值valparent.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为根节点rootTreeNode parent = null; // 初始化父节点指针parent为空while(cur != null){ // 循环条件:当前节点cur不为空if(cur.val < key){ // 如果当前节点的值cur.val小于待删除值keyparent = cur; // 更新父节点指针parent为当前节点curcur = cur.right; // 将当前节点指针cur移动到右子节点cur.right} else if (cur.val > key) { // 如果当前节点的值cur.val大于待删除值keyparent = cur; // 更新父节点指针parent为当前节点curcur = 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; // 初始化目标节点的父节点指针为curTreeNode target = cur.right; // 初始化目标节点指针为cur的右子节点while(target.left != null){ // 寻找cur右子树中的最小节点targetParent = target; // 更新目标节点的父节点指针为targettarget = 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.mzph.cn/news/729400.shtml

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

相关文章

github操作记录(踩坑)

github是程序员绕不开的东西。 网站打不开&#xff1f; 向雇主或有关部门申请合法信道连接互联网。 明明账号密码都对却登录失败&#xff1f; 向雇主或有关部门申请合法信道连接互联网。 重置密码失败&#xff1f; 向雇主或有关部门申请合法信道连接互联网。 TortoiseGit如…

模型压缩-剪枝算法概述

文章目录 1. 前言1.1 模型剪枝定义2 深度神经网络的稀疏性2.1,权重稀疏2.2 激活稀疏2.3 梯度稀疏2.4,小结3. 结构化稀疏3.1,结构化稀疏分类3.1.1 Channel/Filter 剪枝3.1.2 阶段级别剪枝3.2 结构化稀疏与非结构化稀疏比较参考资料参考自:

Facebook商城号为什么被封?防封养号技巧

由于Facebook商城的高利润空间&#xff0c;越来越多的跨境电商商家注意到它的存在。Facebook作为全球最大、用户量最大的社媒平台&#xff0c;同时也孕育了一个巨大的商业生态&#xff0c;包括广告投放、商城交易等。依托背后的大流量&#xff0c;Facebook商城起号较快&#xf…

优思学院|拉丁方实验设计是什么?

今天&#xff0c;我要给大家带来一个六西格玛实验设计的小窍门——拉丁方设计。这是一种巧妙的方式&#xff0c;帮助我们探索不同因素&#xff08;输入&#xff09;对结果&#xff08;输出&#xff09;的影响&#xff0c;同时巧妙地处理那些我们不想要的“噪音因素”。 想象一…

Linux 常用命令汇总(三):查看文件 内容处理

一、查看文件及内容处理命令 1.1 cat 1.1.1 介绍 cat 是 Linux 和其他 Unix-like 系统中一个非常基础且常用的命令&#xff0c;用于显示、合并或复制文本文件的内容 1.1.2 使用方法 cat [选项] [文件...] 1.1.3 参数详解 [选项]&#xff1a;控制 cat 命令的行为。[文件.…

vscode 远程开发golang

1.安装配置golang 下载解压 wget golangurl tar -xzvf golang 解压到/usr/local环境配置 在~/.bashrc文件最后加入下面三行 export GOROOT/usr/local/go # 这里就是go的源码目录 export PATH$PATH:$GOROOT/bin export GOPATH$HOME/goProject # 这里是home目录下的你自己建…

HEUFT电源维修x-ray发生器维修HBE211226

HEUFT电源维修x-ray发生器维修HBE211253;海富HEUFT在线液位检测X射线发生器维修&#xff0c;不限型型号系列。 德国海富推出HEUFT在线液位检测装置,满瓶检测系统HEUFT有着强大的功能,它的模块机构能整合很多程序,并依据不同的产品及其包装特性,照相技术,高频技术或X-ray技术。…

LInux-多线程基础概念

文章目录 前言预备页表详解缺页中断页表的映射 一、多线程是什么&#xff1f;轻量级进程 二、Pthread库pthread_create 前言 从本章的多线程开始&#xff0c;我们开始进入Linux系统的尾声&#xff0c;所以&#xff0c;在学习多线程的过程中&#xff0c;我们也会逐步对之前的内…

邮件营销新手必读指南?怎样做好邮件营销?

邮件营销的全流程及步骤&#xff1f;做好邮件营销有哪些注意点&#xff1f; 邮件营销作为一种传统却依然高效的推广手段&#xff0c;被众多企业所青睐。对于新手来说&#xff0c;如何开展邮件营销&#xff0c;却是一个值得探讨的话题。AokSend将为你提供一份邮件营销新手必读指…

国家妇女节放假是法定的假日

在这个充满活力和希望的春天&#xff0c;我们迎来了一个特殊的节日——国家妇女节。这是一个属于所有女性的节日&#xff0c;是一个庆祝女性成就、关爱女性权益的时刻。在这个特殊的日子里&#xff0c;我们不禁要问&#xff1a;国家妇女节放假是法定假日吗&#xff1f;让我们一…

Ps:辅助类工具组

工具箱里的辅助类工具为图像编辑和设计提供了重要的支持&#xff0c;能帮助用户更准确地测量、选取颜色或添加注释等。 快捷键&#xff1a;I 吸管工具 Eyedropper Tool 用于从图像中采样颜色。 用户可以通过点击屏幕上的任何点来选取那里的颜色&#xff0c;该颜色随即成为当前的…

Java+SpringBoot+Vue+MySQL实战:打造智能餐厅点餐系统

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

FreeRTOS学习笔记-基于stm32(2)任务的创建与删除,挂起与恢复

一、任务创建与删除 1、动态任务创建 xTaskCreate( TaskFunction_t pxTaskCode,const char * const pcName,const uint16_t usStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask ); ①&#xff1a;使用此函数前需将宏 confi…

消息队列以及Kafka的使用

什么是消息队列 消息队列&#xff1a;一般我们会简称它为MQ(Message Queue)。其主要目的是通讯。 ps&#xff1a;消息队列是以日志的形式将数据顺序存储到磁盘当中。通常我们说从内存中IO读写数据的速度要快于从硬盘中IO读写的速度是对于随机的写入和读取。但是对于这种顺序存…

SQL 中 IN 与 <= 且 >= 的效率比较

1. 索引利用 当查询条件中的值是离散的、非连续的&#xff0c;或者是在多个不相邻的范围内时&#xff0c;使用 IN 可以更高效&#xff0c;因为 IN 可以直接跳到索引中的这些特定值。而 < 且 > 通常用于连续范围的查询&#xff0c;如果查询的是一个连续的区间&#xff0c…

2024年:AI领航研发新纪元

导言&#xff1a; 在21世纪的科技巨浪中&#xff0c;人工智能&#xff08;AI&#xff09;已经崭露头角&#xff0c;成为研发领域的核心变革者。其强大的潜力和无所不在的应用正在改变人类解决问题的方式&#xff0c;为未来的发展开启了无限可能。随着机器学习、自然语言处理、计…

模型精度fp16和fp32

FP16和FP32是两种不同的浮点数精度格式&#xff0c;在计算机科学特别是深度学习领域中广泛应用。 FP32&#xff08;单精度浮点数&#xff09;&#xff1a; FP32代表32位&#xff08;4字节&#xff09;单精度浮点数格式&#xff0c;这是传统上大多数深度学习模型训练和推理的标准…

代码随想录算法训练营第五十四天|392.判断子序列、115.不同的子序列

392.判断子序列 思路&#xff1a;判断子序列的问题&#xff0c;其实与最大公共子序列的问题是一样的&#xff0c;所以基本上写出来是一样的&#xff0c;但是今天又犯了一个错误&#xff0c;对于非连续子序列&#xff0c;不仅要在相等的时候进行更新&#xff0c;而且不等的时候也…

周立功USBCAN-E-mini分析仪的安装测试笔记

一、介绍 USBCAN-E-mini 智能 CAN 接口卡是系列 USBCAN 便携版本&#xff0c;与 USBCAN—E-U 单路智能 CAN 接口卡完全兼容。USBCAN-E-mini 智能 CAN 接口卡与 USB1.1 总线兼容的&#xff0c;集成 1 路 CAN 接口的智能型 CAN-bus 总线通讯接口卡。采用 USBCAN-E-mini 智能 CAN …

【Linux】Linux操作命令—最全版

目录 一、Linux下基本指令 01. ls 指令 02.cd 指令 03.touch指令 04 mkdir指令 05 man指令 06 cp指令 07 mv指令 08 cat指令 09 more指令 10 less指令 11 head 命令 12 tail 命令 二、时间相关的指令 01 date显示 1.在显示方面&#xff0c;使用者可以设定…