查找结构(二叉树、红黑树、B树、B+树、LSM)

本文深入探讨了多种树型数据结构,包括二叉查找树、平衡二叉树、红黑树、B树、B+树及LSM树,分析了它们的特性、应用场景及优缺点。特别关注了B+树和LSM树在文件系统和数据库索引中的应用。

转载自:https://blog.youkuaiyun.com/laiwenqiang/article/details/40893157
    https://www.cnblogs.com/bonelee/p/6244810.html

二叉查找树

  二叉查找树(BST)又叫二叉排序树,二叉搜索树。二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:

  1. 若左子树不空,则左子树上所有结点的值均小于它的根节点的值;
  2. 若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  3. 左、右子树也分别为二叉排序树;
  4. 没有键值相等的节点。

  平均情况下查找时间复杂度为O(logn);最坏情况则是退化成一个链表,查找时间复杂度为O(n)。

  

平衡二叉树

  平衡二叉查找树,又称 AVL树。 它除了具备二叉查找树的基本特征之外,还具有一个非常重要的特点:它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值(平衡因子)不超过1。也就是说AVL树每个节点的平衡因子只可能是-1、0和1(左子树高度减去右子树高度)。
  平衡二叉树的优势在于不会出现普通二叉查找树的最差情况。二叉平衡树的严格平衡策略是以牺牲插入、删除的代价,换来了稳定的O(logN) 查找时间复杂度。

  但是平衡二叉树也有缺陷:
  (1)为了保证严格平衡(|bf|<=1),每个插入和删除操作的代价也随之增加。红黑树将解决这个问题。
  (2)所有二叉查找树结构的查找代价都与树高是紧密相关的,能否通过减少树高来进一步降低查找代价呢?这个是可以通过多路查找树的结构来做到这一点。
  (3) 在大数据量查找环境下(比如说系统磁盘里的文件目录,数据库中的记录查询等),所有的二叉查找树结构都不合适。如此大规模的数据量(几G数据),全部组织成平衡二叉树放在内存中是不可能做到的。那么把这棵树放在磁盘中吧?问题就来了:假如构造的平衡二叉树深度有1W层,那么从根节点出发到叶子节点很可能就需要1W次的硬盘IO读写。硬盘的机械部件读写数据的速度是远远赶不上内存。 查找效率在IO读写过程中将会付出巨大的代价。在大规模数据查询这样一个实际应用背景下,平衡二叉树的效率就很成问题了。

  

红黑树

  红黑树(red-black tree) 是一棵满足下述性质的二叉查找树:

  1. 每一个结点要么是红色,要么是黑色。
  2. 根结点是黑色的。
  3. 所有叶子结点都是黑色的(实际上都是Null指针,下图用NIL表示)。叶子结点不包含任何关键字信息,所有查询关键字都在非终结点上。
  4. 每个红色结点的两个子节点必须是黑色的。从每个叶子到根的所有路径上不能有两个连续的红色结点。
  5. 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。

  查找效率最好情况下时间复杂度为O(logN),但在最坏情况下比AVL要差一些,但也远远好于二叉查找树。
  插入和删除操作改变树的平衡性的概率要远远小于AVL(RBT不是高度平衡的)。任何不平衡都会在3次旋转之内解决。这一点是AVL所不具备的。


  二叉树结构其查找的时间复杂度与树高相关。那么降低树高自然对查找效率是有所帮助的。另外还有一个比较实际的问题:对大数据存储的查询,平衡二叉树由于树深度过大而造成磁盘IO读写过于频繁,进而导致效率低下。那么如何减少树的深度,一个基本的想法就是:

  • 每个节点存储多个元素
  • 摒弃二叉树结构,采用多叉树

  

B树

  B树,又叫平衡多路查找树。一棵m阶的B树 (m叉树)的特性如下:

  1. 树中每个结点至多有m个孩子;
  2. 除根结点和叶子结点外,其它每个结点至少有[m/2]个孩子;
  3. 若根结点不是叶子结点,则至少有2个孩子;
  4. 所有叶子结点都出现在同一层,叶子结点不包含任何关键字信息(可以看做是外部接点或查询失败的接点,实际上这些结点不存在,指向这些结点的指针都为null);
  5. 每个非终端结点中包含有n个关键字信息: (n,P1,K1,P2,K2,P3,…,Kn,Pn)。其中,
      a. Ki (i=1…n)为关键字,且关键字按顺序排序Ki < K(i-1)。
      b. Pi为指向子树根的结点,且指针Pi指向的子树种所有结点的关键字均小于Ki,但都大于K(i-1)。
      c. 关键字的个数n必须满足: [m/2]-1 <= n <= m-1

  例如:下面就是一棵3阶B树:

B+树

  B+树是应文件系统所需而产生的一种B树的变形树。 一棵m阶的B+树和m阶的B树的差异在于:

  1. 有n棵子树的结点中含有n个关键字; (B树是n棵子树有n+1个关键字)
  2. 所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。 (B树的叶子节点并没有包括全部需要查找的信息)
  3. 所有的非叶子结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。 (B树的非叶子节点也包含需要查找的有效信息)
  4. B+树所有节点数据都能在叶子节点里找到,所以非叶子节点只需要存 索引范围和指向下一级索引(或者叶子节点)的地址 ,不需要存整行的数据,所以占用空间非常小,直到找到叶子节点才加载进来整行的数据。
  5. 叶子节点之间加了指针,形成了链表。(这里体现了B+树 select * 时的优势,B树需要做局部的中序遍历,可能要跨层访问。而B+树由于所有数据都在叶子结点,不用跨层,同时由于有链表结构,只需要找到首尾,通过链表就能把所有数据取出来了)

  例如:下面就是一棵3阶B+树。可以和B树做一个明显的对比。

  B+树的叶子结点包含了所有待查询关键字,而非叶子节点只是作为叶子结点中最大(最小)关键字的索引。因此B+树的非叶子结点没有文件内容所在物理存储的地址,而B树所有结点均有文件内容所在的磁盘物理地址(B树结构图中结点内部的小红方块)。 这个特点是B+树的一个重要优势所在。

