关于二叉查找树的一些事儿(bst详解,平衡树入门)
最近刚学了平衡树,然后突发奇想写几篇博客纪念一下,可能由于是刚学的缘故,还有点儿生疏,望大家海涵
说到平衡树,就不得不从基础说起,而基础,正是二叉查找树
什么是二叉查找树??
大家观察一下下面的这棵二叉树

相信大家一眼就能发现,这棵树从左往右是递增的(也就是右儿子大于左儿子)
那么这样的一棵树有什么用呢?
就比如说图上的这个数列 12 10 15 6 13 19 2 8 14 22
如果你想找第n大的数,你明显需要冒泡排序或快速排序,最坏复杂度分别为:O(n^2/2),O(nlogn);
如果数列再改大一下,而你每次都要跑这么高的复杂度,电脑累死了
但如果你用上图这棵树,根据他(右儿子>自己>左儿子)的特性,可以在期望复杂度O(logn)的时间内查出一个数据
同时修改也变得简单,O(logn)的时间查找位置,O(1)的时间连边
首先先看看插入
以7为例,我们从根节点开始

很明显,根节点大于7,那么我把7下放到当前的左儿子

还是大,怎么办?继续下放到当前的左儿子
这次是小了,放当前的右儿子

右儿子又大了,我们打算把他往当前的左儿子上放
但是,8这里并没有左儿子
可以恭喜了,7终于找到了自己的位置,我们现在新建一个结点,连一条边,使他变为8的左儿子

当然,有重复时也很好办,在该结点加一个数量标记,告诉他有几个即可
插入的代码:
void add(int wz,int v)
{
x[wz].size++;
if(x[wz].val==v)
{
x[wz].cnt++;
return;
}
if(x[wz].val>v)
{
if(x[wz].ls!=)
{
add(x[wz].ls,v);
}
else
{
bnt++;
x[bnt].val=v;
x[bnt].size=;
x[bnt].cnt=;
x[wz].ls=bnt;
}
}
else
{
if(x[wz].rs!=)
{
add(x[wz].rs,v);
}
else
{
bnt++;
x[bnt].val=v;
x[bnt].size=;
x[bnt].cnt=;
x[wz].rs=bnt;
}
}
}
然后是删除
删除其实可以通过改变该数字连的边来做到,但这我放到讲treap时再说,这个图的删除可以利用cnt标记,找到这个数后cnt--即可,如果为cnt=0,就表示此时已经没有这个数;
同时,cnt的改变不会影响大小关系,所以这棵树还是一定满足bst的性质的
真正的重头戏————查询
不得不说bst的功能还是很强大,常规的也可以支持找前驱,找后继,按值查排名和按排名查值,下面我一个一个来说
1.找前驱和后继:
先说前驱
根据定义,x的前驱是小于x的最大的数,所以前驱一定比x小,前驱就有可能在x的左子树或x的左父亲里
这里以9为例
我们知道前驱一定比9小,那么从根开始

我们发现根比9大,不满足前驱小于本身的条件,那么根据二叉查找树的性质,我们找他的左子树

发现还是大,找他的左子树

这回终于碰上一个比9小的了,我们把他记下来,为了确保6是最小的,我们要看他的右子树

这回是8,还是比9小,我们更新一下
在我们想向右跑时,发现当前已经没有右子树了,说明不可能有别的满足条件的,所以答案是8
总结一下,其实找前驱,就是从根节点开始,递归子树,如果当前节点大于你要找的数,就找他的左子树,反之找他的右子树,直到没有可以找的为止;
至于后继,其实道理一样,不过是反过来,这里我就不多说了,大家自己手码
代码:
找前驱:
int GetPre(int wz,int val,int ans)
{
if(x[wz].val>=val)
{
if(x[wz].ls==)
{
return ans;
}
else
{
GetPre(x[wz].ls,val,ans);
}
}
else
{
if(x[wz].rs==)
{
if(x[wz].val<val)
{
return x[wz].val;
}
else
{
return ans;
}
}
if(x[wz].cnt!=)
{
return GetPre(x[wz].rs,val,x[wz].val);
}
else
{
return GetPre(x[wz].rs,val,ans);
}
}
}
找后继:
int GetNext(int wz,int val,int ans)
{
if(x[wz].val<=val)
{
if(x[wz].rs==)
{
return ans;
}
else
{
GetNext(x[wz].rs,val,ans);
}
}
else
{
if(x[wz].ls==)
{
if(x[wz].val>val)
{
return x[wz].val;
}
else
{
return ans;
}
}
if(x[wz].cnt!=)
{
return GetNext(x[wz].ls,val,x[wz].val);
}
else
{
return GetNext(x[wz].ls,val,ans);
}
}
}
2.按排名找值和按值找排名
按排名找值:
相信大家在刚才的代码中发现了不和谐的东西——size,现在他就要派上用场了
还是刚才的图,我要找排名第7的数,怎么办呢?
看图(绿色的是子树及本身的大小)

