C++二叉搜索树BinarySearchTree

一、介绍

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

1.若它的左子树不为空,则左子树上所有节点的值都小于根节点的值

2.若它的右子树不为空,则右子树上所有节点的值都大于根节点的值

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

因此,在二叉搜索树中,每个值都有对应且唯一的位置。

二、二叉搜索树的模拟实现

1.Insert 插入

插入一个数据,根据二叉搜索树的要求,需要找到合适的位置,并且进行链接

首先是找到要插入的位置,用一个cur遍历,当插入的值cur小走左边,比cur大则走右边,找到对应位置后,我们还需要链接,因此还需要一个父节点,可以定义一个parent一起走,找到后定义一个新节点插入数据,进行链接即可。

		//插入数据bool Insert(const K& key){node* cur = _root;node* parent = nullptr;while (cur){if (key > cur->_key){parent = cur;cur = cur->_right;}else if (key < cur->_key){parent = cur;cur = cur->_left;}else{return false;}}cur = new node(key);if (key < parent->_key){parent->_left = cur;}else{parent->_right = cur;}}

2.InOrder 中序遍历

实现了数据的插入以后,为了验证实现的有没有bug,可以先将中序遍历给实现,根据二叉搜索树的特性,中序变量刚好是升序排序,将数据乱序插入后,中序打印,观察是否完成排序

中序遍历的实现是通过递归方式,在类里面实现递归需要用到_root作为参数,但外部接口一般不允许其获得内部成员变量,哪怕是提供一个函数接口获取,使用上也比较麻烦,因此可以在实现的时候再嵌套一层函数,在内部传参

		//中序遍历void InOrder(){_InOrder(_root);cout << endl;}void _InOrder(const node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}

3.Find 查找

查找的实现非常简单,只需要cur变量去按照二叉搜索树的特性去遍历即可,若是没找到则返回空指针,找到返回节点地址

		node* 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 cur;}}return nullptr;}

4.Erase 删除

删除是本章模拟实现部分的重点,也是最为复杂的,要分类讨论被删除对象的各种情况,条件判断上较为复杂

1.被删除的节点是叶子节点(既没有左孩子,也没有右孩子),则直接删除即可

2.被删除的节点有一个孩子节点,则此时需要将孩子进行托孤(将孩子节点与被删除的节点的父节点进行链接),此时需要分类讨论,当被删除节点是其父节点的左孩子时,则托孤给父节点的左指针,被删除节点是右孩子时,则托孤给右节点

观察发现,其实情况1和情况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//找到要删除的节点了{if (cur->_left == nullptr || cur->_right == nullptr)//托孤{if (cur == _root)//被删的为根时,需要更新根{if (cur->_left != nullptr){_root = cur->_left;}else{_root = cur->_right;}}else//分情况链接{if (parent->_left == cur)//要删除的节点在父节点的左边,则无论如何都是链接左边{if (cur->_left != nullptr){parent->_left = cur->_left;}else{parent->_left = cur->_right;}}else if (parent->_right == cur)//要删除的节点在父节点的左边,则无论如何都是链接左边{if (cur->_left != nullptr){parent->_right = cur->_left;}else{parent->_right = cur->_right;}}}delete cur;return true;}else//要删除的节点有两个孩子{node* min_right = cur->_right;node* pmin_right = cur;while (min_right->_left){pmin_right = min_right;min_right = min_right->_left;}cur->_key = min_right->_key;if (pmin_right->_left == min_right){pmin_right->_left = min_right->_right;}else{pmin_right->_right = min_right->_right;}delete min_right;return true;}}}//找不到要删除的对象return false;}

三、递归实现接口

递归实现在思路上相对没那么直接,但是在代码上会简洁很多,当然在类里面递归都需要封装一层

1.Finer

递归实现查找很简单,遇到空则说明没找到,则返回nullptr,值比根小则往左子树找,值比根大则在右子树找,找到返回地址。

		node* Findr(const K& key){return _Findr(_root, key);}node* _Findr(node* root, const K& key){if (root == nullptr){return nullptr;}if (key < root->_key){return _Findr(_root->_left, key);}else if (key > root->_key){return _Findr(_root->_right, key);}else{return root;}}

