C++——⼆叉搜索树

文章目录

  • 一、 ⼆叉搜索树的概念
  • 二、⼆叉搜索树的性能分析
  • 三、⼆叉搜索树的插⼊
  • 四、⼆叉搜索树的查找
  • 五、⼆叉搜索树的删除
  • 六、二叉搜索树的有序遍历
  • 七、⼆叉搜索树的实现代码
  • 八、二叉搜索树key与key_value的应用
    • key的应用
    • key_value的应用
    • key/value⼆叉搜索树代码实现

一、 ⼆叉搜索树的概念

⼆叉搜索树⼜称⼆叉排序树,它或者是⼀棵空树,或者是具有以下性质的⼆叉树:

• 若它的左⼦树不为空,则左⼦树上所有结点的值都⼩于等于根结点的值
• 若它的右⼦树不为空,则右⼦树上所有结点的值都⼤于等于根结点的值
• 它的左右⼦树也分别为⼆叉搜索树
• ⼆叉搜索树中可以⽀持插⼊相等的值,也可以不⽀持插⼊相等的值,具体看使⽤场景定义,map/set/multimap/multiset系列容器底层就是⼆叉搜索树,其中map/set不⽀持插⼊相等值,multimap/multiset⽀持插⼊相等值。

在这里插入图片描述

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

最优情况下,⼆叉搜索树为完全⼆叉树(或者接近完全⼆叉树),其⾼度为:O(log2 N)
最差情况下,⼆叉搜索树退化为单⽀树(或者类似单⽀),其⾼度为:O( N / 2)

所以综合⽽⾔⼆叉搜索树增删查改时间复杂度为:O(N)

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

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

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

这⾥也就体现出了平衡⼆叉搜索树的价值。
在这里插入图片描述

三、⼆叉搜索树的插⼊

插⼊的具体过程如下:

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

在这里插入图片描述

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){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key);if (parent->_key > key){parent->_left = cur;}else{parent->_right = cur;}return true;
}

四、⼆叉搜索树的查找

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

