C++学习笔记:二叉搜索树

二叉搜索树

  • 什么是二叉搜索树?
  • 搜索二叉树的操作
    • 查找
    • 插入
    • 删除
  • 二叉搜索树的应用
  • 二叉搜索树的代码实现
    • K模型:
    • KV模型
  • 二叉搜索树的性能怎么样?

什么是二叉搜索树?

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

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

在这里插入图片描述
由上图可明显得知搜索二叉树的构建方式

搜索二叉树的操作

这里插入一个搜索二叉树为样例:
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
在这里插入图片描述

查找

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

插入

  • 当这个树为空树时不用多说直接插入成根节点_root
  • 当这个树不为空时,先找到所插入节点的位置,再进行插入
    比如要在下面这棵搜索树中插入9
    在这里插入图片描述

可以看出,9的位置应该在10的左子树位置:
在这里插入图片描述

在数据结构上这样就算成功插入

删除

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

  1. 当这个节点是叶子节点或者只有左孩子时,先让父节点指向该节点的左孩子,再删除掉该节点–直接删除
  2. 当这个节点是叶子节点或者只有右孩子时,先让父节点指向该节点的右孩子,再删除掉该节点–直接删除
  3. 这个节点既有左子树又有右子树,则先将该节点的左子树链接到右子树的最左节点,再用右子树来替换该节点–替换法删除

前两种情况都好说,例如删除下列节点中的 7 和 14:
在这里插入图片描述

删7:直接删除
在这里插入图片描述
删14:删掉14并将14的左子树链接到10的右子树
在这里插入图片描述

比较复杂的是最后一种情况:当要删除的节点既有左子树又有右子树的时候,则需要进行一些特殊调整,例如,删除下面这颗树中的3 和 8 时:
在这里插入图片描述

删除3,使用上述的第三种方法:
先将该节点的左子树链接到右子树的最左节点,再用右子树来替换该节点
在这里插入图片描述
删除 8 ,使用上述的第三种方法:
先将该节点的左子树链接到右子树的最左节点,再用右子树来替换该节点
在这里插入图片描述

以上就是二叉搜索树的插入和删除的主要思想,接下来是具体实现

二叉搜索树的应用

二叉搜索树分为两种模型,一种是单键值的K模型,另一种是拥有键值对的KV模型,而后面需要学习的map,AVL树等都会涉及到KV模型.

  1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
    比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
  • 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
  1. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:
  • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
  • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对

二叉搜索树的代码实现

K模型:

#pragma once#include<iostream>using namespace std;//单键值的搜索二叉树  K模型
template <class K>
struct BSTNode
{BSTNode<K>* _left;BSTNode<K>* _right;K _key;BSTNode(const K& key):_left(nullptr), _right(nullptr), _key(key){}};template<class K>
class BSTree
{
public:typedef BSTNode<K> Node;bool Insert(const K& key){if (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;Node* parent = cur;while (cur){parent = cur;if (cur->_key > key){cur = cur->_left;}else if (cur->_key < key){cur = cur->_right;}else{return false;}}cur = new Node(key);if (parent->_key > key){parent->_left = cur;}else{parent->_right = cur;}return true;}void InOrder(){_InOrder(_root);cout << endl;}bool Find(const K& key){if (_root == nullptr)return false;Node* cur = _root;while (cur){if (cur->_key > key){cur = cur->_left;}else if (cur->_key < key){cur = cur->_right;}else{cout << " 存在" << endl;return true;}}return false;}bool Erase(const K& key){//空树返回falseif (_root == nullptr)return false;//寻找keyNode* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key > key){//这里注意一下,一定要往下一步走的时候父节点才随着改变parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}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* subright = cur->_right;while (subright->_left){parent = subright;subright = subright->_left;}swap(cur->_key, subright->_key);//判断这个节点是parent的左孩子还是右孩子  若删除的是根节点就是右孩子,因此一定要判断if (parent->_left == subright){parent->_left = subright->_right;}else{parent->_right = subright->_right;}//删除delete subright;}//找到返回truereturn true;}}//没找到return false;}bool FindR(const K& key){return _FindR(_root ,key);}bool InsertR(const K& key){return _InsertR(_root, key);}bool EraseR(const K& key){return _EraseR(_root, key);}//C++11 强制默认构造函数BSTree() = default;~BSTree(){Destroy(_root);}BSTree(const BSTree<K>& t){_root = Copy(t._root);}BSTree<K>& operator=(BSTree<K> t){swap(_root, t->_root);return *this;}private:Node* Copy(Node* root){if (root == nullptr){return nullptr;}Node* NewRoot = new Node(root->_key);NewRoot->_left = Copy(root->_left);NewRoot->_right = Copy(root->_right);return NewRoot;}void Destroy(Node*& root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;}bool _EraseR(Node*& root, const K& key){if (root == nullptr){cout << "没找到" <<key<< endl;return false;}//先找节点if (root->_key > key){return _EraseR(root->_left, key);}else if (root->_key < key){return _EraseR(root->_right, key);}else{//三种情况  左孩子空 右孩子空 左右都不空if (root->_left == nullptr){Node* tmp = root;root = root->_right;delete tmp;return true;}else if (root->_right == nullptr){Node* tmp = root;root = root->_left;delete tmp;return true;}else{//左右孩子都不为空  用左孩子的最右节点 或 右孩子的最左节点Node* subleft = root->_left;while (subleft->_right){subleft = subleft->_right;}swap(root->_key, subleft->_key);return _EraseR(root->_left,key);}}}bool _FindR(Node* root, const K& key){if (root == nullptr)return false;if (root->_key > key){return _FindR(root->_left , key);}else if (root->_key < key){return _FindR(root->_right, key);}else{cout << "找到了" << endl;return true;}}//注意 这里要用 *& 传值,以保证每次递归访问到每一个节点并能够对节点进行操作bool _InsertR(Node*& root, const K& key){if (root == nullptr){root = new Node(key);return true;}if (root->_key > key){return _InsertR(root->_left, key);}else if(root->_key < key){return _InsertR(root->_right, key);}else{return false;}}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}Node* _root = nullptr;
};

KV模型

#pragma once#include<iostream>using namespace std;namespace 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{typedef BSTreeNode<K, V> Node;public:bool Insert(const K& key, const V& value){if (_root == nullptr){_root = new Node(key, value);return true;}Node* cur = _root;Node* parent = cur;while (cur){parent = cur;if (cur->_key > key){cur = cur->_left;}else if (cur->_key < key){//parent = cur;cur = cur->_right;}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){if (_root == nullptr)return nullptr;Node* cur = _root;while(cur){if (cur->_key > key){cur = cur->_left;}else if (cur->_key < key){cur = cur->_right;}else{return cur;}}return nullptr;}bool Erase(const K& key){//先找节点if (_root == nullptr)return false;Node* cur = _root;Node* parent = cur;while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}else{//左子树为空if (cur->_left == nullptr){if (cur == _root){_root = _root->_right;}if (cur == parent->_left){parent->_left = cur->_right;}else if (cur == parent->_right){parent->_right = cur->_right;}delete cur;}//右子树为空else if (cur->_right == nullptr){if (cur == _root){_root = _root->_left;}if (cur == parent->_left){parent->_left = cur->_left;}else if (cur == parent->_right){parent->_right = cur->_left;}delete cur;}//左右都不为空else{Node* parent = cur;//左子树的最右节点和cur交换Node* subleft = cur->_left;while (subleft->_right){subleft = subleft->_right;}swap(cur->_key, subleft->_key);delete subleft;}}}//没找到return false;}void InOrder(){_InOrder(_root);cout << endl;}private:void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << root->_value << " ";_InOrder(root->_right);}Node* _root = nullptr;};
}

二叉搜索树的性能怎么样?

在二叉搜索树中,插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能.
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
在这里插入图片描述

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N

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

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

相关文章

Linux安装Nginx详细步骤

1、创建两台虚拟机&#xff0c;分别为主机和从机&#xff0c;区别两台虚拟机的IP地址 2、将Nginx素材内容上传到/usr/local目录&#xff08;pcre,zlib,openssl,nginx&#xff09; 附件 3、安装pcre库   3.1 cd到/usr/local目录 3.2 tar -zxvf pcre-8.36.tar.gz 解压 3.3 cd…

MATLAB图像噪声添加与滤波

在 MATLAB 中添加图像噪声和进行滤波通常使用以下函数&#xff1a; 添加噪声&#xff1a;可以使用imnoise函数向图像添加各种类型的噪声&#xff0c;如高斯噪声、椒盐噪声等。 滤波&#xff1a;可以使用各种滤波器对图像进行滤波处理&#xff0c;例如中值滤波、高斯滤波等。 …

前端学习、HTML

html是由一些标签构成的&#xff0c;标签之间可以嵌套&#xff0c;每个标签都有开始标签和结束标签&#xff0c;也有部分标签只有开始标签&#xff0c;没有结束标签。html的标签也可以成为元素。&#xff08;树形结构&#xff09; html文件的最顶层标签就是html。 head用来放…

**蓝桥OJ 178全球变暖 DFS

蓝桥OJ 178全球变暖 思路: 将每一座岛屿用一个颜色scc代替, 用dx[]和dy[]判断他的上下左右是否需要标记颜色,如果已经标记过颜色或者是海洋就跳过.后面的淹没,实际上就是哪个块上下左右有陆地,那么就不会被淹没,我用一个tag标记,如果上下左右一旦有海洋,tag就变为false.如果tag…

用冒泡排序模拟C语言中的内置快排函数qsort!

目录 ​编辑 1.回调函数的介绍 2. 回调函数实现转移表 3. 冒泡排序的实现 4. qsort的介绍和使用 5. qsort的模拟实现 6. 完结散花 悟已往之不谏&#xff0c;知来者犹可追 创作不易&#xff0c;宝子们&#xff01;如果这篇文章对你们有帮助的话&#xff0c;别忘了给个免…

机器学习:模型评估和模型保存

一、模型评估 from sklearn.metrics import accuracy_score, confusion_matrix, classification_report# 使用测试集进行预测 y_pred model.predict(X_test)# 计算准确率 accuracy accuracy_score(y_test, y_pred) print(f"Accuracy: {accuracy*100:.2f}%")# 打印…

整数和浮点数在内存中的存储(大小端字节序,浮点数的存取)

目录 1.整数在内存中的存储 2.大小端字节序和字节序判断 2.1什么是大小端&#xff1f; 2.2为什么会有大小端 3.浮点数在内存中的存储 3.1浮点数的存储 3.1.1 浮点数存的过程 3.1.2 浮点数取的过程 3.2 解析 3.3 验证浮点数的存储方式 1.整数在内存中的存储 整数的二进…

亿道信息轻工业三防EM-T195,零售、制造、仓储一网打尽

厚度仅10.5mm&#xff0c;重量仅0.65千克的EM-T195&#xff0c;其紧凑而纤薄的设计为以往加固型平板带来了全新的轻薄概念。尽管设计时尚、轻薄&#xff0c;但经过军用认证的强固性仍然能够承受所有具有挑战性的环境条件。随身携带无负担的轻便性加上抗震功能使其成为餐厅、酒店…

数独游戏(dfs)

代码注释如下 #include <iostream> using namespace std; const int N 10; bool col[N][N], rol[N][N], cell[3][3][N]; char g[N][N]; bool dfs(int x, int y) { //用bool这样在找到一个方案就可以迅速退出if(y 9) x, y 0; //若y超出边界&#xff0c;则第二…

S1---FPGA硬件板级原理图实战导学

视频链接 FPGA板级实战导学01_哔哩哔哩_bilibili FPGA硬件板级原理图实战导学 【硬件电路设计的方法和技巧-哔哩哔哩】硬件电路设计的方法和技巧01_哔哩哔哩_bilibili&#xff08;40min&#xff09; 【高速板级硬件电路设计-哔哩哔哩】 高速板级硬件电路设计1_哔哩哔哩_bil…

【RT-Thread基础教程】邮箱的使用

文章目录 前言一、邮箱的特性二、邮箱操作函数2.1 创建邮箱创建动态邮箱创建静态邮箱 2.2 删除邮箱2.3 发邮件2.4 取邮件 三、示例代码总结 前言 RT-Thread是一个开源的实时嵌入式操作系统&#xff0c;广泛应用于各种嵌入式系统和物联网设备。在RT-Thread中&#xff0c;邮箱是…

输入一个整数,输出其最长连续因子。

输入一个整数&#xff0c;输出其最长连续因子。 例如 输入&#xff1a;60 输出&#xff1a;2 3 4 5 6 注意&#xff1a;1不算因子 输入输出格式 输入描述: 输入一个整数N&#xff0c;N<10000。 输出描述: 输出其最长连续因子&#xff0c;如果有多个最长&#xff0c;输出…

Linux UnixODBC安装配置

配置 UnixODBC 梦之上关注IP属地: 香港 0.2322020.12.09 13:23:10字数 1,202阅读 5,447 麒麟&达梦适配系列: 1.麒麟服务器上安装 DM8 2.配置 UnixODBC 3.beego-ORM 适配达梦 资源紧张的时候&#xff0c;服务器是大家共用的&#xff0c;上面部署了一堆服务。所以选用doc…

ShardingJdbc实战-分库分表

文章目录 基本配置分库分表的分片策略一、inline 行表达时分片策略algorithm-expression行表达式完整案例和配置如下 二、根据实时间日期 - 按照标准规则分库分表标准分片 - Standard完整案例和配置如下 基本配置 逻辑表 逻辑表是指&#xff1a;水平拆分的数据库或者数据表的相…

大小端问题

0. 介绍 大小端计算机存储数据而安排字节的两种顺序。 针对的是字节。 大端与我们平时书写的顺序一致。 1. 大小端的判定 不需要手动判断。 有一个头文件endian.h; 可能会有宏 __BYTE_ORDER __BIG_ENDIAN __LITTLE_ENDIAN通过库来进行判断。 手动判断 根据字节存取的顺序…

【JSON2WEB】07 Amis可视化设计器CRUD增删改查

总算到重点中的核心内容&#xff0c;CRUD也就是增删改查&#xff0c;一个设计科学合理的管理信息系统&#xff0c;95%的就是CRUD&#xff0c;达不到这个比例要重新考虑一下你的数据库设计了。 1 新增页面 Step 1 启动amis-editor Setp 2 新增页面 名称和路径随便命名&#xf…

Dynamo幕墙探究系列(一)

一直想写个系列教程&#xff0c;但是没有那么多时间整理资料&#xff0c;这次呢&#xff0c;先弄个小系列吧&#xff0c;还是和之前差不多的幕墙测试&#xff0c;我们分几节课&#xff0c;一步一步深入研究。 今天先开个小头儿&#xff0c;要弄的&#xff0c;就是下面这么个模型…

对象锁与类锁

不同锁互不影响&#xff0c;共用一个锁&#xff0c;可能会发生阻塞。 1.在修饰静态方法时&#xff0c;锁定的是当前类的 Class 对象&#xff0c;在下面的例子中就是SycTest1.class 2.当修饰非静态方法时&#xff0c;锁定的就是 this 对象&#xff0c;即当前的实例化对象 public…

【Git教程】(四)版本库 —— 存储系统,存储目录,提交对象及其命名、移动与复制~

Git教程 版本库 1️⃣ 一种简单而高效的存储系统2️⃣ 存储目录&#xff1a;Blob 与 Tree3️⃣ 相同数据只存储一次4️⃣ 压缩相似内容5️⃣ 不同文件的散列值相同6️⃣ 提交对象7️⃣ 提交历史中的对象重用8️⃣ 重命名、移动与复制&#x1f33e; 总结 事实上&#xff0c;我们…

keil MDK安装armcc V5编译器

不知道从什么时候开始&#xff0c;Keil MDK默认不支持V5的编译器了&#xff0c;里面默认只有V6的编译器&#xff0c;设置界面跟V5有很大的差异不太熟悉。最可怕的是&#xff0c;之前使用V5编译的工程&#xff0c;换成V6编译器后居然报错...虽然修改一下应该也可以正常编译&…