2.Insertr

在插入数据中,有个很巧妙的对引用的运用,就是在传root指针时,传引用,则往下递归的root就是上一层指针的别名,因此在找到需要插入的位置时,不需要多记录一个父节点去进行链接,而是此时的root就是上一层父节点的指针的别名,因此可以直接new一个节点进行链接

		bool Insertr(const K& key){return _Insertr(_root, key);}bool _Insertr(node*& root, const K& key){if (root == nullptr){root = new node(key);return true;}if (key < root->_key){return _Insertr(root->_left, key);}else if(key > root->_key){return _Insertr(root->_right, key);}else{return false;}}

3.Eraser

删除的思路和非递归的一样,递归部分主要可以利用root指针传引用的巧妙之处去省下很多处理

参考代码:

		bool Eraser(const K& key){return _Eraser(_root, key);}bool _Eraser(node*& root, const K& key){if (root == nullptr)return false;if (key < root->_key){return _Eraser(root->_left, key);}else if (key > root->_key){return _Eraser(root->_right, key);}else{node* del = root;if (del->_left == nullptr){root = root->_right;}else if (del->_right == nullptr){root = root->_left;}else{node* min_right = del->_right;while (min_right->_left){min_right = min_right->_left;}root->_key = min_right->_key;return _Eraser(root->_right, min_right->_key);}delete del;return true;}}

四、构造与析构

1.构造函数

默认的构造即可,写一份出来是为了写拷贝构造时也能调默认的构造

		BSTree():_root(nullptr){}

2.拷贝构造

拷贝构造只需要递归遍历,前序遍历拷贝节点即可,可以用引用传root

		BSTree(const BSTree& n):_root(nullptr){copy(_root, n._root);}void copy(node*& root,const node* n){if (n == nullptr)return;root = new node(n->_key);copy(root->_left, n->_left);copy(root->_right, n->_right);}

3.赋值重载operator=

		BSTree& operator=(BSTree tmp){swap(_root, tmp._root);return *this;}

4.析构函数

		~BSTree(){Destroy(_root);}void Destroy(node*& root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;}

五、二叉搜索树的应用场景

1. K模型

K模型即只有key作为关键字,结构中只需要存储Key即可,关键字即为需要搜索到的值。

比如:给一个单词word,判断该单词是否拼写正确,具体方式如下: 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树,在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

K模型针对的是“在不在”的问题

2.KV模型

KV模型比起K模型多了一个关键字val,每一个关键码key,都有与之对应的值Value,即<Key,Value>的键值对。

比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文就构成一种键值对;

再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是就构成一种键值对;

3.实际场景应用

对应K模型的底层,就是最初我们实现的那一个版本,只需要稍微修改一下,就可以改成KV模型

在输入部分(构造,拷贝构造,节点定义,插入等等),多加一个参数value,基本逻辑都是用key去操作,因此不需要做大变动

例一:中英互译

void TestBSTree3()
{// 输入单词,查找单词对应的中文翻译BSTree<string, string> dict;dict.Insert("string", "字符串");dict.Insert("tree", "树");dict.Insert("left", "左边、剩余");dict.Insert("right", "右边");dict.Insert("sort", "排序");// 插入词库中所有单词string str;while (cin>>str){BSTreeNode<string, string>* ret = dict.Find(str);if (ret == nullptr){cout << "单词拼写错误,词库中没有这个单词:" <<str <<endl;}else{cout << str << "中文翻译:" << ret->_value << endl;}}
}

例二:水果统计

void TestBSTree4()
{// 统计水果出现的次数string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", 
"苹果", "香蕉", "苹果", "香蕉" };BSTree<string, int> countTree;for (const auto& str : arr){// 先查找水果在不在搜索树中// 1、不在,说明水果第一次出现,则插入<水果, 1>// 2、在,则查找到的节点中水果对应的次数++//BSTreeNode<string, int>* ret = countTree.Find(str);auto ret = countTree.Find(str);if (ret == NULL){countTree.Insert(str, 1);}else{ret->_value++;}}countTree.InOrder();
}

六、二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二 叉搜索树的深度的函数,即结点越深,则比较次数越多。

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:O(log2 N)

最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:O(N)