bool 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 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 (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{//删除if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;}else{// 左右都不为空// 右子树最左节点//替换的可以是左子树的最大值,也可使用右子树的最小值Node* replaceparent = cur;Node* replace = cur->_right;//右子树的最小值while (replace->_left){replaceparent = replace;replace = replace->_left;}cur->_key = replace->_key;if (replaceparent->_left == replace){replaceparent->_left = replace->_right;}else{replaceparent->_right = replace->_right;}delete replace;}return true;}}return false;
}

六、二叉搜索树的有序遍历

根据二叉搜索树的规则,使用中序遍历(左->根->右)可以让出现的结果是有序排列的。

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

七、⼆叉搜索树的实现代码

//Binary Search Tree
//二叉搜索树
namespace key
{template<class k>struct BSTNode{k _key;BSTNode<k>* _left;BSTNode<k>* _right;BSTNode(const k& key):_key(key), _left(nullptr), _right(nullptr){}};template<class k>class BSTree{public://下面这两个作用相等//typedef BSTNode<k> Node;using Node = BSTNode<k>;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){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key);if (parent->_key > key){parent->_left = cur;}else{parent->_right = cur;}return true;}bool 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 true;}}return false;}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{//删除if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;}else{// 左右都不为空// 右子树最左节点//替换的可以是左子树的最大值,也可使用右子树的最小值Node* replaceparent = cur;Node* replace = cur->_right;//右子树的最小值while (replace->_left){replaceparent = replace;replace = replace->_left;}cur->_key = replace->_key;if (replaceparent->_left == replace){replaceparent->_left = replace->_right;}else{replaceparent->_right = replace->_right;}delete replace;}return true;}}return false;}void InOrder(){_InOrder(_root);cout << endl;}private://中序遍历void _InOrder(const Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}private:Node* _root = nullptr;};
}

八、二叉搜索树key与key_value的应用

key的应用

有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值,搜索场景只需要判断key在不在
key的搜索场景实现的⼆叉树搜索树⽀持增删查,但是不⽀持修改,修改key破坏搜索树结构了。

场景1:⼩区⽆⼈值守⻋库,⼩区⻋库买了⻋位的业主⻋才能进⼩区,那么物业会把买了⻋位的业主的⻋牌号录⼊后台系统,⻋辆进⼊时扫描⻋牌在不在系统中,在则抬杆,不在则提⽰⾮本⼩区⻋辆,⽆法进⼊。
场景2:检查⼀篇英⽂⽂章单词拼写是否正确,将词库中所有单词放⼊⼆叉搜索树,读取⽂章中的单词,查找是否在⼆叉搜索树中,不在则波浪线标红提⽰。

key_value的应用

每⼀个关键码key,都有与之对应的值value,value可以任意类型对象。 树的结构中(结点)除了需要存储key还要存储对应的value,增/删/查还是以key为关键字⾛⼆叉搜索树的规则进⾏⽐较,可以快速查找到key对应的value。
key/value的搜索场景实现的⼆叉树搜索树⽀持修改,但是不⽀持修改key,修改key破坏搜索树结构了,可以修改value。

场景1:简单中英互译字典,树的结构中(结点)存储key(英⽂)和vlaue(中⽂),搜索时输⼊英⽂,则同时查找到了英⽂对应的中⽂。
场景2:商场⽆⼈值守⻋库,⼊⼝进场时扫描⻋牌,记录⻋牌和⼊场时间,出⼝离场时,扫描⻋牌,查找⼊场时间,⽤当前时间-⼊场时间计算出停⻋时⻓,计算出停⻋费⽤,缴费后抬杆,⻋辆离场。
场景3:统计⼀篇⽂章中单词出现的次数,读取⼀个单词,查找单词是否存在,不存在这个说明第⼀次出现,(单词,1),单词存在,则++单词对应的次数。

可以参考下面的两个代码:

void test03()
{key_value::BSTree<string, string> t;t.insert("insert", "插入");t.insert("erase", "删除");t.insert("find", "查找");t.insert("mihayou", "米哈游");string s;while (cin >> s){auto cur = t.find(s);if (cur){cout << cur->_value << endl;}else{cout << "no find" << endl;}}
}

在这里插入图片描述

void test04()
{string arr[] = {"aaa","bbb","ddd","ccc","aaa","bbb","ccc","ddd","qqq"};key_value::BSTree<string, int> t;for (auto& e : arr){auto cur = t.find(e);if (cur){cur->_value++;}else{t.insert(e, 1);}}t.InOrder();/*key_value::BSTree<string, int> tt = t;tt.InOrder();key_value::BSTree<string, int> ttt;ttt = t;ttt.InOrder();*/}

在这里插入图片描述

key/value⼆叉搜索树代码实现

namespace key_value
{template<class k,class v>struct BSTNode{k _key;v _value;BSTNode<k,v>* _left;BSTNode<k,v>* _right;BSTNode(const k& key,const v& value):_key(key),_value(value), _left(nullptr), _right(nullptr){}};template<class k,class v>class BSTree{public://下面这两个作用相等//typedef BSTNode<k> Node;using Node = BSTNode<k,v>;BSTree() = default;BSTree(const BSTree& t){_root = Copy(t._root);}BSTree& operator=(BSTree t){swap(_root, t._root);return *this;}~BSTree(){Destroy(_root);_root = nullptr;}bool Insert(const k& key,const v& value){if (_root == nullptr){_root = new Node(key,value);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key,value);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->_right;}else if (cur->_key > key){cur = cur->_left;}else{return cur;}}return 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{//删除if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;}else{// 左右都不为空// 右子树最左节点//替换的可以是左子树的最大值,也可使用右子树的最小值Node* replaceparent = cur;Node* replace = cur->_right;//右子树的最小值while (replace->_left){replaceparent = replace;replace = replace->_left;}cur->_key = replace->_key;if (replaceparent->_left == replace){replaceparent->_left = replace->_right;}else{replaceparent->_right = replace->_right;}delete replace;}return true;}}return false;}void InOrder(){_InOrder(_root);cout << endl;}private://中序遍历void _InOrder(const Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " " << root->_value << " ";_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* newnode = new Node(root->_key, root->_value);newnode->_left = Copy(root->_left);newnode->_right = Copy(root->_right);return newnode;}    private:Node* _root = nullptr;};}

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

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

相关文章

C++类与对象深度解析(一):从抽象到实践的全面入门指南

文章目录 C 类与对象——详细入门指南前言1. 类的定义1.1 类定义的基本格式示例代码解释 1.2 访问限定符示例代码解释 1.3 类域示例代码解释 1.4 成员命名规范常见的命名约定&#xff1a;示例&#xff1a;拓展&#xff1a; 1.5 class与struct的默认访问权限示例&#xff1a; 2.…

搭建Windows下的Rust开发环境

【图书介绍】《Rust编程与项目实战》-CSDN博客 《Rust编程与项目实战》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 (jd.com) Rust编程与项目实战_夏天又到了的博客-CSDN博客 2.1.1 安装vs_buildtools 在Windows系列操作系统中&#xff0c;Rust开发环境需要依…

归并排序(Merge Sort)

什么是归并排序 归并排序&#xff08;Merge Sort&#xff09;是一种经典的排序算法&#xff0c;它采用分治法&#xff08;Divide and Conquer&#xff09;策略&#xff0c;将一个大数组分为两个小数组&#xff0c;分别进行排序&#xff0c;然后将这两个已排序的小数组合并成一个…

音视频开发常见的开源项目汇总

FFmpeg 地址&#xff1a;https://ffmpeg.org/介绍&#xff1a;FFmpeg 是一个非常强大的开源多媒体框架&#xff0c;它可以用来处理视频和音频文件。它支持多种格式的转换、编码、解码、转码、流处理等。FFmpeg 包括了 libavformat、libavcodec、libavutil、libswscale、libpos…

组播 2024 9 11

PIM&#xff08;Protocol Independent Multicast&#xff09;是一种常用的组播路由协议&#xff0c;其独立于底层的单播路由协议&#xff0c;能够在多种网络环境中有效地实现多播路由功能。PIM主要有两种模式&#xff1a;PIM Sparse Mode (PIM-SM) 和 PIM Dense Mode (PIM-DM)&…

【C++题解】1580. 扫雷(mine)

欢迎关注本专栏《C从零基础到信奥赛入门级&#xff08;CSP-J&#xff09;》 问题&#xff1a;1580. 扫雷&#xff08;mine&#xff09; 类型&#xff1a;二维数组 题目描述&#xff1a; 扫雷游戏是一款十分经典的单机小游戏。在 n 行 m 列的雷区中有一些格子含有地雷&#x…

I/O 多路复用:`select`、`poll`、`epoll` 和 `kqueue` 的区别与示例

I/O 多路复用是指在一个线程内同时监控多个文件描述符&#xff08;File Descriptor, FD&#xff09;&#xff0c;以便高效地处理多个 I/O 事件。在 UNIX/Linux 和 BSD 系统中&#xff0c;select、poll、epoll、kqueue 都是实现 I/O 多路复用的系统调用。它们各有特点&#xff0…

el-input设置type=‘number‘和v-model.number的区别

el-input设置typenumber’与设置.number修饰符的区别 1. 设置type‘number’ 使用el-input时想收集数字类型的数据&#xff0c;我们首先会想到typenumber&#xff0c;设置完type为number时会限制我们输入的内容只能为数字&#xff0c;不能为字符/汉字等非数字类型的数值&…

性能诊断的方法(五):架构和业务诊断

关于性能诊断的方法&#xff0c;我们可以按照“问题现象—直接原因—问题根源”这样一个思路去归纳。我们先从问题的现象去入手&#xff0c;包括时间的分析、资源的分析和异常信息的分析。接下来再去分析产生问题现象的直接原因是什么&#xff0c;这里我们归纳了自上而下的资源…

外观模式详解:如何为复杂系统构建简洁的接口

&#x1f3af; 设计模式专栏&#xff0c;持续更新中 欢迎订阅&#xff1a;JAVA实现设计模式 &#x1f6e0;️ 希望小伙伴们一键三连&#xff0c;有问题私信都会回复&#xff0c;或者在评论区直接发言 外观模式 外观模式&#xff08;Facade Pattern&#xff09;为子系统中的一组…

linux系统安装miniconda3

一、下载minconda3 下载地址&#xff1a;https://docs.conda.io/en/latest/miniconda.html 一般国内访问比较困难&#xff0c;可到清华软件镜像站 Index of /anaconda/miniconda/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 需要特别注意自己的下载版本和自己的…

初识爬虫2

requests学习&#xff1a; 小技巧&#xff0c;如果你用的也是pycharm&#xff0c;对于控制台输出页面因为数据很长一行&#xff0c;不方便进行查看&#xff0c; 可以让它自动换行&#xff1a; 1.requests文档阅读学习链接&#xff1a;快速上手 — Requests 2.18.1 文档 需掌…

【运维监控】Prometheus+grafana监控zookeeper运行情况

运维监控系列文章入口&#xff1a;【运维监控】系列文章汇总索引 文章目录 一、prometheus二、grafana三、prometheus集成grafana监控zookeeper1、修改zookeeper配置2、修改prometheus配置3、导入grafana模板4、验证 本示例通过zookeeper自带的监控信息暴露出来&#xff0c;然后…

基于imx6ull平台opencv的图像采集和显示屏LCD显示功能(带Qt界面)

目录 一、概述二、环境要求2.1 硬件环境2.2 软件环境三、开发流程3.1 编写测试3.2 验证功能一、概述 本文档是针对imx6ull平台opencv的图像采集和显示屏LCD显示功能,创建Qt工程,在工程里面通过点击按钮,实现opencv通过摄像头采集视频图像,将采集的视频图像送给显示屏LCD进…

LabVIEW编程快速提升的技术

在LabVIEW程序员的成长过程中&#xff0c;很多技术和概念看似简单、常用&#xff0c;但真正掌握并能熟练运用&#xff0c;往往需要踏踏实实的实践与积累。没有什么是能够一蹴而就的&#xff0c;唯有通过不断的专注与深入&#xff0c;才能获得显著的提升。要想在LabVIEW开发上取…

SSM框架学习(三、MyBatis实践:提高持久层数据处理效率)

目录 一、Mybatis简介 1.简介 2.持久层框架对比 3.快速入门&#xff08;基于Mybatis3方式&#xff09; 4.ibatis方式的实现和原理 5.ibatis与mybatis之间的关系 二、Mybatis基本使用 1.向 sql 语句传参 &#xff08;1&#xff09;mybatis日志输出配置 &#xff08;2&…

为什么矩阵特征值之和等于主对角线元素之和,特征值乘积等于行列式值

首先给出特征值和特征向量的定义。 设A是n阶矩阵&#xff0c;如果数λ和n维非零向量x使关系式 Axλx &#xff08;1&#xff09; 成…

微信小程序使用canvas画图保存图片到手机相册

微信小程序要实现使用canvas绘制一个图&#xff0c;然后保存到手机相册 **最终效果&#xff1a;**实现生成以下图片 一、初始化canvas // wxml页面设置canvas标签 <canvas style"width: {{windowW}}px; height: {{windowH}}px;" disable-scrolltrue canvas-id&…

C++(2)之Linux多线程服务端编程总结

C之Linux多线程服务端编程读书笔记 Author: Once Day Date: 2023年1月31日/2024年8月23日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: Linux实践…

C++:类与对象

一、面向对象编程 (一) 面向过程vs面向对象 面向过程&#xff08;Procedural-Oriented-Programming&#xff0c; POP&#xff09;和面向对象&#xff08;Object-Oriented-Programming&#xff0c;OOP&#xff09;&#xff0c;是两种典型的编程范式&#xff0c;通常是作为划分编…