红黑树是一棵二叉搜索树,它在每一个结点上增长了一个存储位来表示结点的颜色,能够是RED 或 BLACK。经过对任何一条根到叶子的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径回避其余路径长处2倍,于是是近似平衡的。html
树的每一个结点包含 5 个属性:color,key,left,right和p。若是一个结点没有子结点或者父结点,则该结点相应的指针属性的值为NULL。咱们能够把这些NULL视为指向二叉搜索树叶结点的指针,而把带关键字的结点视为树的内部结点。算法
一棵红黑树是知足下面红黑性质的二叉搜索树:数据结构
1.每一个结点或是红色的,或是黑色的函数
2.根节点是黑色的spa
3.每一个叶结点(NULL)是黑色的指针
4.若是一个结点是红色的,那么他的两个子结点都是黑色的code
5.对于每一个结点,从该结点到其全部后代叶结点的简单路径上,包含相同数目的黑色结点htm
这 5 个性质中1,2,4都比较好理解。3与咱们常说的(大部分数据结构书上说的)叶结点有一点点区别,以下图:对象
那性质5又是什么意思呢?咱们再来看一个图:blog
由红黑树的 5 个性质可知,上幅图中左图是红黑树,而右图非红黑树。右图中知足红黑树的性质1.2.3.4,可是不知足性质5:从根节点6(不包括根节点)到各叶结点的简单路径上的黑色黑色结点个数并不相等。例如:6-1有2个,而6-8和6-10都是有三个。
这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径很少于最短的可能路径的两倍长。结果是这个树大体上是平衡的。由于操做好比插入、删除和查找某个值的最坏状况时间都要求与树的高度成比例,这个在高度上的理论上限容许红黑树在最坏状况下都是高效的,而不一样于普通的二叉查找树。
要知道为何这些特性确保了这个结果,注意到属性4致使了路径不能有两个毗连的红色节点就足够了。最短的可能路径都是黑色节点,最长的可能路径有交替的红色和黑色节点。由于根据属性5全部最长的路径都有相同数目的黑色节点,这就代表了没有路径能多于任何其余路径的两倍长。
在不少树数据结构的表示中,一个节点有可能只有一个子节点,而叶子节点包含数据。用这种范例表示红黑树是可能的,可是这会改变一些属性并使算法复杂。为此,本文中咱们使用 "nil 叶子" 或"空(null)叶子",如上图所示,它不包含数据而只充当树在此结束的指示。这些节点在绘图中常常被省略,致使了这些树好像同上述原则相矛盾,而实际上不是这样。与此有关的结论是全部节点都有两个子节点,尽管其中的一个或两个多是空叶子。
由于每个红黑树也是一个特化的二叉查找树,所以红黑树上的只读操做与普通二叉查找树上的只读操做相同。然而,在红黑树上进行插入操做和删除操做会致使再也不符合红黑树的性质。恢复红黑树的属性须要少许(O(log n))的颜色变动(实际是很是快速的)和不超过三次树旋转(对于插入操做是两次)。虽然插入和删除很复杂,但操做时间仍能够保持为 O(log n) 次。咱们在这只讲讲红黑树的插入和删除。
1.插入
下面看看算法导论中给的伪代码:
1 /* 2 注意如下的T.nil,是一个与普通红黑树结点相同的对象。他的color是BLACK,他也是根节点的父节点 3 RB-INSERT(T,z) //向树T中增长结点z 4 y = T.nil //根节点的父节点 5 x = T.root //根节点 6 while x != T.nil //while循环内是为了寻找插入结点z的位置 7 y = x //y始终是x的父节点 8 if z.key < x.key 9 x = x.left 10 else 11 x = x.right 12 //跳出while循环以后,说明y结点的某个孩子是T.nil了,能够插入了! 13 z.p = y //z的父结点是y 14 if y == T.nil //若是y就是 T.nil说明该树为空,插入z后,z就是根节点 15 T.root = z 16 else if z.key < y.key //若是z比y结点值小,则插到y的左孩子上 17 y.left = z 18 else 19 y.right = z //不然插到y的右孩子上 20 z.left = T.nil 21 z.right = T.nil //将z的左右孩子都设为T.nil 22 z.color = RED //z的颜色设为红色 23 RB-INSERT-FIXUP(T,Z) //插入一个红色结点会破坏红黑树的性质,须要调整 24 */
好比咱们插入一个值为3的结点:在RB-INSERT-FIXUP函数执行以前,执行的结果以下图:
由上图能够看出T.nil的做用是充当一个哨兵,它也是一个红黑树结点对象,且颜色为黑色,其余的值任意!插入3,并将3的颜色涂成红色以后,有可能会破坏红黑树的性质2和4(上图就破坏了性质5).因此咱们要调用RB-INSERT-FIXUP来保持红黑树的性质。RB-INSERT-FIXUP的伪代码以下:
1 /* 2 如下是实现RB-INSERT-FIXUP(T,Z)伪代码 3 while z.p.color == RED //由于z自己是红色,若是他的父结点是红色那这个循环就要继续---调节树 4 if z.p == z.p.p.left //若是z的父亲是z祖父的左孩子 5 y = z.p.p.right //令y为z祖父的右孩子,也就是说y是z的叔叔 6 if y.color == RED //若是y的颜色是红色 7 z.p.color = BLACK //case 1 既然z是红色,为了避免破坏性质4,将z的父节点涂成黑色 8 y.color = BLACK //case 1 同时也要讲z的叔叔结点涂成黑色 9 z.p.p.color=RED //case 1 同时将z的祖父结点(y的父节点)涂成红色 10 z = z.p.p //case 1 令z 等于 z的祖父,循环继续 11 else if z == z.p.right //若是z是父结点的右孩子 12 z = z.p //case 2 z等于z的父结点 13 LEFT-ROTATE(T,Z) //case 2 右旋 14 z.p.color = BLACK //case 3 将z的父结点颜色涂成黑色 15 z.p.p.color = RED //case 3 将z的祖父结点涂成红色 16 RIGHT-ROTATE(T,Z.P.P) //case 3 右旋 17 else(same as then clause with 'right' and 'left' exchanged) 18 T.root.color = BLACK 19 */
这里伪代码里面有两个函数要注意下,LEFT-ROTATE() 和 RIGHT-ROTATE().这个分别是左旋和右旋的函数。左旋和右旋的过程我已经在个人另外一篇博客中用图解释的很清楚了:http://www.cnblogs.com/zhuwbox/p/3636783.html。
下面是左旋的伪代码:
1 /* 2 LEFT-ROTATE(T,x)--参考上图 3 y = x.right //给y赋值 4 x.right = y.left //将x的右结点指向y的左结点 5 if y.left != T.nil 6 y.left.p = x //设置y左结点的父节点为x 7 y.p = x.p //y的父结点是x的父节点 8 if x.p == T.nil //若是 x 是根节点 9 T.root = y; 10 elseif x == x.p.left //若是x是父结点的左孩子 11 x.p.left = y; // 12 else x.p.right = y //若是x是父结点的右孩子 13 y.left = x; //y的左孩子是x 14 x.p = y //x的父节点是y 15 */
RB-INSERT-FIXUP要处理的状况有三种。
a).状况一:插入结点后的结点z。z和父结点都是红色,违反性质4.以下图:
解决方法是:将z的父结点和叔叔结点涂成黑色,而且z的指针沿z树上升(对应RB-INSERT-FIXUP代码中的case 1部分)。所得状况以下图
b).状况二:调整后的结点z(此时是7)和父结点(结点2)都是红色,可是叔叔结点(结点1)是黑色,此时出现状况二。解决方法:将2做为根节点T进行左旋。获得以下图:
c).状况三:调整后的结点z(此时是2)和父结点是红色,可是叔叔结点(8)是黑色。要进行以下操做:将z结点的父结点涂成黑色,将z的祖父结点涂成红色。再以z的父结点为根T,做一次右旋转便可获得一棵合法的红黑树,以下图:
此时的z的父节点再也不是红色,退出while循环(若是不退出循环,状况确定是这三种中的一种)。一棵合法的红黑树造成!