B树、B+树、红黑树、AVL树

B树、B+树、红黑树、AVL树

定义及概念

B树

二叉树的深度较大,在查找时会造成I/O读写频繁,查询效率低下,所以引入了多叉树的结构,也就是B树。关于B树的由来,这里阐述了B-tree名字来源以及相关的开源地址。
阶为M的B树具有以下性质:

1、根节点在不为叶子节点的情况下儿子数为 2 ~ M
2、除根结点以外的非叶子结点的儿子数为 M/2(向上取整) ~ M
3、拥有 K 个孩子的非叶子节点包含 k-1 个keys(关键字),且递增排列
4、所有叶子结点在同一层,即深度相同

(叶节点可以看成是一种外部节点,不包含任何关键字信息)
  在B-树中,每个结点中关键字从小到大排列,并且当该结点的孩子是非叶子结点时,该k-1个关键字正好是k个孩子包含的关键字的值域的分划。
  因为叶子结点不包含关键字,所以可以把叶子结点看成在树里实际上并不存在外部结点,指向这些外部结点的指针为空,叶子结点的数目正好等于树中所包含的关键字总个数加1。
《B树、B+树、红黑树、AVL树》

B+树

B+ 树通常用于数据库和操作系统的文件系统中。特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+ 树元素自底向上插入。
B+树是B-树的变体,也是一种多路搜索树,其定义基本与B-树相同,不同如下:

1、拥有 K 个孩子的非叶子节点包含 k 个keys(关键字),且递增排列。每个关键字不保存数据,只用来索引。
2、所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
3、所有的非叶子结点可以看成是索引部分,结点中仅含有其子树(根结点)中最大(或最小)关键字
4、非叶子结点的子树指针P[i],指向关键字值属于[ K[i], K[i+1] )的子树
5、为所有叶子结点增加一个链指针

《B树、B+树、红黑树、AVL树》

红黑树

一棵二叉树如果满足下面的红黑性质,则为一棵红黑树:

1、每个结点或是红的,或是黑的。
2、根结点是黑的。
3、每个叶结点 (NIL) 是黑的。
4、如果一个结点是红的,则它的两个儿子都是黑的。
5、对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。

《B树、B+树、红黑树、AVL树》

红黑树引入了“颜色”的概念。引入“颜色”的目的在于使得红黑树的平衡条件得以简化。正如著名的密码学专家Bruce Schneier所说的那样,“Being Partly balanced can be good enough”,红黑树并不追求“完全平衡”——它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能。

关于红黑树的插入和删除可以自行查阅一些资料,因为不常用到,就不深究了。我扫了一下,发现这个还不错。

AVL树

关于AVL树,之前写过一篇文章《数据结构——AVL树》,可以直接看这里,就不再熬诉。
定义:
1、左子树和右子树都是AVL树
2、左子树和右子树的高度差不超过1 ,|HL-HR|<=1
性质:

1、一棵n个结点的AVL树的其高度保持在0(log2(n)),不会超过3/2log2(n+1)
2、一棵n个结点的AVL树的平均搜索长度保持在0(log2(n)).
3、一棵n个结点的AVL树删除一个结点做平衡化旋转所需要的时间为0(log2(n)).

B树和B+树的区别

B/B+树用在磁盘文件组织、数据索引和数据库索引中。其中B+树比B 树更适合实际应用中操作系统的文件索引和数据库索引,因为:
1、B+树的磁盘读写代价更低
B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。

举个例子,假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes,一个关键字具体信息指针2bytes。一棵9阶B-tree(一个结点最多8个关键字)的内部结点需要2个盘快。而B+ 树内部结点只需要1个盘快。当需要把内部结点读入内存中的时候,B 树就比B+ 树多一次盘块查找时间(在磁盘中就是盘片旋转的时间)。

2、B+-tree的查询效率更加稳定
由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

3、B树在元素遍历的时候效率较低
B+树只要遍历叶子节点就可以实现整棵树的遍历。在数据库中基于范围的查询相对频繁,所以此时B+树优于B树。

红黑树的应用及和B树区别

应用:

1、广泛用在C++的STL中。map和set都是用红黑树实现的。
2、著名的linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块
3、epoll在内核中的实现,用红黑树管理事件块
4、nginx中,用红黑树管理timer等
5、Java的TreeMap实现
等等

和B树比较
  一言而知就是树的深度较高,在磁盘I/O方面的表现不如B树。
  要获取磁盘上数据,必须先通过磁盘移动臂移动到数据所在的柱面,然后找到指定盘面,接着旋转盘面找到数据所在的磁道,最后对数据进行读写。磁盘IO代价主要花费在查找所需的柱面上,树的深度过大会造成磁盘IO频繁读写。根据磁盘查找存取的次数往往由树的高度所决定。
  所以,在大规模数据存储的时候,红黑树往往出现由于树的深度过大而造成磁盘IO读写过于频繁,进而导致效率低下。在这方面,B树表现相对优异,B树可以有多个子女,从几十到上千,可以降低树的高度。

AVL树和红黑树

红黑树的算法时间复杂度和AVL相同,但统计性能比AVL树更高。