B+树的优势所在

  为什么说B+树比B树更适合实际应用中操作系统的文件索引和数据库索引?
  1、B+树的磁盘读写代价更低
  磁盘是可以块存储的,也就是同一个磁道上同一盘块中的所有数据都可以一次全部读取。而B+树的内部结点并没有指向关键字具体信息的指针(比如文件内容的具体地址),因此其内部结点相对B树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。这样,一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。
  2、B+树的查询效率更加稳定。
  由于B+树非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须从根结点到叶子结点。所有关键字查询的路径长度相同,所以每一个数据的查询效率相当。

LSM树

  B+树最大的性能问题是会产生大量的随机IO,随着新数据的写入,叶子节点会慢慢分裂,逻辑上连续的叶子节点在物理上往往不连续,甚至分离的很远,但做范围查询时,会产生大量读随机IO。为了克服B+树的弱点,HBase引入了LSM树的概念,即Log-Structured Merge-Trees。
  为了更好的说明LSM树的原理,下面举个比较极端的例子:现在假设有1000个节点的随机key,对于磁盘来说,肯定是把这1000个节点依次写入磁盘最快,但是这样一来,读就悲剧了,因为key在磁盘中完全无序,每次读取都要全扫描;那么,为了让读性能尽量高,数据在磁盘中必须得有序,这就是B+树的原理,但是写就悲剧了,因为会产生大量的随机IO,磁盘寻道速度跟不上。

  LSM树本质上就是在读写之间取得平衡,和B+树相比,它牺牲了部分读性能,用来大幅提高写性能。
  LSM树的原理是把一颗大树拆分成N棵小树, 它首先写入到内存中,在内存中构建一颗有序小树,随着小树越来越大,内存的小树会flush到磁盘上,磁盘中的树定期可以做merge操作,合并成一棵大树,以优化读性能。当读时,需要合并磁盘中历史数据和内存中最近修改操作,由于不知道数据在哪棵小树上,因此必须遍历所有的小树,但在每颗小树内部数据是有序的。

  以上就是LSM树最本质的原理。
  1. LSM引入了WAL,为什么要有WAL(Write Ahead Log)?因为数据是先写到内存中,如果断电,内存中的数据会丢失,因此为了保护内存中的数据,需要在磁盘上先记录logfile,当内存中的数据flush到磁盘上时,就可以抛弃相应的Logfile。
  2. 什么是memstore, storefile?很简单,上面说过,LSM树就是一堆小树,在内存中的小树即memstore,每次flush,内存中的memstore变成磁盘上一个新的storefile。
  3. 为什么会有compact?很简单,随着小树越来越多,读的性能会越来越差,因此需要在适当的时候,对磁盘中的小树进行merge,多棵小树变成一颗大树。

  LSM弄了很多个小的有序结构,比如每m个数据,在内存里排序一次,下m个数据,再排序一次……这样依次做下去,就可以获得N/m个有序的小的有序结构。
  在查询的时候,因为不知道这个数据到底是在哪里,所以就从最新的一个小的有序结构里做二分查找,找得到就返回,找不到就继续找下一个小有序结构,一直到找到为止。
  很容易可以看出,这样的模式,读取的时间复杂度是(N/m)*log2N 。读取效率是会下降的。
  这就是最本来意义上的LSM tree的思路。那么这样做,性能还是比较慢的,于是需要再做些事情来提升,怎么做才好呢?

LSM Tree优化方式

  a、Bloom filter: 就是个带随即概率的bitmap,可以快速的告诉你,某一个小的有序结构里有没有指定的那个数据的。于是就可以不用二分查找,而只需简单的计算几次就能知道数据是否在某个小集合里啦。效率得到了提升,但付出的是空间代价。
  b、compact:小树合并为大树。因为小树性能有问题,所以要有个进程不断地将小树合并到大树上,这样大部分的老数据查询也可以直接使用log2N的方式找到,不需要再进行(N/m)*log2n的查询了

B+树与LSM树比较

  1. B+树的特点决定了能够对主键进行高效的查找和删除,B+树能够提供高效的的范围扫描功能得益于相互连接且按主键有序,扫描时避免了耗时的遍历操作。
  LSM树在查找时先查找内存的存储,如果在内存中未命中就去磁盘文件中查找文件,找到key之后返回最新的版本。
  B树和LSM树最主要的区别在于他们的结构和如何利用硬件,特别是磁盘。

  2. 在没有太多的修改时,B+树表现得很好,因为修改要求执行高代价的优化操作以保证查询能在有限的时间内完成。LSM以磁盘传输速率工作,并能较好地扩展以处理大量数据,他们使用日志文件和内存存储来将随机写转换成顺序写,因此也能够保证稳定的数据插入速率。由于读写分离,两个操作也不存在冲突的问题。

  3. LSM树的主要目标是快速的建立索引,B树是建立索引的通用技术,但是在大并发插入数据的情况下,B树需要大量的随机IO,这些随机IO严重影响索引建立速度。LSM通过磁盘序列写,来达到最优的写性能,因为这个降低了磁盘的寻道次数,一次IO可以写入多个索引块。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值