根据bst的性质,右面的元素严格大于左面的元素,所以右面的排名也大于左面的,自然,排名为n的数就是第n靠左的节点
在这里我以n=7为例
我还是从根开始
因为root的左子树的大小为5,说明root本节点及他的左子树大小为6,而我要找的排名为7,显然不足,于是我可以假装把树切了一半(如图),然后查询新树中排名第1的数

因为我们知道5的左子树大小为2,而2+1=3>1,所以我们找他的左子树(如图)

13没有左子树,所以他在该子树中的排名为1,满足,所以答案为13。
由上面的例子可以得出,我们在按排名查值时,当前位置的排名为他左子树的大小加上自己cnt(该节点有几个)的大小,如果当前排名小于要找的排名,就去右子树找,并更新要找的排名,反之先自查,不行再去左子树找
看代码:
int GetValByRank(int wz,int rank)
{
if(wz==)
{
return INF;
}
if(x[x[wz].ls].size>=rank)
{
return GetValByRank(x[wz].ls,rank);
}
if(x[x[wz].ls].size+x[wz].cnt>=rank)
{
return x[wz].val;
}
return GetValByRank(x[wz].rs,rank-x[x[wz].ls].size-x[wz].cnt);
}
按值找排名:
这个其实和按排名查值是一样的,只不过变量从排名成了值,但其实也很简单,我们以13为例:
首先们从根开始(蓝色为当前数的总数)

12显然是小于13的,所以我们找12的右子树,同时我们已知的小于13的数有了6个

15比13大,所以我们找他的左子树,比他小的还是6个

我们发现15的左儿子的值为13,到达终点。这时我们知道他的排名是所有比他小的数+1,比他小的有路上的6个,而他的左子树大小为0,所以排名为6+0+1=7
总结一下,按值找排名时,从根开始,如果该位置的值小于要查询的值,就找他的右子树,同时记住他左子树的大小,如果小于,就查询他的左子树,直到大小相等,他的排名就是该点左子树的大小加上一路上比他小的节点个数再加上1
看代码:
int GetRankByVal(int wz,int val)
{
if(wz==)
{
return ;
}
if(val==x[wz].val)
{
return x[x[wz].ls].size+;
}
if(val<x[wz].val)
{
return GetRankByVal(x[wz].ls,val);
}
return GetRankByVal(x[wz].rs,val)+x[x[wz].ls].size+x[wz].cnt;
}
有关二叉查找树的内容暂时告一段落,这个数据结构相对较优,但在遇到下图时会被卡成O(n)。所以一些优化还是必不可少的,过一段时间我会继续写下去,给大家带来关于treap,splay和替罪羊树的一些事儿

