红黑树:它是许多“平衡”搜索树中的一种,可以保证在最坏情况下基本动态集合操作的时间复杂度为O(lgn)
性质:
红黑树是一棵二叉树,它在每个节点上增加一个存储位来表示节点的颜色,可以使red或black,通过对任何一条叶子的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而是近似于平衡的。
树中每个结点包含5个属性:color、key、left、right和p、如果一个结点没有子结构或父结点,则该结点相应指针属性的值为NIL。可以把这些NIL视为指向二叉搜索树的叶结点的指针,而把带关键字的结点视为树的内部结点。
一棵红黑树是满足下面红黑性质的二叉搜索树:
1.每个结点或是红色的,或是黑色的。
2.根节点是黑色的。
3.每个叶结点是黑色的。
4.如果一个结点是红色的,则它的两个子节点都是黑色的。
5.对每个结点,从该结点到其他所有后代叶结点的简单路径上,均包含相同数目的黑色结点。
红黑树允许存在连续黑色的节点存在
为了便于处理红黑树代码中的边界条件,使用一个哨兵来代表NIL。对于一棵红黑树T,哨兵T.nil是一个与树中普通结点有相同属性的对象。它的color属性为black,而其他属性p、left、right和key可以设为任意值。所有指向NIL的指针都用指向哨兵T.nil的指针替换。
从某个结点x出发(不包含该结点)到达一个叶结点的任意一条简单路径上的黑色结点个数成为该结点的黑高,记为bh(x);
引理:一棵有n个内部结点的红黑树的高度至多为2lg(n+1)
红黑树的旋转:
搜索树操作Tree-Insert和Tree-Delete在含n个关键字的红黑树上,运行时间为O(lg(n))。由于这两个操作对树做了修改,结果可能违反红黑树的性质,为了维护这些性质,必须要改变树中某些结点的颜色以及指针结构。
指针结构的修改是通过旋转来完成的,这是一种能保持二叉搜索树性质的搜索局部操作。旋转分为:左旋转和右旋转。
举例左旋:
假设y为x的右子树,当左旋时,y成为x的根节点,x成为y的左子树,y的左子树成为x的右子树。右旋转正好相反,不再叙述。
对于红黑树的操作:
一 、红黑树的插入操作Insert
当红黑树进行节点插入操作的时候,性质1和性质3继续成立,因为新插入的红节点的两个子节点都是哨兵节点。性质5,即从一个指定节点开始的每条简单路径上的黑节点的个数都是相等的,也会成立,因为节点z代替了(黑色)哨兵,并且节点z本身是有哨兵孩子的红节点。这样看来,仅可能被破坏的就是性质2和性质4,即根节点需要为黑色以及一个红节点不能有红孩子。这两个性质可能被破坏是因为z被着色为红色。如果z是根节点,则破坏了性质2;如果z的父节点是红节点,则破坏了性质4。
那么为什么需要进行旋转呢?下面来具体讨论一下:
由于红黑树:对每个结点,从该结点到其他所有后代叶结点的简单路径上,均包含相同数目的黑色结点。对于插入的节点z并且染色成红色之后,对于这个性质并没有打破,只有可能出现z的节点和它的父亲节点都为红色,这样就违背了:如果一个结点是红色的,则它的两个子节点都是黑色的。
有的人会想到为什么不直接将这个插入的节点直接弄成黑色?
个人认为:对于红黑树来讲,首先它是一颗二叉搜索树,其次它是一颗平衡的树,如果直接将这个插入的节点染色成黑色有可能打破这个平衡,也即打破对每个结点,从该结点到其他所有后代叶结点的简单路径上,均包含相同数目的黑色结点的性质,也就是说性质5其实是一个保持红黑树平衡的一个约束条件。所以插入一个节点后,要么整体红黑树的树高加 一,要么树高保持不变且保持红黑树性质。
插入操作中涉及到红黑处理函数的伪代码:
Insert_Fixup(T,z)
if z.p == z.p.p.left
y = z.p.p.right
if y.color == RED
z.p.color = BLACK
y.color = BLACK
z.p.p.color = RED
z = z.p.p
else if z == z.p.right
z=z.p
Left-Rotate(T,z)
z.p.color = BLACK
z.p.p.color = RED
Right-Rotate(T,z.p.p)
else(跟处于左侧的一样处理)
T.root.color = BLACK
例子最初的红黑树和z插入的地方起始点的图片:
Z在值为4的节点处,Z为红色而且他的父节点为红色,他的叔节点也为红色,这时将Z的父节点和Z的叔节点染成黑色,Z的祖父节点Z.P.P染成红色,将指针上移到Z.P.P祖父的位置上,这时又有Z.P与Z的颜色同时为红色,违反了性质四,进行下一步的处理情况二进行左旋处理。
左旋处理之后,Z处于值为2的节点,这时还违反性质4,即节点2和节点7,这时候进入情况3,首先将Z的Z.P染黑,即7染黑然后将Z.P.P的祖父节点染红,即11节点染红,然后进行右旋处理:
之后继续进行循环,周而复始的进行上述的操作:指针上调两级->检验叔父节点的颜色->旋转染色->上调指针。。。。。。
下面我们看看上述情况的对称形式,由于上面已经解释了,所以对称的我在这里只用图片说明:
初始情况:z在值为18处
第二部因为z->p->p->left = y 为红色则将 y 和 z->p染成黑色,将z->p->p染成红色
然后进行右旋转:
然后将z的父亲染成黑色,将z的祖父染成红色,之后进行左旋转:
最后经过左旋转之后变成最后的图片: