当前位置: 首页 > news >正文

⼆叉搜索树详解

在这里插入图片描述1. ⼆叉搜索树的概念
⼆叉搜索树⼜称⼆叉排序树,它或者是⼀棵空树,或者是具有以下性质的⼆叉树:
• 若它的左⼦树不为空,则左⼦树上所有结点的值都⼩于等于根结点的值
• 若它的右⼦树不为空,则右⼦树上所有结点的值都⼤于等于根结点的值
• 它的左右⼦树也分别为⼆叉搜索树
• ⼆叉搜索树中可以⽀持插⼊相等的值,也可以不⽀持插⼊相等的值,具体看使⽤场景定义,后续我们学习map/set/multimap/multiset系列容器底层就是⼆叉搜索树,其中map/set不⽀持插⼊相等值,multimap/multiset⽀持插⼊相等值
称为二叉排序树的原因:这颗树是严格遵守左边小右边大的方式.当我们去按中序遍历去走一边,它就会排好升序,所以叫二叉排序树。
在这里插入图片描述
2. ⼆叉搜索树的性能分析
最优情况下,⼆叉搜索树为完全⼆叉树(或者接近完全⼆叉树),其⾼度为: log2 N最差情况下,⼆叉搜索树退化为单⽀树(或者类似单⽀),其⾼度为: N
所以综合⽽⾔⼆叉搜索树增删查改时间复杂度为: O(N)
那么这样的效率显然是⽆法满⾜我们需求的,我们后续课程需要继续讲解⼆叉搜索树的变形,平衡⼆叉搜索树AVL树和红⿊树,才能适⽤于我们在内存中存储和搜索数据。
另外需要说明的是,⼆分查找也可以实现 O(log2 N) 级别的查找效率,但是⼆分查找有两⼤缺陷:
3. 需要存储在⽀持下标随机访问的结构中,并且有序。
4. 插⼊和删除数据效率很低,因为存储在下标随机访问的结构中,插⼊和删除数据⼀般需要挪动数据。
这⾥也就体现出了平衡⼆叉搜索树的价值。
在这里插入图片描述
3.二叉搜索树相关功能实现
初始化,insert(插入),Find(查找),Erase(删除),析构,打印(中序遍历),构造(深拷贝)

初始化

template<class K>
struct BSTreeNode
{//二叉搜索树节点BSTreeNode * left;BSTreeNode* right;K _key;//构造函数BSTreeNode(const K& key):left(nullptr), right(nullptr), _key(key){}
};template<class K>
class BSTree
{typedef BSTreeNode<K> Node;public:private:
Node* _root=nullptr;

insert(插入)
在这里插入图片描述

//不带重复的搜索二叉树
//插入
bool Insert(const K& key)
{//如果为第一个节点,直接创建新节点赋予_rootif (_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所在位置即为插入点//创建出节点+真确链接(判断出为parent左边还是右边)cur = new Node(key);if (parent->_key < key){parent->right = cur;}else{parent->left = cur;}return true;}

其中while那部分代码为核心,利用二叉搜索树左边小·右边大特性找到插入节点位置。

Find(查找)
关键代码(while利用二叉搜索树特性寻找)

bool Find(const K& key)
{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 true;}}return false;
}

Erase(删除)
⾸先查找元素是否在⼆叉搜索树中,如果不存在,则返回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,可以直接删除。
    采用替换法,然后删除情况会变成1,2,3进行操作
    细节:删除节点要判断为,parent的左,还是parent的右。经过分析为parent左为一般情况,parent的右删除节点为根节点。
bool Erase(const K& key)
{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{//已存在这种值到达此处,准备删除//孩子左为空if (cur->left == nullptr){if (cur == _root){_root = cur->right;}else{if (cur == parent->left){parent->left = cur->right;}else{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{parent->right = cur->left;}}delete cur;}else//到达这里为左右孩子都存在{//替换法Node* pMinright = cur;Node* minRight = cur->right;while (minRight->left){pMinright = minRight;minRight = minRight->left;}swap(cur->_key, minRight->_key);//删除节点要判断为,parent的左,还是parent的右if (pMinright->left == minRight){pMinright->left = minRight->right;}else{pMinright->right = minRight->right;}delete minRight;}return true;}}return false;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
打印(中序遍历),构造(深拷贝),析构
1.分析知道,递归遍历需要传入根节点参数,如果由使用者传入根节点是不方便的,因为_root为private不便于访问,解决方案1.成员函数GetRoot()将遍历包裹一层,具体实现看代码
2.深拷贝利用前序遍历一个个取值构造
3.析构利用后序遍历

public://默认构造函数//方法一/*BSTree(){}*///方法二 强制生成BSTree() = default;//拷贝构造BSTree(const BSTree<K>& t){//这种写法肯定存在问题这是一种浅拷贝,通过析构来观察//_root = t._root;//完成深拷贝就要利用t给*this构造_root = Copy(t._root);}//赋值运算符重载BSTree<K>& operator=(const BSTree<K> t){swap(_root, t._root);return *this;}
void InOrder()
{_InOrder(_root);cout << endl;
}~BSTree()
{Destory(_root);
}
private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->left);cout << root->_key << " ";_InOrder(root->right);}Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* copy = new Node(root->_key);copy->left = Copy(root->left);copy->right = Copy(root->right);return copy;}void Destory(Node* root){if (root == nullptr){return;}Destory(root->left);Destory(root->right);delete root;}

