二叉搜索树相关

embedded/2024/9/24 4:24:26/

二叉搜索树

  • 定义:
  • 对二叉搜索树的一些操作
    • 基本结构
    • Insert操作
    • Find操作
    • Erase操作
  • InOrder遍历二叉树操作
  • 模拟字典
  • 模拟统计次数

定义:

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:若它的左子树不为空,则左子树上所有节点的值都小于根节点的值。 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值。 它的左右子树也分别为二叉搜索树

对二叉搜索树的一些操作

基本结构

首先,我们先定义出一些结构
第一个结构是树里面的内容,包含左指针右指针,以及key和value。
接下来的一个类,用来表示这是一个二叉搜索树

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:Node* _root = nullptr;
};

Insert操作

Insert的思路是通过key去查找,看在树里面有没有这个节点,如果有就插入失败(因为二叉搜索树中不允许有重复的值出现,否则就失去了意义)。 如果没有那就去找一个合适的位置插入。
找合适的位置:①如果根为空,直接创建新节点当根节点插入 ②根不为空,就去子树上找。 这里我们要注意为了方便控制我们的循环条件,我们大循环是以cur为循环条件去找的,所以我们需要有parent来记录cur的父亲位置,这样后面才方便插入。(为什么不直接改循环条件? 因为这里判断不仅仅是一个节点,它既需要判断左节点又要判断右节点,所以我们选择以parent的形式去记录)

bool Insert(const K& key, const V& value)
{// 1.根为空,直接插入一个新节点if (_root == nullptr){_root = new Node(key, value);return true;}// 2.根不为空,则去找到合适位置插入Node* parent = _root;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已经是空指针了,我们应该需要的是cur的parent位置,所以上面需要有parent来记录cur = new Node(key, value);   // 直接重新赋值cur就可以了,不用再加一个新的变量进来if (key > parent->_key)parent->_right = cur;if (key < parent->_key)parent->_left = cur;return true;
}

Find操作

Find操作非常简单,找根,左孩子右孩子,如果遍历完了依然没有找到,那就返回空指针。

	Node* Find(const K& key){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;}

Erase操作

Erase操作相对就会比较的麻烦了。
Erase的思路:首先,我们需要去判断这个节点是否存在,不存在返回false。存在才会有下面的删除操作(不存在的情况放在最外面了)。
删除节点分为三个情况:①删除的节点只有左孩子
②删除节点只有右孩子(没有孩子包含在这两种情况)
③既有左孩子又有右孩子
那么针对三种情况,我们有不同的要求,建议通过图形来进行理解:
①、②种是类似的。在这里插入图片描述
我们进行判断又分三种情况,第一种:我们要删除的节点就是根节点,那么就直接对根进行处理(我们说了删除节点无左右孩子也包含在①、②的情况里,这里就体现出来了,这里也可以处理)。 第二种:我们要删除的节点cur是parent的左孩子 第三种: 我们要删除的节点cur是parent的右孩子。 第二种和第三种情况是不一样的,这里结合图片就可以看出来。

难处理的是第③种情况
如果说我们删除的节点既有左孩子又有右孩子,那么在处理的时候需要用到交换法。 交换的对象是cur和cur左孩子中的最大值或右孩子中的最小值。
所以第一步是找到最大的左孩子或最小的右孩子(这里我们以找到最小右孩子为例)
找到之后,还要分情况讨论: 一种是这个最小的右孩子有左节点也有右节点的情况
还一种是这个最小的右孩子只有右节点的情况(注意:这里不可能出现这个节点还有左节点的情况,因为在此之前我们找到的这个RightMin是右子树的最小节点,就不可能出现它还有比他小的左节点的情况)
第二种可以看到,我们的RightMin还有一个右节点的情况,这个情况就得特殊处理了,如果RightMinParent->left = RightMin。我们RightMinParent的左节点就得指向RightMin的右结点(同样,RightMin不会有左节点)
这里我们怎么样更好的理解记忆呢?
我们可以看第一种情况是RightMinParent和RightMin二者成一条线。 第二种情况是不成一条线,RightMin为RightMinParent左孩子的情况。
在这里插入图片描述

bool Erase(const K& key)
{// 删除分为3个情况// 1.删除节点只有左孩子 2.删除节点只有右孩子(没有孩子包含在这两种情况)  3.既有左孩子又有右孩子Node* parent = _root;Node* cur = _root;while (cur){// 先找节点,找到了才有后面的删除操作,否则要返回falseif (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(cur == parent->_left)   // 这个节点为parent的左孩子的情况{parent->_left = cur->_right;}else if (cur == parent->_right) // 这个节点为parent的右孩子的情况{parent->_right = cur->_right;}delete cur;}else if (cur->_right == nullptr)  // 无右孩子{if (cur == _root)_root = cur->_left;  // 直接让左孩子作为根就可以了else if (cur == parent->_left){parent->_left = cur->_left;}else if (cur == parent->_right){parent->_right = cur->_left;}delete cur;}else  // 此时为左右孩子都有的情况{Node* RightMinParent = cur;Node* RightMin = cur->_right;while (RightMin->_left){RightMinParent = RightMin;RightMin = RightMin->_left;}swap(RightMin->_key, cur->_key);swap(RightMin->_value, cur->_value);if (RightMinParent->_right == RightMin)   // 特殊情况得特殊处理RightMinParent->_right = RightMin->_right;elseRightMinParent->_left= RightMin->_right;  //此时,RightMin//只可能有右孩子,因为它是最小的右子树节点(不能有比它更小的了)delete RightMin;}}return true;}return false;
}

