普通二叉搜索树的模拟实现【C++】

二叉搜素树简单介绍

二叉搜索树又称二叉排序树,是具有以下性质的二叉树:

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

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

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

注意:空树也是二叉搜索树


二叉搜素树的模型

  1. K模型:

K模型即只有key作为关键字,节点中只需要存储Key即可,关键字即为需要搜索到的值。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:

以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

  1. KV模型:

每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。
该种方式在现实生活中非常常见:
比如
英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;

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

K模式和KV模式实现上基本一样,就是节点中存储的是key还是<key,val>的区别


二叉搜素树的性能

二叉搜索树的性能取决于树的高度,因为一次查找最多查找高度次,而删除和插入也是在查找的基础上增加了一些O(1)的操作
在最理想的情况下,树是完全平衡的,平均查找、插入和删除的时间复杂度O(log N)。
但在最坏的情况下,树可能退化成一个链表,此时这些操作的时间复杂度将增加到O(N)。

例:
在这里插入图片描述


全部的实现代码放在了文章末尾

准备工作

创建两个文件,一个头文件BSTree.hpp,一个源文件test.cpp

【因为模板的声明和定义不能分处于不同的文件中,所以把成员函数的声明和定义放在了同一个文件BSTree.hpp中】

  1. RBTree.hpp:存放包含的头文件,命名空间的定义,成员函数和命名空间中的函数的定义

  2. test.cpp:存放main函数,以及测试代码


包含头文件

iostream:用于输入输出


类的成员变量

在这里插入图片描述


构造函数和拷贝构造

构造函数没什么好说的,默认构造就行了

BSTree() :_root(nullptr)
{}

拷贝构造:
因为节点都是从堆区new出来的,所以要深拷贝

使用递归实现深拷贝:
因为拷贝构造不能有多余的参数,但是递归函数又必须使用参数记录信息
所以再封装一个成员函数,专门用来递归拷贝:
在这里插入图片描述
然后在拷贝构造里面调用一下这个函数就行了

拷贝构造
BSTree(const BSTree& obj)
{_root = Copy(obj._root);
}

swap和赋值运算符重载

交换两颗二叉搜索树的本质就是交换两颗数的资源(数据),而它们的资源都是从堆区申请来的,然后用指针指向这些资源
在这里插入图片描述

并不是把资源存储在了二叉搜索树对象中

所以资源交换很简单,直接交换_root指针的指向即可

void Swap(BSTree& obj)
{std::swap(_root, obj._root);
}

赋值运算符重载

BSTree& operator=(BSTree obj)
{this->Swap(obj);return *this;
}

为什么上面的两句代码就可以完成深拷贝呢?
这是因为:

使用了传值传参,会在传参之前调用拷贝构造,再把拷贝构造出的临时对象作为参数传递进去

赋值运算符的左操作数,*this再与传入的临时对象obj交换,就直接完成了拷贝

在函数结束之后,存储在栈区的obj再函数结束之后,obj生命周期结束

obj调用析构函数,把指向的从*this那里交换来的不需要的空间销毁


析构函数

使用递归遍历,把所有从堆区申请的节点都释放掉:
因为析构函数不能有多余的参数,但是递归函数又必须使用参数记录信息
所以再封装一个成员函数,专门用来递归释放:
在这里插入图片描述
然后在拷贝构造里面调用一下这个函数就行了

析构函数
~BSTree()
{Destroy(_root);_root = nullptr;
}

find

具体流程:
从根节点开始,将目标值(传入的key)与当前节点的key进行比较。
如果目标值小于当前节点值,则在左子树中继续查找;
如果目标值大于当前节点值,则在右子树中继续查找。

这个过程一直进行,直到找到与目标值或者到达空节点为止。

把上述过程转成代码:
在这里插入图片描述


insert

插入的具体过程如下:

  1. 树为空,则直接新增节点,赋值给二叉搜索树的成员变量_root指针

  2. 树不空,则按照查找(find)的逻辑找到新节点应该插入的位置

  3. 树不空,如果树中已经有了一个节点的key值与要插入的节点的key相同,就插入失败

