【C++进阶】二叉搜索树(BSTree)

在这里插入图片描述

​👻内容专栏:C/C++编程
🐨本文概括:二叉搜索树的基本操作(查找、删除、插入)、二叉搜索树的应用,KV模型。
🐼本文作者:阿四啊
🐸发布时间:2023.11.22

一、二叉搜索树

1.1 二叉搜索树的概念

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

  1. 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值.
  2. 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值.
  3. 它的左右子树也分别为二叉搜索树.

在这里插入图片描述

1.2 二叉搜索树的基本操作

类的创建

template<class T>
struct BSTreeNode
{BSTreeNode<T>* _left;BSTreeNode<T>* _right;T _key;BSTreeNode(const T& key):_left(nullptr),_right(nullptr),_key(key){ }
};
template<class T>
class BSTree
{
public:typedef BSTreeNode<T> Node;
private:Node* _root = nullptr;
};

在这里插入图片描述

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

查找操作

分为两种情况:
a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
b、最多查找高度次,走到到空,还没找到,说明这个值不存在。

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

插入操作

插入的具体过程如下:
a. 树为空,则直接新增节点,赋值给root指针
b. 树不空,需要定义cur节点指针往后寻找,按二叉搜索树性质查找插入位置,插入新节点。其中我们还需定义一个parent节点指针,是为了让插入的节点在parent节点的左边还是右边。

⚠️注意:二叉搜索树中是不允许出现相等的值的,出现相等情况的值,插入操作返回false即可。

bool Insert(const T& key)
{//树为空if (_root == nullptr){_root = new Node(key);return true;}//树不为空Node* parent = nullptr;Node* cur = _root;while (cur != nullptr){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}if (parent->_key < key) parent->_right = new Node(key);else parent->_left = new Node(key);return true;
}

删除操作

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:

  1. 要删除的结点无孩子结点。
  2. 要删除的结点只有左孩子结点。
  3. 要删除的结点只有右孩子结点。
  4. 要删除的结点有左、右孩子结点。

看起来有待删除节点有4中情况,无孩子结点也可以被分为只有左孩子或者只有孩子的情况,所以实际情况a可以与情况b或者c合并起来,因此真正的删除过程。如下:
情况a:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点 ==>直接删除
情况b:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点 ==>直接删除
情况c:在它的右子树中寻找中序下的最左节点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题。或者在它的左子树中寻找最右节点(关键码最大),用它的值填补到被删除节点中,再来处理该结点的删除问题 ==>替换法删除
直接删除:

在这里插入图片描述
替换法删除:

在这里插入图片描述
以上替换法两种方法都可行,作者便使用法二,寻找右子树的最左节点(最小节点)进行交换。另外一种方法友友们可以自己实现。

bool Erase(const T& key)
{//树为空if (_root == nullptr){return false;}Node* parent = nullptr;Node* cur = _root;while (cur != nullptr){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{//准备删除操作//情况b 要删除的结点只有右孩子结点if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{//左子树中if (cur == parent->_left){parent->_left = cur->_right;}else//右子树中{parent->_right = cur->_right;}}	}else if (cur->_right == nullptr)//情况a 要删除的结点只有左孩子结点{if (cur == _root){_root = cur->_left;}else{//左子树中if (cur == parent->_left){parent->_left = cur->_left;}else//右子树中{parent->_right = cur->_left;}}}else//情况c 要删除的结点有左、右孩子结点{//右子树的最小节点(最左节点)Node* parent = cur;//parent为什么初始化为cur,因为在删除根节点时为特例,否则会出现循环进不去,出现空指针解引用Node* subLeft = cur->_right;while (subLeft->_left != nullptr){parent = subLeft;subLeft = subLeft->_left;}swap(cur->_key, subLeft->_key);if (parent->_left == subLeft){parent->_left = subLeft->_right;}else{parent->_right = subLeft->_right;}}return true;}}return false;
}

我们可以在类中手写一个中序遍历,验证删除操作是否正确。
中序遍历

public:
void InOrder()
{_InOrder(_root);cout << endl;
}
private:
void _InOrder(Node* root)
{if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);
}

在main函数中进行验证

