搜索二叉树

devtools/2024/10/18 12:21:48/

一、二叉搜索树的概念

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

  • 若它的左⼦树不为空,则左⼦树上所有结点的值都⼩于等于根结点的值
  • 若它的右⼦树不为空,则右⼦树上所有结点的值都⼤于等于根结点的值
  • 它的左右⼦树也分别为⼆叉搜索树

如下:

⼆叉搜索树中可以⽀持插⼊相等的值,也可以不⽀持插⼊相等的值,具体看使⽤场景定义。

二、二叉搜索树性能

最优情况下,⼆叉搜索树为完全⼆叉树(或者接近完全⼆叉树),其⾼度为:O(logN) 
最差情况下,⼆叉搜索树退化为单⽀树(类似链表),其⾼度为:O(N) 
所以综合⽽⾔⼆叉搜索树增删查改时间复杂度为:O(N) 
        如果要追求较高的查找、插入和删除效率,通常会选择使用平衡二叉搜索树(如AVL树、红黑树等),这些树通过特定的旋转操作来保持树的平衡,从而确保操作的时间复杂度保持在O(log n)。
另外需要说明的是,⼆分查找也可以实现O(logN) 级别的查找效率,但是⼆分查找有两⼤缺陷
        1. 需要存储在⽀持下标随机访问的结构中,并且有序。
        2. 插⼊和删除数据效率很低,因为存储在下标随机访问的结构中,插⼊和删除数据⼀般需要挪动数据。
这⾥也就体现出了平衡⼆叉搜索树的价值。

三、二叉搜索树的增删查

1.插入

1.树为空树:直接新增节点赋值给root

2.树不为空:按⼆叉搜索树性质,插⼊值⽐当前结点⼤往右⾛,插⼊值⽐当前结点⼩往左⾛,找到空位置,插⼊新结点。

3. 如果⽀持插⼊相等的值,插⼊值跟当前结点相等的值可以往右⾛,也可以往左⾛,找到空位置,插⼊新结点。(要注意的是要保持逻辑⼀致性,插⼊相等的值不要⼀会往右⾛,⼀会往左⾛。)

如下:

bool Insert(const K& key)
{if (_root == nullptr){_root = new Node(key);return true;}Node* ky = new Node(key);Node* cur = _root, *parent = nullptr;while (cur){parent = cur;if (key < cur->_key) cur = cur->left;else if (key > cur->_key) cur = cur->right;else return false;}if (key < parent->_key) parent->left = ky;else parent->right = ky;return true;
}

2.查找

1. 从根开始⽐较,查找key,key⽐根的值⼤则往右边⾛查找,key⽐根值⼩则往左边⾛查找。
2. 最多查找⾼度次,⾛到空还没找到,这个值不存在。
3. 如果不⽀持插⼊相等的值,找到key即可返回。
4. 如果⽀持插⼊相等的值,意味着有多个key存在,⼀般要求查找中序的第⼀个key。

查找与插入的逻辑类似如下:

Node* Find(const K& key)
{Node* cur=_root;while (cur){if (key < cur->_key) cur = cur->left;else if (key > cur->_key) cur = cur->right;else return cur;}return nullptr;
}

3.删除

        对于二叉搜索树的删除操作是比较麻烦的因为不仅要把对应的节点删除还要保持它原有的性质不变,那么比如要删除key我们来分两种情况来讨论:

1.key的还子小于两个(即叶子节点或单枝)

        对于该情况可以直接让key的父节点指向它的孩子,然后删除key就行。因为这种情况只有一个孩子或没有孩子比较好管理。

2.key有两个孩子

        因为key有两个孩子,所以把key删除后如何管理它的孩子是个问题,很容易把整颗树的结构和性质改变。这里的解决方法是不直接把key对应的这块空间删除,而是找到key左树上的最大值或者右树上的最大值(记为cur)。然后把cur的值与key的值做交换(或者不用交换,把cur的值存储在key里面),然后删除cur这块空间。

