红黑树的概念
红黑树的五个基本特性:
In additionto the requirements imposed on a binary search trees, withred–black trees: //有搜索二叉树的基础上红黑树还有以下特性
- A node is either red or black. //节点不是红的就是黑的
- 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是黑的
- All leaves (NIL) are black. (All leaves are same color as the root.) //所有的NIL,即最下面的节点是黑的
- Both children of every red node are black. //红节点的子叶全都是黑色的
- 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设置成红色,再进行右旋。如图(二)
红黑树插入
红黑树的五个基本特性:
- A node is either red or black. //节点不是红的就是黑的
- 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是黑的
- All leaves (NIL) are black. (All leaves are same color as the root.) //所有的NIL,即最下面的节点是黑的
- Both children of every red node are black. //红节点的子叶全都是黑色的
- 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同时满足。
代码:
一般情况下下面那个函数是按顺序同时使用的,第一个函数是将节点插入进去,第二个函数是插进去以后的颜色变换
//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的颜色是红色的。
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); //根节点是黑的
}