因此后续会继续学习平衡搜索二叉树:AVL树和红黑树,解决上面歪脖子的问题

总结

本章介绍了搜索二叉树的特性,并且用C++模拟实现,详细分析了代码思路

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

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

相关文章

react中的useReducer复杂的状态管理

一、useReducer reducer官网教程 useReducer 是 React 提供的一个用于状态管理的 Hook。它可以替代 useState&#xff0c;更适用于处理复杂的状态逻辑。 useReducer 接受一个reducer函数和一个初始状态&#xff0c;并返回当前状态以及一个 dispatch 函数&#xff0c;用来触发…

【Git企业开发】第五节.远程操作

文章目录 前言一、理解分布式版本控制系统二、远程仓库 2.1 新建远程仓库 2.2 克隆远程仓库 2.3 向远程仓库推送 2.4 拉取远程仓库总结 前言 一、理解分布式版本控制系统 我们目前所说的所有内容(工作区&#xff0c;暂存区&#xff0c;版本库等等)&#x…

geoserver服务shp样式设计

最近在使用geoserver发服务&#xff0c;发影像的时候还没啥感觉&#xff0c;但是到了发shp数据的时候&#xff0c;发完嗯&#xff1f;样式咋是个这&#xff0c;咋看都不满意&#xff0c;于是就搜了搜&#xff0c;看看有什么能设计样式的东西&#xff0c;于是万能网友给了答案&a…

C++之队列queue

1.知识百科 队列是一种特殊的线性表&#xff0c;特殊之处在于它只允许在表的前端&#xff08;front&#xff09;进行删除操作&#xff0c;而在表的后端&#xff08;rear&#xff09;进行插入操作&#xff0c;和栈一样&#xff0c;队列是一种操作受限制的线性表。进行插入操作的…

【Azure】存储服务:Azure 的存储账户

文章目录 一、前提知识&#xff08;建议了解&#xff09;二、介绍 Azure 存储帐户三、使用 Microsoft Azure 门户创建存储帐户 一、前提知识&#xff08;建议了解&#xff09; 在每一个云厂商中&#xff0c;都有自身的云存储&#xff0c;也有根据不同功能进行区分的不同类型的…

资源限流 + 本地分布式多重锁——高并发性能挡板,隔绝无效流量请求

前言 在高并发分布式下&#xff0c;我们往往采用分布式锁去维护一个同步互斥的业务需求&#xff0c;但是大家细想一下&#xff0c;在一些高TPS的业务场景下&#xff0c;让这些请求全部卡在获取分布式锁&#xff0c;这会造成什么问题&#xff1f; 瞬时高并发压垮系统 众所周知…

USB PD v1.0快速充电通信原理

1 原理 本篇文章讲的快速充电是指USB论坛所发布的USB Power Delivery快速充电规范&#xff08;通过VBUS直流电平上耦合FSK信号来请求充电器调整输出电压和电流的过程&#xff09;&#xff0c;不同于本人发布的另一篇文章所讲的高通Quick Charger 2.0规范&#xff0c;因为高通QC…

Servlet 上下文参数

7)Servlet上下文对象&#xff1a;ServletContext生活中的例子&#xff1a;张三和李四在不远处窃窃私语&#xff0c;并且频繁的对着你坏笑。你肯定会跑过去问&#xff1a;你们俩在聊什么&#xff1f;注意&#xff1a;此处的聊什么&#xff0c;其实就是你在咨询他们聊天的上下文&…

【进程控制⑦】:制作简易shell理解shell运行原理

【进程控制⑦】&#xff1a;制作简易shell&&理解shell运行原理 一.交互问题&#xff0c;获取命令行二.字串分割问题&#xff0c;解析命令行三.指令的判断四.普通命令的执行五.shell原理本质 一.交互问题&#xff0c;获取命令行 shell刚启动时就会出现一行命令行&#x…

组件化npm包打包和使用

背景&#xff1a;本地环境对功能组件提取&#xff0c;开发环境下通过本地路径引用&#xff0c;发布模式下走npm包引用 1、项目下新建packages/HelloWorld文件夹&#xff0c;在此文件夹下运行终端 npm init 新建packages/HelloWorld/index.vue文件 新建packages/HelloWorld/ind…

