最近刚学了平衡树,然后突发奇想写几篇博客纪念一下,可能由于是刚学的缘故,还有点儿生疏,望大家海涵

说到平衡树,就不得不从基础说起,而基础,正是二叉查找树

什么是二叉查找树??

大家观察一下下面的这棵二叉树

相信大家一眼就能发现,这棵树从左往右是递增的(也就是右儿子大于左儿子)

那么这样的一棵树有什么用呢?

就比如说图上的这个数列 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的左儿子

当然,有重复时也很好办,在该结点加一个数量标记,告诉他有几个即可

插入的代码:

  1. void add(int wz,int v)
  2. {
  3. x[wz].size++;
  4. if(x[wz].val==v)
  5. {
  6. x[wz].cnt++;
  7. return;
  8. }
  9. if(x[wz].val>v)
  10. {
  11. if(x[wz].ls!=)
  12. {
  13. add(x[wz].ls,v);
  14. }
  15. else
  16. {
  17. bnt++;
  18. x[bnt].val=v;
  19. x[bnt].size=;
  20. x[bnt].cnt=;
  21. x[wz].ls=bnt;
  22. }
  23. }
  24. else
  25. {
  26. if(x[wz].rs!=)
  27. {
  28. add(x[wz].rs,v);
  29. }
  30. else
  31. {
  32. bnt++;
  33. x[bnt].val=v;
  34. x[bnt].size=;
  35. x[bnt].cnt=;
  36. x[wz].rs=bnt;
  37. }
  38. }
  39. }

然后是删除

删除其实可以通过改变该数字连的边来做到,但这我放到讲treap时再说,这个图的删除可以利用cnt标记,找到这个数后cnt--即可,如果为cnt=0,就表示此时已经没有这个数;

同时,cnt的改变不会影响大小关系,所以这棵树还是一定满足bst的性质的

真正的重头戏————查询

不得不说bst的功能还是很强大,常规的也可以支持找前驱,找后继,按值查排名和按排名查值,下面我一个一个来说

1.找前驱和后继:

先说前驱

根据定义,x的前驱是小于x的最大的数,所以前驱一定比x小,前驱就有可能在x的左子树或x的左父亲里

这里以9为例

我们知道前驱一定比9小,那么从根开始

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

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

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

这回是8,还是比9小,我们更新一下

在我们想向右跑时,发现当前已经没有右子树了,说明不可能有别的满足条件的,所以答案是8

总结一下,其实找前驱,就是从根节点开始,递归子树,如果当前节点大于你要找的数,就找他的左子树,反之找他的右子树,直到没有可以找的为止;

至于后继,其实道理一样,不过是反过来,这里我就不多说了,大家自己手码

代码:

找前驱:

  1. int GetPre(int wz,int val,int ans)
  2. {
  3. if(x[wz].val>=val)
  4. {
  5. if(x[wz].ls==)
  6. {
  7. return ans;
  8. }
  9. else
  10. {
  11. GetPre(x[wz].ls,val,ans);
  12. }
  13. }
  14. else
  15. {
  16. if(x[wz].rs==)
  17. {
  18. if(x[wz].val<val)
  19. {
  20. return x[wz].val;
  21. }
  22. else
  23. {
  24. return ans;
  25. }
  26. }
  27. if(x[wz].cnt!=)
  28. {
  29. return GetPre(x[wz].rs,val,x[wz].val);
  30. }
  31. else
  32. {
  33. return GetPre(x[wz].rs,val,ans);
  34. }
  35. }
  36. }

找后继:

  1. int GetNext(int wz,int val,int ans)
  2. {
  3. if(x[wz].val<=val)
  4. {
  5. if(x[wz].rs==)
  6. {
  7. return ans;
  8. }
  9. else
  10. {
  11. GetNext(x[wz].rs,val,ans);
  12. }
  13. }
  14. else
  15. {
  16. if(x[wz].ls==)
  17. {
  18. if(x[wz].val>val)
  19. {
  20. return x[wz].val;
  21. }
  22. else
  23. {
  24. return ans;
  25. }
  26. }
  27. if(x[wz].cnt!=)
  28. {
  29. return GetNext(x[wz].ls,val,x[wz].val);
  30. }
  31. else
  32. {
  33. return GetNext(x[wz].ls,val,ans);
  34. }
  35. }
  36. }