关于二叉查找树的一些事儿(bst详解,平衡树入门)的更多相关文章
- ggplot2作图详解:入门函数qplot
ggplot2作图详解:入门函数qplot ggplot2的功能不用我们做广告,因为它的作者Hadley Wickham就说ggplot2是一个强大的作图工具,它可以让你不受现有图形类型的限制,创 ...
- Python基础知识详解 从入门到精通(七)类与对象
本篇主要是介绍python,内容可先看目录其他基础知识详解,欢迎查看本人的其他文章Python基础知识详解 从入门到精通(一)介绍Python基础知识详解 从入门到精通(二)基础Python基础知识详 ...
- 反射实现Model修改前后的内容对比 【API调用】腾讯云短信 Windows操作系统下Redis服务安装图文详解 Redis入门学习
反射实现Model修改前后的内容对比 在开发过程中,我们会遇到这样一个问题,编辑了一个对象之后,我们想要把这个对象修改了哪些内容保存下来,以便将来查看和追责. 首先我们要创建一个User类 1 p ...
- AspNetCore.Identity详解1——入门使用
今年在面试的时候被问到单点登录的知识,当时支支吾吾不知该如何作答,于是面试失败.回到住所便开始上网查找资料,但苦于难于找到详尽的demo,总是无法入门.又由于我正在学习了解asp.net core,里 ...
- mysql存储过程详解(入门)
delimiter // #修改结束符号为// create procedure pro_wyx() /*创建存储过程*/ begin declare i int ; #定义变量 set i=1 ...
- SilverLight命名空间详解-新手入门
1.核心命名空间 1.xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"是silverlight的核 ...
- 【转】three.js详解之入门篇
原文链接:https://www.cnblogs.com/shawn-xie/archive/2012/08/16/2642553.html 开场白 webGL可以让我们在canvas上实现3D效 ...
- 【JavaEE WEB 开发】Tomcat 详解 Servlet 入门
转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/47146817 一. Tomcat 下载安装配置 1. Tomcat 下载 T ...
- C#简单继承示例详解——快速入门
在面向对象当中继承是非常重要的,也是面向对象的三大特性之一(继承.封装.多态),今天我们来揭开他的神秘面纱. 话不多说,我们上菜. using System; using System.Collect ...
随机推荐
- RedHat系列软件管理(第二版) --源码包安装
RedHat系列软件管理 --源码包安装 源码包特点: 拥有广泛的平台支持性,可以装在所有的类UNIX操作系统上,不用考虑CPU架构. 灵活性,可以在安装过程中指定特有的选项. 定制度非常高,可以自己 ...
- CentOS 7下编译安装Boost_1_57_0
之前对库的理解太肤浅(现在也仍很肤浅),导致走了挺多的弯路,现记录以备后查. 现在可以从Boost官网下载到最新的Boost源代码boost_1_57_0.tar.gz. 现将步骤记录如下: 1. 解 ...
- Android模拟器启动不了解决办法
问题描述:Windows2008中的MyEclipse项目在Windows2003中运行时无法启动模拟器. 解决要点:启动模拟器管理工具,在启动中设置属性中不勾选默认尺寸显示. 系统错误如下: [20 ...
- SharePoint2007 管理中心TimerJob”计时器作业定义”页面报错
部署完成自己开发的TimerJob,发现"计时器作业定义"页面报错,进不去了,但是TimerJob可以正常运行,这样,肯定不行的,估计还是代码有问题,把这个脆弱的sharepoin ...
- Cookie、sessionStorage、localStorage的区别
共同点:都是保存在浏览器端,且同源的.区别:cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递.而sessionStorage和localStora ...
- javah tool for Android Native Application
javah可以在Eclipse中配置成为External Tools,选择External Tools Configurations,配置如下,经过测试通过. Location: ${system_p ...
- angularjs作用域之transclude
transclude是一个可选的参数.如果设置了,其值必须为true,它的默认值是false.嵌入有时被认为是一个高级主题,但某些情况下它与我们刚刚学习过的作用域之间会有非常好的配合.使用嵌入也会很好 ...
- 项目中Orcale存储过程优化记录
今天对之前写的Orcale存储过程做了一些优化,使其变得更加灵活,之前写的存储过程是使用游标存储SQL执行结果,但是使用游标带来的弊端是,在声明时,它所执行的SQL就已经被写死,无法修改.若想更加灵活 ...
- Python_PyMySQL数据库操作
连接数据库: conn=pymysql.connect(host=,user=',charset='utf8') 建立游标: cur = conn.cursor() 创建一个名字叫 lj 的数据库: ...
- WSL与Windows交互实践
1. WSL是什么 2. WSL新特性 3. WSL管理配置 4. WSL交互 5. 解决方案 * 5.1 使用别名 * 5.2 多复制一份 * 5.3 重定向 * 5.4 symlink 6 ...