int main()
{int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };BSTree<int> bt;for (auto e : a){bt.Insert(e);}bt.InOrder();//删除14bt.Erase(14);bt.InOrder();//删除3bt.Erase(3);bt.InOrder();//删除8bt.Erase(8);bt.InOrder();return 0;
}

验证结果:

1 3 4 6 7 8 10 13 14
1 3 4 6 7 8 10 13
1 4 6 7 8 10 13
1 4 6 7 10 13

1.3 二叉搜索树的基本操作(递归版本)

查找操作

public:
bool FindR(const T& key){return _FindR(_root, key);}
private:
bool _FindR(Node* root, const T& key)
{if (root == nullptr){return false;}if (root->_key < key){return _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return true;}
}

插入操作

我们发现你插入操作的难点在于如何进行链接节点,这里我们只需在形参部分给Node* root添加上&之后,这一点很巧妙,然后我们执行root = new Node(key),就能成功链接新插入的节点。不懂的友友们可以试着画一画递归展开细节图。

public:
bool InsertR(const T& key)
{return _InsertR(_root, key);
}
private:
bool _InsertR(Node*& root, const T& key)
{if (root == nullptr){root = new Node(key);return true;}if (root->_key < key){return _InsertR(root->_right, key);}else if (root->_key > key){return _InsertR(root->_left, key);}else{return false;}
}

删除操作

这里删除操作和插入操作类似,如果删除节点然后链接,一样使用了巧妙的引用操作。

bool EraseR(const T& key)
{return _EraseR(_root, key);
}
private:
bool _EraseR(Node*& root,const T& key)
{if (root == nullptr) return false;if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}else{//删除if (root->_left == nullptr){Node* del = root;root = root->_right;delete del;}else if (root->_right == nullptr){Node* del = root;root = root->_left;delete del;}else{//在右子树中寻找最左节点(最小节点)Node* subLeft = root->_right;while (subLeft->_left != nullptr){subLeft = subLeft->_left;}swap(root->_key, subLeft->_key);//转换成在子树中去递归删除return _EraseR(root->_right, key);}}
}

这里要着重说明一下删除左右孩子都存在的节点,该如何去递归操作,举例说明删除3,我们利用循环去找删除节点右子树中的最左节点,找到之后交换两个节点的值,那么subLeft节点如何删除?这里我们不再使用parent节点,然后条件判断,我们可以转换成在交换前的右子树中去递归删除,如下图,蓝色圆圈标记。
在这里插入图片描述

1.4 二叉搜索树的应用

  1. K模型K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
    比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
    以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
    1.2部分我们实现的就是K模型的底层基本能逻辑。

  2. KV模型每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见
    a.比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
    b.再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。

