二叉树搜索树(上)

二叉树搜索树(上)

在这里插入图片描述

概念

二叉搜索树又称二叉排序树,它或者是一颗空树,或者是具有以下性质的二叉树:

• 若它的左子树不为空,则左子树上所有结点的值都于等于根结点的值

• 若它的右子树不为空,则右子树上所有结点的值都于等于根结点的值

• 它的左右子树也分别为二叉搜索树

• 二叉搜索树中可以支持插⼊相等的值,也可以不支持插⼊相等的值,具体看使⽤场景定义,map/set/multimap/multiset系列容器底层就是二叉搜索树,其中map/set不支持插⼊相等值,multimap/multiset支持插⼊相等值

二叉搜索树的性能分析

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其⾼度为: O ( l o g 2 N ) O(log_2N) O(log2N)

最差情况下,二叉搜索树退化为单支树(或者类似单支),其⾼度为: O ( N 2 ) O(\frac{N}{2}) O(2N)

所以综合而言二叉搜索树增删查改时间复杂度为: O(N)

那么这样的效率显然是无法满⾜我们需求的,二叉搜索树的变形:平衡二叉搜索树AVL树和红⿊树,才能适用于我们在内存中存储和搜索数据。

另外需要说明的是,二分查找也可以实现 O(logN) 级别的查找效率,但是二分查找有两⼤缺陷:

  1. 需要存储在支持下标随机访问的结构中,并且有序。
  2. 插⼊和删除数据效率很低,因为存储在下标随机访问的结构中,插⼊和删除数据⼀般需要挪动数据。

这⾥也就体现出了平衡二叉搜索树的价值。

二叉树的插入

插⼊的具体过程如下:

  1. 树为空,则直接新增结点,赋值给root指针
  2. 树不空,按二叉搜索树性质,插⼊值⽐当前结点大往右走,插⼊值⽐当前结点⼩往左⾛,找到空位置,插⼊新结点。
  3. 如果支持插⼊相等的值,插⼊值跟当前结点相等的值可以往右⾛,也可以往左⾛,找到空位置,插⼊新结点。(要注意的是要保持逻辑⼀致性,插⼊相等的值不要⼀会往右走,⼀会往左走)

参考代码:

template<class K>
class BSTNode
{
public:BSTNode(const K& key):_key(key),_left(nullptr),_right(nullptr){}private:K _key;BTSNode<K>* _left;BTSNode<K>* _right;};template<class K>
class BSTree
{using Node = BSTNode<k>;//用using替代typedef的作用
public:bool Insert(const K& key){//如果是空树if (_root == nullptr){_root = new Node(key);return true;//这时就结束了}//如果不是空树Node* parent = nullptr;Node* cur = _root;while (cur)//当cur不为空{if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}elsereturn false;//避免冗余}//到这里说明cur已经找到空位置了,但是不知道是从右走的还是往左走的,得再比一次cur = new Node(key);if (key < parent->_key){parent->left = cur;}else{parent->right = cur;}return true;}
private:Node* _root = nullptr;//是结点指针类型,不是结点类型
};

如果允许已存在的值插入(或者说运行冗余):

我们会发现,如果走中序遍历(中序遍历顺序是先遍历左子树,然后访问根节点,最后遍历右子树),就是有序的。即使允许冗余的情况下,中序遍历也是有序的。

中序遍历代码参考:

template<class K>
class BSTree
{
//……private:void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key <<" ";_InOrder(root->_right);}Node* _root = nullptr;//是结点指针类型,不是结点类型
};

可以发现一个问题,我们需要传入根节点才能进行中序遍历,但是根节点在类中贝private修饰无法访问。

一种方法是提供GetRoot接口,但还有别的方法:

在C++的类中要写递归时,公开的方法都要套一层。

public:
void InOrder()//类外面拿不到_root,但是类里面是可以的
{_InOrder(_root);
}

类里面二叉树的递归基本都需要像这样套一层。

二叉树搜索树的查找

