二叉搜索树Binary Search Tree, BST)是一种常用的数据结构,在理想情况下,它可以以

的复杂度完成一系列修改和查询,包括:
  • 插入一个数
  • 删除一个数
  • 查询某数的排名(排名定义为比该数小的数的个数+1)
  • 查询指定排名的数
  • 求某数的前驱(前驱定义为小于该数,且最大的数)
  • 求某数的后继(后继定义为大于该数,且最小的数)

维护一个有序的数组,配合二分查找,也可以实现这些操作,但插入和删除的复杂度是

。相对地,

链表可以

插入和删除,但其他操作的复杂度不够优秀。

二叉搜索树即BST对于以上每个操作,都拥有不差的复杂度。

例如,如果我们依次插入6、2、5、1、7、4、2、5、9、5,得到BST的结构如下:

显然,二叉搜索树是一个二叉树,每个节点对应的左子树中的所有数都小于它,右子树中的所有数都大于它。而且每个节点对应的左右子树也是二叉搜索树(这是一个天然递归的结构)。由于我们这里维护的是可重集,所以每个节点还带有额外信息,即该节点储存的数出现的次数。此外,为了方便查询排名,我们还保存每个子树的大小

// L: 左子树根节点编号,R:右子树根节点编号,N:该节点储存的数出现的次数
// val:该节点储存的数,size:以该节点为根节点的子树的节点数目(即子树大小)
int L[MAXN], R[MAXN], N[MAXN], val[MAXN], size[MAXN], cnt = 1;

现在我们分别实现上述的操作。

插入

从根节点开始,递归地搜索。若插入的值小于当前节点的值,则向左搜;反之向右搜。这样最后如果找到一个已有节点,则令其计数+1;否则若到达空节点,则用该节点存储这个值。

void insert(int v, int pos = 1) // 插入
{size[pos]++; // 树大小+1if (N[pos] == 0 && L[pos] == 0 && R[pos] == 0) // 空节点{val[pos] = v;N[pos] = 1;}else if (v < val[pos]) // 向左搜索{if (L[pos] == 0) // 如果应该向左搜,但不存在左节点,则创建一个新节点L[pos] = ++cnt;insert(v, L[pos]);}else if (v > val[pos]) // 向右搜索{if (R[pos] == 0)R[pos] = ++cnt;insert(v, R[pos]);}else // 已经存在值相同的节点N[pos]++;
}

删除

这里直接采取惰性删除的方法:找到要删除的数,令其计数-1。这样写起来比较简单,不用进行较复杂的分类讨论,而且不会增加时间复杂度。

稍微注意一下,采取惰性删除时判断一个节点是不是空节点要用N[pos]==0 && L[pos]==0 && R[pos]==0 而不能仅仅判断N,因为N[pos]==0的点也可能是被删除的中间节点。(注意被删除的叶子节点可以当作空节点处理)

void remove(int v, int pos = 1) // 删除
{size[pos]--; // 树大小-1if (v < val[pos])remove(v, L[pos]);else if (v > val[pos])remove(v, R[pos]);elseN[pos]--;
}

求排名

因为排名被定义为比某数小的数+1,所以我们直接实现两个函数countlcountg,用来求比某数小的数的数量和比某数大的数的数量。(这两个函数后面也会用到)

countl为例,我们递归地求。如果要找的值比当前节点小,则向左边搜;反之则向右边搜,但这时要加上size[L[pos]]+N[pos],因为左子树和根节点的所有数都比要找的数小;如果要找的值恰好等于当前节点,则直接返回左子树的大小。

int countl(int v, int pos = 1) // 求比某数小的数的个数
{if (v < val[pos])return L[pos] ? countl(v, L[pos]) : 0;else if (v > val[pos])return size[L[pos]] + N[pos] + (R[pos] ? countl(v, R[pos]) : 0);elsereturn size[L[pos]];
}

countg完全类似。

int countg(int v, int pos = 1) // 求比某数大的数的个数
{if (v > val[pos])return R[pos] ? countg(v, R[pos]) : 0;else if (v < val[pos])return size[R[pos]] + N[pos] + (L[pos] ? countg(v, L[pos]) : 0);elsereturn size[R[pos]];
}

rank函数其实可以不写了:

int rank(int v)
{return countl(v) + 1;
}

求指定排名的数

与上面的方法类似,每个节点处判断应该往左边还是右边找,递归地往下搜寻。

int kth(int k, int pos = 1) // 求指定排名的数
{if (size[L[pos]] + 1 > k) // 答案在左,在左子树中找排名为k的数return kth(k, L[pos]);else if (size[L[pos]] + N[pos] < k)  // 答案在右,在右子树中找排名为k - size[L[pos]] - N[pos]的数return kth(k - size[L[pos]] - N[pos], R[pos]);elsereturn val[pos];
}

注意,假如某个数的排名为2,且它出现了3次,那么这个函数传入2、3、4都会返回这个数,这也提供了一些方便。

求前驱

根据我们kth函数的性质,直接找到排名比当前数小1的那个数即可。

int pre(int v) // 求前驱
{int r = countl(v);return kth(r);
}

求后继

后继的排名则是小于等于当前数的数的数量+1。

int suc(int v) // 求后继
{int r = size[1] - countg(v) + 1;return kth(r);
}


以上是二叉搜索树的介绍。然而,这种数据结构本身很少使用,因为它的各种操作复杂度是

,其中
为层数。只有在它大致平衡(平衡指所有叶子的深度趋于相同),才具有优秀的复杂度(当它是完全二叉树时,为
),然而这只是理想情况。假如我们依次加入6、5、4、3、2、1,BST的结构会是这样:

