【C++】——二叉搜索树(详解)

一  二叉搜索树概念

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

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

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

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

二  二叉搜索树的操作 

和其他结构差不多,一般都为增删查改

2.1  二叉搜索树的查找

✨从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
✨最多查找高度次,走到到空,还没找到,这个值不存在

比如我们要查找6,那么就会先跟8比较,然后往左边走,比3大,就往右边走,然后就找到了所对应的值。

2.2  二叉搜索树的插入

插入的具体过程如下:
a. 树为空,则直接新增节点,赋值给root指针
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点

 对于插入来说,也就是比查找多了一个插入的过程

2.3  二叉搜索树的删除

对于删除来说就很有讲究了,因为你要保证它删除完以后还是一个二叉搜索树,不然就扯蛋了

  ✨首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:
1. 要删除的结点无孩子结点
2. 要删除的结点只有左孩子结点
3. 要删除的结点只有右孩子结点
4. 要删除的结点有左、右孩子结点


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

2.3 二叉搜索树的应用

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

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

对于容器map和set的底层就是二叉搜索树的优化,所以二叉搜索的应用是非常广泛的。

2.4  二叉搜索树的性能

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

其实从表面上看二叉搜索树的效率是很高的,但是它也有极端情况

最优情况是第一个,类似完全二叉树,平均比较次数为lon(N),

最坏情况是第二个图,二叉搜索树退化为单支树(或者类似单支)

如果是第二个那么就让搜索二叉树没有了优势,但是后面会有对这个的改良。

三  二叉搜索树的实现

3.1  二叉搜索树结构

首先我们肯定得有一个结点用来存储内部结构

template<class T>
class BSTreeNode
{BSTreeNode<T> *left;//左指针BSTreeNode<T> *right;//右指针T val;BSTreeNode(const T&key):val(key),left(nullptr),right(nullptr){}
};

其次我们需要的一颗树,对于二叉搜索树来说,我们主要是删除和插入的操作,其他的其实都大同小于

template <class T>
class BSTree
{typedef BSTreeNode<T> Node;public:bool  insert(const T&key);bool  Find(const T&key);bool  Erase(const T&key);private:Node* root;
};

3.2   插入元素

对于插入元素,我们需要的是先找到那个可以插入的值,如果要插入的值已经存在,那么就插入失败,如果不存在,那么我就必须按照二叉搜索树的规则进行插入

如果不懂,那看看代码就应该会清晰了

