红黑树插入操作超详解

 说明:1. 下述图片多参照网上资源(July红黑树)或者电子书资源(STL源码剖析)等

             2. 个人感觉STL中思路以及实现方式比其他版本都易懂,建议大家好好琢磨

参考: 

          1.STL源码剖析

          2. http://www.cnblogs.com/abatei/archive/2008/12/17/1356565.html

红黑树是平衡搜索树的一种,它的插入,删除,查找算法平均复杂度均为O(log n)。除了满足二叉搜索树的条件以外还要满足以下条件:

(1)      节点颜色只有黑色和红色两种

(2)      根节点和外部节点的颜色为黑色

(3)      如果节点颜色为红色,则子节点颜色必须为黑色

(4)      任意一个节点到子孙外部节点的每条路径中都包含相同数目的黑色节点个数

对以上四个性质,做如下解释:

(1)      所谓外部节点,并非真正的树中的节点,而是认为定义的空(nul)节点,如下图中用正方形表示的节点即为外部节点。

(2)      根据规则(4)新增的节点颜色必须为红色。根据规则(3)新增节点的父节点颜色必须为黑色

 

对于节点的插入,我们可以分为四种情况:(以X节点作为参照节点)

(1)      插入结点位于X的左子节点的左子树上– 左左

(2)      插入结点位于X的左子节点的右子树上– 左右

(3)      插入结点位于X的右子节点的左子树上– 右左

(4)      插入结点位于X的右子节点的右子树上– 右右

其中情况(1)和情况(4)彼此对称,被称为外侧插入。同理情况(2)和情况(3)也对称,成为内侧插入。举例如下:

 

关于外侧插入和内侧插入的注释:在上述左边图中,如果以X作为参照,则节点11 位于X的左子节点(节点14)的左子树(节点12)上,不管节点11插入在以节点12为父节点的左子树中还是右子树中,都称为左左插入。在上述右边图中,节点15位于X的左子节点(节点14)的右子树(节点16)上,不管节点15插入在以节点16为父节点的左子树中还是右子树中,都称为左右插入。 如果以节点14为参照,则左图中节点11为左左插入。右图中节点15为右左插入

 

对于外侧插入,进行一次单旋转即可保持二叉树平衡,对于内侧插入,则要进行一次双旋转(其实就是两次单旋转,第一次旋转可以不准确的理解为将内侧插入旋转成外侧插入的形式。然后再进行第二次旋转)

在讨论红黑树的插入和删除之前,我们先为某些特殊节点定义下名字。 我们假设待插入节点为X,其父节点为P,组父节点为G,父节点的兄弟节点为S,曾组父节点为GG

 

红黑树的插入:

根据各种资料,本人总结的红黑树插入情况如下(情况1,情况2,情况3中没有详细展开外侧插入是LL还是RR,内侧插入是LR还是RL,这样便于理解记忆):

根据规则(4)在一个非空的树中新增的节点颜色必须为红色。如果其新增节点的父节点颜色为黑色,那么直接将新节点按照二叉搜索树方法插入即可,无需做任何改变。如下图:

如果父节点为红色,则插入后需要根据父节点的兄弟节点S进行判断。我们进行如下考虑(以下判断插入是外侧还是内侧的参考点为组父节点G):

情况1 如果S为黑色并且X为外侧插入,则先对P和G进行一次单旋转(左旋还是右旋根据LL型或者RR型来定,下同),再更改P,G颜色,如图:

情况2如果S为黑色并且X为内侧插入,则先对P,X做一次单旋转并更改G,X的颜色,再将结果对G做一次单旋转即可

情况3如果S为红色 将父节点P和叔父节点S变为黑色,同时将组父节点G变为红色。此时如果GG为黑,则结束,否则继续往上回溯检查,直到不再有父子连续为红色的情况。

图1: 不用继续往上检查:

图2:需要继续往上检查

 回溯向上检查示例如下:

假设新增一个节点A,我们沿着A的路径,只要有某个节点X的两个子节点都为红色,就把X改为红色,同时把两个子节点改为黑色,如下图所示:

 

如果改变之后父节点的父节点也是红色(如上图右侧节点X的父节点也是红色),那么就要根据情况1一样做一次单旋转并且改变颜色或者像情况2一样做一次双旋转并且改变颜色。

如下:

 

下面给出STL源码剖析中插入算法的C语言代码部分实现形式:

