红黑树以及插入

红黑树的概念

红黑树的五个基本特性:

In additionto the requirements imposed on a binary search trees, withred–black trees:   //有搜索二叉树的基础上红黑树还有以下特性

  1. A node is either red or black.   //节点不是红的就是黑的
  1. The root is black. (This rule is sometimes omitted. Since the root can always be changed from red to black, but not necessarily vice-versa, this rule has little effect on analysis.)     //root是黑的
  1. All leaves (NIL) are black. (All leaves are same color as the root.)  //所有的NIL,即最下面的节点是黑的
  1. Both children of every red node are black. //红节点的子叶全都是黑色的
  1. Every simple path from a given node to any of its descendant leaves contains the same number of black nodes.  //每一个路径上的黑节距离是一样的 

源文档 <http://en.wikipedia.org/wiki/Red-black_tree> 


    如果红黑树的节点数n,那么它的叶子数则为n+1

    红黑树只算黑点的话,它的高度h<=log(n+1)


 

    如上图所示,将所有的红点,跟它们的父节点结合在一起,形成一颗新的树,图中可明显观察出2^h<=n+1,得出h<=log(n+1)

    由于特性4,那么整棵红黑树的高度H<=2h,那么H<=2log(n+1),复杂度Olgn

红黑树节点在linux lib中的数据结构:

structrb_node

{

unsignedlong  rb_parent_color;

#define        RB_RED                0

#define        RB_BLACK        1

structrb_node *rb_right;

structrb_node *rb_left;

}__attribute__((aligned(sizeof(long))));

       因为rb_nodesizeof(long)对齐的,那么sizeof(rb_node)12。那么它的地址分配也就是12(ox0c)分配的,那么还有4个是空着的,也就是还有两位是空着的。那么这两位就另有用处,其中最低位可以用来表示颜色。而假设parent的地址是Addr,那么(Addr&0x2)==0(Addr&0x1)==0是恒成立的,所以node->rb_parent_color只需高三十位来表示parent地址,因为低两位可留作他用。

左旋右旋

         当在红黑树上进行插入和删除操作时,对树做了修改,结果可能会违反红黑树的那五条性质。为了保持这些性质,就要改变树中某些结点的颜色和指针结构。

         指针结构的修改时通过旋转来完成的,这是一种能保持二叉查找树性质的查找树局部操作:即左旋和右旋:


 

 

    左旋:比如说,需要把x旋转为y的左结点。整个算法的思路非常清晰:从上至下,先得到y指针,x的右指针指向y的左结点,然后利用parent函数得到x的父亲结点,如果为NULL,则y为新的根,如果不为NULL,则根据x是其父亲的左孩子还是右孩子,将指针指向y。最后将y的左指针指向x,完成旋转。值得注意的是,算法是具有顺序的逻辑步骤,不能够调换顺序,如果改变赋值的顺序会造成内存失去指针指向,出现内存错误。

    个人理解的旁门左道:假如X左旋,就是把X的右孩子Y作为X的父节点,且XY的左孩子,Y原来的左孩子成为X的右孩子,填补Y的空缺。如果是X右旋的话,就是把X的左孩子Y作为X的父节点,且XY的右孩子,Y原来的右孩子成为X的左孩子。更简洁明了一点左旋就是交换右子树和父节点,右旋就是交换左子树和父节点。

 

什么时候用左旋,什么时候用右旋?

    当有不满足特性4的情况出现,且无法通过涂改颜色之类的操作来满足(叔、父节点颜色不同,具体的话就是叔节点是黑的,父节点是红的,必然是这样的)。那么祖父节点(必然是黑节点)就需要旋转。右边比左边多,就需要右转,左边比右边多就需要左旋。

 

    下图所示,G的右路黑路径(2)比左路(1)高,G需要右旋。如果G要进行右旋,但是导致失衡的NPG不在同一直线上。(不在一条直线上,旋转会带走插入的红点N,下一步变颜色的时候会导致新一轮的失衡),所以先要搞成一条直线,NP的右节点,所以P要先来一个左旋,如下图一。

 


()

图一中已经左旋完毕了,这时候要干正事了,G的右旋。but由于NP都是红色,且P是要被拉上去的,G是要被拉

下来的,所以按照第二条原则,可以先把P设置成黑色,G设置成红色,再进行右旋。如图()

 





红黑树插入

红黑树的五个基本特性:

  1. A node is either red or black.   //节点不是红的就是黑的
  1. The root is black. (This rule is sometimes omitted. Since the root can always be changed from red to black, but not necessarily vice-versa, this rule has little effect on analysis.)     //root是黑的
  1. All leaves (NIL) are black. (All leaves are same color as the root.)  //所有的NIL,即最下面的节点是黑的
  1. Both children of every red node are black. //红节点的子叶全都是黑色的
  1. Every simple path from a given node to any of its descendant leaves contains the same number of black nodes.  //每一个路径上的

插入:

Case 1:如果插入的是root节点,直接插入,如果不是进入case2

void insert_case1(struct node *n)
{
       
if(n->parent== NULL)
                n
->color= BLACK;
       
else
                insert_case2
(n);
} 

Case  2:父节点是黑色的,那么特性4跟特性5依旧是有效的

void insert_case2(struct node *n)
{
       
if(n->parent->color== BLACK)
               
return;/* Tree is still valid*/
       
else
                insert_case3
(n);
}