// 改造二叉搜索树为KV结构
template<class K, class V>
struct BSTreeNode
{BSTreeNode<K, V>* _left;BSTreeNode<K, V>* _right;K _key;V _value;BSTreeNode(const K& key, const V& value):_left(nullptr), _right(nullptr), _key(key), _value(value){ }
};
template<class K, class V>class BSTree{public:typedef BSTreeNode<K, V> Node;bool Insert(const K& key, const V& value);Node* Find(const K& key);void InOrder();bool Erase(const K& key);private:Node* _root = nullptr;};
//KV模型
//1.英汉词典
int main()
{Key_Vaule::BSTree<string, string> dictionary;dictionary.Insert("sort", "排序");dictionary.Insert("left", "左边");dictionary.Insert("right", "右边");dictionary.Insert("insert", "插入");dictionary.Insert("erase", "删除");string str;while (cin >> str){Key_Vaule::BSTreeNode<string, string>* ret = dictionary.Find(str);if (ret){//找到单词输出cout << ret->_value << endl;}else{//未找到单词cout << "No words found!" << endl;}}
}//2统计单词次数
int main()
{// 统计水果出现的次数string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };Key_Vaule::BSTree<string, int> countWords;for (auto& e : arr){Key_Vaule::BSTreeNode<string, int>* ret = countWords.Find(e);if (ret == nullptr){countWords.Insert(e, 1);}else{ret->_value++;}}countWords.InOrder();return 0;
}

1.5 二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
在这里插入图片描述
最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N

问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?那么等待后续我们的AVL树和红黑树章节讲到再说。

二、二叉搜索树源码

👉😉 gitee ==> 二叉搜索树的基本实现及二叉搜索树的应用(K模型、KV模型)

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

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

相关文章

Maven中常用命令以及idea中使用maven指南

文章目录 Maven 常用命令compiletestcleanpackageinstallMaven 指令的生命周期maven 的概念模型 idea 开发maven 项目idea 的maven 配置idea 中创建一个maven 的web 工程在pom.xml 文件添加坐标坐标的来源方式依赖范围编写servlet maven 工程运行调试 Maven 常用命令 compile …

大华智能物联综合管理平台readpic接口任意文件读取漏洞复现 [附POC]

文章目录 大华智能物联综合管理平台readpic接口任意文件读取漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 0x06 修复建议 大华智能物联综合管理平台readpic接口任意文件读取漏洞复现 [附POC] 0x01 前言 免责…

(论文阅读58-66)视频描述

58.文献阅读笔记&#xff08;LRCNs&#xff09; 简介 题目 Long-term Recurrent Convolutional Networks for Visual Recognition and Description 作者 Jeff Donahue, Lisa Anne Hendricks, Marcus Rohrbach, Subhashini Venugopalan, Sergio Guadarrama, Kate Saenko, T…

AIGC 点亮创作之旅,「重内容」行业也能轻装出发

毋庸置疑&#xff0c;AIGC 的普及成为了内容产业的一束光。 不仅策划们可以从信息挖掘、素材调用、修改编辑等基础文案工作中解放出来&#xff0c;美术也成为 AIGC 的应用强项&#xff0c;基本的加文字、换背景、改尺寸、延展素材等&#xff0c;都能快速解决。 内容创作者们也因…

leetcode:1773. 统计匹配检索规则的物品数量(python3解法)

难度&#xff1a;简单 给你一个数组 items &#xff0c;其中 items[i] [typei, colori, namei] &#xff0c;描述第 i 件物品的类型、颜色以及名称。 另给你一条由两个字符串 ruleKey 和 ruleValue 表示的检索规则。 如果第 i 件物品能满足下述条件之一&#xff0c;则认为该物…

使用内网穿透工具实现远程访问本地部署的Odoo企业管理系统

文章目录 前言1. 下载安装Odoo&#xff1a;2. 实现公网访问Odoo本地系统&#xff1a;3. 固定域名访问Odoo本地系统 前言 Odoo是全球流行的开源企业管理套件&#xff0c;是一个一站式全功能ERP及电商平台。 开源性质&#xff1a;Odoo是一个开源的ERP软件&#xff0c;这意味着企…

阿里云学生认证可领300元无门槛代金券(高效计划)

阿里云高校计划学生和教师均可参与&#xff0c;完成学生认证和教师验证后学生可以免费领取300元无门槛代金券和3折优惠折扣&#xff0c;适用于云服务器等全量公共云产品&#xff0c;订单原价金额封顶5000元/年&#xff0c;阿里云百科aliyunbaike.com分享阿里云高校计划入口及学…

下载安装升讯威在线客服系统时提示风险的解决办法

客服系统的服务端程序、客服端程序、配套的配置工具涉及磁盘文件读写、端口监听&#xff0c;特别是经过混淆加密后&#xff0c;可能被部分浏览器或部分杀毒软件提示风险。请忽略并放心使用&#xff0c;如果开发软件是为了植入木马&#xff0c;这个代价可太大了&#xff0c;不如…

危险了:蓝牙协议爆严重安全漏洞!

导读据外媒报道&#xff0c;美国的物联网安全研究公司Armis在蓝牙协议中发现了8个零日漏洞&#xff0c;而这些漏洞将会影响全球超过53亿的设备&#xff0c;包括Android、iOS、Windows、Linux系统设备以及使用短距离无线通信技术的物联网设备。 Armis的研究人员利用这些漏洞构建…

二进制插桩:静态插桩和动态intel pin插桩

目前有两类插桩平台&#xff1a;静态插桩&#xff08;SBI&#xff09;和动态插桩&#xff08;DBI&#xff09; SBI使用二进制重写方法永久修改磁盘上的二进制文件&#xff1b;DBI不会修改磁盘上的二进制程序&#xff0c;而是监视二进制程序的执行状态&#xff0c;并在其运行时…

C语言杨辉三角(ZZULIOJ1130:杨辉三角)

题目描述 还记得中学时候学过的杨辉三角吗&#xff1f;具体的定义这里不再描述&#xff0c;你可以参考以下的图形&#xff1a;1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 输入&#xff1a;输入只包含一个正整数n&#xff08;1 < n < 30&#xff09;&#xff0c;表示将…

基于PHP的动漫周边购物系统

有需要请加文章底部Q哦 可远程调试 基于PHP的动漫周边购物系统 一 介绍 此动漫周边购物系统系统基于原生PHP开发&#xff0c;数据库mysql&#xff0c;前端bootstrap。用户可注册登录&#xff0c;购物下单&#xff0c;评论等。管理员登录后台可对动漫周边商品&#xff0c;用户…

跨越行业边界,CodeMeter护航AI领域安全与合规

在人工智能&#xff08;AI&#xff09;技术如ChatGPT的推动下&#xff0c;工业视觉、医疗诊断和智能驾驶等领域正在经历重大变革。这些技术不仅扩大了应用范围&#xff0c;也带来了数据安全、软件授权保护和合规性等新挑战。 AI工业视觉正在推动制造和自动化的快速发展&#x…

【腾讯云云上实验室-向量数据库】腾讯云VectorDB:深度学习场景下的新一代数据存储方案

引言 ​  在深度学习领域的实践中&#xff0c;一般会涉及到向量化处理的数据&#xff0c;如图像、文本、音频等&#xff0c;这些数据的存储和检索对于许多深度学习任务至关重要。传统的关系型数据库和NoSQL数据库在存储和检索这类大规模向量数据时&#xff0c;通常不能满足高…

Redis 与其他数据库的不同之处 | Navicat

Redis&#xff0c;即远程字典服务器&#xff08;Remote Dictionary Server&#xff09;&#xff0c;它是一个多功能且高性能的键值存储系统&#xff0c;在数据库领域中已获得广泛关注和认可。在处理简单数据结构方面&#xff0c;它因其快速和高效而著称。本文中&#xff0c;我们…

electron入门(一)环境搭建,实现样例

1、首先需要安装git和node&#xff0c;配置环境变量&#xff0c;确保npm和git命令可用 2、 然后安装依赖 npm install -g electronnpm install -g electron-forgenpm install -g electron-prebuilt-compile3、 创建样例工程 electron-forge init my-new-app # 我这里碰见报错…

qlik为app添加定时调度

1&#xff0c;进入qmc/Apps 2&#xff0c;搜索需要添加调度的APP 3&#xff0c;搜索到后双击点开Tasks 4&#xff0c;新增Tasks---点击Create New 5&#xff0c;添加调度器 6&#xff0c;设置调度&#xff0c;双击新增的调度&#xff0c;注意选择时区

[数据结构]—栈和队列

&#x1f493;作者简介&#x1f389;&#xff1a;在校大二迷茫大学生 &#x1f496;个人主页&#x1f389;&#xff1a;小李很执着 &#x1f497;系列专栏&#x1f389;&#xff1a;数据结构 每日分享✨&#xff1a;到头来&#xff0c;有意义的并不是结果&#xff0c;而是我们度…

MONGODB 的基础 NOSQL注入基础

首先来学习一下nosql 这里安装就不进行介绍 只记录一下让自己了解mongodb ubuntu 安装后 进入 /usr/bin ./mongodb即可进入然后可通过 进入的url链接数据库 基本操作 show dbshow dbsshow tablesuse 数据库名插入数据db.admin.insert({json格式的数据})例如 db.admin.inse…

低代码!小白用10分钟也能利用flowise构建AIGC| 业务问答 | 文本识别 | 网络爬虫

一、与知识对话 二、采集网页问答 三、部署安装flowise flowise工程地址&#xff1a;https://github.com/FlowiseAI/Flowise flowise 官方文档&#xff1a;https://docs.flowiseai.com/ 这里采用docker安装&#xff1a; step1&#xff1a;克隆工程代码 &#xff08;如果网络…