首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

红黑树以及安插

2012-11-26 
红黑树以及插入红黑树的概念红黑树的五个基本特性:In additionto the requirements imposed on a binary s

红黑树以及插入
红黑树的概念

红黑树的五个基本特性:

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),复杂度O(lgn)

红黑树节点在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_node是sizeof(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的父节点,且X是Y的左孩子,Y原来的左孩子成为X的右孩子,填补Y的空缺。如果是X右旋的话,就是把X的左孩子Y作为X的父节点,且X是Y的右孩子,Y原来的右孩子成为X的左孩子。更简洁明了一点左旋就是交换右子树和父节点,右旋就是交换左子树和父节点。

 

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

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

 

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

 

红黑树以及安插

图(一)

图一中已经左旋完毕了,这时候要干正事了,G的右旋。but由于N、P都是红色,且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:父节点是红色的,,但是叔节点是黑色,节点是父节点的左节点,父节点是祖父节点的左节点,那么就按着祖父节点进行右旋操作。结果这操作有,P是N和G的父节点,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 4跟case 5同时满足。


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

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

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

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 5rb_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 3rb_set_black(uncle);rb_set_black(parent);rb_set_red(gparent);node = gparent;continue;}}if (parent->rb_left == node){//case 4register struct rb_node *tmp;__rb_rotate_right(parent, root);tmp = parent;parent = node;node = tmp;}//case 5rb_set_black(parent);rb_set_red(gparent);__rb_rotate_left(gparent, root);}}rb_set_black(root->rb_node);//根节点是黑的}






热点排行