一、红黑树(Red-Black Tree)是二叉搜索树(Binary Search Tree)的一种。二叉搜索树在最坏的情况下可能会变成一个链表(当所有节点按从小到大的顺序依次插入后)。这种低效产生的原因是树没有维持一定的平衡性,要提高搜索效率,就要想办法来维持树左边的平衡,也就是要尽时降低树的高度,可行的做法就是用一些策略在每次修改树的内容之后都调整树的结构,使之满足一定的平衡条件。其中一种满足一定平衡条件而且目前应用广泛的是红黑树。它可以在每一次插入或删除节点之后都会花O(log N)的时间来对树的结构作修改,以保持树的平衡。而红黑树的查找方法与二叉搜索树完全一样,也能够在O(log N)时间上完成。而红黑树的插入和删除节点的的方法只是在原二叉搜索树搜索和删除算法之后经过一定的过程来维持红黑树的性质不变。
红黑树的每个节点上的属性除了有一个key、3个指针:parent、left、right以外,还多了一个属性:color。它只能是两种颜色:红或黑,当然也可以再加一些以key来索引的数据。而红黑树除了具有二叉搜索树的所有性质之外,还具有以下5点性质:
1. 每个节点是黑色的或是红色的。
2. 根节点是黑色的。
3. 空节点是黑色的(红黑树中,根节点的parent以及所有叶节点left、right都不指向NULL,而是指向一个定义好的空节点,这样可以保持算法的一致性,简化算法)。
4. 红色节点的父、左子、右子节点都是黑色。
5. 在任何一棵子树中,每一条从根节点向下走到空节点的路径上包含的黑色节点数量都相同。
如下图就是一棵红黑树:
小记:
1. 在一棵红黑树中null节点是很多的,如果每个null节点都用一个真的节点的话那就太浪费空间了,我们可以对所有的null节点都使用同一个节点,这样可以简化算法又能节约空间。
2. 由于红黑树的结构比较复杂,代码的书写一定要常规,比如一般null的left,right和parent都指向树的根节点,但是一般不应该用null节点来索引根节点或是做相应的判断,因为在一些地方我们为了保持算法的一致性,可能改变null节点的left,right,parent的指向。
3. 这里的红黑树实现并不是真的想拿来应用,而是拿来练习,因此在代码的实现上没有过份的考虑效率而影响可读性,因为红黑树结构复杂,里面很多部分的代码并不能随意写出而无错误,直接写出易引入很多细小的bug,因此很多地方要用数学方法来严格证明算法的正确性。
二、前备知识
1.树的旋转
a)左旋
b)右旋
对于树的旋转,能保持不变的只有原树的搜索性质,而原树的红黑性质则不能保持,在红黑树的数据插入和删除后可利用旋转和颜色重涂来恢复树的红黑性质。对于有些材料中有对双旋的描述,其实双旋只是单旋的两次应用,并无新的内容,因此这里就不再介绍了,而且左右旋也是相互对称的,只要理解其中一种旋转就可以了。
2.前趋和后继
由于在搜索树中总是可能操作到它的中序前趋和后继,因些在树中找到它的前趋和后继也是比较重要的。
在二叉搜索树中,一个节点的前趋只能出现在两个地方
1)节点左子树的最右节点,如果节点左子树为则为情况2
2)节点左子树为空,节点前趋只能是它的双亲节点中第一个为右双亲的节点,下面为此种情况的一个示例,B为F中序遍历的前趋。
![红黑树的介绍和实现[原创] - saturnman - 一路](http://img313.ph.126.net/iFsvOxUc5X71SuQnVZMYqQ==/3668181896493951806.bmp)
三、树的修改操作
1. 插入操作
我们总是插入红色的节点,因为这样就可以在插入过程中尽量避免产生对树的调整,我们新插入一个节点后可能使原树的哪些性质改变呢。由于我们是按二叉树的方式插入,因此原树的搜索性质不会改变。如果插入的节点是根节点,性质2会被破坏,如果插入的节点的父节点是红色,则会破坏性质4,因此总的来说,插入一个红色节点只会破坏到性质2或是性质4,我们的恢复树性质的策略很简单,其一就是保持一定的性质不变,之后把出现违背红黑树性质地方向上移,如果能移到根节点,那么很容易就可以通过直接修改根节点来恢复红黑树应满足的性质。其二就是穷举所有的可能性,之后把能归于同一类方法处理的归为同一类,不能直接处理的化归到其它可以直接处理的情况,能直接处理的就直接处理。
下面看看一共有几种情况,
情况1:插入的是根节点(原树是空树)
此情况只会违反性质2。
解法:直接把此节点涂为黑色
情况2:插入的节点的的父节点是黑色
此时不会违反性质2也不会违反性质4,红黑树没有被破坏。
解法:什么都不做
情况3:当前节点的父节点是红色且祖父节点的另一个子节点(叔叔节点)是红色(此时父节点的父节点一定存在,否则插入前就已不是红黑树,此时又分为父节点是祖父节点的左子或者右子,对于对称性,只要解开一个方向就可以了,我们只考虑父节点为祖父左子的情况)。此时还可分为当前节点是其父节点的左子还是右子,但是这两种情况的处理方式是一样的,我们将其归为同一类。
解法:将当前节点的父节点和叔叔节点涂黑,祖父结点涂红,把当前结点指向祖父节点,从新的当前节点重新开始算法。
变换前
变换后
情况4:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的右子
解法:当前节点的父节点做为新的当前节点,以新当前节点为支点左旋。
如下图所示
变换前
变换后
情况5:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左子
解法:父节点变为黑色,祖父节点变为红色,在祖父节点为支点右旋
如下图所示
变换前
变换后
在上面的插入算法中,所有递归都是尾递归,可以改写为循环,其中以当前节点的父节点是否为红色是十分方便的。
2.删除操作
我们删除的节点的方法与常规二叉搜索树中删除节点的方法是一样的,如果被删除的节点不是有双非空子女,则直接删除这个节点,用它的唯一子节点顶替它的位置,如果它的子节点分是空节点,那就用空节点顶替它的位置,如果它的双子全为非空,我们就把它的直接后继节点内容复制到它的位置,之后以同样的方式删除它的后继节点,它的后继节点不可能是双子非空,因此此传递过程最多只进行一次。在册除节点后,原红黑树的性质可能被改变,如果删除的是红色节点,那么原红黑树的性质依旧保持,此时不用做修正操作,如果删除的节点是黑色节点,原红黑树的性质可能会被改变,我们要对其做修正操作。那么哪些树的性质会发生变化呢,如果删除节点不是树唯一节点,那么删除节点的那一个支的到各叶节点的黑色节点数会发生变化,此时性质5被破坏。如果被删节点的唯物主唯一非空子节点是红色,而被删节点的父节点也是红色,那么性质4被破坏。如果被删节点是根节点,而它的唯一非空子节点是红色,则删除后新根节点将变成红色,违背性质2。现在情况看起来有些复杂,这里面我们用一个分析技巧,这引技巧是从《算法导论》一书中学来的。我们从被删节点后来顶替它的那个节点开始调整,并认为它有额外的一重黑色。这里额外一重黑色是什么意思呢,我们不是把红黑树的节点加上除红与黑的另一种颜色,这里只是一种假设,我们认为我们当前指向它,因此空有额外一种黑色,可以认为它的黑色是从它的父节点被删除后继承给它的,它现在可以容纳两种颜色,如果它原来是红色,那么现在是红+黑,如果原来是黑色,那么它现在的颜色是黑+黑。有了这重额外的黑色,原红黑树性质5就能保持不变。现在只要花时是恢复其它性质就可以了,做法还是尽量向根移动和穷举所有可能性。
情况1:当前节点是红+黑色
解法,直接把当前节点染成黑色,结束。
此时红黑树性质全部恢复。
情况2:当前节点是黑+黑且是根节点
解法:什么都不做,结束
情况3:当前节点是黑+黑且兄弟节点为红色(此时父节点和兄弟节点的子节点分为黑)。
解法:把父节点染成红色,把兄弟结点染成黑色,之后重新进入算法(我们只讨论当前节点是其父节点左孩子时的情况)。此变换后原红黑树性质5不变,而把问题转化为兄弟节点为黑色的情况。
变换前
变换后
情况4:当前节点是黑加黑且兄弟是黑色且兄弟节点的两个子节点全为黑色
解法:把当前节点和兄弟节点中抽取一重黑色追加到父节点上,把父节点当成新的当前节点,重新进入算法。(此变换后性质5不变)
变换前
变换后
情况5:当前节点颜色是黑+黑,兄弟节点是黑色,兄弟的左子是红色,右子是黑色。
解法:把兄弟结点染红,兄弟左子节点染黑,之后再在兄弟节点为支点解右旋,之后重新进入算法。此是把当前的情况转化为情况6,而性质5得以保持。
变换前
变换后
情况6:当前节点颜色是黑-黑,它的兄弟节点是黑色,但是兄弟节点的右子是红色,兄弟节点左子的颜色任意。
解法:把兄弟节点染成当前节点父节点的颜色,把当前节点父节点染成黑色,兄弟节点右子染成黑色,之后以当前节点的父节点为支点进行左旋,此时算法结束,红黑树所有性质调整正确。
变换后
下面的地址包含了一个插入序列和一个删除除序列中红黑树的状态,可以通过观查,以加深理解。
http://saturnman.blog.163.com/blog/static/557611201097221570/
下面是相关代码,仅供参考。
Ref:< xmlnamespace prefix ="o" ns ="urn:schemas-microsoft-com:office:office" />
[1]Introduction to Algorithms, Second Edition
http://www.amazon.com/Introduction-Algorithms-Second-Thomas-Cormen/dp/0262032937
[2]Wikipedia Red Black Tree entry
http://en.wikipedia.org/wiki/Red_black_tree
本文出自:http://saturnman.blog.163.com/blog/static/5576112010969420383/