搜索二叉树的认识以及底层实现

news2024/9/20 16:59:23

  如果说到对一个数组进行查找相应的数据,要求效率最高,大家会想到什么方式呢?二分查找?二分查找的效率确实很高,时间复杂度为O(logN)。但是如果我们想要在数组当中添加新的数据呢?加上这一功能之后二分查找的效率可能就不那么好了。因为二分查找的时候要求数组连续并且有序,那么我们在插入数据的时候就一定会伴随着数据的移动操作。那么我们插入数据的时间复杂度就成为了O(N)。为了解决这个困扰我们将进行学习搜索二叉树。

  (一)认识搜索二叉树

  1.什么是搜索二叉树

 所谓的搜索二叉树其实就是具有特殊规则要求的二叉树。搜索二叉树要求我们每一个子树都应该满足其左孩子节点当中存储的值都应该小于父节点当中存储的值,其右孩子节点当中存储的值都应该大于我们父节点当中存储的值。搜索二叉树结构如下图所示:

  只要我们满足这样的规则就说明我们的二叉树是一颗搜索二叉树。

  2.如何构建一颗搜索二叉树

  想要构建一颗搜索二叉树我们就需要熟悉搜索二叉树的规则,也就是左子树当中的值一定小于根节点,右子树当中的值一定大于根节点。之后我们一次将数据根据这种规则加入到树当中即可。

  如上图中的例子所示。我们只需要将数组当中的数据依次根据规则插入到树当中即可。小于我们的根节点就进行左子树当中进行判断,如果小于我们的父节点当中的值就作为左叶子节点插入到树当中。如果大于我们的父节点当中的值就作为右叶子节点插入到树当中。直到最后所有的数据均插入到树当中为止。

  3.搜索二叉树的作用

  相信大家都很奇怪,为什么我们的搜索二叉树可以代替二分查找算法呢?我们来仔细进行观察。由于我们构建搜索二叉树的规则,所以我们想要对一个数据进行查找的时候每一次都可以删除掉近似乎一般的数据。例如加入我们想要对23进行查找的时候。我们需要先根32进行比较,小于32之后直接进入左子树进行下一步的查找,右子树当中的数据直接忽略不计。因为右子树当中的值是大于32的,而我们的23小于32因此不可能在右子树当中出现。

  就这样每次删除一般的数据所以我们查找一个数据的时间复杂度为O(logN)。这个效率是跟我们二分查找的时间复杂度基本相符的。但是搜索二叉树在插入数据的时候的时间复杂度要远优于二分查找数组的时间复杂度。

  对于搜索二叉树来说,插入一个数据我们只需要进行高度次的判断之后就可以插入我们的目标数据。所需要的时间复杂度只有在查找插入为止的时候的O(logN)而已。找到数据应该的插入为止之后直接进行插入即可。

  同时搜索二叉树还有一个优点——当我们构建完成搜索二叉树之后相当于我们已经对数据完成了排序操作。因为对于搜索二叉树进行中序遍历我们就可以得到一个有序的数组。

  但是对于我们的搜索二叉树来说也有一个很明显的弊端,那就是极端的情况下效率并不高。

  想象一下:跟我们上述结构相同的搜索二叉树,想要搜索5实际上就是对我们数组进行了依次遍历操作时间复杂度就变成了O(N)同样的我们的插入数据的操作的时间复杂度也变成了O(N)。如果是这种情况其实并不比我们的二分查找数组好。但是毕竟是极端情况很少会遇到,并且之后我们为了避免这种情况的产生还引入了一种平衡二叉搜索树的结构,使得我们每一棵子树的高度相差小于2,这样就避免了我们这种情况的产生,当然这都是后话,我们先进行学习二叉搜索树。

  (二)二叉搜索树的迭代实现

  那么接下来我们就通过迭代循环的方式实现我们搜索二叉树的相关的功能。

  首先我们要搭建好二叉搜索树的一个框架。有了之前实现list底层的经验之后相信这一部分对大家来说都很简单了。首先我们需要创建一个BSTree作为搜索二叉树的主体。之后再创建一个BSNode类作为我们二叉搜索树当中的单个节点。在BSNode当中我们需要存储左子树的指针,右子树的指针,以及该节点当中保存的数据即可。

  如上图说是,我们在BSTree类当中创建了一个节点作为root节点,之后通过BSNode类对我们常见的节点进行初始化操作,也就是将指针置为空,并赋予相对应的值的操作。

  1.insert功能

  首先我们来实现搜索二叉树的插入功能。

  根据我们前面的分析我们可以知道,想要插入一个新的数据。我们需要先对根节点进行判断操作,如果根节点为空,那么我们可以直接构建一个newnode节点作为我们的根节点即可。如果根节点不为空,我们就需要依次根节点当中所已经存储的数据进行比较,如果想要插入的数据的值小于我们节点当中所存储的值,那么我们在左子树当中继续查找,知道找到数据需要插入的位置即可。右子树同样的道理,如果插入的数据大于我们节点当中存储的值就进入右子树当中进行查找知道找到我们的目标位置即可。

  2.inorder函数

  之后为了便于打印输出检查我们构建的搜索二叉树功能是否一切正常,我们可以编写一个中序遍历的函数,因为根据搜索二叉树的性质,在构建好搜索二叉树的时候,数据就已经被排好序了,我们只需要走一遍中序遍历将数据打印出来即可。

  由于我们的根节点是一个私有的成员变量,对于我们的每一个搜索二叉树对象来说只有一个唯一的根节点。所以为了减少函数之间的耦合程度我们可以进行一次小小的封装。我们在类的内部单独创建一个inorder函数,传入我们的根节点的指针。使得我们在外部使用的时候不需要知道我们根节点的指针具体是什么了。之后直接编写一个简单的中序遍历树的函数即可。测试内容如下:

  经过测试我们可以发现我们的插入函数以及中序遍历函数功能一切正常。

  3.find函数

  接下来我们来进行数据的查找操作。

  查找所需要进行的操作无非就是和我们节点当中的数据进行比较,如果大于根节点当中的数据就到右子树当中继续进行查找。如果小于节点当中存储的数据就到左子树当中继续进行查找,直到查找到目标的数据或者到叶子节点位置。

  运行代码发现结果一切正常。

  4.erase函数

  对于搜索二叉树来说比较复杂的部分就是我们的删除操作了。首先我们需要进行分类讨论。根据我们具体的删除情况来说总共会有以下几种需要删除数据的情况:

  具体数据的删除操作大致分为以上三种形式:

  第一种:我们需要删除的数据在叶子节点的位置,查找到需要删除的数据之后我们直接将数据删除即可。

  第二种:我们需要删除的数据只拥有一个子节点,也就是说另一个左孩子或者右孩子节点为空。这种情况也很好处理,我们只需要将需要删除的节点的指针替换为唯一的孩子节点即可。

  第三种:需要删除的节点同时拥有左孩子和右孩子节点,这个时候我们就需要着重进行处理了。首先我们需要选出一个可以替换我们删除节点的子节点。并且替换之后要保持我们的搜索二叉树的结构不变。我们仔细观察会发现,当我们选择左子树当中的最大值或者右子树当中的最小值进行替换可以满足搜索二叉树的结构不变。为了方便我们采用左子树当中的最大值进行替换。编写代码如下:

  对于我们需要删除节点没有孩子节点的情况其实可以和只有一个子节点的情况进行合并处理。对于没有孩子的情况我们可以将其看成是将另一个子节点当中的空赋值给我们父亲节点对应的指针。实现上述逻辑就可以正确编写我们的erase函数。运行效果如下:

代码运行一切正常。

  5.析构函数

  之后我们只需要进行简单的补充即可。首先我们来实现析构函数。对于我们的析构函数来说我们只需要进行特定顺序的节点的释放即可。

  代码如上述所示。我们需要注意的是最好使用后序遍历操作。因为如果我们使用其他方式进行遍历释放的时候都会因为释放掉根节点造成一部分节点的缺失,带来内存泄漏的问题。

  6.拷贝构造和赋值运算符重载函数

  对于构造函数其实我们需要进行的就是按照指定的顺序进行数据的插入操作。但是我们需要注意我们数据的插入的顺序,因为插入的顺序不同我们构建的树的结构也不同。所以我们需要按照特定的顺序进行数据的插入操作才可以构建出一棵一模一样的搜索二叉树。在这里我们采用前序遍历的方式进行插入操作。

  之后对于我们的赋值运算符重载函数只需要使用我们的新型写法即可。也就是将赋值的操作交给我们的拷贝构造进行,我们将构造好的内容进行交换即可。

  此上我们的搜索二叉树也就实现完毕了。

  (三)二叉搜索树的递归实现

  对于二叉搜索树的递归形式我们仅需要改变一些很小的细节即可。

1.insert函数

  首先我们想要编写递归函数,就需要传入一个可以作为递归标志的变化的参数。通常对于树来说,我们要想实现递归的形式就需要将根节点的指针作为参数传入给我们的函数。因此为了完成递归操作我们在insert函数当中嵌入一个_insert函数,手动的将_root作为参数传入给我们的_insert函数当中。

  相信大家对于大部分的逻辑都是清楚的,我们只需要以递归的形式找到叶子节点当中需要插入数据的位置即可。但是有一点很新颖的地方。那就是我们对于引用的使用,之前我们在使用循环编写的时候都会需要找到父节点,之后通过父节点起到插入节点的作用。否则当我们找到空节点的时候还得回过头重新查找该空节点的父节点是谁。

  但是当我们使用引用的时候就不需要进行这个操作了。因为使用引用之后我们得到的就是“父亲”节点指向的那个空间,直接进行修改就可以达到我们想要的效果。

  运行代码,结果一切正常。

