二叉树,B-树,B+树,红黑树,LSM树,AVL树,堆

树可以说是某些人最喜欢的数据结构。

二叉树的概念

在计算机领域,二叉树是每个节点最多有两个子树的结构。通常子树被称为左子树和右子树。

二叉树的特例:

满二叉树
满二叉树是完全二叉树的特例。

  • 所有叶节点必须在同一层上
  • 除了叶子节点的所有节点都有两个子节点

完全二叉树
完全二叉树可以看成是满二叉树的最后一行右侧部分连续缺失。(最大堆和最小堆就是完全二叉树)

平衡二叉树

对于任何一个节点,左树和右树的绝对值差不超过1

二叉树遍历

二叉树的遍历主要有深度优先和广度优先两种。深度优先又包含前序遍历,中序遍历和后序遍历。个有个的应用场景

以如下例子说明
《二叉树,B-树,B+树,红黑树,LSM树,AVL树,堆》

前序遍历

首先遍历根节点,然后是根节点的左侧节点,然后继续遍历左侧节点的左侧节点,…一直到最左边的叶节点。然后后退一层,访问右节点,然后继续后退一层。
简单来说就是先根节点,然后左节点,最后右节点。
A B C D E F G
除了采用递归的办法,前序遍历还可以使用栈实现。

  1. 访问结点P,并将结点P入栈;
  2. 判断结点P的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点P,循环至1);若不为空,则将P的左孩子置为当前的结点P;
  3. 直到P为NULL并且栈为空,则遍历结束。

中序遍历

从根节点开始找,如果根节点有左侧节点,就将寻找的指针向左侧移动。
首先是左节点,然后是根节点,最后是右节点
其实反过来更好理解。如果有下列两者之一情况,任何一个右节点都不能被遍历

  1. 对应父节点没有被遍历
  2. 对应的左节点没有被遍历
    C B D A E G F
    注意,如果有多个节点符合被遍历的条件,离最后一个遍历点最近的点先被遍历。

非递归实现方法如下

  1. 对于节点P,若其左孩子不为空,则将P入栈并将P的左孩子置为当前的P,然后对当前结点P再进行相同的处理;
  2. 若其左孩子为空,则取栈顶元素并进行出栈操作,访问该栈顶结点,然后将当前的P置为栈顶结点的右孩子;
  3. 直到P为NULL并且栈为空则遍历结束。

二叉查找树的中序遍历就是一个递增序列

后序遍历

首先是左节点,然后是右节点,最后是根节点
和上面的道理类似。
C D B G F E A

后续遍历的非递归实现是最复杂的。
对于任一结点P,将其入栈,然后沿其左子树一直往下搜索,直到搜索到没有左孩子的结点,此时该结点出现在栈顶,但是此时不能将其出栈并访问, 因此其右孩子还为被访问。所以接下来按照相同的规则对其右子树进行相同的处理,当访问完其右孩子时,该结点又出现在栈顶,此时可以将其出栈并访问。这样就 保证了正确的访问顺序。可以看出,在这个过程中,每个结点都两次出现在栈顶,只有在第二次出现在栈顶时,才能访问它。因此需要多设置一个变量标识该结点是 否是第一次出现在栈顶。

后序遍历保证在操作某个节点时,肯定已经操作过其两个子节点,可以用于二叉树的节点删除

广度优先遍历

广度优先遍历也被成为层次遍历,使用队列实现。

  • 从队列头取出节点P,访问节点P,如果其有子节点,将左节点和右节点先后入队。
    广度优先遍历常被用于序列化二叉树,优点是不需要读取完整序列就可以开始重构过程。

树的旋转

E节点左旋。常见的例子比如说要在下面的S的左子节点下添加一个新节点作为左子节点的子节点,就会导致下图的二叉树失去平衡,左旋以下就可以解决。
《二叉树,B-树,B+树,红黑树,LSM树,AVL树,堆》
类似的我们有右旋

《二叉树,B-树,B+树,红黑树,LSM树,AVL树,堆》

B-树

首先明确一点,B-树不读做B减树。中间的是连字符。这个B代表平衡的意思,但是B-树不是平衡二叉树。

B-tree被成为多路查找树,相对于二叉树,B-tree更加矮胖。这样做的原因是因为物理实现磁盘IO限制,矮胖有助于减少磁盘读取索引的io次数。

B+树