InOrder遍历二叉树操作

这里我们需要注意两个点: 第一个是如何遍历,也就是我们调用采用递归的方法。
第二个是为什么我们这里要封装一层,而不是直接用,直接传值root并接收。 因为root是一个私有变量,我们在类外是无法访问到的,此时我们要执行遍历操作又必须得从根开始。想访问到,就需要通过函数进入到内部,才能访问到这个私有变量。

	void InOrder()  // 为什么这里要封装一层而不是直接传入root,因为root是私有成员,//我们在外面传参时是访问不到这个私有成员变量的。我们只有通过函数进入到类内,//才能访问到root,并传参给包了一层的InOrder函数{_InOrder(_root);cout << endl;}
private:void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << ":" << root->_value << endl;_InOrder(root->_right);}

模拟字典

下面的代码可以用来模拟字典操作

BSTree<string, string> dict;
dict.Insert("insert", "插入");
dict.Insert("erase", "删除");
dict.Insert("left", "左边");
dict.Insert("string", "字符串");
dict.Erase("insert");
dict.Erase("string");
string str;
while (cin >> str)
{auto ret = dict.Find(str);if (ret){cout << str << ":" << ret->_value << endl;}else{cout << "单词拼写错误" << endl;}
}

模拟统计次数

下面的这个代码,可以用来统计次数

	string strs[] = { "苹果", "西瓜", "苹果", "樱桃", "苹果", "樱桃", "苹果", "樱桃", "苹果" };// 统计水果出现的次BSTree<string, int> countTree;for (auto str : strs){auto ret = countTree.Find(str);if (ret == NULL){countTree.Insert(str, 1);   // 不存在就插入}else{ret->_value++;  //存在就让value++}}countTree.InOrder();

http://www.ppmy.cn/embedded/37257.html

相关文章

Thinkphp5 中常见的session 操作方法

在 ThinkPHP 框架中&#xff0c;session 是用于在多个页面或请求之间存储用户信息的机制。以下是在 ThinkPHP 中进行 session 常见操作的一些示例&#xff1a; 启动 Session 在 ThinkPHP 中&#xff0c;通常不需要手动启动 Session&#xff0c;因为框架会在应用启动时自动处理…

【ElasticSearch】IK分词器中停用词问题

问题描述 在ES中进行部分关键词搜索时&#xff0c;搜索无结果&#xff0c;如搜索 【IT】 环境描述 中文分词插件 这里使用的是 analysis-ik 分词调试 POST test_index/_analyze {"text":"IT Manager","analyzer": "ik_max_word"…

国家电网某地电力公司网络硬件综合监控运维项目

国家电网某地电力公司是国家电网有限公司的子公司&#xff0c;负责当地电网规划、建设、运营和供电服务&#xff0c;下属多家地市供电企业和检修公司、信息通信公司等业务支撑实施机构。 项目现状 随着公司信息化建设加速&#xff0c;其信息内网中存在大量物理服务器、存储设备…

pgsql和mysql比较

pgsql相对于mysql的优势主要体现在以下几个方面&#xff1a; 稳定性和可靠性&#xff1a;PostgreSQL的稳定性极强&#xff0c;即使在崩溃、断电等灾难场景下也能表现出很好的抗打击能力。相比之下&#xff0c;很多MySQL用户都遇到过Server级的数据库丢失的情况。此外&#xff…

【软测学习笔记】Python入门Day02

&#x1f31f;博主主页&#xff1a;我是一只海绵派大星 &#x1f4da;专栏分类&#xff1a;软件测试笔记 &#x1f4da;参考教程&#xff1a;黑马教程❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ python安装 1、进入Python的官方下载页面&#xff1a; Download Python | Py…

VMware导入ova/ovf虚拟机文件

1.文件-打开-ova文件 2.为新虚拟机起名称 3.等待导入 4.导入完成&#xff0c;可以开始使用 参考链接&#xff1a;VMware导入ova/ovf虚拟机文件

如何更好地使用Kafka? - 运行监控篇

要确保Kafka在使用过程中的稳定性&#xff0c;需要从kafka在业务中的使用周期进行依次保障。主要可以分为&#xff1a;事先预防&#xff08;通过规范的使用、开发&#xff0c;预防问题产生&#xff09;、运行时监控&#xff08;保障集群稳定&#xff0c;出问题能及时发现&#…

Pascal Content数据集

如果您想使用Pascal Context数据集&#xff0c;请安装Detail&#xff0c;然后运行以下命令将注释转换为正确的格式。 1.安装Detail 进入项目终端 #即 这是在我自己的项目下直接进行克隆操作&#xff1a; git clone https://github.com/zhanghang1989/detail-api.git $PASCAL…