2.find函数

  对于我们的find函数来说甚至更加简单了。我们连数据的插入都不需要了,只需要在找到数据的时候返回相对应的节点即可。

  当我们找到相应的值的时候就返回该节点的指针,当查找到叶子节点还没有找到相应的值的时候就返回nullptr。

  3.erase函数

  最后将erase函数修改成为递归的形式即可。

  我们只需要将erase函数当中查找的操作修改成为递归的形式即可。对于子树的判断操作,我们仍需要手动进行。

  经过上述的操作之后我们的代码也就完全改编成了递归的形式了。

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

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

相关文章

KVM创建的虚拟机无法访问外网

基础环境如下: [rootlocalhost ~]# virsh domifaddr CentOS7_YFName MAC address Protocol Address -------------------------------------------------------------------------------vnet0 52:54:00:cb:a6:0d ipv4 192.168.…

后台数据管理系统 - 项目架构设计-Vue3+axios+Element-plus(0917)

十一、登录注册页面 [element-plus 表单 & 表单校验] 注册登录 静态结构 & 基本切换 安装 element-plus 图标库 pnpm i element-plus/icons-vue静态结构准备 <script setup> import { User, Lock } from element-plus/icons-vue import { ref } from vue cons…

P2865 [USACO06NOV] Roadblocks G

*原题链接* 次短路模版题 在刚学最短路时&#xff0c;我做过这道题集合位置&#xff0c;那时博客上写的是枚举删除最短路上的边&#xff0c;然后求解。不过这种做法最坏时间复杂度可以有&#xff0c;对于这道题数据范围较大&#xff0c;所以可以用更好写&#xff0c;思维难度…

Linux学习记录十四----------线程的创建和回收

文章目录 五、Linux线程1.守护进程1.1.守护进程的特点1.2.进程组1.3会话1.4创建守护进程模型 2.线程的概念3.线程的创建及相关函数3.1.创建线程‐‐pthread_create3.2.单个线程退出 --pthread_exit3.3.阻塞等待线程退出&#xff0c;获取线程退出状态--pthread_join3.4.线程分离…

python怎么运行cmd命令

使用os.system(“cmd”) 该方法在调用完shell脚本后,返回一个16位的二进制数,低位为杀死所调用脚本的信号号码,高位为脚本的退出状态码,即脚本中“exit 1”的代码执行后,os.system函数返回值的高位数则是1,如果低位数是0的情况下,则函数的返回值是0100,换算为10进制得到256。 …

JavaScript web API完结篇---多案例

