红黑树简介(Introduction to Red-black tree)

红黑树简介(Introduction to Red-black tree)

作者:Bluemapleman(tomqianmaple@outlook.com)

麻烦不吝star和fork本博文对应的github上的技术博客项目吧!谢谢你们的支持!

知识无价,写作辛苦,欢迎转载,但请注明出处,谢谢!

文章目录

前言:红黑树以操作复杂,但性能优异著称(增删查节点最坏情况的时间复杂度都是O(lgn))。Java的TreeSet和TreeMap都是基于红黑树实现的。红黑树属于BST(二叉查找树)的一种,但它相比BST多了平衡的(balanced)特征,平衡的含义是:“最坏情况下的树高也只是O(lgn)”。而平衡的特性,也就是红黑树能保证优异性能的根本所在。

定义

(可以先尝试了解2-3-4树的概念,2-3-4树也是一种自平衡的树,可以保证在O(lgn)内完成增删查操作,但是由于实现相对较为困难,所以在要求实现高性能的树时往往用性能相似的红黑树来替代。2-3-4树资料

而红黑树具备以下五个特征的二叉搜索树:

  • 每个节点要么是红色,要么是黑色
  • 根是黑色
  • 每个叶子节点(NIL)都是黑色
  • 不能父子节点同时为红色
  • 任意一条从根到叶子的路径上都有相同数目的黑色结点

《红黑树简介(Introduction to Red-black tree)》

浅色为红色,深色为黑色,图来自Introduction To Algorithm: Third Edition, Thomas et al. Page 309 [1]

NIL表示默认存在的黑色叶子节点,后面画图和分析时都可以忽略这些NIL结点。它们存在的意义主要在于满足红黑树的叶子节点颜色的要求。

红黑树的高度h有这么一条可以证明的特性:有n个keys的红黑树的高度h,小于等于2lg(n+1)。(证明可参看[1])

另外,就平衡这个特性来说,我们还可以将红黑树与AVL树做一个对比:

红黑树与AVL树的比较:(来自AVL树与红黑树(R-B树)的区别与联系

AVL是严格的平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多;

红黑树是用非严格的平衡来换取增删节点时候旋转次数的降低开销;

所以简单说,如果你的应用中,搜索的次数远远大于插入和删除,那么选择AVL树,

如果搜索,插入删除次数几乎差不多,应选择红黑树。即,有时仅为了排序(建立-遍历-删除),不查找或查找次数很少,R-B树合算一些。
--------------------- 
作者:码农的小梦想 
来源:CSDN 
原文:https://blog.csdn.net/zhangkunrun/article/details/38336543 

查找操作分析

红黑树的查找操作与BST树完全相同,用查找的值与根节点的key做比较,找到即返回,没找到就递归地在根节点的左子树或右子树里继续找即可。时间复杂度显然=树高=O(lgn)。

插入操作分析

红黑树的插入和标准的BST树的插入类似:我们首先通过不断地比较,找到新节点应当插入的叶子节点的位置,如果发现插入结点的key已存在,那么就不需要后续操作了。如果没有,我们就将新节点接在相应的位置上,并将新节点染成红色

这样做的潜在问题是:如果插入新节点位置的父亲节点也是个红色结点,那么这样做就会导致父子节点都是红色,这是不符合红黑树规定的。所以,红黑树完整的插入操作还包含一步:解决可能存在的“红-红”问题。

为了更好地解决“红红问题”,我们把新节点插入后,可能出现的所有情况分成以下四种:

  • case 0:父亲节点为黑色,搞定收工!
  • case 1:父亲和叔伯结点均为红色,则将爷爷结点(一定为黑色)染成红色,父亲和叔伯结点均染成黑色,换言之,做一个颜色翻转!之后,我们再看爷爷结点与爷爷结点的父亲节点是否有“红红问题”存在;
  • case 2:父亲为红色,叔伯不存在或叔伯为黑色,则若父亲和爷爷结点此时处在插入结点的不同侧,旋转一次;
  • case 3:父亲为红色,叔伯不存在或叔伯为黑色,则若父亲和爷爷结点此时处在插入结点的相同侧,旋转一次;

我们根据图来看一下这几种情况,并详细讲述一下相关的具体操作:

《红黑树简介(Introduction to Red-black tree)》

(1) 我们用变量z来指向我们新插入的节点,或者指向我们后续要处理的结点。可以看上图中,我们新插入的结点是4,按照BST的方式,判断它应该成为结点5的左子节点,于是我们插入4,并将4染成红色(a)。

(2) 然后,我们发现4的父亲4和叔伯8都是红色,符合case1,于是我们将爷爷结点和父亲叔伯结点的颜色互换,之后变量z指向爷爷结点7,因为我们已经保证爷爷结点以下的结点没有“红红问题”了(b)。

(3) 然而此时,我们又发现z指向的结点7的父亲2也是红色,然而叔伯14是黑色,并且父亲2和爷爷11不在同一侧,故符合case2,此时,我们先将z指向z本来的父亲2,然后我们针对现在的z做一次树的旋转(此处是左旋)(c)。

(4) 左旋完后,我们再看z,返现此时z的父亲7为红色,叔伯14为黑色,而父亲7和爷爷11在同一侧,故满足case3,于是我们针对z的爷爷结点11做一个旋转(此处是右旋)(d)。

(5) 最后,我们会发现z的父亲7跟着旋转到根节点,但是父亲本身是红色的,而红黑树要求根是黑色的,所以我们把父亲结点7染成黑色。

现在,这个红黑树就在插入新节点后依然满足所有红黑树的要求。这样,我们就彻底完成了红黑树的插入。

红黑树插入的伪代码:(先插入,后修复)

《红黑树简介(Introduction to Red-black tree)》
《红黑树简介(Introduction to Red-black tree)》

这里需要注意的一点是:从修复部分的伪代码也可以看出,当我们遇到case2并完成处理后,我们一定会紧接着遇到case3,而处理完case3后,我们一定就已经基本解决了所有的“红红问题”(可能还需要把根节点染黑)。而只有case1的情况是可能出现自我循环的,即需要多次翻转父亲叔伯与爷爷的颜色,而case1若不循环,我们还有可能再碰到case 0,2,3。了解这四个case之间的一个转换关系可以帮助我们分析插入操作的时间复杂度。

《红黑树简介(Introduction to Red-black tree)》

课堂上老师的板书。

  • 时间复杂度

由于具有n个节点的红黑树的高度是O(lgn),除开修复部分的插入操作显然只花费O(lgn)的时间,而在修复操作中,while loop修复“红红问题”的过程中,只有case1可能使while loop继续循环(case2会转到case3,case3代表结束),而每次case1循环会使得指针z指向指向更高两层的节点(仔细理解上面的修复过程),因此while loop循环的次数也最多只有O(lgn)。另外,case2和case3中的旋转操作也都是O(1)的时间复杂度。因此,红黑树插入操作的总时间复杂度为O(lgn)。

删除操作

开始的步骤类似BST中的删除操作(Hibbard Deletion):首先找到要删除的结点z:

  • 若该结点只有一个子节点,则直接用该子节点替换在待删除结点z的位置就好;

  • 若待删除结点有两个子节点(左子节点为l):

    • 若右子节点y作为根节点的子树没有左子树/左子节点,则直接用y替换在z的位置,然后l作为z的左子节点即可;
    • 则若右子节点r作为根节点还有左子树,则取该左子树中key最小的那个结点作为y,那么我们需要将r作为y的右子树,l作为y的左子树,用y替换在z的位置即可。

《红黑树简介(Introduction to Red-black tree)》

但是红黑树还需要除以上正常的BST删除步骤之外进行一些其它的调整,这些调整比较复杂,本文就不具体分析了。(我们老师也只讲到这里,但是给大家推荐一篇讲删除的博文

但记住红黑树的删除操作时间复杂度也是O(lgn)即可。

参考文献

[1] Introduction to Algorithms: Third Edition, Thomas et al.

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