这样,BST就退化成了链表,复杂度变为

为了使BST能够大致平衡,人们想了很多种改进方法,它们称为平衡树。今后的笔记会介绍一些常用的平衡树。


Pecco:算法学习笔记(目录)​zhuanlan.zhihu.com

l2-004 这是二叉搜索树吗?_算法学习笔记(45): 二叉搜索树相关推荐

  1. 大顶堆删除最大值_算法学习笔记(47): 二叉堆

    堆(Heap)是一类数据结构,它们拥有树状结构,且能够保证父节点比子节点大(或小).当根节点保存堆中最大值时,称为大根堆:反之,则称为小根堆. 二叉堆(Binary Heap)是最简单.常用的堆,是一 ...

  2. 基于MVS的三维重建算法学习笔记(二)— 立体视觉的几何基础总结

    基于MVS的三维重建算法学习笔记(二)- 立体视觉的几何基础总结 声明 概述 1. 常见三维数据类型 2. 三维形状的几种表达形式 3. 三维空间刚体运动 4. 李群和李代数 5. 相机标定 6. 非 ...

  3. uml图中的各种箭头_设计模式学习笔记(二):UML与面向对象设计原则

    1 UML 1.1 UML UML(Unified Modeling Language)是统一建模语言,1997年11月UML1.1版本提交给OMG并正式通过,成为建模语言的个那个也标准.2003年6 ...

  4. 图解算法学习笔记(二): 选择排序

    目录 1)数组和链表: 2)选择排序算法: 3)小结 本章内容: 两种基本数据结构:数组和链表: 选择排序算法: 1)数组和链表: 数组是连续的内存单元,链表可以不连续: 链表存储单元如图所示,每一个 ...

  5. 莫队算法学习笔记(二)——带修莫队

    前言:什么是莫队 莫队算法,是一个十分优雅的暴力. 普通的莫队可以轻松解决一些离线问题,但是,当遇上了一些有修改操作的问题,普通莫队就无能为力了. 于是,改进后的莫队--带修莫队就这样产生了. L i ...

  6. OpenCV学习笔记(二十一)——绘图函数core OpenCV学习笔记(二十二)——粒子滤波跟踪方法 OpenCV学习笔记(二十三)——OpenCV的GUI之凤凰涅槃Qt OpenCV学习笔记(二十

    OpenCV学习笔记(二十一)--绘图函数core 在图像中,我们经常想要在图像中做一些标识记号,这就需要绘图函数.OpenCV虽然没有太优秀的GUI,但在绘图方面还是做得很完整的.这里就介绍一下相关 ...

  7. 线性代数学习笔记(二十九)——方程组解的结构(一)

    停更2年多了,做事得有始有终,继续更新... 本篇笔记回顾了线性方程组解的三种情况,并讨论了齐次线性方程组解的结构,并介绍了齐次线性方程组解的相关性质.其中重点讨论了基础解系定义,以及基础解系的求法和 ...

  8. Windows x64内核学习笔记(二)—— IA-32e模式

    Windows x64内核学习笔记(二)-- IA-32e模式 IA-32e模式 模式检测 强制平坦段 任务切换 中断门描述符 FS / GS 模式切换 32位程序进内核 64位程序进内核 实验:模式 ...

  9. VHDL硬件描述语言学习笔记(二)

    本文主要参考b站视频:[考研]EDA技术(vhdl技术),建议有时间的跟着听一下,从第8节开始,一直到31节都是讲VHDL,讲的很全面,赶时间的可以直接看我这个笔记. 文章目录 1.3 VHDL语言要 ...

最新文章

  1. CUDA 8混合精度编程
  2. java date类_Java的败笔-Date类
  3. python基础教程视频(全13集)-Python基础视频教程全集
  4. WEB框架研究笔记六(Spring WEB)
  5. SDNU 1464.最大最小公倍数(思维)
  6. 机器学习实战教程(四):朴素贝叶斯基础篇之言论过滤器
  7. docker nginx1.7.6+keepalived实现双机热备
  8. 【转贴】Oracle查询重复数据与删除重复记录方法
  9. php array 数组函数,php数组函数-array_地图()
  10. (转)Linux查看CPU,硬盘,内存的大小
  11. 西门子wincc消息队列服务器,安装Wincc 7.0 ASIA时,消息队列装不上,提示错误-工业支持中心-西门子中国...
  12. 【图像隐藏】基于matlab像素预测和位平面压缩的加密图像可逆数据隐藏【含Matlab源码 2218期】
  13. goldendict无法导入字典
  14. python输入名字、输出欢迎你_Python交互环境下打印和输入函数的实例内容
  15. Pure Virtual Function
  16. 如何把照片做成视频?抖音爆款的图片视频切换教程,快速上手!
  17. 国内外开源社区资源汇总
  18. 为什么牛逼程序员都秃顶了....?
  19. 2010年12月大学英语四级最新资料精品
  20. 基于Qt的FreeType字体轮廓解析

热门文章

  1. Vs2010创建WebService
  2. 2013-开始新的一年
  3. 卡车紧急刹车加强系统(发明畅想)
  4. 让你的网站首页自动选择语言转跳
  5. 【论文写作】Springboot人才招聘网站如何画系统流程图
  6. 万创帮逆向解析,让你也能体验技术变现【Python爬虫实战系列之万创帮闲置资源整合逆向】
  7. c++ 返回引用_【Excel必知必会】引用(链接)小专题
  8. linux rsync 目录同步,linux下使用rsync同步目录
  9. java quartz web.xml_quartz定时执行任务,并配置web.xml的操作方法
  10. C++向函数传递数组