BOM window对象 >包含docment Browser Object Model 定时器–延时函数 之前学的是间歇函数 让代码延迟执行 仅执行一次 setTimeout(回调函数&#xff0c;等待毫秒数) 消除 clearTimeout(timer) > 用于递归时需要进行去除 JS执行机制 单线程 > 一个任务结束&…

ROS组合导航笔记2:使用外部定位系统

在上一单元中&#xff0c;我们了解了如何合并不同传感器的数据以生成机器人的姿势估计。因此&#xff0c;基本上&#xff0c;我们介绍了图表的以下部分&#xff0c;其中向 robot_localization 节点提供了不同的传感器&#xff0c;以便通过卡尔曼滤波器进行合并。 但是...图表的…

【浅水模型MATLAB】尝试复刻SCI论文中的溃坝流算例

【浅水模型MATLAB】尝试复刻SCI论文中的溃坝流算例 前言问题描述控制方程及数值方法浅水方程及其数值计算方法边界条件的实现 代码框架与关键代码模拟结果 更新于2024年9月17日 前言 这篇博客算是学习浅水方程&#xff0c;并利用MATLAB复刻Liang (2004)1中溃坝流算例的一个记录…

【FreeRL】Rainbow_DQN的实现和测试

文章目录 前言环境1 PER note2 C51 note3 Noisy note4 Rainbow note其他 前言 具体代码实现见&#xff1a;https://github.com/wild-firefox/FreeRL/blob/main/DQN_file/DQN_with_tricks.py 将其中所有的trick都用上即为Rainbow_DQN。 效果如下&#xff1a;&#xff08;学习曲…

word文档的写入(1)

Word文档的写入 我们手动复制Excel信息&#xff0c;再粘贴进Word&#xff0c;进行文件保存的整个操作。属于机械性的重复劳动&#xff0c;并不能带来太大价值。在Excel和Word的操作内&#xff0c;也没有能很好解决此类问题的方法。如果遇到信息一多&#xff0c;几十上百个文件&…

Win11小技巧之调节音量

无意中发现&#xff0c;鼠标悬停在喇叭&#x1f508;处可通过滚轮调节音量&#xff0c;无需每次都点开音量面板&#xff0c;再悬停在音量滚动条处通过滚轮调节&#xff01;&#xff08;设计师……怎么不早告诉我……&#xff09; 不用点开&#xff0c;之前一直都是这么调节音量…

c++—多态【万字】【多态的原理】【重写的深入学习】【各种继承关系下的虚表查看】

目录 C—多态1.多态的概念2.多态的定义及实现2.1多态的构成条件2.2虚函数的重写2.2.1虚函数重写的两个例外&#xff1a;2.2.1.1协变2.2.1.2析构函数的重写 2.3 c11的override和final2.3.1final2.3.2override 2.4 重载、重写、重定义的对比 3.抽象类3.1抽象类的概念3.2接口继承和…

5款录屏软件电脑版,哪一款更适合你?

身边不少做行政的小伙伴&#xff0c;经常需要制作一些培训视频、会议记录或是演示文稿。这就要求他们必须掌握一款好用的录屏软件。作为一个经常搜索各种办公软件的人&#xff0c;今天&#xff0c;我就来分享一下我使用过的五款录屏软件在录制电脑屏幕时的表现。 1、福昕录屏大…

枚举类题目练习心得

两数之和 题目如下&#xff1a; 一点思路&#xff1a;该题目仅限于数据量少的情况使用枚举&#xff0c;从题目分析来看&#xff0c;需求是给定一个数字&#xff0c;要求在给定数组中找到两个数字并使这两个数字和为给定数字且返回目标数字下标。参考题解思路结合本身思路代码…

Leetcode—环形链表||

题目描述 思路 快慢指针 结论 我们需要用到一个重要的结论&#xff1a;让一个指针从链表起始位置开始遍历链表,同时让一个指针从判环时相遇点的位置开始绕环运行,两个指针都是每次均走一步,最终肯定会在入口点的位置相遇。 画图解释 1.利用快慢指针找到相遇点 2. 定义两个…

java138-异常处理_java 138错误

//异常 public class test79 { //定义方法声明定义异常&#xff0c;在满足条件时抛出异常对象&#xff0c;程序转向异常处理 public double count(double n,double m)throws Exception { if (m 0) {//如果除数等于0.则抛出异常实例 throw new Ex…

day03 - Java集合和常用类

第一章 Collection集合 1. Collection概述 集合&#xff1a;java中提供的一种容器&#xff0c;可以用来存储多个数据 集合和数组既然都是容器&#xff0c;它们有啥区别呢&#xff1f; 数组的长度是固定的。集合的长度是不固定的。集合可以随时增加元素&#xff0c;其大小也随…

kubeadm方式安装k8s+基础命令的使用

一、安装环境 二、前期准备 1.设置免密登录 [rootk8s-master ~]# ssh-keygen [rootk8s-master ~]# ssh-copy-id root192.168.2.77 [rootk8s-master ~]# ssh-copy-id root192.168.2.88 2.yum源配置 3.清空创建缓存 4.主机映射&#xff08;三台主机都要设置&#xff09; 5.安装…

vivado中选中bd文件后generate output product是什么用,create HDL wrapper是什么用

vivado中选中bd文件后generate output product是什么用 在Vivado中&#xff0c;“Generate Output Products” 是一个重要的步骤&#xff0c;它用于生成IP核的输出产品&#xff0c;这些产品是将IP核集成到设计中所需的文件。这些输出产品包括&#xff1a; 综合文件&#xff…

多线程下的共享变量访问数据竞争的问题

多线程下对共享变量的写存在数据竞问题可导致数据与预期不一致。最近在研究race conditions漏洞&#xff0c;用以下python 代码记录一下&#xff0c;以此论证&#xff0c;如下&#xff1a; from concurrent.futures import ThreadPoolExecutor globalNum 5 def test():global…