Case 3:如果父节点跟叔节点都是红色的,那么他们都将变成黑色,祖父节点将变成红色(因为特性5)。但是如果这祖父节点变成root节点了,又要满足特性2,所以祖父节点要进入case1进行必要的判断,递归祖父节点。(因为递归到的是祖父节点的,所以所消耗的时间最多log(n+1),复杂度O(logn)

void insert_case3(struct node *n)
{
       
structnode*u= uncle(n),*g;
 
       
if((u!= NULL)&&(u->color== RED)){
                n
->parent->color= BLACK;
                u
->color= BLACK;
                g
= grandparent(n);
                g
->color= RED;
                insert_case1
(g);
       
}else{
                insert_case4
(n);
       
}
}

Case 4:如果父节点是红色的,但是叔节点是黑色的,插入的节点是父节点的右孩子,父节点又是祖父节点的左孩子。那么就进行左旋操作切换当前的节点和父节点,然后进入case5(因为这时候特性4还不满足)。

如果说子节点是父节点的左孩子,父节点是祖父节点的右孩子,那么进行右旋操作,再进入case 5.

(无节操分析:插入的是红色节点,父节点也是红色的)

void insert_case4(struct node *n)
{
       
structnode*g=grandparent(n);
 
       
if((n== n->parent->right)&&(n->parent== g->left)){
                rotate_left
(n->parent);
                n
= n->left;
       
}elseif((n== n->parent->left)&&(n->parent== g->right)){
                rotate_right
(n->parent);
                n
= n->right;
       
}
        insert_case5
(n);
}

图为case第一种情况:左旋




Case 5:父节点是红色的,,但是叔节点是黑色,节点是父节点的左节点,父节点是祖父节点的左节点,那么就按着祖父节点进行右旋操作。结果这操作有,PNG的父节点,G已知是黑色的,这时候不满足特性4,所以要将祖父节点颜色涂红,父节点颜色涂黑。

void insert_case5(struct node *n)
{
       
structnode*g=grandparent(n);
 
        n
->parent->color= BLACK;
        g
->color= RED;
       
if(n== n->parent->left)
                rotate_right
(g);
       
else
                rotate_left
(g);
}




上面那幅图的分析,可看左旋右旋部分的内容

 

 

从上面可以观察红黑树书插入最坏的情况是case 3的时候进入到递归里,那么最多操作也就log(n+1)。至于旋转操作,这个插入操作中也就至多两次,case 4case 5同时满足


代码:

这部分代码,是linux lib里面的rbtree.c里的代码

一般情况下下面那个函数是按顺序同时使用的,第一个函数是将节点插入进去,第二个函数是插进去以后的颜色变换

//node->rb_parent_color= (unsigned long )parent;想出这个的绝逼是个天才。因为rb_nodesizeof(long)对齐的,那么sizeof(rb_node)12。那么它的地址分配也就是12(ox0c)分配的,那么还有4个是空着的,也就是还有两位是空着的。那么这两位就另有用处,其中最低位可以用来表示颜色。而假设parent的地址是Addr,那么(Addr&0x2)==0(Addr&0x1)==0是恒成立的,所以node->rb_parent_color只需高三十位来表示parent地址,因为低两位全都是0。低两位是0的话,就是说node的颜色是红色的。

struct rb_node
{
	unsigned long  rb_parent_color;
#define	RB_RED		0
#define	RB_BLACK	1
	struct rb_node *rb_right;
	struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
static inline void rb_link_node(struct rb_node * node, struct rb_node * parent,
				struct rb_node ** rb_link)
{
	node->rb_parent_color = (unsigned long )parent;    //指向父节点,并且node是红色的,RED is 0
	node->rb_left = node->rb_right = NULL;

	*rb_link = node;  //rb_link一般在调用前,就说明了是parent的左叶还是右叶
}
void rb_insert_color(struct rb_node *node, struct rb_root *root)
{
	struct rb_node *parent, *gparent;

	while ((parent = rb_parent(node)) && rb_is_red(parent))	//父节点存在,且父节点是红色
	{
		gparent = rb_parent(parent);

		if (parent == gparent->rb_left)	//如果父节点是祖父节点的左孩子
		{
			{
				register struct rb_node *uncle = gparent->rb_right;
				if (uncle && rb_is_red(uncle))	//叔节点是红色 case 3
				{	//叔节点跟祖父节点都是红色
					rb_set_black(uncle);
					rb_set_black(parent);
					rb_set_red(gparent);
					node = gparent;	//因为祖父节点颜色变了,递归祖父节点
					continue;
				}
			}

			if (parent->rb_right == node)	//node是父节点的右节点
					//case 4
			{
				register struct rb_node *tmp;
				__rb_rotate_left(parent, root);		//左旋操作
				tmp = parent;		//将父节点跟node进行交换,因为此时父节点是node的子树
				parent = node;
				node = tmp;
			}
			//case 5
			rb_set_black(parent);
			rb_set_red(gparent);
			__rb_rotate_right(gparent, root);	//右旋
		} else {
			{
				register struct rb_node *uncle = gparent->rb_left;
				if (uncle && rb_is_red(uncle))
				{	//case 3
					rb_set_black(uncle);
					rb_set_black(parent);
					rb_set_red(gparent);
					node = gparent;
					continue;
				}
			}

			if (parent->rb_left == node)
			{
				//case 4
				register struct rb_node *tmp;
				__rb_rotate_right(parent, root);
				tmp = parent;
				parent = node;
				node = tmp;
			}
			//case 5
			rb_set_black(parent);
			rb_set_red(gparent);
			__rb_rotate_left(gparent, root);
		}
	}

	rb_set_black(root->rb_node);	//根节点是黑的
}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值