1、红黑树和AVL树都能够以O(log2 n)的时间复杂度进行搜索、插入、删除操作。
2、由于设计,红黑树的任何不平衡都会在三次旋转之内解决。AVL树增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。

查找方面:
  红黑树的性质(最长路径长度不超过最短路径长度的2倍),其查找代价基本维持在O(logN)左右,但在最差情况下(最长路径是最短路径的2倍少1),比AVL要略逊色一点。
  AVL是严格平衡的二叉查找树(平衡因子不超过1)。查找过程中不会出现最差情况的单支树。因此查找效率最好,最坏情况都是O(logN)数量级的。

所以,综上:
  AVL比RBtree更加平衡,但是AVL的插入和删除会带来大量的旋转。 所以如果插入和删除比较多的情况,应该使用RBtree, 如果查询操作比较多,应该使用AVL

AVL是一种高度平衡的二叉树,维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应用不多,更多的地方是用追求局部而不是非常严格整体平衡的红黑树。当然,如果场景中对插入删除不频繁,只是对查找特别有要求,AVL还是优于红黑的。

B树(M阶)的插入和删除

注意点:
1、非叶子节点的孩子个数超过 M-1 时要分裂,分裂时,将中间的key向上移植父节点。
2、小于 M/2 – 1(向上取整) 时要合并,将最左边或者最右边的key向上移至父节点。

实例(这里 M = 5),所以 [M/2] – 1 = 2, M – 1 = 4

插入

1、初始状态
《B树、B+树、红黑树、AVL树》

2、插入E,K,Q
不需要任何分裂操作
《B树、B+树、红黑树、AVL树》

3、插入M
需要一次分裂,注意M恰好是中间关键字元素,以致向上移到父节点中
《B树、B+树、红黑树、AVL树》

4、插入F,W,L,T
不需要任何分裂操作
《B树、B+树、红黑树、AVL树》

5、插入Z时
最右的叶子结点空间满了,需要进行分裂操作,中间元素T上移到父节点中,注意通过上移中间元素,树最终还是保持平衡,分裂结果的结点存在2个关键字元素
《B树、B+树、红黑树、AVL树》

6、插入D,P,R,X,Y
最左边的叶子结点被分裂,D恰好也是中间元素,上移到父节点中,然后字母P,R,X,Y陆续插入不需要任何分裂操作(别忘了,树中至多5个孩子)
《B树、B+树、红黑树、AVL树》

7、最后,插入S
含有N,P,Q,R的结点需要分裂,把中间元素Q上移到父节点中,但是情况来了,父节点中空间已经满了,所以也要进行分裂,将父节点中的中间元素M上移到新形成的根结点中,注意以前在父节点中的第三个指针在修改后包括D和G节点中。
《B树、B+树、红黑树、AVL树》

插入操作完成。

删除

1、初始状态
《B树、B+树、红黑树、AVL树》

2、删除元素H
首先查找H,H在一个叶子结点中,且该叶子结点元素数目3 > 2
移动K至原来H的位置,移动L至K的位置(也就是结点中删除元素后面的元素向前移动)
《B树、B+树、红黑树、AVL树》

3、删除T
在中间结点中找到T,此时删了T后该节点关键字个数 1 < 2
将W上移到T的位置,然后将原包含W的孩子结点中的W进行删除,这里恰好删除W后,该叶子结点中元素个数 > 2,无需进行合并操作
《B树、B+树、红黑树、AVL树》

4、删除R
R所在叶子结点中元素数目为2,删除导致只有1个元素

如果其某个相邻兄弟结点中比较丰满(元素个数 > [M/2] – 1),则可以向父结点借一个元素,然后将最丰满的相邻兄弟结点中上移最后或最前一个元素到父节点中

在这个实例中,右相邻兄弟结点中比较丰满(3 > 2),所以先向父节点借一个元素W下移到该叶子结点中,代替原来S的位置,S前移;然后X在相邻右兄弟结点中上移到父结点中,最后在相邻右兄弟结点中删除X,后面元素前移。
《B树、B+树、红黑树、AVL树》

5、删除E
删除后会导致很多问题,因为E所在的结点数目刚好达标,刚好满足最小元素个数([M/2] – 1),而相邻的兄弟结点也是同样的情况,删除一个元素都不能满足条件
所以需要该节点与某相邻兄弟结点进行合并操作:

首先,移动父结点中的元素(该元素在两个需要合并的两个结点元素之间)下移到其子结点中;
然后将这两个结点进行合并成一个结点。

即,将父节点中的元素D下移到已经删除E而只有F的结点中,然后将含有D和F的结点和含有A,C的相邻兄弟结点进行合并成一个结点。
《B树、B+树、红黑树、AVL树》
此时G所在节点只有一个元素,不行。
此时该结点的相邻兄弟又不丰满,只能与兄弟结点进行合并成一个结点,而根结点中的唯一元素M下移到子结点,这样,树的高度减少一层。
《B树、B+树、红黑树、AVL树》

以上图片来源这里

    原文作者:算法小白
    原文地址: https://blog.csdn.net/xiaqunfeng123/article/details/52534468
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