bool insert(const T& key){if (root == nullptr)//特判,如果是跟,那么直接插入不需要去找{root = new Node(key);return true;}Node* cur = root;Node* parent = nullptr;//以便我们找到父亲结点while (cur){if (key>cur->val)//如果插入值大于当前值,往右走{parent = cur;cur = cur->right;}else if (key < cur->key)//同理,往左走{parent = cur;cur = cur->left;}else//相等,说明已经存在了,插入失败{return false;}}//这里说明找到位置了进行插入cur = new Node(key);if (parent -> val<key)//插入之前也要判断是插入到左边还是右边{parent->right = cur;}else{parent->left = cur;}return true;}

以上就是插入的代码,总结下来就是,先特判,然后去找位置,找到位置进行插入,插入的时候也要判断是插左边还是右边

3.3  寻找元素

寻找元素就是插入元素的简单版本,思路就是插入元素上面的,下面给出代码

bool Find(const T& key){if (root == nullptr) return false;Node* cur = root;Node* parent = nullptr;while (cur){if (key > cur->val){cur = cur->right;}else if (key < cur->val){cur = cur->left;}else{return true;}}return false;}

3.4  删除元素

对于删除元素,里面的细节就很多了

情况1:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除
情况2:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除
情况3:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点
中,再来处理该结点的删除问题--替换法删除

下面我们就根据这些操作一步一步去完成

首先我们肯定是先去寻找要删除的元素在哪里

bool Erase(const T& key){if (root == nullptr) return false;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{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* parent = cur;//这里需要把parent置成curNode* subLeft = cur->right;//这里我们去右边找最小的,也就是右边最左值while (subLeft->left){parent = subLeft;subLeft = subLeft->left;}swap(cur->val, subLeft->val);//找到了交换if (parent->left == subLeft)//然后判断是在父亲的左边还是右边{parent->left = subLeft->right;//因为是最左边,所以他只会有右子树}else if (parent->right = subLeft){parent->right = subLeft - right;}delete subLeft;//链接上了,就直接删除}return true;}}return false;}

以上就是二叉搜索树的最关键的操作函数实现,其实不难,注意细节就行了

完整代码

#pragma once
template<class T>
class BSTreeNode
{BSTreeNode<T> *left;BSTreeNode<T>* right;T val;BSTreeNode(const T&key):val(key),left(nullptr),right(nullptr){}
};template <class T>
class BSTree
{typedef BSTreeNode<T> Node;
public:bool insert(const T& key){if (root == nullptr){root = new Node(key);return true;}Node* cur = root;Node* parent = nullptr;while (cur){if (key>cur->val){parent = cur;cur = cur->right;}else if (key < cur->key){parent = cur;cur = cur->left;}else{return false;}}cur = new Node(key);if (parent -> val<key){parent->right = cur;}else{parent->left = cur;}return true;}bool Find(const T& key){if (root == nullptr) return false;Node* cur = root;Node* parent = nullptr;while (cur){if (key > cur->val){cur = cur->right;}else if (key < cur->val){cur = cur->left;}else{return true;}}return false;}bool Erase(const T& key){if (root == nullptr) return false;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{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* parent = cur;Node* subLeft = cur->right;while (subLeft->left){parent = subLeft;subLeft = subLeft->left;}swap(cur->val, subLeft->val);if (parent->left == subLeft){parent->left = subLeft->right;}else if (parent->right = subLeft){parent->right = subLeft - right;}delete subLeft;}return true;}}return false;}void InOder(){_InOder(root);cout << endl;}private:bool _EraseR(Node*& root, const K& key){if (root == nullptr) return;if (root->val < key){return _EraseR(root->right, key);}else if (root->val > key){return _EraseR(root->left, key);}else{//删除if (root->left == nullptr){Node* del = root;root = root->right;delete del;return true;}else if (root->right == nullptr){Node* del = root;root = root->left;delete del;return true;}else{Node* subLeft = root->right;while (subLeft->left){subLeft = subLeft->left;}swap(subLeft->val, root->val);return _EraseR(root->right, key);}}}void _InOder(Node*root){if (root == nullptr)return;_InOder(root->left);cout << root->val << ' ';_InOder(root->right);}Node* root=nullptr;
};

四  二叉搜索树递归实现

对于二叉搜索树的递归实现,这里着重去解释删除操作的递归

bool _EraseR(Node*& root, const K& key){if (root == nullptr) return;if (root->val < key){return _EraseR(root->right, key);/递归去找删除的数}else if (root->val > key){return _EraseR(root->left, key);}else{//删除if (root->left == nullptr)//这里不需要特判根结点,因为用了引用,不需要父亲结点 //自然也不会出现空的情况{Node* del = root;root = root->right;delete del;return true;}else if (root->right == nullptr){Node* del = root;root = root->left;delete del;return true;}else{Node* subLeft = root->right;while (subLeft->left){subLeft = subLeft->left;}swap(subLeft->val, root->val);return _EraseR(root->right, key);//这里递归去右树删除,这样就省去很多麻烦}}

从上面代码中我们可以看出,其实少了很多的判断,尤其是父亲结点的链接判断,之所以会这样,就是因为我们用了引用这样可以直接改变指针指向的位置,在连接的时候,就直接连接上了,自然就不需要父亲结点。

有了上面删除的递归版本,那么其他的理解起来就很容易了

bool _InsertR(Node*& root, const K& key){if (root == nullptr){root = new Node(key);return true;}if (root->val < key)return _InsertR(root->right, key);else if (root->val > key)return _InsertR(root->left, key);elsereturn false;}bool _FindR(Node* root, const K& key){if (root == nullptr){return false;}if (root->val < key){return _FindR(root->right, key);}else if (root->val > key){return _FindR(root->left, key);}else{return true;}}

五 总结

以上就是搜索二叉树的大概内容了,希望对你有用

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

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

相关文章

Go 与 Java 字符编码选择:UTF-8 与 UTF-16 的较量

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

淦!在外包开发的三年给整废了,备战两个月终拿到Android阿里字节哈啰offer总结,阿里P6+这回稳了!

面试时候就感觉不靠谱&#xff0c;因为面试地点是位于近江附近的望江国际里面的温州银行&#xff0c;面试前网上搜了广电运通的信息&#xff0c;说是国企&#xff0c;所以我就硬着头皮接下 offer 了&#xff0c;没想到面试 Android 结果做的 C&#xff0c;而且也是驻场开发。 …

RocketMQ 和 Kafka 关于消息队列的推拉模式是怎么做的?

引言&#xff1a;在当今的大数据和分布式系统中&#xff0c;消息队列扮演着至关重要的角色&#xff0c;它们作为系统之间通信和数据传输的媒介&#xff0c;为各种场景下的数据流动提供了可靠的基础设施支持。在消息队列的设计中&#xff0c;推拉模式是两种常见的消息传递机制&a…

02 Shell编程之条件语句(补充实验部分)

1、双分支if语句的补充&#xff08;实验部分&#xff09; 例如&#xff0c;要编写一个连通性测试脚本&#xff0c;通过位置参数来提供目标主机地址&#xff0c;然后根据ping检测结果给出相应的提示 &#xff08;能ping通的&#xff0c;回馈一个信息&#xff1a;该服务器是开启…

浔川AI社宣布正式开创“浔川AI助手”——浔川AI社

这是浔川AI社的标志。 2024.6.22晚8点35分宣布&#xff0c;浔川AI社正式开创“浔川AI助手” 全面发展。 据浔川AI社报道称‘“浔川AI助手”内容包含全部&#xff0c;写作、聊天......都有。’ 让我们敬请期待&#xff01;

【JAVA】精致的五角星

输出的这幅图像中&#xff0c;一颗精致的金色五角星跃然于深红色背景之上&#xff0c;绽放出迷人的光彩。 要绘画这颗五角星&#xff0c;首先要了解五角星的构造和角度问题。我们可以分为内五边形&#xff0c;和外五边形。内五边形从他的中心到每个外点&#xff0c;连接起来&am…

ECharts词云图(案例一)+配置项详解

ECharts词云图&#xff08;案例一&#xff09;配置项详解 ECharts 是一款由百度团队开发的基于 JavaScript 的开源可视化图表库&#xff0c;它提供了丰富的图表类型&#xff0c;包括常见的折线图、柱状图、饼图等&#xff0c;以及一些较为特殊的图表&#xff0c;如词云图。从版…

带百分比的进度条控件(ProgressBar)源码

带百分比的进度条控件&#xff08;ProgressBar&#xff09;&#xff1a; 源码下载地址&#xff1a;https://download.csdn.net/download/wgxds/89472915

打破数据分析壁垒:SPSS复习必备(六)

一、数据的报表呈现 1.报表概述 (1).SPSS中的报表功能 1&#xff09;Base 模块 2&#xff09;Custom Tables 模块 3) Original Tables 模块 (2).报表的基本绘制步骤 步骤一:确定基本结构 步骤二:使用对话框绘制表格的基本结构 步骤三:完善细节 步骤四:添加其余变…

Javase.图书管理系统基本框架

图书管理系统基本框架 1.核心类介绍2. book包详解2.1 Book 类2.1.2 代码展示2.1.2 代码解析 2.2 BookList 类2.2.2 代码展示2.2.2 代码解析 2.3Book类和BookList类的联系 3. 用户角色与管理3.1 User 类3.1.1 代码展示3.1.2 代码解析 3.2 adminUser 类3.2.1 代码展示3.2.2代码解…

我做了个Hexo博客

最近花了两个周末的时间边学变做Hexo博客&#xff0c;最终成品地址如下&#xff1a; https://blog.mybatis.io 下面先说说做博客的经过&#xff0c;想做Hexo博客一开始是因为看到了 hexo-theme-icarus 主题&#xff0c;这个主题样式如下&#xff1a; 首页 内容页 这个主题是…

KEIL5软件仿真观察PIN脚电平(软件仿真逻辑分析仪的使用)

仿真前的调整&#xff1a; 例&#xff1a;STM32F103C8T6 &#xff08;如果是F4的板子稍微对着修改一下&#xff09; 逻辑分析仪的使用 输入 PORTA.6( PORAT(哪一组).(哪一个引脚) )

通过rpm命令查看特定rpm包的安装时间

通过rpm命令查看特定rpm包的安装时间 命令解读 [aqjgmaster ~]$ rpm -q --qf "%{INSTALLTIME}\n" kernel 1681468253 [aqjgmaster ~]$ [aqjgmaster ~]$ date -d rpm -q --qf "%{INSTALLTIME}\n" kernel Fri Apr 14 18:30:53 CST 2023 [aqjgmaster ~]$ [a…

已解决java.rmi.AlreadyBoundException异常的正确解决方法,亲测有效!!!

已解决java.rmi.AlreadyBoundException异常的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 目录 问题分析 出现问题的场景 报错原因 解决思路 解决方法 分析错误日志 检查重复绑定情况 解除已有的绑定 优化代码逻辑 使用同步机制 总结 …

基于格网的边缘点检测(python)

1、背景介绍 前文已介绍对点云进行格网处理&#xff0c;可以计算平面点云面积、格网拓扑关系构建&#xff0c;相关博客如下&#xff1a; &#xff08;1&#xff09;点云格网过程可视化&#xff08;C PCL&#xff09;-CSDN博客 &#xff08;2&#xff09;平面点云格网过程及可…

Kimichat使用案例026:AI翻译英语PDF文档的3种方法

文章目录 一、介绍二、腾讯交互翻译TranSmart https://transmart.qq.com/三、沉浸式翻译三、谷歌网页翻译一、介绍 短的文章,直接丢进kimichat、ChatGPT里面很快就可以翻译完成,而且效果很佳。但是,很长的PDF文档整篇需要翻译,怎么办呢? 二、腾讯交互翻译TranSmart https…

VScode如何调节编辑器字体大小

首先&#xff0c;在vscode界面&#xff0c;依照顺序输入“Ctrlk”、“Ctrls”&#xff0c;即可进入键盘快捷方式设定界面。&#xff08;如下图所示&#xff09; 其次&#xff0c;在搜索框中输入“缩小”或者“放大”&#xff0c;就会出现对应的“缩小编辑器字体”或者“放大编…

算法刷题总结

1. 排序算法 1.1 快速排序算法 public abstract class Sort<T extends Comparable<T>> {public abstract void sort(T[] array);protected boolean less(T first, T two) {return first.compareTo(two) < 0;}protected void swap(T[] array, int i, int j) {T…

Python数据分析-糖尿病数据集数据分析

一、研究背景介绍 糖尿病是美国最普遍的慢性病之一&#xff0c;每年影响数百万美国人&#xff0c;并对经济造成重大的经济负担。糖尿病是一种严重的慢性疾病&#xff0c;其中个体失去有效调节血液中葡萄糖水平的能力&#xff0c;并可能导致生活质量和预期寿命下降。。。。糖尿…

CentOS系统查看版本的各个命令

cat /etc/centos-release 查看CentOS版本 uname -a 命令的结果分别代表&#xff1a;当前系统的内核名称、主机名、内核发型版本、节点名、系统时间、硬件名称、硬件平台、处理器类型以及操作系统名称 cat /proc/version 命令用于查看Linux内核的版本信息。执行该命令后&#xf…