AVL 树与红黑树

为什么要用平衡二叉树?

因为二叉树没有平衡约束,当输入数据比较有序时,将导致树的高度极端不平衡,最严重的时候退化为一个类似链表的单路径树(比如输入完全有序数组)。

如:输入1,2,3,4。

1
 \
  2
   \
     3
      \
       4

当然,更平衡的代价是插入和删除变的更复杂。

AVL 树

AVL 树本质还是一种二叉树,只不过带有更严格的平衡条件:每个结点的左右子树的高度只差的绝对值(平衡因子)最多为1。

平衡因子 = 左子树高度 – 右子树高度

       5
      / \
     3   6
    / \   \
   2   4   7
  /
 1

红黑树

红黑树本质上也是一种二叉树,但在每一个结点上增加了一个存储位来表示结点的颜色。一般而言,满足下面性质的树可以称为红黑树:

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

如下图所示,既是一颗红黑树:

《AVL 树与红黑树》

AVL 树高度

AVL 树:最小高度 h=lg(n+1) ,满树,显然,不再证明

AVL 树:最大高度 h=1.44lg(n+2)0.328

证明:

假设 Ni 表示高度为i的 AVL 树的最小结点个数。

  1. N0=0 (AVL 树高度为0,空树)
  2. N1=1 (AVL 树高度为1,只有一个根结点)
  3. N2=2 (AVL 树高度为2,只有一个根结点和一个叶子结点)

       2
      /
     1
    
  4. N3=4 (AVL 树高度为3,根节点的一个子树(不妨设为左子树)为 N2 树,另一个子树(不妨设为右子树)为 N1 树)

         3 
        / \ 
       2   4 
      /
     1
    
  5. N4=7 (AVL 树高度为3,根节点的一个子树(不妨设为左子树)为 N3 树,另一个子树(不妨设为右子树)为 N2 树)

           5
          / \
         3   6
        / \   \
       2   4   7
      /
     1
    

综上, Nn=Nn1+Nn2+1

Nn=Fn+21

所以: Fn+2=Fn+1+Fn

其中, F2=1 F3=2 ,如果令 F0=0 F1=1 ,并不会改变数列中的其他值,通过这种转换,此数列变为典型的 Fibonacci 数列,可求其通项公式:

Fn=ϕn515ϕn

Nn=ϕn+2515ϕn+21

其中 ϕ=5+12

事实上,当n较大时,上述式子的第二项相对第一项较小,可以忽略,简写为:

Fn=ϕn5

Nn=ϕn+251

但考虑到求的是 Nn 固定时,n 的最大值,也就是求 Fn 的下限,而 1<15ϕn+2<1 ,所以上述式子又可写为:

Fn=ϕn51

Nn=ϕn+252

所以,有 Nn 个结点的 AVL 树的最大高度为:

n=logϕ(5(Nn+2))2=log2(5(Nn+2))log2(ϕ)2=logϕ2log2(5(Nn+2))2=1.44log2(Nn+2)0.328

结论参考《计算机程序设计》第3卷 6.2.3 以及《维基百科:AVT Tree》

Fibonacci 数列通项的推导过程可参考《编程之美》2.9节,或自行百度。

红黑树高度

红黑树:最小高度 h=lg(n+1) ,满树,显然,不再证明。 红黑树:最大高度 h=2lg(n+1)

证明:

可参考《算法导论》第13章-红黑树的证明方法 或者参考《麻省理工-算法导论》公开课中的证明,将红黑树转换为2-3-4树,通过2-3-4树的特性间接证明。

一颗大小为N的红黑树中,根节点到任意结点的平均路径长度约为:

1.001lg(N)

参考《Algorithms》第4版3.3.2

AVL 树和红黑树高度

AVL 树的高度范围为:

[lg(n+1),1.44lg(n+2)0.328]

红黑树的高度范围为:

[lg(n+1),2lg(n+1)]

如果但从此看,AVL 树更适合查找,效率更高。

AVL 树插入

  1. 查找插入位置,复杂度为: O(lg(n))
  2. 追踪不平衡位置,最坏情况从叶子结点到根结点,复杂度为: O(lg(n))
  3. 最后经过一到两次旋转完成插入操作,复杂度为: O(lg(1))

但经验测试表明,除非 n 非常小,否则插入第 n 项所需要的查找次数平均为: 1.01lg(n)+0.1

参考《计算机程序设计》第3卷 6.2.3 以及《维基百科:AVT Tree》

AVL 树删除

  1. 查找删除位置,复杂度为: O(lg(n))
  2. 追踪不平衡位置,最坏情况从叶子结点到根结点,复杂度为: O(lg(n))
  3. 最后至多需要 O(lg(n)) 次旋转,因为要沿父节点一路调整,直到根结点。

参考《计算机程序设计》第3卷 6.2.3

红黑树插入

  1. 查找插入位置,复杂度为: O(lg(n))
  2. 经过一到两次旋转完成插入操作,复杂度为: O(lg(1))
  3. 插入操作影响了树的颜色,可以对其重新着色,做少量 O(lg(n)) 调整(重新着色的效率非常高)。

红黑树删除

  1. 查找删除位置,复杂度为: O(lg(n))
  2. 经过不超过三次旋转完成删除操作,复杂度为: O(lg(1))
  3. 删除和旋转操作影响了树的颜色,可以对其重新着色,做少量 O(lg(n)) 调整。

从此也可以看出,在删除操作中,AVL 树可能需要比红黑树更多的旋转操作。

AVL 树与红黑树区别

AVL 树是严格平衡二叉树,红黑树是非严格平衡二叉树,平衡条件比 AVL 树弱,它追求的是局部的平衡,而 AVL 树追求的是全局的平衡。

AVL 树用平衡因子来标识结点的高度,红黑树用颜色来标识。 空间占用方面:红黑树只需多余的1bit额外空间,而 AVL 树至少需要2bits。

但是考虑到内存的对齐问题,很多时候都额外分配4Bytes空间。

应用

在 C++ STL中,set、multiset和map、multiset都是用红黑树实现的。另外,linux的内核中也使用了红黑树。

结论

当输入数据完全随机时,不平衡二叉树有更大的优势;当输入数据比较随机(偶尔有序)时,红黑树更有优势,因为需要更少的平衡保持操作;当输入数据比较有序时,普通二叉树速度最慢,因为需要较多的查询操作(树的高度值最大),而 AVL 树性能最好,因为树的高度最低,需要更少的查询,其次是红黑树。

另外,使用父结点能带来更好的效率,因为可以更快的查询父节点,而不需遍历。当然,每个结点也需要额外的一个指针空间。

可参考《Performance Analysis of BSTs in System Software》中的实验。

AVL 树和红黑树的查找,插入和删除的复杂度都是 O(lg(n)) ,但是这个 O(lg(n)) 也是有差距的。网上很多说法是: AVL 树的删除操作复杂度为 O(lg(n)) ,而红黑树是 O(1) ,主要考虑的就是肯能进行的旋转操作的次数,从这方面考虑是对的,最也是红黑树可能性能更好的一个主要方面,但是不能忽略删除之前所需要的查找的复杂度 O(lg(n))

一个不完全准确的说法:红黑树的插入删除更快,但是查询可能会稍慢。所以,如果经常插入删除的化还是使用红黑树,这样的情况也比较多;而偶尔进行插入删除,经常进行查询操作,可能 AVL 树性能更优。

补充

对于 AVL 树和红黑树的插入删除操作没有做更多介绍,因为太复杂,外加能力有限(其实很多我也不懂)。另外,很多涉及统计特性的内容也未详细探讨,有兴趣可参考下列文献。

对于 AVL 树的更多数学内容可参考《计算机程序设计》第3卷 6.2.3

对于红黑树的更多详细内容可参考《Algorithms》第4版3.3.2

对于 AVL 树和红黑树的对比可参考《Performance Analysis of BSTs in System Software》中的大量实验

另外,对于红黑树也可参考《Left-Leaning Red-Black Trees》

文中可能存在诸多错误,望多多指正。

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