BST

基础芝士

给定一棵二叉树,每个节点有权值,定义“BST 性质”为:

对于树中的任意一个节点 \(x\) 都有:

  • \(x\) 的权值大于 \(x\) 的左子树中任意节点的权值。
  • \(x\) 的权值小于 \(x\) 的右子树中任意节点的权值。

即 \(左子树任意点的权值 < x < 右子树任意点的权值\)。

满足上述性质的二叉树即为“二叉搜索树”(BST)。可以发现,二叉搜索树的中序遍历的点权值单调递增。

一般情况下 BST 中无相同权值,当然有相同权值的也可以处理为无相同权值的。如图:



中序遍历结果为 \({1,2,3,4,5,6,7,8,9}\)。

维护操作

建立

为避免越界,一般在 BST 中插入正负无穷。仅由这两个节点构成的 BST 即为空 BST。如图:

建立的操作即为建立一个空 BST:

  1. const int inf=1<<30;
  2. struct bst
  3. {
  4. int l,r,v;
  5. }a[N];
  6. int tot,root;
  7. int New(int v)
  8. {
  9. a[++tot].v=v;
  10. return tot;
  11. }
  12. void build()
  13. {
  14. New(-inf),New(inf);
  15. root=1,a[1].r=2;
  16. }

查找

查找 BST 中是否存在权值为 \(v\) 的节点。从根节点开始,利用“BST 性质”,一层层往下查找。

设当前所在节点为 \(x\),权值为 \(y\):

  1. \(y=v\),则已找到。
  2. \(y>v\),若 \(x\) 无左儿子,则说明不存在;若 \(x\) 有左儿子,进入左子树继续查找。
  3. \(y<v\),若 \(x\) 无右儿子,则说明不存在;若 \(x\) 有右儿子,进入右子树继续查找。

用递归实现:

  1. int find(int x,int v)
  2. {
  3. int y=a[x].v;
  4. if(y==v) return x;
  5. if(x==0) return 0; //若x不存在
  6. return y>v ? find(a[x].l,v) : find(a[x].r,v);
  7. }

while 实现:

  1. int find(int v)
  2. {
  3. int x=root;
  4. while(x)
  5. {
  6. int y=a[x].v;
  7. if(y==v) break;
  8. x= y>v ? a[x].l : a[x].r;
  9. }
  10. return x;
  11. }

插入

在 BST 中插入一个权值为 \(v\) 的点(假设原来不存在)。

类似于查找操作:

设当前所在节点为 \(x\),权值为 \(y\):

  1. \(y>v\),进入左子树继续查找。
  2. \(y<v\),进入右子树继续查找。
  3. 若接下来进入的点为空,则直接建立。

    用递归实现:
  1. int insert(int &x,int v)//使用&,更新其父节点的信息
  2. {
  3. int y=a[x].v;
  4. if(y==v) return ;
  5. if(x==0)
  6. {
  7. x=New(v);
  8. return ;
  9. }
  10. return y>v ? insert(a[x].l,v) : insert(a[x].r,v);
  11. }

求最大/最小值

求以节点 \(x\) 为根节点的树的最大/最小值。在 BST 中,任一子树都是 BST,而 BST 的最小值一定在最左边的点上,最大值一定在最右边的点上(“BST 性质”)。如图:



则求最大值就是从 \(x\) 开始一直向左子树走直到没有左子树,求最小值就是从 \(x\) 开始一直向右子树走直到没有右子树,当然要注意避开正负无穷。

  1. int get_min(int x)
  2. {
  3. while(a[x].l) x=a[x].l;
  4. return x;
  5. }
  6. int get_max(int x)
  7. {
  8. while(a[x].r) x=a[x].r;
  9. return x;
  10. }

求前驱/后继

设节点为 \(x\),权值为 \(y\)。

  • \(x\) 的前驱:指中序遍历 BST 后,位于 \(x\) 前的第一个点,即满足 \(v<y\) 的最大的 \(v\) 所对应的节点。
  • \(x\) 的后继:指中序遍历 BST 后,位于 \(x\) 后的第一个点,即满足 \(v>y\) 的最小的 \(v\) 所对应的节点。

