(被问了好多次红黑树的实现原理,每次都说不太清楚,决定认真整理下。)
R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。
红黑树的应用比较广泛,主要是用它来存储有序的数据,它的时间复杂度是O(lgn),效率非常之高。
例如,Java集合中的TreeSet和TreeMap,C++ STL中的set、map,以及Linux虚拟内存的管理,都是通过红黑树去实现的。
红黑树的特性:
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
红黑树的基本操作是插入、删除。在对红黑树进行插入或删除之后,都会用到旋转方法,因为插入或删除红黑树中的节点之后,红黑树就发生了变化,可能不满足红黑树的5条性质,也就不再是一颗红黑树了,而是一颗普通的树。而通过旋转,可以使这颗树重新成为红黑树。旋转的目的是让树保持红黑树的特性。
旋转包括两种:左旋 和 右旋。下面分别对它们进行介绍。
左旋
z x / / \ --(左旋)--> x y z / y
对x进行左旋,意味着,将“x的右孩子”设为“x的父亲节点”;即,将 x变成了一个左节点(x成了为z的左孩子)。 因此,左旋中的“左”,意味着“被旋转的节点将变成一个左节点”。
右旋
y x \ / \ --(右旋)--> x y z \ z
对x进行右旋,意味着,将“x的左孩子”设为“x的父亲节点”;即,将 x变成了一个右节点(x成了为y的右孩子)。 因此,右旋中的“右”,意味着“被旋转的节点将变成一个右节点”。
第一步: 将红黑树当作一颗二叉查找树,将节点插入。红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。此外,无论是左旋还是右旋,若旋转之前这棵树是二叉查找树,旋转之后它一定还是二叉查找树。
第二步:将插入的节点着色为"红色"。
第三步: 通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。
根据被插入节点的父节点的情况,可以将调整操作分为三种情况来处理:
① 情况说明:被插入的节点是根节点。
处理方法:直接把此节点涂为黑色。
② 情况说明:被插入的节点的父节点是黑色。
处理方法:什么也不需要做。节点被插入后,仍然是红黑树。
③ 情况说明:被插入的节点的父节点是红色。
处理方法:该情况与红黑树的“特性(5)”相冲突。这种情况下,被插入节点是一定存在非空祖父节点的;进一步的讲,被插入节点也一定存在叔叔节点(即使叔叔节点为空,我们也视之为存在,空节点本身就是黑色节点),依据"叔叔节点的情况",将这种情况进一步划分为3种情况:
1. 叔叔是红色
当前节点的父节点是红色,且当前节点的祖父节点的另一个子节点(叔叔节点)也是红色。
处理策略:
(01) 将“父节点”设为黑色。
(02) 将“叔叔节点”设为黑色。
(03) 将“祖父节点”设为“红色”。
(04) 将“祖父节点”设为“当前节点”(红色节点);即,之后继续对“当前节点”进行操作。
2. 叔叔是黑色,且当前节点是右孩子
当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子
处理策略:
(01) 将“父节点”作为新的“当前节点”。
(02) 以“新的当前节点”为支点进行左旋。
3. 叔叔是黑色,且当前节点是左孩子
当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子
处理策略:
(01) 将“父节点”设为“黑色”。
(02) 将“祖父节点”设为“红色”。
(03) 以“祖父节点”为支点进行右旋。
第一步:将红黑树当作一颗二叉查找树,将节点删除。
这和"删除常规二叉查找树中删除节点的方法是一样的"。分3种情况:
① 被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就OK了。
② 被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。
③ 被删除节点有两个儿子。那么,先找出它的后继节点(左儿子中的最大元素或者右儿子中的最小元素,区别不大);然后把“它的后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”。在这里,后继节点相当于替身,在将后继节点的内容复制给"被删除节点"之后,再将后继节点删除。这样就巧妙的将问题转换为"删除后继节点"的情况了,下面就考虑后继节点。 在"被删除节点"有两个非空子节点的情况下,它的后继节点不可能是双子非空。既然"的后继节点"不可能双子都非空,就意味着"该节点的后继节点"要么没有儿子,要么只有一个儿子。若没有儿子,则按"情况① "进行处理;若只有一个儿子,则按"情况② "进行处理。
第二步:通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。
我们从被删节点后来顶替它的那个节点开始调整,假设它不仅有自己原来的一个颜色,还继承了被删除的父节点的颜色位,为黑色,所以它现在可以容纳两种颜色,如果它原来是红色,那么现在是“红+黑”,如果原来是黑色,那么它现在是“黑+黑”。有了这个假设的黑色,原红黑树性质5就能保持不变。现在只要恢复其它性质就可以了,做法还是尽量向根移动和穷举所有可能性。
① 情况说明:当前节点是“红+黑”节点。
处理方法:直接把当前节点设为黑色,结束。此时红黑树性质全部恢复。
② 情况说明:当前节点是“黑+黑”节点,且当前节点是根。
处理方法:什么都不做,结束。此时红黑树性质全部恢复。
③ 情况说明:当前节点是“黑+黑”节点,且当前节点不是根。
处理方法:这种情况又可以划分为4种子情况。这4种子情况如下表所示:
1. 当前节点是"黑+黑"节点,它的兄弟节点是红色
此时当前节点的父节点和它的兄弟节点的子节点都是黑节点。
处理策略
(01) 将当前节点的兄弟节点设为“黑色”。
(02) 将当前节点的父节点设为“红色”。
(03) 对当前节点的父节点进行左旋。
(04) 左旋后,重新设置当前节点的兄弟节点。
2.当前节点是"黑+黑"节点,它的兄弟节点是黑色,他的兄弟节点的两个孩子都是黑色
处理策略
(01) 将当前节点的兄弟节点设为“红色”。
(02) 设置“当前节点的父节点”为新的“当前节点”。
3.当前节点是“黑+黑”节点,他的兄弟节点是黑色;它的兄弟节点的左孩子是红色,右孩子是黑色的
处理策略
(01) 将当前节点兄弟节点的左孩子设为“黑色”。
(02) 将当前节点兄弟节点设为“红色”。
(03) 对当前节点的兄弟节点进行右旋。
(04) 右旋后,重新设置当前节点的兄弟节点。
4.当前节点是“黑+黑”节点,他的兄弟节点是黑色;它的兄弟节点的右孩子是红色的,他的兄弟节点的左孩子任意颜色
处理策略
(01) 将当前节点的父节点颜色 赋值给 x的兄弟节点。
(02) 将当前节点的父节点设为“黑色”。
(03) 将当前节点的兄弟节点的右子节点设为“黑色”。
(04) 对当前节点的父节点进行左旋。
(05) 设置当前节点为“根节点”。
在线生成红黑树
参考:
红黑树(一)之 原理和算法详细介绍 (插入那一块构造的树有问题,插入值也有问题,删除Case 1图示也是错的,但原理是对的)
教你初步了解红黑树