这个过程一直进行,直到找到与传入的key相等的节点或者到达空节点为止。

把上述过程转成代码:
在这里插入图片描述


erase

删除操作较为复杂,需要先在数中找到要删除的节点,再根据要删除节点的子节点数量进行不同的处理:

  1. 如果要删除节点没有子节点,则直接删除该节点。

  2. 如果要删除节点有一个子节点(子树),则用其子节点(子树)替换该节点。

  3. 如果要删除节点有两个子节点(子树)
    在右子树中找到最小值的节点(或左子树中找到最大值的节点)来替换待删除节点,然后删除那个最小值(或最大值)的节点

情况1可以和情况2合并一下

把上述过程转成代码:

bool Erase(const K& key)
{Node* cur = _root;从根节点开始Node* parent = nullptr;先找到要删除的节点(cur)while (cur)如果到了空节点就结束循环{if (cur->_key < key) 目标值`大于`当前节点值,则在`右子树`中继续查找{parent = cur;cur = cur->_right;}else if (cur->_key > key) 目标值'小于'当前节点值,则在'左子树'中继续查找{parent = cur;cur = cur->_left;}else{break;找到要删除的节点了,结束循环}}如果找到空节点了,还没找到要删除的节点就说明树里面本来就没有这个key,不需要删除if (cur == nullptr){return false;  删除失败,返回false}else{如果 左 子树为空, 右 子树不为空(或者左右都为空)即只有右子节点(右子树)或者没有子节点if (cur->_left == nullptr){如果父亲节点为空,就表示cur为根节点if (parent == nullptr){使用右子节点,代替根节点_root = cur->_right;}else  根据cur与它的父亲节点的链接关系{if (cur == parent->_left){使用右子节点,代替curparent->_left = cur->_right;}else{使用右子节点,代替curparent->_right = cur->_right;}}delete cur;  删除cur节点,即要删除的节点}如果 右 子树为空, 左 子树不为空(或者左右都为空)即只有左子节点(左子树)或者没有子节点else if (cur->_right == nullptr){如果父亲节点为空,就表示cur为根节点if (parent == nullptr){使用左子节点,代替根节点_root = cur->_left;}else  根据cur与它的父亲节点的链接关系{if (cur == parent->_left){使用左子节点,代替curparent->_left = cur->_left;}else{使用左子节点,代替curparent->_right = cur->_left;}}delete cur;  删除cur节点,即要删除的节点}else  如果左右子树都不为nullptr{去cur(要删除的节点)的右子树中找key最小的节点Node* tmp = cur->_right;Node* prev = cur;二叉搜索树的最小节点,一定在这颗树的最左边while (tmp->_left)  所以一直往左走,直到左子树为nullptr{prev = tmp;tmp = tmp->_left;  往左走}用右子树中key最小的节点的数据,替换cur中的数据也就相当于把cur(要删除的节点)删除了cur->_key = tmp->_key;cur->_val = tmp->_val;如果prev == cur,就说明tmp就是key最小的节点了此时tmp在cur(prev)的右边if (prev == cur){cur(prev)的  右边  连上tmp的右子树因为tmp虽然是最左节点,但是它有可能还有右孩子cur->_right = tmp->_right;delete tmp;}else{把prev的  左边  连上tmp的右子树因为tmp虽然是最左节点,但是它有可能还有右孩子prev->_left = tmp->_right;delete tmp;}}}return true;  删除成功,返回true
}

empty

bool Empty()
{如果_root为空,那么树就是空的return _root == nullptr;
}

size

使用递归实现二叉搜索树的节点个数统计:
因为我们经常使用的stl的容器的size都是没有参数的,但是递归函数又必须使用参数记录信息
所以再封装一个成员函数,专门用来递归:
在这里插入图片描述
然后再size里面调用一下就行了

size_t Size()
{return _Size(_root);
}

中序遍历

中序遍历的递归函数:

在这里插入图片描述
然后再调用递归函数

void InOrder()
{_InOrder(_root);
}

全部代码

#include<iostream>
using namespace std;template<class K, class V>
struct BSTreeNode
{K _key;V _val;BSTreeNode<K, V>* _left;BSTreeNode<K, V>* _right;BSTreeNode(const K& key, const V& val):_left(nullptr), _right(nullptr){_key = key;_val = val;}
};template<class K, class V>
class BSTree
{typedef BSTreeNode<K, V> Node;
public:BSTree() :_root(nullptr){}BSTree(const BSTree& obj){_root = Copy(obj._root);}BSTree& operator=(BSTree obj){this->Swap(obj);return *this;}~BSTree(){Destroy(_root);_root = nullptr;}void Swap(BSTree& obj){std::swap(_root, obj._root);}bool Insert(const K& key, const V& val){if (_root == nullptr)//树为空,则直接新增节点{//赋值给二叉搜索树的成员变量`_root`指针_root = new Node(key, val);return true;//返回true,代表插入成功}Node* cur = _root;//从根节点开始//定义parent来保存cur的父亲节点//假设根节点的父亲节点为nullptrNode* parent = nullptr;while (cur){if (cur->_key < key)//目标值`大于`当前节点值,则在`右子树`中继续查找{parent = cur;cur = cur->_right;}else if (cur->_key > key)//目标值'小于'当前节点值,则在'左子树'中继续查找{parent = cur;cur = cur->_left;}else{return false;}}Node* newnode = new Node(key, val);if (parent->_key > key){parent->_left = newnode;}else{parent->_right = newnode;}return true;}Node* Find(const K& key){Node* cur = _root;//从根节点开始while (cur)//如果到了空节点就结束循环{if (cur->_key < key)//目标值`大于`当前节点值,则在`右子树`中继续查找{cur = cur->_right;}else if (cur->_key > key)//目标值'小于'当前节点值,则在'左子树'中继续查找{cur = cur->_left;}else//如果相等,就找到了{return cur;}}return nullptr;//找不到就返回nullptr}bool Erase(const K& key){Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{break;}}if (cur == nullptr)return false;else{if (cur->_left == nullptr){if (parent == nullptr){_root = cur->_right;}else{if (cur == parent->_left)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;}else if (cur->_right == nullptr){if (parent == nullptr){_root = cur->_left;}else{if (cur == parent->_left)parent->_left = cur->_left;elseparent->_right = cur->_left;}delete cur;}else{Node* tmp = cur->_right;Node* prev = cur;while (tmp->_left){prev = tmp;tmp = tmp->_left;}cur->_key = tmp->_key;cur->_val = tmp->_val;if (prev == cur){cur->_right = tmp->_right;delete tmp;}else{prev->_left = tmp->_right;delete tmp;}}}return true;}void InOrder(){_InOrder(_root);}bool Empty(){return _root == nullptr;}size_t Size(){return _Size(_root);}size_t Height(){return _Height(_root);}
private:Node* _root = nullptr;size_t _Height(Node* root){if (root == nullptr)return 0;int left = _Height(root->_left);int right = _Height(root->_right);return left > right ? left + 1 : right + 1;}Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* newnode = new Node(root->_key, root->_val);newnode->_left = Copy(root->_left);newnode->_right = Copy(root->_right);return newnode;}//使用  后序遍历  释放void Destroy(Node* root){//空节点不需要释放,直接返回if (root == nullptr)return;Destroy(root->_left);//递归释放左子树Destroy(root->_right);//递归释放右子树delete root;//释放根节点}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);//遍历左子树//打印信息cout << root->_key << ":" << root->_val << endl;_InOrder(root->_right);//遍历右子树}//直接遍历二叉树进行节点统计size_t _Size(Node* root){if (root == nullptr)return 0;//统计左子树节点个数int left = _Size(root->_left);//统计右子树节点个数int right = _Size(root->_right);return left + right + 1;//1是当前节点}
};

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

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

相关文章

[ComfyUI]Flux 出图背景太模糊?一招解决!

小伙伴们在使用 Flux 出图的时候&#xff0c;应该也遇到过大多数情况下背景都是比较模糊的&#xff0c;虽然大多数时候没啥影响&#xff0c;毕竟我们很多时候都只是看主体嘛。 但是也有些场景&#xff0c;我们希望整体的构图中背景也可以高清一些&#xff0c;这样在看整张图片…

【第十六章:Sentosa_DSML社区版-机器学习之生存分析】

【第十六章&#xff1a;Sentosa_DSML社区版-机器学习之生存分析】 16.1 加速失效时间回归 1.算子介绍 加速失效时间回归模型Accelerated failure time (AFT)是一个监督型参数化的回归模型&#xff0c;它可以处理删失数据。它描述了一个生存时间的对数模型&#xff0c;所以它通…

【CSS】透明度 、过渡 、动画 、渐变

opacity 透明度transition 过渡animation 动画background 渐变 ( 线性渐变 \ 径向渐变 \ 锥形渐变 ) opacity 透明度 设置元素的透明度&#xff0c;会影响元素及其所有子元素的透明度&#xff0c;值范围&#xff1a;0&#xff08;完全透明&#xff09;到 1&#xff08;完全不透…

经济不好,但是遍地都是赚钱的机会

现在职场越来越内卷&#xff0c;裁员风波也是不断&#xff0c;前些天看到一个帖子&#xff0c;裁员都裁到应届生头上了。 都说00后整治职场&#xff0c;在如今环境下也要掂量一下了。 大家都在抱怨环境&#xff0c;可是你有没有想过&#xff0c;有些人在闷声发着小财。 下面…

Pygame中Sprite实现逃亡游戏5

在《Pygame中Sprite实现逃亡游戏4》中通过碰撞检测实现了玩家、飞龙与飞火之间的碰撞处理&#xff0c;基本上实现了逃亡功能。最后&#xff0c;实现这个逃亡游戏中文字提示的功能。 1 操作提示 当进入游戏后&#xff0c;会在玩家下方的位置给出操作提示&#xff0c;如图1所示…

数据集-目标检测系列-鲨鱼检测数据集 shark >> DataBall

数据集-目标检测系列-鲨鱼检测数据集 shark >> DataBall 数据集-目标检测系列-鲨鱼检测数据集 shark 数据量&#xff1a;6k 数据样例项目地址&#xff1a; gitcode: https://gitcode.com/DataBall/DataBall-detections-100s/overview github: https://github.com/Te…

3款照片人物开口说话AI工具,跟真人说话一样~免费!短视频带货必备!(附教程)

大家好&#xff0c;我是画画的小强 今天给大家分享一个AI图片口播数字人讲认知思维&#xff0c;单号佣金赚5W的AI带货信息差玩法&#xff0c;许多小伙伴表示对这类AI带货玩法感兴趣。 说实话&#xff0c;现在AI照片人物对口型工具&#xff0c;越来越逼真&#xff0c;很难辨识出…

JAVA开源项目 技术交流分享平台 计算机毕业设计

本文项目编号 T 053 &#xff0c;文末自助获取源码 \color{red}{T053&#xff0c;文末自助获取源码} T053&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 新…

数据集-目标检测系列-豹子 猎豹 检测数据集 leopard>> DataBall

数据集-目标检测系列-豹子 猎豹 检测数据集 leopard>> DataBall 数据集-目标检测系列-豹子 猎豹 检测数据集 leopard 数据量&#xff1a;5k 想要进一步了解&#xff0c;请联系。 DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&#x…

赋值运算符重载

背景&#xff1a; 在EHR模块进行调试时&#xff0c;发现QVector3D对象进行赋值时&#xff0c;出现变量未赋值成功问题。 问题描述&#xff1a; 在进行代码调试时&#xff0c;发现赋值操作未成功&#xff0c;导致代码逻辑异常&#xff0c;经过分析&#xff0c;发现QVector3D 赋…

前台项目启动/打包报错 Error: error:0308010C:digital envelope routines::unsupported

在package.json中修改启动/打包语句 如图&#xff0c;我这里是打包时候报错&#xff0c;就在build里前面加上 set NODE_OPTIONS--openssl-legacy-provider && 再次打包&#xff0c;成功。