  1. 从根开始⽐较,查找x,x⽐根的值⼤则往右边⾛查找,x⽐根值⼩则往左边⾛查找。
  2. 最多查找⾼度次,⾛到到空,还没找到,这个值不存在。
  3. 如果不⽀持插⼊相等的值,找到x即可返回
  4. 如果⽀持插⼊相等的值,意味着有多个x存在,⼀般要求查找中序的第⼀个x。如下图,查找3,要找到1的右孩⼦的那个3返回

参考代码:

bool Find(const K& key)
{Node* cur = _root;while (cur){if (key < cur->_key){cur = cur->_left;}else if (key > cur->_key){cur = cur->_right;}else{return true;}}return false;
}

二叉搜索树的删除

⾸先查找元素是否在二叉搜索树中,如果不存在,则返回false。

如果查找元素存在则分以下四种情况分别处理:(假设要删除的结点为N)

  1. 要删除结点N左右孩⼦均为空
  2. 要删除的结点N左孩⼦位空,右孩⼦结点不为空
  3. 要删除的结点N右孩⼦位空,左孩⼦结点不为空
  4. 要删除的结点N左右孩⼦结点均不为空

对应以上四种情况的解决⽅案:

  1. 把N结点的⽗亲对应孩⼦指针指向空,直接删除N结点(情况1可以当成2或者3处理,效果是⼀样的)

  2. 把N结点的⽗亲对应孩⼦指针指向N的右孩⼦,直接删除N结点

  3. 把N结点的⽗亲对应孩⼦指针指向N的左孩⼦,直接删除N结点

  4. ⽆法直接删除N结点,因为N的两个孩⼦⽆处安放,只能⽤替换法删除。

    找N左⼦树的值最⼤结点R(最右结点)或者N右⼦树的值最⼩结点R(最左结点)替代N,因为这两个结点中任意⼀个,放到N的位置,都满⾜⼆叉搜索树的规则。