2.按排名找值和按值找排名

按排名找值:

相信大家在刚才的代码中发现了不和谐的东西——size,现在他就要派上用场了

还是刚才的图,我要找排名第7的数,怎么办呢?

看图(绿色的是子树及本身的大小)

根据bst的性质,右面的元素严格大于左面的元素,所以右面的排名也大于左面的,自然,排名为n的数就是第n靠左的节点

在这里我以n=7为例

我还是从根开始

因为root的左子树的大小为5,说明root本节点及他的左子树大小为6,而我要找的排名为7,显然不足,于是我可以假装把树切了一半(如图),然后查询新树中排名第1的数

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

13没有左子树,所以他在该子树中的排名为1,满足,所以答案为13。

由上面的例子可以得出,我们在按排名查值时,当前位置的排名为他左子树的大小加上自己cnt(该节点有几个)的大小,如果当前排名小于要找的排名,就去右子树找,并更新要找的排名,反之先自查,不行再去左子树找

看代码:

  1. int GetValByRank(int wz,int rank)
  2. {
  3. if(wz==)
  4.   {
  5. return INF;
  6.   }
  7. if(x[x[wz].ls].size>=rank)
  8.   {
  9. return GetValByRank(x[wz].ls,rank);
  10.   }
  11. if(x[x[wz].ls].size+x[wz].cnt>=rank)
  12.   {
  13. return x[wz].val;
  14.   }
  15. return GetValByRank(x[wz].rs,rank-x[x[wz].ls].size-x[wz].cnt);
  16. }

按值找排名:

这个其实和按排名查值是一样的,只不过变量从排名成了值,但其实也很简单,我们以13为例:
首先们从根开始(蓝色为当前数的总数)

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

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

我们发现15的左儿子的值为13,到达终点。这时我们知道他的排名是所有比他小的数+1,比他小的有路上的6个,而他的左子树大小为0,所以排名为6+0+1=7

总结一下,按值找排名时,从根开始,如果该位置的值小于要查询的值,就找他的右子树,同时记住他左子树的大小,如果小于,就查询他的左子树,直到大小相等,他的排名就是该点左子树的大小加上一路上比他小的节点个数再加上1

看代码:

  1. int GetRankByVal(int wz,int val)
  2. {
  3. if(wz==)
  4.   {
  5. return ;
  6.   }
  7. if(val==x[wz].val)
  8.   {
  9. return x[x[wz].ls].size+;
  10.   }
  11. if(val<x[wz].val)
  12.   {
  13. return GetRankByVal(x[wz].ls,val);
  14.   }
  15. return GetRankByVal(x[wz].rs,val)+x[x[wz].ls].size+x[wz].cnt;
  16. }

有关二叉查找树的内容暂时告一段落,这个数据结构相对较优,但在遇到下图时会被卡成O(n)。所以一些优化还是必不可少的,过一段时间我会继续写下去,给大家带来关于treap,splay和替罪羊树的一些事儿

