今天看了看红黑树,重点是红黑树的插入和删除
红黑树特性
1) 节点是红色或者黑色2) 根节点是黑色
3) 所有的叶节点(NIL节点)是黑色
4) 如果有个节点是红色,则其子节点必然是黑色
5) 从任一节点到其叶子节点的所有简单路径包含有相同数目的黑色节点
从根到叶节点最短路径的情况,所有节点全为红色
从根到叶节点最长路径的情况,为一个红色节点一个黑色节点间隔路径
一个节点包含,key,left,right,color,parent
红黑树的性质:从根到节点点的最长的可能路径不多于最短的可能路径的两倍长
一颗n个内节点的红黑树的高度至多为2lg(n+1),所以复杂度近似为2lg(n+1)
AVL树比红黑树要求严格,所有左右子树高度相差不能超过1,其查询复杂度相比红黑树会更低
红黑树的左旋,右旋
left-Rotate(T,x),就是以x和x的右孩子为“轴”,使得x变成原x右孩子的左孩子right-Rotate(T,x)就是以x和x的左孩子为轴,使得x变成原x左孩子的右孩子
伪代码:
Left-Rotate(T,x)
{
y = x -> right; //记录右孩子
x->right = y -> left;//转移右孩子的左子树到右支
if( y->left )
y->left->parent = x; //如果右孩子的左子树不空,则设置其父节点
y->parent = x->parent; //将y的父节点置为原x的父节点
if (x -> parent == nil)
root[T] = y; //如果原x是根
else{
if (x == x->parent->left) //y替代原来x的位置
x->parent->left = y;
else
x->parent->right = y;
}
y->left = x; //把x挂到y的左子树
x->parent = y; //设置x的父指针
}
红黑树的插入操作
现在讲插入节点,分两步,1)寻找插入点,插入红色节点,2)调整红黑树,使其符合红黑树特性
步骤1:寻找插入点,插入节点
rb_tree_insert(T,N)
{
y = nil[T]; //初始化父节点
x=root[T];
while(x != nil[T])
{
y = x;
if(key(N) < key(x))
x = x->left;
else
x = x->right;
}
//此时y记为插入点的父节点
if(y == nil[T]) //y为空,表示是根节点
{ root[T] = N; color[N] = BLACK; //根节点直接设置为黑色}
else{
if (key(N) < key(y))
y->left = N;
else
y->right = N;
N->left = N->right = nil[T]; //初始化
color[N]=RED; //初始化为红色
if (color[y] == RED) //如果父节点是红色,则需要进行Fixup调整,否则,不需要进行任何调整动作
{ rb_tree_insert_Fixup(T,N); }
}
}
步骤2:调整红黑树,主要是完成上面伪代码的rb_tree_Fixup(T,N)
设定每个插入的节点,都设置为红色,(因为设置为黑色,那么会造成红黑树的黑高度变化,从而造成更多的旋转操作)
设置插入的红色节点为x,其父节点为p,其父节点的兄弟节点,即叔父节点,设为s,祖父节点设为g,
0)如果节点x为根节点,则只需要把红色改成黑色即可,停止
如果不是根节点:
0)如果父节点p为黑色,则满足条件,不做任何调整,停止
如果父节点p为红色,则不满足“不能出现连续红色节点”的性质,需要进一步调整(3种情况)
1)如果叔父节点s也为红色,则可以直接调整父节点p和叔父节点s颜色由红色变成黑色,祖父节点g由黑色变成红色(祖父节点必然为黑色,因为父节点为红色!),在直接变换颜色之后,将x上溯至祖父节点g,设置祖父节点g为x,继续从此最开始调整(需要循环)(因为刚才祖父节点变成红色了,有可能造成红黑树性质违反了)
(继续从G点往上进行调整)
2)如果叔父节点s为黑色,那么分以下两种情况:
2A) 如果节点x是父节点s的右孩子,则需要进行left-rotate(T,p) 以父节点p为轴,左旋,将x,p,g三个节点置于一条线上(这个其实是case 2B) ,将case 2A) 问题转换为case 2B) 来解决,需要继续进行case 2B 的调整)
(转换成case 2B) 来处理)
2B) 如果节点x是父节点s的左孩子,通俗的说“节点x,p,g在一条线上” 那么需要先进行变色,将父节点和祖父节点变色,然后进行右旋right-Rotate(T,G), 以祖父节点G为轴进行右旋,这样就完全满足红黑树的性质了,所以对于case 2B)转换完之后,可以结束调整了
(先变色,父节点p变成黑色,祖父节点g变成红色,然后右旋,调整完成)
当然这个只是处理了其父节点是祖父节点的左子树的情况,对于父节点是祖父节点右子树的情况,类似的处理
红黑树的删除操作
删除操作也分两步走,1)删除节点,2)调整红黑树 (相对插入操作的调整有点复杂)
步骤1,删除节点,找到后继节点,分以下几种情况
case 1) 如果要删除的节点只有一个节点,或者没有节点(nil叶节点除外),直接将唯一的子节点“替换”要删除的节点(如果删除节点为左孩子,则其孩子节点挂到父节点的左子树,右孩子同理
case 2) 如果要删除的节点有两个孩子节点,那么就不能直接删除这个节点了,需要寻找其右子树上的“最左下"节点,设定为N,(可以推断出这个节点只有一个右孩子或者没有孩子,为什么?因为如果有两个孩子,那么这个节点就不可能是最左下节点),这时候,我们需要用这个N去替换要删除的节点(注意是替换,不是删除,而且要保留原来要删除节点的颜色),后面则”实际“删除其后继的最左下节点N,同时对这个节点N进行红黑树调整(因为只有一个右孩子或者没有孩子,所以其对应了上面的case1情况)
伪代码
rb_tree_delete(T,z)
{
//设定y为实际要删除的节点
if (z -> left == nil || z -> right == nil)
y = z;
else
y = tree_Successor(T,z); //寻找x的中序后继节点,也就是x的右子树的”最左下“节点
if( y->left != nil)
x = y->left; //x为要删除节点的"补充位置"节点
else
x = y->right; //这里包含了如果y->left 和y->right 都为空的情况,即y无孩子节点的情况
if( x != nil)
x -> parent = y->parent;
if (y->parent == nil) //删除点为根节点
root[T] = x;
else{
if (y == y->parent->left) //继续挂在被删节点的树枝上
y->parent->left = x;
else
y->parent->right=x;
}
//处理一下被删节点左右孩子都存在的情况
if (y != z)
{
copy(y,z); //copy y节点内容到z,但是保留z节点的颜色
}
if (y -> color == BLACK) //如果”实际“删除的节点为黑色,那么需要进行红黑树balance调整,红色不用调整也满足红黑树性质
rb_tree_delete_Fixup(T, y);
return y;
}
步骤2,对于"实际删除"的节点,如果是黑色,需要进行调整,因为黑色删除了,那么这个节点下的子树的黑高度就减1了,这样就不满足红黑树任意路径黑高度一致的特性了
如果删除节点为红色,那么不会影响整个红黑树的性质,那么什么调整也不用做
如果删除节点为黑色,如上原因,那么必须进行调整,以便红黑树的各个路径黑高度一致,需要分4种情况进行分析
下面我们的讨论都是基于被删除节点为黑色,设定后面补上的节点为N,N的父节点设为P,兄弟节点设为S
0) 如果补上的节点N(原删除节点的儿子节点)为红色,则直接将红色变为黑色,即可
1)如果N为黑色,(就是说原来删除的节点和后面补上的儿子节点都是黑色),那么这一支上就会少一个黑色节点,需要进行调整
以下讨论基于补上的儿子节点为黑色
case 1A) 如果节点N的兄弟节点S 为红色,则先将父节点由黑色变成红色(原来节点肯定是黑色的,因为其右孩子是红色的!),兄弟节点S由红变黑,然后对兄弟节点S和父节点P为轴进行左旋操作left_rotate(T,P), 以父节点P为轴,进行左旋,这样红色兄弟节点S的左(黑)孩子就变成了节点N的兄弟节点了,这样节点N的兄弟节点就由红色的S变成了黑色的S节点的左孩子了(这将使后面的1B,1C,1D几种case)
(先对父节点和兄弟节点变色,然后再左旋,转向case 1B, 1C, 1D)
Case 1B) 如果节点N的兄弟节点S为黑色,并且节点S的右孩子为黑色,并且S的左孩子也为黑色
***) 同时S的父节点P为黑色,则直接将S变为红色,即可,这样兄弟这一支上黑高度就减了1,这样就需要把检查节点N移到父节点P继续进行平衡调整了
***) 同时S的父节点P为红色,则将S变为红色,并将父节点P变为黑色,这样N这一支上黑高度就加了1,这样就满足红黑特性了,就不需要再进行平衡调整了,停止
(父节点为黑的情况,只变兄弟节点S,进一步的调整)
(父节点为红的情况,变S为红,变P为黑,就不需要继续调整了,因为父节点增加了左子树的黑高度,满足红黑性质,停止)
Case 1C) 如果节点N的兄弟节点S为黑色,并且S的右孩子为黑色,但是S的左孩子为红色;
这样需要变色,将S的左孩子由红色变成黑色,S由黑色变为红色,然后对S左孩子和S进行右旋right-rotate(T,S) 以S为轴,进行右旋,使得其变成后面的case 1D), 即变成N的兄弟节点S为黑色,且S的右孩子为红色的情况,继续进行Case 1D的处理
(变色,然后右旋,变成Case 1D 的情况)
Case 1D) 如果节点N的兄弟节点S为黑色,并且S的右孩子为红色,无论左孩子为黑色或者红色;
需要将父节点P变成黑色,并将黑色的兄弟节点变成原来父节点的颜色(注意是原父节点的颜色),S的红色的右孩子变为黑色,然后对父节点P和兄弟节点S左左旋操作,这里原来的父节点变成黑色"填补"了被删的黑色的节点的黑高度,同时右子树也保持了原有的黑高度,这样到此就不需要进行进一步的调整了,停止
(注意兄弟节点S变成原来父节点P的颜色,无论是黑色或者是红色,在变色,并左旋之后,就可以停止调整了,因为远父节点变黑,填补了删除黑色节点所剪掉的黑高度)
对于删除节点为右子树的情况,只需相对于的变换即可。
参考:
这是一个讲的很好的红黑树的讲义