红黑树的基本插入和删除操作

       红黑树是一种二叉搜索树,它在每个结点上增加了一个存储位来表示结点的颜色,可以是RED或者BLACK,通过对任何一条从根到叶子的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而是近似于平衡的。
       在之前笔者总结过AVL树的一些内容,相对于红黑树增加的颜色属性,AVL树增加的是高度属性,两者有异曲同工之处,红黑树中存在很多隐藏的条件,操作更为灵活和复杂,如果学习起来一头雾水的话可以先学习AVL树。笔者将结合AVL树来总结红黑树中。

红黑树的性质

       红黑树的每个结点包括5个属性:color、key、left、right和p,一颗红黑树满足的性质如下:
1. 每个结点不是红色的就是黑色的;
2. 根结点是黑色的;
3. 所有的空结点NIL是黑色的;
4. 如果一个结点是红色的,那么它的两个子结点都是黑色的;
5. 对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。
       从某个结点x出发(不含该结点)到达一个叶结点的任意一条简单路径上的黑色结点个数称为该结点的黑高(black height),记为bh(x)。定义红黑树的黑高为其根结点的黑高。

旋转:维持红黑树性质的操作

       在AVL树中,相邻结点的高度相差超过1时,破坏了平衡性质,此时就需要通过旋转将结点“下沉”或者“上升”达到增加或减少高度的目的。同理,红黑树中的一些操作不满足性质5时,需要通过旋转来将增加或者减少黑色结点从而保持红黑树的性质 。红黑树的旋转操作包含左旋转(逆时针旋转)和右旋转(顺时针旋转),旋转只包括6个指针指向的变动,并不会改变结点的color属性。下面给出旋转操作的伪代码:

LEFT_ROTATE(T, x)
y = x.right
x.right = y.left  //1
if y.left != NIL
  y.left.p = x  //2
y.p = x.p  //3
if x.p == NIL
  T.root = y
else if x == x.p.left
  x.p.left = y     //4
else x.p.right =y  //4
y.left = x  //5
x.p = y  //6
RIGHT_ROTATE(T, y)
x = y.left
y.left = x.right  //1
if x.right != NIL
  x.right.p = y  //2
x.p = y.p  //3
if y.p == NIL
  T.root = x
else if x == x.p.left
  x.p.left = y      //4
else x.p.right = y  //4
x.right = y  //5
y.p = x  //6

插入

       在AVL树中插入结点时,先常规插入结点,然后再进行性质的维护。红黑树的插入也采用同样的策略,对插入结点的color属性着红色,新结点的插入只可能违反性质4,对其余性质均不产生影响,通过从插入结点自底向上对比子结点和父结点的颜色、旋转和重新着色来维持性质4。插入和维护操作的伪代码如下:

RB_INSERT(T, z)
y = NIL
x = T.root
while x != NIL
  y = x
  if z.key < x.key
x = x.left
  else x = x.right
z.p = y
if y == NIL
  T.root = z
else if z.key < y.key
  y.left = z
else y.right = z
z.left = NIL
z.right = NIL
z.color = RED
RB_INSERT_FIXUP(T, z)
RB_INSERT_FIXUP(T, z)
while z.p.color == RED
  if z.p == z.p.p.left
    y = z.p.p.right
    if y.color == RED  //case 1
      z.p.color = BLACK
      y.color = BLACK
      z.p.p.color =RED
      z = z.p.p
    else if z ==  z.p.right  //case 2
      z = z.p
      LEFT_ROTATE(T, z)
    z.p.color = BLACK  //case 3
    z.p.p.color = RED  //case 3
    RIGHT_ROTATE(T, z.p.p)  //case 3
  else 
    y = z.p.p.left
    if y.color == RED  //case 4
      z.p.color = BLACK
      y.color = BLACK
      z.p.p.color = RED
    else if z == z.p.left  //case 5
      z = z.p
      RIGHT_ROTATE(T, z)
    z.p.color = BLACK  //case 6
    z.p.p.color = RED  //case 6
    RIGHT_ROTATE(T, z.p.p)  //case 6
T.root.color = BLACK

违反红黑性质的几种情况分析

       在研究AVL树的插入时,我们分成了4种情况,其中两种是另外两种的对称,并且AVL树的平衡性质只有1条。在红黑树的插入中,所遇到的情形更为复杂些,红黑性质虽然5条,但插入操作违反的红黑性质实际只有性质4,一共有6种,其中3种是另外3种的对称,这里我们只分析插入结点的父节点是左结点的情况,对称的情况类比即可。

情况1:z的叔结点y是红色的

insert case 1
       y为红色时,z.p与y同色,将z.p与y着黑色,同时将z.p.p着红色,此时z与其父结点和爷爷结点都满足红黑性质,将z结点的爷爷结点变为新的z结点。

情况2:z的叔结点y是黑色的且z是一个右孩子

insert case 2
       情况2和3的唯一不同就是z是属于右孩子,可以做一次左旋转,将z的左孩子作为新的z结点,此时转化为情况3解决。

情况3:z的叔结点y是黑色的且z是一个左孩子