uniapp黑马优购

配置tabbar 使用 npm install escook/request-miniprogram 进行http请求 挂载到 uni.$http 上 uniapp小程序分包 访问的时候 携带分包目录 /subpkg/goods_detail/goods_detail git分支使用 # 创建并使用分支 git checkout -b home git commit # 推送到远程的home分支…

自己动手实现一个深度学习算法——三、神经网络的学习

文章目录 1.从数据中学习1&#xff09;数据驱动2&#xff09;训练数据和测试数据 2.损失函数1)均方误差2)交叉熵误差3)mini-batch学习 3.数值微分1&#xff09;概念2&#xff09;数值微分实现 4.梯度1&#xff09;实现2&#xff09;梯度法3&#xff09;梯度法实现4&#xff09;…

cpu算力DMIPS说明

DMIPS即以dhrystone程式为测量方式标准的mips值&#xff0c;DMIPS即million instruction per second&#xff0c;每秒百万个指令&#xff0c;即处理器每秒能运行多少百万个指令。 D是Dhrystone的缩写&#xff0c;表示的是基于Dhrystone这样一种测试方法下的 MIPSQ。Dhrystone是…

二叉树(9.7)

目录 1.树概念及结构 1.1树的概念 1.2 树的相关概念 1.3 树的表示 2.二叉树概念及结构 2.1概念 2.2 特殊的二叉树 2.4 二叉树的存储结构 3.二叉树顺序结构及实现 3.1 二叉树的顺序结构 3.2 堆的概念及结构 1.树概念及结构 1.1树的概念 前面我们学习的都是组成简…

记一次有趣的免杀探索

文章目录 前记查杀排查源码修改免杀效果测试 前记 evilhiding昨天被提issue不能绕过火绒了&#xff0c;于是今天更新了evilhiding v1.1&#xff0c;已经可以继续免杀了。 期待各位的stars&#xff0c;项目地址如下&#xff1a; https://github.com/coleak2021/evilhiding查杀…

在Win10系统进行MySQL的安装、连接、卸载

在Win10系统进行MySQL的安装、连接、卸载 MySQL的安装 本教程在Win10系统下安装部署MySQL-8.0.32版。 MySQL安装参考地址 MySQL安装包地址 提取码: rnbc。 选择下载mysql-installer-community-8.0.32.0安装包。 连接数据库 方式一&#xff1a; 安装后&#xff0c;可以在开始…

非洲“支付宝”PalmPay搭载OceanBase:成本降低80%

10 月 30 日&#xff0c;非洲支付公司PalmPay 的核心系统搭载国产自研数据库OceanBase&#xff0c;正式投入使用。PalmPay 也是 OceanBase 首个非洲商业用户。 作为一家非洲领先的金融科技公司&#xff0c;PalmPay 于 2019 年在尼日利亚推出电子钱包应用&#xff0c;其功能类似…

中兴再推爆款,双2.5G网口的巡天AX3000Pro+仅需299元

10月30日消息,中兴新款路由器中兴巡天AX3000Pro将于10月31日20:00正式开售,当前可在天猫、京东及红魔商城进行预约,首发价格299元。 据了解,中兴巡天AX3000Pro是中兴智慧家庭推出的巡天系列新品,也是当前市场上唯一一款300元价位内配备双2.5G网口的路由器。 中兴巡天AX3000Pro…

在云栖,一场关于数据洞察的创新实践

云布道师 数据驱动创新创新鉴于未来。做好数据洞察&#xff0c;是鉴往知来的必备条件。阿里云将携手广大开发者&#xff0c;进一步完善相关技术和工具&#xff0c;提供更好的产品和方案&#xff0c;让数据洞察的应用更加广泛和深入。 2023 年 11 月 2 日&#xff0c;是为期三…

Git 删除本地和远程分支

目录 删除本地和远程分支分支删除验证验证本地分支验证远程分支 开源项目微服务商城项目前后端分离项目 删除本地和远程分支 删除 youlai-mall 的 dev 本地和远程分支 # 删除本地 dev 分支&#xff08;注&#xff1a;一定要切换到dev之外的分支才能删除&#xff0c;否则报错&…