    替代N的意思就是N和R的两个结点的值交换,转⽽变成删除R结点,R结点符合情况2或情况3,可以直接删除

参考代码(比较多要注意的细节,写在注释里了):

bool Erase(const K& key)
{Node* cur = _root;Node* parent = nullptr;while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if(key>cur->_key){parent = cur;cur = cur->_right;}else{//找到了要删除的数据,接下来进行删除,但是要判断情况//cur的左孩子为空(左右孩子都为空也被包含在内了),然后把cur的右孩子给给父(父可能为空)if (cur->_left == nullptr){//父为空,也就是说要删的是头结点if (parent==nullptr){_root = cur->_right;}else{//要判断父节点的左还是右指针指向cur的孩子if(parent->_left==cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;return true;//别忘了}//cur的右孩子为空,把cur的左孩子给父else if (cur->_right == nullptr){if (parent == nullptr){_root = cur->_left;}else{//判断父节点的左还是右指针指向cur孩子if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;return true;//别忘了}else//现在是cur的左右孩子都不为空{//第一步,找R(这里采用cur的右子树的最小结点/也就是最左结点)Node* p = cur;//这里如果初始给空,后面如果不进循环,会造成对空指针的解引用Node* r = cur->_right;while (r->_left){p = r;r = r->_left;}//第二步,进行交换(但不用真的把cur的值再去给r)cur->_key = r->_key;//第三步,删除R——很容易考虑不全面!!删除R又要把删除的问题考虑全面,但这里不能用递归,会找不到//在删除R时要把R的右孩子(只可能是右孩子)给给父,但是父的左还是右指针不确定if (p->_right == r){p->_right = r->_right;}else{p->_left = r->_right;}delete r;return true;}}}return false;
}

那么,增删查改我们现在已经完成了增、删、查了,但是普通二叉树是不允许修改的。

本文到此结束。

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

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

相关文章

人群计数制作私有数据集教程-----自用

一、人群计数的数据集包括两部分&#xff1a;图像部分和标签部分 1.公开数据集格式 标签部分主要包括每个人头的坐标点&#xff1a;&#xff08;x, y&#xff09;&#xff1b; 常见的标签格式例如&#xff1a;ShanghaiTech数据集中的格式&#xff0c;用mat文件存储每个人头的坐…

SpringBoot项目快速打包成jar项目与部署

上文中,tomcat配置完成了。接下来我们需要将我们的项目打包部署至tomcat服务器。 传统的Web应用进行打包部署时,通常会打成War包的形式,然后将War包部署到Tomcat等服务器中,而SpringBoot应用使用的是嵌入式Servlet容器,也就是说,SpringBoot应用默认是以jar包形式进行打包…

【YOLOv8图像分类】YOLOv8图像分类源代码

前言 此程序是使用YOLOv8训练自己的图像并测试。Yolo系列模型可以说是比较特殊的模型&#xff0c;因为不像其他公开网络ResNet、GoogLeNet等等&#xff0c;可以自己构建和更改层。Yolo只能整体调用这个网络&#xff0c;这个可能是让初学者比较头疼的问题&#xff0c;就是看不到…

【干货】金融数据分析:风险评估中的数据分析

风险评估中的数据分析 金融风险评估因是金融行业的核心任务之一&#xff0c;也是保障金融稳定和机构可持续发展的关键。在当今数字化时代&#xff0c;数据分析已经成为金融风险评估的有力武器&#xff0c;能够帮助我们拨开复杂现象的迷雾&#xff0c;洞察风险的本质。 金融风…

【Hadoop】【hdfs】【大数据技术基础】实验三 HDFS Java API编程实践

实验三&#xff1a; HDFS Java API编程实践 实验题目 HDFS Java API编程实践 实验目的 熟悉HDFS操作常用的Java API。 实验平台 操作系统&#xff1a;Linux Hadoop版本&#xff1a;2.6.0或以上版本 JDK版本&#xff1a;1.6或以上版本 Java IDE&#xff1a;Eclipse 实验…

第R3周:RNN-心脏病预测(TensorFlow版)

>- **&#x1f368; 本文为[&#x1f517;365天深度学习训练营]中的学习记录博客** >- **&#x1f356; 原作者&#xff1a;[K同学啊]** &#x1f37a; 要求&#xff1a; 找到并处理第8周的程序问题&#xff08;本文给出了答案&#xff09;了解循环神经网络&#xff08…

数据结构 ——— 链式二叉树oj题:将链式二叉树的前序遍历存放在数组中

题目要求 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历 手搓一个链式二叉树 代码演示&#xff1a; // 数据类型 typedef int BTDataType;// 二叉树节点的结构 typedef struct BinaryTreeNode {BTDataType data; //每个节点的数据struct BinaryTreeNode* l…

前端中的 File 和 Blob两个对象到底有什么不同

JavaScript 在处理文件、二进制数据和数据转换时&#xff0c;提供了一系列的 API 和对象&#xff0c;比如 File、Blob、FileReader、ArrayBuffer、Base64、Object URL 和 DataURL。每个概念在不同场景中都有重要作用。下面的内容我们将会详细学习每个概念及其在实际应用中的用法…

酒店叮咚门铃的类型有哪些

在酒店的环境中&#xff0c;叮咚门铃虽小&#xff0c;却有着重要的作用&#xff0c;它是客人与酒店服务人员沟通的重要桥梁。酒店叮咚门铃主要有以下几种类型&#xff1a; 有线叮咚门铃 这是较为传统的一种类型。它通过电线连接&#xff0c;通常安装在客房的墙壁上&#xff0c;…

SFW3009 多功能移动照明系统

SFW3009 多功能移动照明系统 适用范围 广泛适用于铁路、水利、电网等抢险救援现场大范围移动照明。 结构特性 灯具体积小、重量轻&#xff0c;可以实现拖行、手提、背行三种携带方式。灯具底部也可以安装铁轨轮&#xff0c;便于用户在铁轨上作业。 灯头组件由左右两个灯头…

JavaWeb——Web入门(8/9)- Tomcat:基本使用(下载与安装、目录结构介绍、启动与关闭、可能出现的问题及解决方案、总结)

目录 基本使用内容 下载与安装 目录结构介绍 启动与关闭 启动 关闭 可能出现的问题及解决方案 问题一&#xff1a;启动时窗口一闪而过 问题二&#xff1a;端口号冲突 问题三&#xff1a;部署应用程序 总结 基本使用内容 Tomcat 服务器在 Java Web 开发中扮演着至关重…

w032基于web的阿博图书馆管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0…

Java:使用Jackson解析json时如何正确获取节点中的值?

使用Jackson解析json时&#xff0c;经常会需要获取到某一节点下的值&#xff0c;例如&#xff1a; { “data”: { "test1": "value1", "test2": null, "test3": 10 } } 以Jackson2.13.5为例&#xff0c;使用at(jsonPtrExp)这种API&…

前端必懂:常见排序算法深度解析

在前端开发中&#xff0c;排序算法是一种非常重要的工具。无论是对数组进行排序以展示数据&#xff0c;还是对复杂对象进行排序以实现特定的功能&#xff0c;理解和掌握常见的排序算法对于提高开发效率和代码质量至关重要。本文将介绍几种前端常见的排序算法。 一、冒泡排序(Bu…

vue 依赖注入(Provide、Inject )和混入(mixins)

Prop 逐级透传问题​ 通常情况下&#xff0c;当我们需要从父组件向子组件传递数据时&#xff0c;会使用 props。想象一下这样的结构&#xff1a;有一些多层级嵌套的组件&#xff0c;形成了一棵巨大的组件树&#xff0c;而某个深层的子组件需要一个较远的祖先组件中的部分数据。…

开启鸿蒙开发之旅:核心组件及其各项属性介绍——布局容器组件

写在前面 组件的结构 rkTS通过装饰器 Component 和 Entry 装饰 struct 关键字声明的数据结构&#xff0c;构成一个自定义组件。 自定义组件中提供了一个 build 函数&#xff0c;开发者需在该函数内以链式调用的方式进行基本的 UI 描述 今天我们要学习的就是写在build 函数里的系…

数据结构OJ题

目录 轮转数组原地移除数组中所有元素val删除有序数组中的重复项合并两个有序数组 轮转数组 思路1&#xff1a; 1.利用循环将最后一位数据放到临时变量&#xff08;n&#xff09;中 2.利用第二层循环将数据往后移一位 3.将变量&#xff08;n&#xff09;的数据放到数组第一位 时…

Pencils Protocol 推出新板块 Auction ,为什么重要且被看好?

Pencils Protocol 上线了又一新产品板块 Auction&#xff0c;预示着生态版图的进一步完善&#xff0c;该板块的推出无论是对于 Pencils Protocol 协议本身&#xff0c;还是 Scroll 生态都是极为重要的。 社区正在成为主导加密市场发展的重要力量 自 DeFi Summer 以来&#xf…

Pytorch学习--神经网络--完整的模型训练套路

一、下载数据集 train_data torchvision.datasets.CIFAR10(root"datasets",trainTrue,transformtorchvision.transforms.ToTensor(),downloadTrue) train_data torchvision.datasets.CIFAR10(root"datasets",trainFalse,transformtorchvision.transform…

常用数字器件的描述-组合逻辑器件

目录 基本逻辑门 编码器 译码器 数据选择器 数值比较器 三态缓冲器 奇偶校验器 组合逻辑器件有逻辑门、编码器与译码器、数据选择器和数值比较器、加法器、三态器件和奇偶校验器等多种类型。 基本逻辑门 Verilog HDL中定义了实现七种逻辑关系的基元&#xff0c;例化这些…