二叉搜索树的key/key-value的实现场景
1 key搜索场景:
只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值,搜索场景只需要判断key在不在。key的搜索场景实现的⼆叉树搜索树⽀持增删查,但是不⽀持修改,修改key破坏搜索树结构了。
场景1:⼩区⽆⼈值守⻋库,⼩区⻋库买了⻋位的业主⻋才能进⼩区,那么物业会把买了⻋位的业主的⻋牌号录⼊后台系统,⻋辆进⼊时扫描⻋牌在不在系统中,在则抬杆,不在则提⽰⾮本⼩区⻋辆,⽆法进⼊。
场景2:检查⼀篇英⽂⽂章单词拼写是否正确,将词库中所有单词放⼊⼆叉搜索树,读取⽂章中的单词,查找是否在⼆叉搜索树中,不在则波浪线标红提⽰。

2.key/value搜索场景:
每⼀个关键码key,都有与之对应的值value,value可以任意类型对象。树的结构中(结点)除了需要存储key还要存储对应的value,增/删/查还是以key为关键字⾛⼆叉搜索树的规则进⾏⽐较,可以快速查找到key对应的value。key/value的搜索场景实现的⼆叉树搜索树⽀持修改,但是不⽀持修改key,修改key破坏搜索树性质了,可以修改value。
场景1:简单中英互译字典,树的结构中(结点)存储key(英⽂)和vlaue(中⽂),搜索时输⼊英⽂,则同时查找到了英⽂对应的中⽂。
场景2:商场⽆⼈值守⻋库,⼊⼝进场时扫描⻋牌,记录⻋牌和⼊场时间,出⼝离场时,扫描⻋牌,查找⼊场时间,⽤当前时间-⼊场时间计算出停⻋时⻓,计算出停⻋费⽤,缴费后抬杆,⻋辆离场。
场景3:统计⼀篇⽂章中单词出现的次数,读取⼀个单词,查找单词是否存在,不存在这个说明第⼀次出现,(单词,1),单词存在,则++单词对应的次数。

代码实现

namespace 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:// 强制生成BSTree() = default;// BSTree(const BSTree& t)BSTree(const BSTree<K, V>& t){_root = Copy(t._root);}// t1 = t3// BSTree& operator=(BSTree t)BSTree<K, V>& operator=(BSTree<K, V> t){swap(_root, t._root);return *this;}~BSTree(){Destory(_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->_right = cur;}else{parent->_left = 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* 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{// 准备删除if (cur->_left == nullptr){//if (parent == nullptr)if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left){parent->_left = cur->_right;}else{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{parent->_right = cur->_left;}}delete cur;}else // 两个孩子{Node* pMinRight = cur;Node* minRight = cur->_right;while (minRight->_left){pMinRight = minRight;minRight = minRight->_left;}swap(cur->_key, minRight->_key);if (pMinRight->_left == minRight){pMinRight->_left = minRight->_right;}else{pMinRight->_right = minRight->_right;}delete minRight;}return true;}}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 << endl;_InOrder(root->_right);}void Destory(Node* root){if (root == nullptr){return;}Destory(root->_left);Destory(root->_right);delete root;}Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* copy = new Node(root->_key, root->_value);copy->_left = Copy(root->_left);copy->_right = Copy(root->_right);return copy;}private:Node* _root = nullptr;};
}

在这里插入图片描述

相关文章:

  • 迅为RK3562开发板旋转Uboot logo和内核logo
  • string在c语言中代表什么(非常详细)
  • VitePress 中以中文字符结尾的字体加粗 Markdown 格式无法解析
  • 嵌入式学习笔记 D24 :系统编程之i/o操作
  • PyTorch 之 torch.distributions.Categorical 详解
  • MATLAB中进行语音信号分析
  • USB学习【13】STM32+USB接收数据过程详解
  • 关于element-ui的table type=“expand“ 嵌套表格展开异常问题解决方案
  • CYT4BB Dual Bank 1 - 存储机制
  • 02 基本介绍及Pod基础排错
  • P/Invoke 内存资源处理方案
  • Linux bash shell的循环命令for、while和until
  • C++面向对象——多态
  • 单片机复用功能重映射Remap功能
  • 基于单片机的车辆防盗系统设计与实现
  • 第六部分:第三节 - 路由与请求处理:解析顾客的点单细节
  • 【基础知识】SPI协议的种类及异同
  • OpenCV CUDA 模块特征检测与描述------在GPU上执行特征描述符匹配的类cv::cuda::DescriptorMatcher
  • SetThrowSegvLongjmpSEHFilter错误和myFuncInitialize 崩溃
  • 宝塔+fastadmin:给项目添加定时任务
  • 昆明市委:今年起连续三年,每年在全市集中开展警示教育
  • 可显著提高公交出行率,山东、浙江多县常态化实施城区公交免费
  • “十五五”规划编制工作开展网络征求意见活动
  • 新质观察|低空货运是城市发展低空经济的第一引擎
  • 中国首次当选联合国教科文组织1970年《公约》缔约国大会主席国
  • 专家:炎症性肠病发病率上升,需加强疾病早期诊断