828华为云征文|部署敏捷项目管理系统工具 ZenTao

828华为云征文&#xff5c;部署敏捷项目管理系统工具 ZenTao 一、Flexus云服务器X实例介绍二、Flexus云服务器X实例配置2.1 重置密码2.2 服务器连接2.3 安全组配置2.4 Docker 环境搭建 三、Flexus云服务器X实例部署 ZenTao3.1 ZenTao 介绍3.2 ZenTao 部署3.3 ZenTao 使用 四、总…

急!现在转大模型还来得及吗?零基础入门到精通,收藏这一篇就够了

大模型的出现&#xff0c;让行内和行外大多数人都感到非常焦虑。 行外很多人想了解却感到无从下手&#xff0c;行内很多人苦于没有硬件条件无法尝试。想转大模型方向&#xff0c;相关的招聘虽然层出不穷&#xff0c;但一般都要求有大模型经验。而更多的人&#xff0c;则一直处…

中国科学技术大学《2020年+2021年845自动控制原理真题》 (完整版)

本文内容&#xff0c;全部选自自动化考研联盟的&#xff1a;《25届中国科学技术大学845自控考研资料》的真题篇。后续会持续更新更多学校&#xff0c;更多年份的真题&#xff0c;记得关注哦~ 目录 2020年真题 2021年真题 Part1&#xff1a;2020年2021年完整版真题 2020年真…

【论文解读】ECCV2018细粒度分类:自监督机制NTS-Net模型引领新方向 (附论文地址)

论文地址&#xff1a;https://arxiv.org/pdf/1809.00287 这篇论文由北京大学机器感知国家重点实验室的Ze Yang、Tiange Luo、Dong Wang、Zhiqiang Hu、Jun Gao和Liwei Wang撰写&#xff0c;发表于2018年。论文提出了一种新颖的自监督机制&#xff0c;用于在没有边界框/部分注释…

如果再回到从前——备忘录模式

文章目录 如果再回到从前——备忘录模式如果再给我一次机会……游戏存进度备忘录模式备忘录模式基本代码游戏进度备忘 如果再回到从前——备忘录模式 如果再给我一次机会…… 时间&#xff1a;5月6日18点  地点&#xff1a;小菜、大鸟住所的客厅  人物&#xff1a;小菜、…

VSCode开发Vue3+TS项目中遇到各种波浪线(诊断信息)

一、问题汇总 在使用Visual Studio Code&#xff08;VSCode&#xff09;开发Vue3 TypeScript项目时&#xff0c;会遇到各种波浪线错误&#xff08;诊断信息&#xff09;&#xff0c;这些问题或错误通常由以下几人原因引起的&#xff1a; 1.1 常见问题 1、typeScript配置问题…

计算机的错误计算(一百零六)

摘要 探讨含有变元负的整数次方的多项式的计算精度问题。 计算机的错误计算&#xff08;一百零五&#xff09;给出了一个传统多项式的错误计算案例&#xff1b;本节探讨含有变元负的整数次方的多项式的计算精度问题。 例1. 已知 计算 若在Python下计算&#xff0c;则有&…

Pencils Protocol上线 Vaults 产品,为 $DAPP 深入赋能

Pencils Protocol 是 Scroll 生态一站式综合收益平台&#xff0c;该平台以 DeFi 功能作为抓手&#xff0c;基于 Farming、Vaults、Auction 等功能不断向 LRT、LaunchPad、AI、FHE、RWA 等领域深入的拓展。 近期 Pencils Protocol 生态不断迎来重磅进展&#xff0c;一个是 $DAPP…

【Java】类型转换 —— 自动转换、强制转换与表达式类型自动提升

1&#xff0e;自动类型转换 Java中的自动类型转换就好比将小瓶水倒入到大瓶的换装过程。我们将小瓶水倒入到大瓶中时&#xff0c;由于小瓶的容量比大瓶的容量小&#xff0c;所以倒入的水永远不可能溢出大瓶。同样&#xff0c;在Java中&#xff0c;将取值范围小的数据类型的变量…