深入理解二叉查找树(BST)的重要查找操作
二叉查找树 (Binary Search Tree, 简称 BST) 是一种基本的数据结构,其设计核心在于每个节点的值都满足以下性质:
- 左子树的所有节点值均小于当前节点值。
- 右子树的所有节点值均大于当前节点值。
这使得二叉查找树能够高效地支持一系列查找相关操作,包括普通查找、前驱后继查询、基于排名的查询以及基于值的排名计算。在树形合理时,bst 可以以 log 的时间快速进行集合查找。
本文将结合代码和理论讲解这些操作,帮助读者全面掌握 BST 的查找机制。
假定我们这样构造二叉树:
struct TreeNode {
int value; // 节点的值
int size; // 以该节点为根的子树大小
TreeNode* left; // 指向左子树
TreeNode* right; // 指向右子树
TreeNode(int v) : value(v), size(1), left(nullptr), right(nullptr) {}
};
1. 普通查找
问题描述
给定一个值 $ x $,我们希望在 BST 中找到值等于 $ x $ 的节点。
查找思路
从根节点开始递归或迭代:
- 如果当前节点值等于 $ x $,则查找成功。
- 如果 $ x $ 小于当前节点值,则进入左子树继续查找。
- 如果 $ x $ 大于当前节点值,则进入右子树继续查找。
- 如果到达空节点,查找失败。
代码实现
TreeNode* find(TreeNode* root, int value) {
if (!root || root->value == value) return root;
if (value < root->value) return find(root->left, value);
return find(root->right, value);
}
时间复杂度
- 平衡树:$ O(\log n) $
- 非平衡树(退化为链表):$ O(n) $
即效率取决于树高。
2. 查找前驱和后继
前驱与后继的定义
- 前驱:小于给定值的最大节点。
- 后继:大于给定值的最小节点。
前驱查找
- 如果节点有左子树,前驱是左子树中值最大的节点。
- 如果节点没有左子树,沿着父节点回溯,找到第一个右子树包含当前节点的祖先节点。
后继查找
- 如果节点有右子树,后继是右子树中值最小的节点。
- 如果节点没有右子树,沿着父节点回溯,找到第一个左子树包含当前节点的祖先节点。
代码实现
TreeNode* findPredecessor(TreeNode* root, int value) {
TreeNode* predecessor = nullptr;
while (root) {
if (value > root->value) {
predecessor = root;
root = root->right;
} else {
root = root->left;
}
}
return predecessor;
}
TreeNode* findSuccessor(TreeNode* root, int value) {
TreeNode* successor = nullptr;
while (root) {
if (value < root->value) {
successor = root;
root = root->left;
} else {
root = root->right;
}
}
return successor;
}
3. 以排名查询值
问题描述
给定一个排名 $ k $,找到 BST 中第 $ k $ 小的节点值。
额外信息:子树大小
为了保存排名信息,我们在每个节点存储一个额外的属性 size
,表示以当前节点为根的子树大小。
- 根节点的子树大小为其左子树大小加右子树大小再加 1。
- 在插入节点时动态更新
size
属性。
查询思路
- 计算左子树大小 $ \text{size}_{\text{left}} $。
- 如果 $ k = \text{size}_{\text{left}} + 1 $,当前节点即为所求。
- 如果 $ k \leq \text{size}_{\text{left}} $,在左子树中递归查找。
- 如果 $ k > \text{size}_{\text{left}} + 1 $,在右子树中递归查找,更新 $ k = k - \text{size}_{\text{left}} - 1 $。
代码实现
TreeNode* findByRank(TreeNode* root, int k) {
if (!root) return nullptr;
int leftSize = root->left ? root->left->size : 0;
if (k == leftSize + 1) return root;
if (k <= leftSize) return findByRank(root->left, k);
return findByRank(root->right, k - leftSize - 1);
}
4. 以值查询排名
问题描述
给定一个值 $ x $,求其在 BST 中的排名(即有多少节点值小于 $ x $)。
查询思路
- 初始化排名计数器 $ \text{rank} = 0 $。
- 遍历树路径:
- 如果 $ x $ 小于当前节点值,进入左子树。
- 如果 $ x $ 大于当前节点值,更新 $ \text{rank} += \text{size}_{\text{left}} + 1 $,进入右子树。
- 如果找到值等于 $ x $,返回当前排名。
- 如果到达空节点,说明值不存在。
代码实现
int findRankByValue(TreeNode* root, int value) {
int rank = 0;
while (root) {
if (value < root->value) {
root = root->left;
} else {
rank += (root->left ? root->left->size : 0) + 1;
if (value == root->value) return rank;
root = root->right;
}
}
return -1; // 值不存在
}
总结
操作 | 方法描述 | 时间复杂度 |
---|---|---|
查找值 | 从根节点沿路径递归查找。 | O(h) |
查找前驱/后继 | 利用树的结构特性,寻找最接近的节点值。 | O(h) |
以排名查询值 | 利用子树大小属性,从根递归寻找第 ( k ) 小节点。 | O(h) |
以值查询排名 | 统计路径中左子树大小,计算排名。 | O(h) |
通过本文的讲解和代码示例,希望读者能够掌握 BST 的查找操作,并能在实际开发中灵活运用这些知识。
然而,需要注意的是,查找操作的效率高度依赖于树的平衡性。当 BST 不平衡时,其性能可能退化到与链表类似,导致时间复杂度变为 $ O(n) $。因此,在进行插入和删除操作时,保持树的平衡是至关重要的。
为了解决这一问题,平衡二叉树(如 AVL 树、红黑树)通过特定的旋转操作,保证了树的高度始终保持在 $ O(\log n) $ 的级别。作为延伸思考,你可以研究平衡树的原理及其在保持效率上的作用。
深入理解二叉查找树(BST)的重要查找操作的更多相关文章
- 二叉排序树(BST)创建,删除,查找操作
binary search tree,中文翻译为二叉搜索树.二叉查找树或者二叉排序树.简称为BST 一:二叉搜索树的定义 他的定义与树的定义是类似的,也是一个递归的定义: 1.要么是一棵空树 2.如果 ...
- 查找系列合集-二叉查找树BST
一. 二叉树 1. 什么是二叉树? 在计算机科学中,二叉树是每个结点最多有两个子树的树结构. 通常子树被称作“左子树”(left subtree)和“右子树”(right subtree). 二叉树常 ...
- [学习笔记] 二叉查找树/BST
平衡树前传之BST 二叉查找树(\(BST\)),是一个类似于堆的数据结构, 并且,它也是平衡树的基础. 因此,让我们来了解一下二叉查找树吧. (其实本篇是作为放在平衡树前的前置知识的,但为了避免重复 ...
- 3.2 符号表之二叉查找树BST
一.插入和查找 1.二叉查找树(Binary Search Tree)是一棵二叉树,并且每个结点都含有一个Comparable的键,保证每个结点的键都大于其左子树中任意结点的键而小于其右子树的任意结点 ...
- 二叉查找树BST 模板
二叉查找树BST 就是二叉搜索树 二叉排序树. 就是满足 左儿子<父节点<右儿子 的一颗树,插入和查询复杂度最好情况都是logN的,写起来很简单. 根据BST的性质可以很好的解决这些东 ...
- Go基础之--排序和查找操作
排序操作主要都在sort包中,导入就可以使用了import("sort") 常用的操作 sort.Ints:对整数进行排序sort.Strings:对字符串进行排序sort.Flo ...
- MongoDB学习(查找文档和其他数据查找操作)
理解Cursor对象和查询运算符 cursor对象 cursor对象相当于一个指针,可通过迭代它来访问MongdoDB数据库中的一组对象. 在使用 find() 方法查询时,返回的并非实际文档,而是一 ...
- K:二叉查找树(BST)
相关介绍: 二叉查找树(英语:Binary Search Tree),也称二叉搜索树.有序二叉树(英语:ordered binary tree),排序二叉树(英语:sorted binary tre ...
- 命题作文:在一棵IPv4地址树中彻底理解IP路由表的各种查找过程
这是一篇命题作文.近期一直想写点东西,但一直找不到题目.正好收到一封邮件,有人问我Linux路由表的布局问题以及路由缓存的问题,加之前些日子又帮人做了一个片上路由表,所以认为这是个好题目,索性花了多半 ...
- 二叉查找树(BST)
二叉查找树(BST):使用中序遍历可以得到一个有序的序列
随机推荐
- Go语言对接微信支付与退款全流程指南
目录: 一.准备工作 二.初始化微信支付客户端 三.实现支付功能 1. 付款时序图 2. 实现不同场景下的支付 WAP端支付 PC端支付 Android端支付 3. 解析支付回调 四.实现退款功能 退 ...
- USB协议详解第3讲(USB描述符-设备描述符)
我们第一个学习要点就是USB描述符,所谓描述符其实就是C语言里面的结构体或者数组,数组包含的信息说明当前的设备具有哪些特征.USB描述符有设备描述符.配置描述符.接口描述符.端点描述符.字符串描述符, ...
- 2022年10月中国数据库排行榜:达梦冲刺IPO热度不减,PolarDB立足创新夺锦才
秋风萧瑟,洪波涌起. 2022年10月的 墨天轮中国数据库流行度排行榜 火热出炉,本月共有245个数据库参与排名,相比上月新增七个数据库,本月排行榜前十名变动较大:达梦反超openGauss重摘探花: ...
- 妙用编辑器:使用Notepad--宏功能提高维护指令生成生成效率
应用场景 日常维护工作中,需要快速生成一批指令来完成某些操作,比如:快速添加一批节点. 目标指令列表如下: ADD NODE: ID=1, NAME="NODE_1"; ADD N ...
- 【VMware VCF】更新 VCF 5.1 至 VCF 5.2 版本。
VMware Cloud Foundation(VCF)是一个由众多产品(vSphere.vSAN 以及 NSX 等)所构成的 SDDC 解决方案,这些环境中的不同组件的生命周期统一由 SDDC Ma ...
- 云原生周刊 | 在 Grafana 中显示 K8s Service 之间的依赖关系
开源项目推荐 Caretta 这个项目可以在 Grafana 监控面板中显示 K8s Service 之间的依赖关系.底层使用的是 eBPF,对应用无侵入. busuanzi 这是一个基于 Golan ...
- 写代码被大语言模型坑之使用LocalDateTime比较两个时间差了几天
自从去年ChatGPT3.5发布后使用了几次,现在写代码基本上离不开它和它的衍生产品们了.一方面查资料很方便,快速提炼要点总结:另一方面想写什么样的代码一问就能生成出来,功能大差不差,稍微改改就能用, ...
- redis的CPA三进二原则
CAP C:consistency,数据在多个副本中能保持一致的状态. A:Availability,整个系统在任何时刻都能提供可用的服务,通常达到99.99%四个九可以称为高可用 P:Partiti ...
- Java受保护的访问修饰符protected
声明:我不去上来给大家那一张被扯来扯去的√,×表,什么表想必大家心里清楚 下面进入正题: protected 的主要着眼点在于两点: 1.子类继承 2.是否同包 心里记着上面的那两点,那么protec ...
- oracle下拼同比环比查询sql方法
拼接方法: /// <summary> /// 生成计算同比环比查询语句 /// table:表名称:statColumns:要统计的值字段;yearColumn:年份字段名:monthC ...