再讲左旋和右旋之前,我们先看一下他们之间的转换关系图:


明白了这个转化,就明白了左旋和右旋的操作了

1. 左旋

      举例如图所示(以privot 为旋转点进行左旋):

红黑树节点的结构体如下:

typedef bool rb_tree_color_type;
const rb_tree_color_type rb_tree_red = false;
const rb_tree_color_type rb_tree_black = true;

struct rb_tree_node_base
{
	typedef rb_tree_color_type color_type;
	typedef rb_tree_node_base* base_ptr;
	
	color_type color;
	base_ptr parent;
	base_ptr left;
	base_ptr right;
};


源代码如下:

// x 为旋转点   root 为树根
// 注意: 改变颜色部分在代码外实现
rb_tree_rotate_left(rb_tree_node_base *x, rb_tree_node_base *&root)
{
	rb_tree_node_base *y = x->right; 
	x->right = y->left;
	if(y->left != 0)
	{
		y->left ->parent = x; // 设置父节点
       }
        y->parent = x->parent ; //重新设置y的父节点
      // 令y顶替x的位置
      if(x == root)
      { 
            root = y;
     } 
     else if (x == ->parent->left)   // x为父节点的左子节点
     {
           x->parent->left = y;
    }
    else    // x 为父节点的右子节点
    { 
         x->parent->right = y;
    }
     y->left = x;
     x->parent = y; 
 }


2. 右旋

举例如图所示(以privot 为旋转点进行右旋):



源代码如下;

// x 为旋转点   root 为树根
// 注意: 改变颜色部分在代码外实现
rb_tree_rotate_right(rb_tree_node_base* x, rb_tree_node_base* &root)
{
	rb_tree_noed_base *y = x->left;
         x->left = y->right;
	if(y->right != 0)
        {
              y->right ->parent  = x; // 设定父节点
        }
         y->parent = x->parent;
        // 另y完全替代X的位置
        if(x == root)
         {
              root = y; 
         }
        else if(x == x->parent->right)
          {
                x->parent->right = y;
         }
         else 
         {
                 x->parent ->left = y;
         }
	  y->right = x;
	   x->parent = y; 
}

3. 插入一个节点后保持红黑树平衡的源代码实现:

void rb_tree_rebalance(rb_tree_node_base *x, rb_tree_node_base *&root)
{
	x->color = rb_tree_red; // 新节点为红色
	while(x!=root&&x->parent->color == rb_tree_red)//父节点为红色
	{
	  if(x->parent == x->parent->parent->left) // 父节点为组父节点左子节点
	  {
		rb_tree_node_base *y = x->parent->parent->right; // y为伯父节点
		if(y&&y->color == rb_color_red) // 情况3 伯父节点为红色
		{
			x->parent->color = rb_tree_black; // 更改父节点为黑
			y->color = rb_tree_black; // 更改伯父节点为黑
			x->parent->parent->color = rb_tree_red; // 更改祖父节点为红
		}
		x = x->parent->parent; // 继续往上检查
	  }
	  else  // 无伯父节点(默认节点颜色为黑)或者伯父节点为黑
	  {
		if(x == x->parent->right) // 新节点为父节点右子节点(LR型,要双旋转)
		{
			x = x->parent;   // 以父节点为旋转点进行旋转
			rb_tree_rotate_left(x, root);  // 第一次旋转
		}
		x->parent->color = rb_tree_black;     // 先改变颜色,再单旋转和旋转完再改颜色效果一样
		x->parent->parent->color = rb_tree_red;
		rb_tree_rotate_right(x->parent->parent,root); // 第二次旋转
	  }
	}
	else // 父节点为组父节点的右子节点 
	{ // 操作与上面类似,就不做注释了
		rb_tree_node_base *y = x->parent->parent->left; 
		if(y&&y->color == rb_tree_red)
		{
			x->parent->color = rb_tree_black;
			x->color = rb_tree_black;
			x->parent->parent->color = rb_tree_red;
			x = x->parent->parent;
		}
		else
		{
			if(x == x->parent->left)
			{
				x = x->parent;
				rb_tree_rotate_right(x, root);
			}
			x->parent->color = rb_tree_black;
			x->parent->parent->color = rb_tree_red;
			rb_tree_rotate_left(x->parent->parent, root);
		}
	}
} // while结束
 root ->color = rb_tree_black; // 根节点颜色永远为黑


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值