insert case 3
       在这种情况下如果对z进行颜色的改变可能造成z 子结点违反性质4,而整个while循环是自底向上操作的,所以这种情况下只能对z结点的爷爷进行操作,可以对z的爷爷进行右旋转和重新着色,z.p的color属性为黑色,无需进行下面的while循环,形成新的合法红黑树。

删除

       笔者关于AVL树的删除部分没有描述,这里也就不做对比。红黑树的删除基本策略是按二叉搜索树的常规方式删除,然后进行红黑性质的维护。如果要删除的结点没有子结点时,直接删除即可,否则需要找到它的后继,用后继结点取代它,删除完成后,对后继结点原所在位置自下而上进行红黑性质的维护。红黑树的删除和维护操作的伪代码如下:

RB_TRANSPLANT(T, u, v)
if u.p == NIL
  T.root = v
else if u == u.p.left
  u.p.left = v
else u.p.right = v
v.p = u.p
RB_DELETE(T, z)
y = z
y_original_color = y.color
if z.left == NIL
  x = z.right
  RB_TRANSPLANT(T, z, z.right)
else if z.right == NIL
  x = z.left
  RB_TRANSPLANT(T, z, z.left)
else y = TREE_MINIMUM(z.right)
  y_original_color = y.color
  x = y.right
  if y.p == z
    x.p = y
  else RB_TRANSPLANT(T, y, y.right)
y.right = z.left
y.left.p = y
y.color = z.color
if y_original_color == BLACK
  RB_DELETE_FIXUP(T, x)
RB_DELETE_FIXUP(T, z)
while x != T.root and x.color == BLACK
  if x == x.p.left
    w = x.p.right
    if w.color == RED  //case 1
      w.color = BLACK
      x.p.color = RED
      LEFT_ROTATE(T, x.p)
      w = x.p.right
    if w.left.color == BLACK and w.right.color == BLACK  //case 2
      w.color = RED
      x = x.p
    else if w.right.color == BLACK  //case 3
      w.left.color = BLACK
      w.color =RED
      RIGHT_ROTATE(T, w)
      w = x.p.right
    w.color = x.p.color  //case4
    x.p.color = BLACK  //case4
    w.right.color = BLACK  //case4
    LEFT_ROTATE(T, x.p)  //case4
    x = T.root  //case 4
  else
    w = x.p.left
    if w.color == RED  //case 5
      w.color = BLACK
      x.p.color = RED
      RIGHT_ROTATE(T, x.p)
    if w.left.color == BLACK and w.right.color == BLACK  //case 6
      w.color = RED
      x = x.p
    else if w.left.color == BLACK  //case 7
      w.right.color = BLACK
      w.color = RED
      LEFT_ROTATE(T, w)
      w = x.p.left
    w.color = x.p.color  //case 8 
    x.p.color = BLACK  //case 8
    w.left.color = BLACK  //case 8
    RIGHT_ROTATE(T, x.p)  //case 8
    x = T.root  //case 8
x.color = BLACK

违反红黑性质的几种情况分析

       z是删除结点,y是z的后继,并且y将移动至z的位置,x记录的是y移动之前的位置,y_original_color记录y移动前的color属性。当删除操作完成后,y_original_color如果是红色,树的红黑性质保持不变,如果是黑色,则原先包含y的路径上的黑色结点个数减少1,这将违反性质5,《算法导论》书中讲到也会存在违反性质2和性质4,但笔者认为所有的维护措施实际上还是在恢复性质5。黑结点的个数减少,那么就要在树中添加黑结点,考虑到树的总结点个数不能改变,所以只能通过旋转和改变红结点的color属性来达到恢复性质5的目的,从x结点开始向上寻找,直到可以找到红色结点。
       x结点暂时增加一重黑色,w为x的兄弟结点,y_original_color为黑色,肯定w不为空,且w为黑色或者w的两个子结点(包含空结点)为黑色,一共有8中情况,其中的4种是另外4种的对称,这里只详细讨论x是左结点的4种情况,另外4种可类比理解。

情况1: w是红色的

delete case 1
       w为红色时,w的两个子结点肯定为黑色,对x.p做一次左旋转,改变x.p和w的颜色,红黑性质不被破坏,重新指定w为x的兄弟结点,情况1转化成了情况2、3、4处理。

情况2: w是黑色的,而且两个子结点都是黑色

delete case 2
       因为w也是黑色的,所以从x和w上去掉一重黑色,使得x只有一重黑色而w只为红色,为了补偿从x和w中去掉的一重黑色,将x.p上新增一重黑色,将x.p作为新的x结点重复while循环。

情况3: w是黑色的,它的左孩子为红,右孩子为黑

delete case 3
       可以交换w和其左孩子的颜色,然后对w进行右旋转,红黑性质不被破坏,此时转变为情况4进行处理。

情况4: w是黑色的,它的左孩子为黑,右孩子为红

delete case 4
       对x.p进行左旋转,去掉x的额外黑色,并将x.p和w.right着黑色,w着红色,这样不破坏红黑性质,并且完成维护,将x指向根退出循环。


       红黑树的内容笔者总结中可能存在问题,笔者学习过程过也是花了很多时间去理解,如果有误还望批评指出。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值