因此,求 \(x\) 的前驱即求 \(x\) 左子树中的最大值,求 \(x\) 的后继即求 \(x\) 右子树中的最小值。

  1. int get_pre(int x)
  2. {
  3. return get_max(a[x].l);
  4. }
  5. int get_ne(int x)
  6. {
  7. return get_min(a[x].r);
  8. }

若给出的是 \(y\):

  1. int get_pre(int u,int v)
  2. {
  3. if(u==0) return -inf;
  4. if(a[u].v>=v) return get_pre(a[u].l,v);//先找到左子树
  5. return max(a[u].v,get_pre(a[u].r,v));//再找到最大值
  6. }
  7. int get_ne(int u,int v)
  8. {
  9. if(u==0) return inf;
  10. if(a[u].v<=v) return get_ne(a[u].r,v);//先找到右子树
  11. return min(a[u].v,get_ne(a[u].l,v));//再找最小值
  12. }

删除

从 BST 中删除权值为 \(v\) 的节点 \(x\)。

思考如何在删除 \(x\) 后能维护 BST。

  • 若 \(x\) 的儿子个数小于2,则直接用 \(x\) 的儿子代替 \(x\) 的位置,与 \(x\) 的父节点相连。
  • 若 \(x\) 的儿子个数等于2,考虑删除后,应该是 \(x\) 的后继 \(next\) 代替它的位置,也就是要先删除 \(next\),再用 \(next\) 代替 \(x\)。而且 \(next\) 无左儿子,因为 \(next\) 是 \(x\) 右子树的最小值。所以,用 \(next\) 的右儿子代替 \(next\) 的位置,再用 \(next\) 代替 \(x\) 的位置。
  1. void remove(int v)
  2. {
  3. int &x=root;//同时修改x父节点的信息
  4. while(x)
  5. {
  6. int y=a[x].v;
  7. if(y==v) break;
  8. x= y>v ? a[x].l : a[x].r;
  9. }
  10. if(x==0) return;
  11. if(a[x].l==0) x=a[x].r;
  12. else if(a[x].r==0) x=a[x].l;
  13. else
  14. {
  15. int next=get_ne(x);
  16. remove(a[next].v);
  17. a[next].l=a[x].l,a[next].r=a[x].r;
  18. x=next;
  19. }
  20. }

结语

BST 中每一次操作的期望复杂度为 \(O(\log{n})\),但 BST 容易退化。当 BST 为一条链时,复杂度为 \(O(n)\)。为了解决该问题,出现了各种平衡二叉树,请看:

tips

点权值中需满足“BST性质”的称为“关键码”,平衡二叉树中会区分开。

BST(二叉搜索树)的更多相关文章

  1. 数据结构中很常见的各种树(BST二叉搜索树、AVL平衡二叉树、RBT红黑树、B-树、B+树、B*树)

    数据结构中常见的树(BST二叉搜索树.AVL平衡二叉树.RBT红黑树.B-树.B+树.B*树) 二叉排序树.平衡树.红黑树 红黑树----第四篇:一步一图一代码,一定要让你真正彻底明白红黑树 --- ...

  2. [LeetCode] Serialize and Deserialize BST 二叉搜索树的序列化和去序列化

    Serialization is the process of converting a data structure or object into a sequence of bits so tha ...

  3. bst 二叉搜索树简单实现

    //数组实现二叉树: // 1.下标为零的元素为根节点,没有父节点 // 2.节点i的左儿子是2*i+1:右儿子2*i+2:父节点(i-1)/2: // 3.下标i为奇数则该节点有有兄弟,否则又左兄弟 ...

  4. 数据结构中常见的树(BST二叉搜索树、AVL平衡二叉树、RBT红黑树、B-树、B+树、B*树)

    树 即二叉搜索树: 1.所有非叶子结点至多拥有两个儿子(Left和Right): 2.所有结点存储一个关键字: 非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树: 如: BST树 ...

  5. [LeetCode] Minimum Absolute Difference in BST 二叉搜索树的最小绝对差

    Given a binary search tree with non-negative values, find the minimum absolute difference between va ...

  6. 浅析BST二叉搜索树

    2020-3-25 update: 原洛谷日报#2中代码部分出现一些问题,详情见此帖.并略微修改本文一些描述,使得语言更加自然. 2020-4-9 update:修了一些代码的锅,并且将文章同步发表于 ...

  7. 530 Minimum Absolute Difference in BST 二叉搜索树的最小绝对差

    给定一个所有节点为非负值的二叉搜索树,求树中任意两节点的差的绝对值的最小值.示例 :输入:   1    \     3    /   2输出:1解释:最小绝对差为1,其中 2 和 1 的差的绝对值为 ...

  8. LeetCode #938. Range Sum of BST 二叉搜索树的范围和

    https://leetcode-cn.com/problems/range-sum-of-bst/ 二叉树中序遍历 二叉搜索树性质:一个节点大于所有其左子树的节点,小于其所有右子树的节点 /** * ...

  9. Leetcode938. Range Sum of BST二叉搜索树的范围和

    给定二叉搜索树的根结点 root,返回 L 和 R(含)之间的所有结点的值的和. 二叉搜索树保证具有唯一的值. 示例 1: 输入:root = [10,5,15,3,7,null,18], L = 7 ...

  10. 标准BST二叉搜索树写法

    本人最近被各种数据结构的实验折磨的不要不要的,特别是代码部分,对数据结构有严格的要求,比如写个BST要分成两个类,一个节点类,要给树类,关键是所以操作都要用函数完成,也就是在树类中不能直接操作节点,需 ...