关于二叉查找树的一些事儿(bst详解,平衡树入门)的更多相关文章

  1. ggplot2作图详解:入门函数qplot

    ggplot2作图详解:入门函数qplot   ggplot2的功能不用我们做广告,因为它的作者Hadley Wickham就说ggplot2是一个强大的作图工具,它可以让你不受现有图形类型的限制,创 ...

  2. Python基础知识详解 从入门到精通(七)类与对象

    本篇主要是介绍python,内容可先看目录其他基础知识详解,欢迎查看本人的其他文章Python基础知识详解 从入门到精通(一)介绍Python基础知识详解 从入门到精通(二)基础Python基础知识详 ...

  3. 反射实现Model修改前后的内容对比 【API调用】腾讯云短信 Windows操作系统下Redis服务安装图文详解 Redis入门学习

    反射实现Model修改前后的内容对比   在开发过程中,我们会遇到这样一个问题,编辑了一个对象之后,我们想要把这个对象修改了哪些内容保存下来,以便将来查看和追责. 首先我们要创建一个User类 1 p ...

  4. AspNetCore.Identity详解1——入门使用

    今年在面试的时候被问到单点登录的知识,当时支支吾吾不知该如何作答,于是面试失败.回到住所便开始上网查找资料,但苦于难于找到详尽的demo,总是无法入门.又由于我正在学习了解asp.net core,里 ...

  5. mysql存储过程详解(入门)

    delimiter //    #修改结束符号为// create procedure pro_wyx() /*创建存储过程*/ begin declare i int ; #定义变量 set i=1 ...

  6. SilverLight命名空间详解-新手入门

    1.核心命名空间 1.xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"是silverlight的核 ...

  7. 【转】three.js详解之入门篇

    原文链接:https://www.cnblogs.com/shawn-xie/archive/2012/08/16/2642553.html   开场白 webGL可以让我们在canvas上实现3D效 ...

  8. 【JavaEE WEB 开发】Tomcat 详解 Servlet 入门

    转载请注明出处 :  http://blog.csdn.net/shulianghan/article/details/47146817 一. Tomcat 下载安装配置 1. Tomcat 下载 T ...

  9. C#简单继承示例详解——快速入门

    在面向对象当中继承是非常重要的,也是面向对象的三大特性之一(继承.封装.多态),今天我们来揭开他的神秘面纱. 话不多说,我们上菜. using System; using System.Collect ...

随机推荐

  1. android 如何添加第3方lib库到kernel中

    注意:只能将lib库放在kernel编译到的地方,如下: alps/kernel/ alps/mediatek/custom/common/kernel/ alps/mediatek/custom/$ ...

  2. Android开发概要记录

    1..o文件. .ko和.so文件的路径 \kernel\out\mediatek---------------.o文件 .c/.cpp文件编译生成的目标文件 \out\target\product\ ...

  3. 利用JQuery直接调用asp.net后台方法

    利用JQuery的$.ajax()可以很方便的调用asp.net的后台方法. [WebMethod]   命名空间 1.无参数的方法调用, 注意:1.方法一定要静态方法,而且要有[WebMethod] ...

  4. Android群英传笔记——第四章:ListView使用技巧

    Android群英传笔记--第四章:ListView使用技巧 最近也是比较迷茫,但是有一点点还是要坚持的,就是学习了,最近离职了,今天也是继续温习第四章ListView,也拖了其实也挺久的了,list ...

  5. LeetCode之“数学”:Rectangle Area

    题目链接 题目要求: Find the total area covered by two rectilinear rectangles in a 2D plane. Each rectangle i ...

  6. PS图像特效算法——百叶窗

    这个只要设置好条纹的宽度和条纹的间隔,建立一个遮罩层,等间隔的对原图进行等间距的遮罩. clc; clear all; Image=imread('4.jpg'); Image=double(Imag ...

  7. glib-dbus 在ubuntu9.10 和 ubuntu10.04 上安装环境的搭建

    dbus-glib 安装环境搭建 安装 dbus apt-get install dbus 安装 d-feet ,用于查看 session bus 和 system bus apt-get insta ...

  8. 存储引擎-Buffered tree

    Buffered-tree 也称为COLA,即cache-oblivious,可以不需要知道具体内存大小和一个块的大小,使用一套逻辑进行处理,因此内存大小可知,内存可能被临时占用去做其它事情. Buf ...

  9. 和菜鸟一起学linux之dlna的学习记录

    关于DLNA框架 1.Networking & Connectivity 为了解决物理设备连通问题, 主要依赖于Ethernet,802.11,Ipv4协议栈,Ipv6协议栈. TCP/IP协 ...

  10. redis简单主从复制

    两台ubuntu 云服务器,分别redis主从服务器,ip地址是:123.207.96.138(主)139.199.167.251(从) 安装redis,在这里我建议给redis设置密码,之前看过一篇 ...