【C++笔记】二叉搜索树
前言
各位读者朋友们大家好!上期我们讲完了面向对象编程三大属性之一的多态,这一期我们再次开始数据结构二叉搜索树的讲解。
目录
- 前言
- 一. 二叉搜索树的概念
- 二. 二叉搜索树的性能分析
- 三. 二叉搜索树的插入
- 四. 二叉搜索树的查找
- 五. 二叉搜索树的删除
- 六. 二叉搜索树key和key/value使用场景
- 6.1 key搜索场景
- 6.2 key/value搜索场景
- 七. 析构函数、拷贝构造以及赋值重载的实现
- 结语
一. 二叉搜索树的概念
二叉搜索树又称二叉排序树,它或者是一颗空树,或者是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上的所有节点的值都小于等于根节点的值
- 若它的右子树不为空,则右子树上的所有节点的值都大于等于根节点的值
- 它的左右子树也是二叉搜索树
- 二叉搜索树中可以支持插入相同的值,也可以不支持插入相同的值,具体看使用场景定义,后续我们会学习map/set/multimap/multiset系列容器底层就是二叉搜索树,其中map/set不支持插入相等的值,multimap/multiset支持插入相等的值。
二. 二叉搜索树的性能分析
最优情况下,二叉搜索树为完全二叉树或者接近于完全二叉树,最多的搜索次数是高度次,即O(logN)
最坏情况下,二叉树退化为单枝树或者类似于单枝树,最坏的搜索次数是高度次,即O(N)
所以综合而言二叉搜索树的增删查改的时间复杂度是O(N)
这样的效率显然是无法满足我们的需求的,我们后面会继续讲解二叉搜索树的变形,平衡搜索树AVL树和红黑树,才能适合我们在内存中存储和搜索数据。
另外需要说明的是,二分查找也可以实现O(logN)的查找效率,但是二分查找有两大缺陷:
- 需要存储在支持下标随机访问的结构中并且有序
- 插入和删除数据的效率很低,因为在支持下标随机访问的结构中,插入和删除数据一般都需要挪动数据
这里就体现出了平衡二叉搜索树的价值。
三. 二叉搜索树的插入
插入的具体过程如下:
- 如果树为空,则直接新增节点,赋值给root指针
- 树不为空,按二叉搜索树性质,插入值比当前节点值大的往右走,插入值比当前节点小的往左走,找到空位置,插入新节点
- 如果支持插入相等的值,插入值跟当前节点相等的值可以往左走,也可以往右走,找到空位置,插入新节点。(要注意的是保持逻辑的一致,插入相等的值不要一会往左走,一会往右走)
二叉搜索树的结构:
template<class K>// 节点
struct BTNode
{K _key;BTNode<K>* _left;BTNode<K>* _right;BTNode(const K& key):_key(key), _left(nullptr), _right(nullptr){}
};
template<class K>
class BSTree
{typedef BTNode<K> Node;
public:// 成员函数
private:Node* _root;
};
二叉搜索树插入代码的实现
bool Insert(const K& key)
{if (_root == nullptr){_root = new Node(key);return true;}Node* parent = nullptr;// 父亲节点Node* cur = _root;// 当前节点while (cur){if (cur->_key > key)// 当前节点的值大于key,往左走{parent = cur;cur = cur->_left;}else if (cur->_key < key)// 当前节点的值小于key,往右走{parent = cur;cur = cur->_right;}else{return false;}}// 找到空位置cur = new Node(key);if (parent->_key > key){parent->_left = cur;}else{parent->_right = cur;}return true;
}
四. 二叉搜索树的查找
- 从根节点开始比较,查找key, key比根节点的值大,往右找,比根结点的值小,往左边找。
- 最多查找高度次,走到空,如果还没有找到,这个值就不存在
- 如果不支持插入相等的值,找到key返回即可
- 如果支持插入相等的值,意味着有多个key存在,一般要求查找中序中的第一个key。
二叉搜索树查找代码实现:
bool Find(const K& key){Node* cur = _root;while (cur){if (cur->_key > key)// 往左走{cur = cur->_left;}else if (cur->_key < key){cur = cur->_right;}else{return true;}}return false;}
五. 二叉搜索树的删除
首先查找元素是否在二叉搜索树中,如果不存在就返回false。
如果查找的元素存在,则分以下四种情况处理:
- 要删除的节点左右子树均为空
- 要删除的节点的左子树为空,右子树不为空
- 要删除的节点的右子树为空,左子树不为空
- 要删除的节点左右子树均不为空
对应以上四种情况的解决方法:
对于前三种比较好处理:如果左子树为空,右子树不为空,就将该节点的父亲节点指向该节点的右子树,删除该节点;如果右子树为空,左子树不为空,,就将该节点的父亲节点指向该节点的左子树,然后删除该节点;对于左右子树均为空树的情况,上面的方法就可以解决,需要注意的是,在父亲节点连接子树的时候,需要判断删除的节点是父亲节点的左子树还是右子树。
对于左右子树均不为空的情况:我们要直接删的话难度很大,二叉搜索树的性质:左右子树均是二叉树搜索树,我们把这个节点删除之后还要保持这个性质,所以可以将左子树最大的节点(最右节点)找到,把最大节点的值赋值给要删除的节点,然后连接上最大节点的左子树,删除这个最大的节点;或者找右子树中最小的节点(最左节点),重复上面的操作即可。
bool Erase(const K& key)
{Node* cur = _root;Node* parent = nullptr;// 找key节点while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else// 找到key了{// 左为空,父亲节点连右子树if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{//当前节点是父亲节点的左子树if (cur == parent->_left){parent->_left = cur->_right;}else//当前节点是父亲节点的右子树{parent->_right = cur->_right;}}delete cur;}// 右子树为空,父亲节点连左子树else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{//当前节点是父亲节点的左子树if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;}else //左右子树都不为空,找左子树中最大的(最右节点){Node* dest = cur->_left;Node* parent = cur;while (dest->_right){parent = dest;dest = dest->_right;}cur->_key= dest->_key;if (parent->_left == dest)parent->_left = dest->_left;elseparent->_right = dest->_left;delete dest;}return true;}}return false;
}
这段代码有几个需要注意的点:
这种情况左子树或右子树为空都适用
六. 二叉搜索树key和key/value使用场景
6.1 key搜索场景
只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值,搜索场景只需要判断key在不在。key的搜索场景实现的⼆叉树搜索树支持增删查,但是不支持修改,修改key破坏搜索树结构了。
场景1:小区无人值守车库,小区车库买了车位的业主的车才能进小区,那么物业会把买了车位的业主的车牌号录入后台系统,车辆进⼊时扫描车牌在不在系统中,在则抬杆,不在则提示非本小区车辆,无法进入。
场景2:检查⼀篇英文文章单词拼写是否正确,将词库中所有单词放入二叉搜索树,读取文章中的单词,查找是否在二叉搜索树中,不在则波浪线标红提示。
6.2 key/value搜索场景
每⼀个关键码key,都有与之对应的值value,value可以任意类型对象。树的结构中(结点)除了需要存储key还要存储对应的value,增/删/查还是以key为关键字按二叉搜索树的规则进行比较,可以快速查找到key对应的value。key/value的搜索场景实现的二叉树搜索树支持修改,但是不支持修改key,修改key破坏搜索树结构了,可以修改value。
场景1:简单中英互译字典,树的结构中(结点)存储key(英文)和vlaue(中文),搜索时输入英文,则同时查找到了英文对应的中文。
场景2:商场无人值守车库,入口进场时扫描车牌,记录车牌和入场时间,出口离场时,扫描车牌,查找入场时间,用当前时间-入场时间计算出停车时长,计算出停车费用,缴费后抬杆,车辆离场。
场景3:统计一篇文章中单词出现的次数,读取一个单词,查找单词是否存在,不存在这个说明第一次出现,(单词,1),单词存在,则++单词对应的次数。
key/value⼆叉搜索树代码实现:
namespace Key_Value
{template<class K, class V>struct BTNode{K _key;V _val;BTNode<K, V>* _left;BTNode<K, V>* _right;BTNode(const K& key, const V& val):_key(key), _val(val), _left(nullptr), _right(nullptr){}};template<class K, class V>class BSTree{typedef BTNode<K, V> Node;public:BSTree() = default;~BSTree(){Destroy(_root);}BSTree(const BSTree& t){_root = Copy(t._root);}BSTree& operator=(BSTree tmp){std::swap(_root, tmp._root);return *this;}bool Insert(const K& key, const V& val){if (_root == nullptr){_root = new Node(key, val);return true;}Node* parent = nullptr;// 父亲节点Node* cur = _root;// 当前节点while (cur){if (cur->_key > key)// 当前节点的值大于key,往左走{parent = cur;cur = cur->_left;}else if (cur->_key < key)// 当前节点的值小于key,往右走{parent = cur;cur = cur->_right;}else{return false;}}// 找到空位置cur = new Node(key, val);if (parent->_key > key){parent->_left = cur;}else{parent->_right = cur;}return true;}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_key > key)// 往左走{cur = cur->_left;}else if (cur->_key < key){cur = cur->_right;}else{return cur;}}return nullptr;}bool Erase(const K& key){Node* cur = _root;Node* parent = nullptr;// 找key节点while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else// 找到key了{// 左为空,父亲节点连右子树if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{//当前节点是父亲节点的左子树if (cur == parent->_left){parent->_left = cur->_right;}else//当前节点是父亲节点的右子树{parent->_right = cur->_right;}}delete cur;}// 右子树为空,父亲节点连左子树else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{//当前节点是父亲节点的左子树if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;}else//左右子树都不为空,找左子树中最大的(最右节点){Node* dest = cur->_left;Node* parent = cur;while (dest->_right){parent = dest;dest = dest->_right;}cur->_key = dest->_key;if (parent->_left == dest)// 删根节点parent->_left = dest->_left;elseparent->_right = dest->_left;delete dest;}return true;}}return false;}void _InOrder(){InOrder(_root);cout << endl;}private:void InOrder(Node* _root){if (_root == nullptr)return;InOrder(_root->_left);cout << _root->_key << ":" << _root->_val << endl;InOrder(_root->_right);}void Destroy(Node* _root){if (_root == nullptr)return;Destroy(_root->_left);Destroy(_root->_right);delete _root;}Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* newRoot = new Node(root->_key, root->_val);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;}Node* _root = nullptr;};
}
七. 析构函数、拷贝构造以及赋值重载的实现
- 析构函数,将二叉树的每一个节点的内存空间都释放掉,之前我们C语言的二叉树的销毁用的后序遍历,由于我们在类外无法访问到_root指针,我们可以将二叉树的销毁实现成私有成员函数,然后在类内让析构函数去调用
~BSTree()
{Destroy(_root);
}
private:
void Destroy(Node* _root)
{if (_root == nullptr)return;Destroy(_root->_left);Destroy(_root->_right);delete _root;
}
- 拷贝构造
拷贝构造的实现我们选择使用递归的方法,先将根节点构造出来,然后构建左子树再构建右子树。
BSTree(const BSTree& t)
{_root = Copy(t._root);
}
private:
Node* Copy(Node* root)
{if (root == nullptr)return nullptr;Node* newRoot = new Node(root->_key, root->_val);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;
}
用下面的例子部分演示一下构造子树的过程
- 赋值重载
有了拷贝构造之后,写现代写法的复制重载就很容易了,只需要交换两个树的根节点的指针即可。
BSTree& operator=(BSTree tmp)
{std::swap(_root, tmp._root);return *this;
}
结语
以上我们就讲完了二叉搜索树的内容,希望对大家有所帮助!感谢大家的阅读,欢迎大家批评指正!
相关文章:

【C++笔记】二叉搜索树
前言 各位读者朋友们大家好!上期我们讲完了面向对象编程三大属性之一的多态,这一期我们再次开始数据结构二叉搜索树的讲解。 目录 前言一. 二叉搜索树的概念二. 二叉搜索树的性能分析三. 二叉搜索树的插入四. 二叉搜索树的查找五. 二叉搜索树的删除六.…...

Fork/Join框架简介
一、Fork/Join框架简介 Fork/Join框架是Java 7引入的一个用于并行执行任务的框架,它可以将一个大任务分割成若干个小任务,并行执行这些小任务,然后将每个小任务的结果合并起来,得到大任务的结果。这种框架特别适合于能够被递归分…...

Java项目实战II基于微信小程序的电子竞技信息交流平台的设计与实现(开发文档+数据库+源码)
目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发,CSDN平台Java领域新星创作者,专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 随着互联网技术的飞速发展…...

Mysql读写分离分库分表
读写分离 什么是读写分离 读写分离主要是为了将对数据库的读写操作分散到不同的数据库节点上。 这样的话,就能够小幅提升写性能,大幅提升读性能。一般情况下,我们都会选择一主多从,也就是一台主数据库负责写,其他的从…...

B站狂神说--springboot项目学习(新建一个springboot项目)
文章目录 1.新建项目java8项目1.解决自带的idea2024无法使用java8的问题 2.新建接口3.项目打包为jar包4.使用jar包 1.新建项目java8项目 1.解决自带的idea2024无法使用java8的问题 将server.url修改为阿里云的地址:https://start.aliyun.com/ 选择Spring Web 创建…...

eltable el-table 横向 滚动条常显
又遇到了难受的问题,el-table嵌入在一个div里面,结果因为内容太多,横向、纵向我都得滚动查看! 结果发现横向滚动时只能让它纵向触底后才能进行横向操作,这就很变态,明显不符合用户操作习惯。如下图: 要先纵…...

centos8 mysql 主从复制
原理 一、一主一从 准备工作 1.主库配置 1、修改配置文件 /etc/my.cnf #mysql 服务ID,保证整个集群环境中唯一,取值范围:1-232-1,默认为 server-id1 #是否只读,1 代表只读,0代表读写 read-only0 #忽略的数据,指不需要同步的数据库 #binlog…...

【C++】入门【五】
本节目标 一、C/C内存分布 二、C语言中动态内存管理方式 三、C中动态内存管理 四、operator new与operator delete函数 五、new和delete的实现原理 六、定位new表达式(placement-new) 七、常见面试题 一、C/C内存分布 一个程序占用的内存主要有以下几部分栈区(stac…...

【React】二、状态变量useState
文章目录 1、React中的事件绑定1.1 基础事件绑定1.2 使用事件对象参数1.3 传递自定义参数1.4 同时传递事件对象和自定义参数 2、React中的组件3、useState 1、React中的事件绑定 1.1 基础事件绑定 语法:on 事件名称 { 事件处理程序 },整体上遵循驼峰…...

SQL Server中的数据处理函数:提升SQL查询能力
文章目录 前言1. 数据类型转换函数CAST()CONVERT()TRY_CAST() 和 TRY_CONVERT() 2. 数学函数ABS()CEILING()FLOOR()ROUND()POWER()SQRT() 3. 日期和时间函数GETDATE()SYSDATETIME()DATEADD()DATEDIFF()YEAR()、MONTH() 和 DAY()FORMAT() 4. 条件处理函数CASEIIF() 总结 前言 在…...

TypeScript 语言学习入门级教程五
在前面的教程中,我们已经逐步深入地学习了 TypeScript 的诸多特性,包括基础语法、类型系统、面向对象编程、装饰器以及一些高级类型等。在本教程中,我们将聚焦于 TypeScript 的模块系统、命名空间与模块的关系、声明文件以及如何在实际项目中…...

上海市计算机学会竞赛平台2022年7月月赛丙组匹配括号(三)
题目描述 如果字符序列仅由 ( 与 ) 构成,则在满足以下条件时,它是匹配的: 空序列是匹配的;如果括号序列 s 是匹配的,那么 (s) 也是匹配的;如果括号序列 s 与 t 是匹配的,那么 st 也是匹配的。…...

108.【C语言】数据结构之二叉树查找值为x的节点
目录 1.题目 代码模板 2.分析 分类讨论各种情况 大概的框架 关键部分(继续递归)的详解 递归调用展开图 3.测试结果 其他写法 4.结论 5.注意事项 不推荐的写法 1.题目 查找值为x的节点并返回节点的地址 代码模板 typedef int BTDataType; typedef struct BinaryT…...

Java学习笔记(10)--面向对象基础
学习资料来自黑马程序员 目录 设计对象并使用 类和对象 定义类 创建类的对象 使用对象 类的几个补充注意事项 设计对象并使用 类和对象 类(设计图):是对象共同特征的描述。 对象:是真实存在的具体东西。 在Java中必须先…...

社群分享在商业引流与职业转型中的作用:开源 AI 智能名片 2+1 链动模式小程序的应用契机
摘要:本文聚焦于社群分享在商业领域的重要性,阐述其作为干货诱饵在引流方面的关键意义。详细探讨了提供有价值干货的多种方式,包括文字分享、问题解答以及直播分享等,并分析了直播分享所需的条件。同时,以自身经历为例…...

nodejs官方文档学习-笔记-1
一、异步工作 process.nextTick(): 回调会在当前操作完成后立即执行,但在事件循环进入下一个阶段之前。它是最先执行的。 Promise.then(): 回调会在 microtask 队列中执行,通常是在当前操作完成后,但在事件循环进入…...

android视频播放器之DKVideoPlayer
一个老牌子的播放器了,项目可能已经有些日子没有维护了。但是使用效果还是不错的。支持多种视频格式,及重力感应、调节亮度等多种效果。想来想去,还是记录下来,我会在文章的最后注明github地址: 首先引入依赖ÿ…...

Linux——基础命令(3)
1.Linux——基础命令(1)-CSDN博客 2.Linux——基础命令(2) 文件内容操作-CSDN博客 一、打包压缩 打包压缩 是日常工作中备份文件的一种方式 在不同操作系统中,常用的打包压缩方式是不同的选项 含义 Windows 常用 rar…...

MySQL备份恢复
华子目录 MySQL日志管理为什么需要日志日志作用日志文件查看方法错误日志通用查询日志慢查询日志示例 撤销日志重做日志二进制日志---重要中继日志 MySQL备份备份类型逻辑备份优缺点备份内容备份工具导入sql文件 MySQL日志管理 为什么需要日志 用于排错用来做数据分析了解程序…...

鲲鹏麒麟安装离线版MySQL5.7
最近有项目需求,需要在鲲鹏ARM服务器上安装数据库MySQL5.7,服务器为鲲鹏920,操作系统Kylin Linux Advanced Server release V10 (Tercel) 安装包 下载地址:https://cloud.189.cn/t/JRVnmeEvMRZ3(访问码:t…...

【不稳定的BUG】__scrt_is_managed_app()中断
【不稳定的BUG】__scrt_is_managed_app函数中断 参考问题详细的情况临时解决方案 参考 发现出现同样问题的文章: 代码运行完所有功能,仍然会中断 问题详细的情况 if (!__scrt_is_managed_app())exit(main_result);这里触发了一个断点很奇怪,这中断就发生了一次,代…...

MyBatis 详解
MyBatis 是一个优秀的 持久层框架,它支持定制化 SQL、存储过程以及高级映射,能够很好地降低 Java 应用程序对数据库操作的复杂性。以下是对 MyBatis 的详细解析: 1. MyBatis 简介 MyBatis 是 Apache 的一款开源框架,其核心特性是…...

Cursor+Devbox AI开发快速入门
1. 前言 今天无意间了解到 Cursor 和 Devbox 两大开发神器,初步尝试以后发现确实能够大幅度提升开发效率,特此想要整理成博客以供大家快速入门. 简单理解 Cursor 就是一款结合AI大模型的代码编辑器,你可以将自己的思路告诉AI,剩下的目录结构的搭建以及项目代码的实现均由AI帮…...

编写按层次顺序(同一层自左至右)遍历二叉树的算法。或:按层次输出二叉树中所有结点;
解:思路:既然要求从上到下,从左到右,则利用队列存放各子树结点的指针是个好办法。 这是一个循环算法,用while语句不断循环,直到队空之后自然退出该函数。 技巧之处:当根结点入队后,会…...

docker 安装mysql8.0.29
docker 安装mysql8.0.29 1、拉取镜像 docker pull mysql:8.0.292、启动容器 docker run -p 3306:3306 --name mysql8.0.29 -e MYSQL_ROOT_PASSWORDroot -d mysql:8.0.29-p 将本地主机的端口映射到docker容器端口(因为本机的3306端口已被其它版本占用,所以使用330…...

vue深入理解输入框字符限制的优化设计
文章目录 深入理解输入框字符限制的优化设计背景与挑战输入框限制的重要性常见需求 多种实现方法解析方法一:基于实时过滤的字符限制方法二:借助正则验证方法三:提交时二次校验 性能优化无障碍设计延伸场景与最佳实践1. 多语言国际化支持2. 动…...

完整指南:在Ubuntu 20.04 ROS 1环境中配置和使用Orbbec SDK
完整指南:在Ubuntu 20.04 ROS 1环境中配置和使用Orbbec SDK 要在Ubuntu 20.04系统中使用ROS 1环境配置和使用Orbbec SDK,可以遵循以下详细且系统化的步骤。这些步骤将引导您从下载必要的工具和SDK到学习如何使用这些资源,确保您能有效地利用…...

【Leetcode Top 100】138. 随机链表的复制
问题背景 给你一个长度为 n n n 的链表,每个节点包含一个额外增加的随机指针 r a n d o m random random,该指针可以指向链表中的任何节点或空节。 构造这个链表的 深拷贝。 深拷贝应该正好由 n n n 个 全新 节点组成,其中每个新节点的值…...

2024年12月HarmonyOS应用开发者基础认证全新题库
注意事项:切记在考试之外的设备上打开题库进行搜索,防止切屏三次考试自动结束,题目是乱序,每次考试,选项的顺序都不同 更新时间:2024年12月3日 这是基础认证题库,不是高级认证题库注意看清楚标…...

Flink问题总结
目录 1、Flink 的四大特征(基石) 2、Flink 中都有哪些 Source,哪些 Sink,哪些算子(方法) 3、什么是侧道输出流,有什么用途 4、Flink 中两个流如何合并为一个流 5、Flink 中两个流如何 join 6、Flink 中都有哪些 window,什么是滑动,滚动窗口 7、flink 中都有哪些…...