随机推荐

  1. Hundred Finance 攻击事件分析

    Hundred Finance 背景知识 Hundred Finance 是 fork Compound 的一个借贷项目,在2023/04/15遭受了黑客攻击.攻击者在发起攻击交易之前执行了两笔准备交 ...

  2. Rasa NLU中的组件

      Rasa NLU部分主要是解决NER(序列建模)和意图识别(分类建模)这2个任务.Rasa NLP是一个基于DAG的通用框架,图中的顶点即组件.组件特征包括有顺序关系.可相互替换.可互斥和可同时使 ...

  3. 从零开始学习Python

    从零开始学习Python是一个令人兴奋和有趣的过程.无论你是完全没有编程经验,还是已经熟悉其他编程语言,Python都可以成为你迈向程序员之路的理想起点. 首先,在开始学习之前,请确保在计算机上安装了 ...

  4. MCU看门狗使用注意事项

    前言 最近因为项目产品硬件设计有问题,导致设计的一款产品把硬件电源开关以及硬件系统复位功能去掉了.更严重的是,这产品已经开始生产了,硬件已经无法修改,所以软件必须上看门狗,否则设备死机或是异常后就只能 ...

  5. AntDesignBlazor示例——创建列表页

    本示例是AntDesign Blazor的入门示例,在学习的同时分享出来,以供新手参考. 示例代码仓库:https://gitee.com/known/AntDesignDemo 1. 学习目标 使用 ...

  6. execl表格if函数and和or的使用方法?

    当在Excel中处理数据时,IF函数是非常有用的函数之一.它允许您根据指定的条件执行不同的操作.在IF函数中,AND和OR函数可以帮助您组合多个条件以实现更复杂的逻辑判断.接下来,我将详细描述IF函数 ...

  7. .NET8顶级调试lldb观察FOH堆字符串分配

    前言 好久没有动用LLDB了,这种未来的下一代高性能调试器应该是用在Linux内核系统的Arm64/Riscv64/X64系统指令集上的,LLDB Debug .NET有点杀鸡用牛刀.本篇通过它来看下 ...

  8. 使用 Power Shell 修改 Hyper-V 虚拟机 UUID 的解决方案

    前言 在研究了一下午 k8s 文档的时候,正准备开干,万万没想到一个 uuid 的问题卡了我几个小时,一直想在系统中解决,没想到最后在外部使用PowerSheel解决了,分享记录一二 问题描述与尝试解 ...

  9. 加速计算,为何会成为 AI 时代的计算力“新宠” 审核中

    随着科技的发展,处理大量数据和进行复杂计算的需求越来越高,人工智能.大数据和物联网等领域更是如此,传统的计算方式已经无法满足这些需求.因此,加速计算作为一种现代计算方式,成了必要的手段.加速计算具有前 ...

  10. 【Python】【OpenCV】【NumPy】图像数据的访问

    接上一随笔,这次学习针对图像数据的访问(Numpy.array) 在OpenCV中,使用 imread() 方法可以访问图像,其返回值是一个数组,而根据传入的不同图像,将会返回不同维度的数组. 针对返 ...