-
目录结构
-
二叉树
-
二叉排序树
-
平衡二叉树
-
红黑树
-
B树
-
B+树
-
B*树
各种各样的树(上):http://www.cnblogs.com/cuglzf/p/8657698.html
在上一篇文章中,介绍过了二叉树、二叉排序树和平衡二叉树,这篇文章介绍红黑树,红黑树的删除操作需要判断的情况太多,因此单独一篇来介绍。
4.红黑树
4.1 为什么会出现红黑树
红黑树和平衡二叉树都是对二叉树的改进,同样都是尽可能降低树的高度,从而提高查询的效率。虽然平衡二叉树的查询效率非常高,但是调整平衡二叉树也要花费非常多的时间,在实时应用的查询时平衡二叉树不适合,所以就出现了红黑树。
4.2 红黑树的性质
一棵红黑树是具有如下性质的二叉排序树:
1)每个结点的颜色只能是红色或黑色。
2)根结点是黑色的。
3)每个叶子结点都带有两个空的黑色节点(NIL),如果节点有一个孩子节点,则另外一个分支上也要带一个黑色节点(NIL)。
4)如果一个结点是红色的,那么它的两个子结点都是黑色的。
5)对每个结点,从该结点到其所有子代叶子节点的路径上均包含相同数目的黑色结点。
4.3 红黑树的操作
对于红黑树的查询,不用再说了,就是二叉树的查询,但是红黑树的插入和删除比平衡二叉树要复杂一些,下面重点看看红黑树的插入和删除操作。
4.3.1 插入元素
首先需要明白的是,不管是插入还是删除的操作,最终的红黑树都要满足上面5条性质,因此在插入的过程中尽可能的保证少破坏它的性质,所以任何一个新插入的节点的颜色必须是红色(保证性质5)。与平衡二叉树不同的是,红黑树的插入还要考虑它的父节点、叔叔节点、爷爷节点,下面就一种情况一种情况看。
为了清楚地表示插入操作以下在结点中使用“新”字表示一个新插入的结点;使用“父”字表示新插入点的父结点;使用“叔”字表示“父”结点的兄弟结点;使用“祖”字表示“父”结点的父结点。
1.插入节点的父节点是黑色
如上图所示,如果插入节点的父结点为黑色结点,那么插入一个红色节点将不会影响红黑树的平衡,此时插入操作完成。
2.插入节点的父节点是红色
如果插入节点的父结点为红色,这时就需要调整红黑树以保证整棵树满足它的性质。
如上图所示,由于父结点为红色,此时可以判定,爷爷结点必定为黑色(爷爷和父节点要满足性质4)。这时需要根据叔叔结点的颜色来决定做什么样的操作,青色结点表示颜色未知。由于有可能需要根结点到新点的路径上进行多次旋转操作,而每次进行不平衡判断的起始点(我们可将其视为新点)都不一样。所以我们在此使用一个蓝色箭头指向这个起始点,并称之为判定点,,下面就讨论当叔叔节点的颜色是红色或黑色时的情况。
1)叔叔节点是红色
当叔叔结点为红色时,如上图左侧所示,无需进行旋转操作,只要将父和叔结点变为黑色,将爷爷结点变为红色即可(性质4)。但由于爷爷结点的父结点有可能为红色,从而违反红黑树性质。此时必须将祖父结点作为新的判定点继续向上进行平衡操作。
2)叔叔节点是黑色
当叔父结点为黑色时,需要进行旋转,有下面四种情况:
上面四中情形是不是很熟悉,是的,就是前面平衡二叉树插入时的四种旋转,如果这里不理解的话可以先去看看平衡二叉树的插入。当旋转完成后,新的旋转根全部为黑色,此时不需要再向上回溯进行平衡操作,插入操作完成。
上面已经将插入的所有情况讨论了,插入的时候判断不同的情况进行不同的操作即可,具体的代码我就不粘了,因为这篇文章的目的是掌握原理,具体的代码可以根据上面说的情况自己尝试写一下,只要思路清晰,编码实现就很容易了。
4.3.2 删除元素
红黑树让人觉得难以理解的地方在于它的删除节点后调整的操作,比平衡二叉树的删除复杂多了,需要判断的情况很多。
和平衡二叉树的删除一样,都是要找到真正的删除节点把它删除,根据二叉排序树的删除规则,找到该节点(要删除节点)的左子树的最大值或右子树的最小值,用该值替换掉要删除的值,然后删除该节点,因此问题就变成了删除这个节点的问题。那么找到的这个节点必定只有一个孩子节点或者没有孩子节点。
下面根据真正要删除节点的颜色来分情况讨论:
在以下讨论中,我们使用蓝色箭头表示真正的删除点,它也是旋转操作过程中的第一个判定点;真正的删除点使用“旧”标注,旧点所在位置将被它的的孩子结点所取代(最多只有一个孩子),我们使用“新”表示旧点的孩子结点。
1.删除节点为红色
若旧点为红色结点,则它必定是叶子节点,直接删除即可。这里可能不太好理解的是为什么要删除的红色节点一定是叶子节点?这里来说明一下,树中所有节点有三种情况:1)节点无孩子节点(叶子节点);2)节点只有一个孩子;3)节点有两个孩子;
假如要删除的节点就是叶子节点,那么就直接删除了,符合前面说的吧
假如要删除的节点只有一个孩子节点,且这个节点是红色的(不明白的可以去看上面插入操作,每次插入完成后还要进行调整,所以不存在这种情况)
假如要删除的节点有两个孩子节点,这种情况会先找到真正要删除的节点,因此又转化成了前面两种情况。
综上,这个红色节点必定是叶子节点。
2.删除节点为黑色孩子节点为红色
当旧节点为黑色结点,新节点为红色结点时,将新节点取代旧点位置后,将新节点染成黑色即可。
3.删除节点和孩子节点都为黑色
当旧点和新点都为黑色时(新点为空结点时,亦属于这种情况),情况比较复杂,需要根据旧点兄弟结点的颜色来决定进行什么样的操作。我们使用“兄”来表示旧点的兄弟结点。这里可分为红兄和黑兄两种情况:
3.1兄弟节点为红色
由于兄弟结点为红色,所以父结点必定为黑色,而旧点被删除后,新点取代了它的位置。下图演示了两种可能的情况:
红兄的情况需要进行RR或LL型旋转,然后将父结点染成红色,兄结点染成黑色。然后重新以新点为判定点进行平衡操作。我们可以观察到,旋转操作完成后,判定点没有向上回溯,而是降低了一层,此时变成了黑兄的情况。
3.2兄弟节点为黑色
黑兄的情况最为复杂,需要根据黑兄孩子结点(这里用“侄”表示)和父亲结点的颜色来决定做什么样的操作。
1)两黑侄红父
这种情况比较简单,只需将父结点变为黑色,兄结点变为黑色,新结点变为黑色即可,删除操作到此结束。
2)两黑侄黑父
此时将父结点染成新结点的颜色,新结点染成黑色,兄结点染成红色即可。当新结点为红色时,父结点被染成红色,此时需要以父结点为判定点继续向上进行平衡操作。
3)红侄
黑兄红侄有以下四种情形,下面分别进行图示:
以上所有删除情况讨论完了,真心不容易,需要进行讨论的情况太多了,在这里感谢原创作者,情况讨论的非常清楚。
4.4 红黑树的应用
红黑树操作与平衡二叉树一样,最坏最好和平均都是O(log2n)。
在C++STL中的map、set用的就是红黑树,Linux进程调度和内存管理中用到了红黑树。
参考:https://blog.csdn.net/zhangtian6691844/article/details/51722214