B+树在B-树的基础上,限制所有数据必须存储在叶节点,这样之后,就可以为叶节点添加一个链表。这让我门按照索引进行快速范围读取更加容易。

  • mysql InnoDB引擎使用B+树作为默认索引

《二叉树,B-树,B+树,红黑树,LSM树,AVL树,堆》

B*树

在非根非叶节点增加了只想相邻兄弟节点的指针。
这是为了树的分裂的需要。当一个节点满后,B+树会访问自己的父节点,然后在父节点上新建一个与自己相邻子节点,最后将自己的一半数据转移到新节点上。
B*树则不会优先创在新节点。利用自己指向兄弟节点的指针,他会首先尝试将自己的数据转移到自己的兄弟节点上。于是,他对树空间利用率较高。

  • B*是oracle中最长用到的索引

《二叉树,B-树,B+树,红黑树,LSM树,AVL树,堆》

二叉搜索树

(1)若左子树非空,则左子树的所有节点小于他的根节点
(2)若右子树非空,则右子树的所有节点大于他的根节点
(3)左右子树也都是二叉排序树
显然排序二叉树可以用作排序,也可以用作快速查找和插入。一般来讲排序算法的时间复杂度为Nlog2N
查找算法的时间复杂度为log2N

最大堆和最小堆

最大堆最小堆是完全二叉树,所以底层可以采用数组实现。
最大堆和最小堆常用语实现优先队列。
最大堆和最小堆用于堆排序。

插入一个数据,就是把这个数据放在最底层的最后一个(也就是数组末尾),然后判断能不能上浮。也就是和它的父节点比较。他的父节点的位置就在他的数组坐标的1/2处。判断到后直接交换即可。

AVL树

自平衡二叉查找树。查找、插入和删除在平均和最坏情况下都是O(logn)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树。

红黑树(RBTree)

红黑树放弃追求绝对的平衡。因为放弃了追求绝对平衡,所以查找效率相对较低。但是,在处理插入带来的失衡的需要进行rebalance上,展现了优点,最多只需要三次旋转就能恢复平衡,时间复杂度为o(1),而AVL树则需要logN
《二叉树,B-树,B+树,红黑树,LSM树,AVL树,堆》

红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

  1. 节点是红色或黑色。
  2. 根节点是黑色。
  3. 每个叶节点(NIL节点,空节点)是黑色的。
  4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
  5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
    适用于数据量小的需要排序的数据集(树的高度比较高,数据量过大需要频繁读取磁盘)
  • linux内部epoll的实现
  • java中的TreeMap和TreeSet

AVL树和RBT的比较

  1. 如果插入一个node引起了树的不平衡,AVL和RB-Tree都是最多只需要2次旋转操作,即两者都是O(1);但是在删除node引起树的不平衡时,最坏情况下,AVL需要维护从被删node到root这条路径上所有node的平衡性,因此需要旋转的量级O(logN),而RB-Tree最多只需3次旋转,只需要O(1)的复杂度。

  2. 其次,AVL的结构相较RB-Tree来说更为平衡,在插入和删除node更容易引起Tree的unbalance,因此在大量数据需要插入或者删除时,AVL需要rebalance的频率会更高。因此,RB-Tree在需要大量插入和删除node的场景下,效率更高。自然,由于AVL高度平衡,因此AVL的search效率更高。

  3. map的实现只是折衷了两者在search、insert以及delete下的效率。总体来说,RB-tree的统计性能是高于AVL的。

LSM树

日志结构的合并树(LSM-tree)是一种基于硬盘的数据结构,与B-tree相比,能显著地减少硬盘磁盘臂的开销,并能在较长的时间提供对文件的高速插入(删除)。然而LSM-tree在某些情况下,特别是在查询需要快速响应时性能不佳。通常LSM-tree适用于索引插入比检索更频繁的应用系统。
HBase和RocksDB都采用了这种实际,在牺牲读性能的前提下增加了写入性能(一旦读操作不命中内存就会导致对内存的读写)。
LSM树原理把一棵大树拆分成N棵小树,它首先写入内存中,随着小树越来越大,内存中的小树会flush到磁盘中,磁盘中的树定期可以做merge操作,合并成一棵大树,以优化读性能。
《二叉树,B-树,B+树,红黑树,LSM树,AVL树,堆》

总结

  • 一般数据存储引擎有三种:
  1. Hash存储引擎
  2. B-/B+/B*树
  3. LSM树
    原文作者:AVL树
    原文地址: https://blog.csdn.net/define_us/article/details/52097681
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