二叉查找树 (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 属性。

查询思路

  1. 计算左子树大小 $ \text{size}_{\text{left}} $。
  2. 如果 $ k = \text{size}_{\text{left}} + 1 $,当前节点即为所求。
  3. 如果 $ k \leq \text{size}_{\text{left}} $,在左子树中递归查找。
  4. 如果 $ 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 $)。

查询思路

  1. 初始化排名计数器 $ \text{rank} = 0 $。
  2. 遍历树路径:
    • 如果 $ x $ 小于当前节点值,进入左子树。
    • 如果 $ x $ 大于当前节点值,更新 $ \text{rank} += \text{size}_{\text{left}} + 1 $,进入右子树。
    • 如果找到值等于 $ x $,返回当前排名。
  3. 如果到达空节点,说明值不存在。

代码实现

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)的重要查找操作的更多相关文章

  1. 二叉排序树(BST)创建,删除,查找操作

    binary search tree,中文翻译为二叉搜索树.二叉查找树或者二叉排序树.简称为BST 一:二叉搜索树的定义 他的定义与树的定义是类似的,也是一个递归的定义: 1.要么是一棵空树 2.如果 ...

  2. 查找系列合集-二叉查找树BST

    一. 二叉树 1. 什么是二叉树? 在计算机科学中,二叉树是每个结点最多有两个子树的树结构. 通常子树被称作“左子树”(left subtree)和“右子树”(right subtree). 二叉树常 ...

  3. [学习笔记] 二叉查找树/BST

    平衡树前传之BST 二叉查找树(\(BST\)),是一个类似于堆的数据结构, 并且,它也是平衡树的基础. 因此,让我们来了解一下二叉查找树吧. (其实本篇是作为放在平衡树前的前置知识的,但为了避免重复 ...

  4. 3.2 符号表之二叉查找树BST

    一.插入和查找 1.二叉查找树(Binary Search Tree)是一棵二叉树,并且每个结点都含有一个Comparable的键,保证每个结点的键都大于其左子树中任意结点的键而小于其右子树的任意结点 ...

  5. 二叉查找树BST 模板

    二叉查找树BST 就是二叉搜索树 二叉排序树. 就是满足 左儿子<父节点<右儿子 的一颗树,插入和查询复杂度最好情况都是logN的,写起来很简单.   根据BST的性质可以很好的解决这些东 ...

  6. Go基础之--排序和查找操作

    排序操作主要都在sort包中,导入就可以使用了import("sort") 常用的操作 sort.Ints:对整数进行排序sort.Strings:对字符串进行排序sort.Flo ...

  7. MongoDB学习(查找文档和其他数据查找操作)

    理解Cursor对象和查询运算符 cursor对象 cursor对象相当于一个指针,可通过迭代它来访问MongdoDB数据库中的一组对象. 在使用 find() 方法查询时,返回的并非实际文档,而是一 ...

  8. K:二叉查找树(BST)

    相关介绍:  二叉查找树(英语:Binary Search Tree),也称二叉搜索树.有序二叉树(英语:ordered binary tree),排序二叉树(英语:sorted binary tre ...

  9. 命题作文:在一棵IPv4地址树中彻底理解IP路由表的各种查找过程

    这是一篇命题作文.近期一直想写点东西,但一直找不到题目.正好收到一封邮件,有人问我Linux路由表的布局问题以及路由缓存的问题,加之前些日子又帮人做了一个片上路由表,所以认为这是个好题目,索性花了多半 ...

  10. 二叉查找树(BST)

    二叉查找树(BST):使用中序遍历可以得到一个有序的序列

随机推荐

  1. 使用VNC连接ubuntu16.4错误Authentication Failure问题

    解决办法:是因为vnc用一套自己的密码系统,不要去输入ssh登录时的密码,所以只需要进入远程服务器中,设置一哈vnc的密码即可! 在终端输入命令:vncpasswd 到此可以试试远程

  2. vue前端开发仿钉图系列(3)右侧画点线面的开发详解

    项目开发是完全仿照钉图的功能,参照钉图的逻辑和高德地图的参考手册以及aip文档,一点点的把功能做出来并呈现最后的效果.选中画点,在地图上获取经纬度并进行反地理编码,添加marker并弹出右侧编辑页面, ...

  3. 使用 vuex 和 本地存储实现永久性token存在 并且在请求拦截统一添加headers token 避免重复代码

    在 vuex 仓库中设置state的token值:从本地中取值: 登录的时候调用唯一可以修改state数据的mutations方法设置token : export default new Vuex.S ...

  4. yarn 和 npm 不能混合使用

    当有 yarn.lock 的时候说明项目使用的yarn 创建的 则后面的都要使用 yarn 操作,比如下载 包 等 : 当项目没有 yarn.lock 而是 package.json.lock 说明项 ...

  5. kotlin更多语言结构——>空安全

    可空类型与非空类型 Kotlin 的类型系统旨在从我们的代码中消除 NullPointerException .NPE 的唯一可能的原因可能是: -  显式调用 throw NullPointerEx ...

  6. SaaS架构:开放平台架构设计

    大家好,我是汤师爷~ 今天聊聊开放平台架构设计. 为什么需要搭建开放平台 增强产品能力 开放平台能够让三方开发者和合作伙伴开发新的应用或服务,增加原有SaaS产品能力.这样就可以满足更多用户需求,从而 ...

  7. harbor磁盘爆满,执行垃圾回收清理镜像

    1.在使用Jenkins发版操作时发现,推送私有仓库harbor报错: received unexpected HTTP status: 500 Internal Server Error 2.想要登 ...

  8. 云原生周刊:Kubernetes v1.28 正式发布 | 2023.8.21

    开源项目推荐 kurt 一个 Kubernetes 插件,可提供 Kubernetes 集群中重启内容的上下文信息. Kubean Kubean 是一个基于 kubespray 的 Kubernete ...

  9. 【小 w 的代数】(提供一种 n^2 log 的解法)

    前言: 卖点 记录 CTH 的发言 CTH:你这真是 n^3 的 CTH:我也不知道你线段树优化个啥,\(n^3 \log n\) CTH:你优化到哪了啊 CTH:······你从赛时打这个题到现在 ...

  10. NES 名词解释

    本文介绍了 NES(FC.红白机.小霸王)中一些名词或者术语,主要与 PPU 有关. Tile 8x8 像素图像.每像素 2 比特, 共 16 字节大小.每个像素可以使用 4 种颜色. Sprite ...