bool Erase(const K& key)
{Node* det = _root, parent = _root;while (det){parent = det;if (key < det->_key) det= det->left;else if (key > det->_key) det = det->right;else break;}if (det == nullptr) return true;if (det->left == nullptr || det->right == nullptr){if (det->left == nullptr){if (parent->left == det) parent->left = det->right;else parent->right = det->right;}else{if (parent->left == det) parent->left = det->left;else parent->right = det->left;}delete det;}else{Node* cur = det->left;while (cur->right){cur = cur->right;}det->_key = cur->_key;delete cur;cur = nullptr;}

四、key和key/valued

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

五、源码

template<class K, class V>
class BSTreeNode
{
public:K _key;V _val;BSTreeNode* left;BSTreeNode* right;BSTreeNode(K key,V val):_key(key),_val(val),left(nullptr),right(nullptr){}
};
template<class K, class V>
class BSTree
{typedef BSTreeNode<K, V> Node;
public:bool Insert(const K& key, const V& val){if (_root == nullptr){_root = new Node(key, val);return true;}Node* ky = new Node(key, val);Node* cur = _root, *parent = nullptr;while (cur){parent = cur;if (key < cur->_key) cur = cur->left;else if (key > cur->_key) cur = cur->right;else return false;}if (key < parent->_key) parent->left = ky;else parent->right = ky;return true;}Node* Find(const K& key){Node* cur=_root;while (cur){if (key < cur->_key) cur = cur->left;else if (key > cur->_key) cur = cur->right;else return cur;}return nullptr;}bool Erase(const K& key){Node* det = _root, parent = _root;while (det){parent = det;if (key < det->_key) det= det->left;else if (key > det->_key) det = det->right;else break;}if (det == nullptr) return true;if (det->left == nullptr || det->right == nullptr){if (det->left == nullptr){if (parent->left == det) parent->left = det->right;else parent->right = det->right;}else{if (parent->left == det) parent->left = det->left;else parent->right = det->left;}delete det;}else{Node* cur = det->left;while (cur->right){cur = cur->right;}det->_key = cur->_key;det->_val = cur->_val;delete cur;cur = nullptr;}}void _InOrder(Node* root){if (root == nullptr) return;_InOrder(root->left);cout << root->_key << ":" << root->_val;_InOrder(root->right);}void InOrder(){_InOrder(_root);}~BSTree(){Free(_root);_root = nullptr;}void Free(Node* root){if (root == nullptr) return;Free(root->left);Free(root->right);delete root;}
private:Node* _root = nullptr;
};


http://www.ppmy.cn/devtools/117888.html

相关文章

跟李沐学AI:自注意力和位置编码

自注意力 自注意力机制&#xff08;Self-Attention Mechanism&#xff09;&#xff0c;也被称为内部注意力&#xff08;Intra-attention&#xff09;或并行注意力&#xff08;Parallel Attention&#xff09;&#xff0c;是一种在深度学习模型中用于处理序列数据的机制。它允许…

linux信号 | 学习信号三步走 | 全解析信号的产生方式

前言&#xff1a;本节内容是信号&#xff0c; 主要讲解的是信号的产生。信号的产生是我们学习信号的第二个阶段。 我们已经学习过第一个阶段——信号的概念与预备知识&#xff08;没有学过的友友可以查看我的前一篇文章&#xff09;。 以及我们还没有学习信号的第三个阶段——信…

Snap AR眼镜Spectacles的技术揭秘:通往真正AR体验的道路

Snap公司自2010年成立以来&#xff0c;一直致力于探索增强现实&#xff08;AR&#xff09;技术的边界。经过多年的研发与迭代&#xff0c;Snap终于在最新一代Spectacles中实现了重大突破&#xff0c;为用户带来了前所未有的沉浸式AR体验。本文将深入探讨Spectacles的发展历程、…

Java EE中的编码问题及解决方案

Java EE中的编码问题及解决方案 在Java EE开发中&#xff0c;处理字符编码是确保数据正确传输和显示的重要环节。不同的编码不一致会导致乱码&#xff0c;影响用户体验。本文将总结在Java EE中可能遇到的编码问题及其解决方案。 1. 输入数据编码问题 在表单提交时&#xff0c…

windows11上超详细JDK17安装教程

1.下载安装包,访问官网地址​&#xff1a; https://www.oracle.com/java/technologies/downloads/#java172、选择jdk-17_windows-x64_bin.exe Installer。 3、接着等待下载&#xff0c;下载完成后双击进行安装 4、点击下一步 5、这里可以选择安装位置 6、等待安装 7、安…

Spark 性能优化高频面试题及答案

目录 高频面试题及答案1. 如何通过调整内存管理来优化 Spark 性能?2. 如何通过数据持久化优化性能?3. 如何通过减少数据倾斜(Data Skew)问题来优化性能?4. 如何通过优化 Shuffle 操作提升性能?5. 如何通过广播变量(Broadcast Variables)优化性能?6. 如何通过序列化机制…

神经网络介绍及其在Python中的应用(一)

作者简介&#xff1a;热爱数据分析&#xff0c;学习Python、Stata、SPSS等统计语言的小高同学~ 个人主页&#xff1a;小高要坚强的博客 当前专栏&#xff1a;Python之机器学习 本文内容&#xff1a;神经网络介绍及其在Python中的线性回归应用 作者“三要”格言&#xff1a;要坚…

【Verilog学习日常】—牛客网刷题—Verilog企业真题—VL62

序列发生器 描述 编写一个模块&#xff0c;实现循环输出序列001011。 模块的接口信号图如下&#xff1a; 要求使用Verilog HDL实现&#xff0c;并编写testbench验证模块的功能。 输入描述&#xff1a; clk&#xff1a;时钟信号 rst_n&#xff1a;复位信号&#xff0c;低电平…