原文地址:
http://imlazy.ycool.com/post.1104022.html
(阅读本文之前请先了解二叉搜索树)
红黑树(Red-Black Tree)
红黑树(Red-Black Tree)是二叉搜索树(Binary Search Tree)的一种改进。我们知道二叉搜索树在最坏的情况下可能会变成一个链表(当所有节点按从小到大的顺序依次插入后)。而红黑树在每一次插入或删除节点之后都会花O(log N)的时间来对树的结构作修改,以保持树的平衡。也就是说,红黑树的查找方法与二叉搜索树完全一样;插入和删除节点的的方法前半部分节与二叉搜索树完全一样,而后半部分添加了一些修改树的结构的操作。
红黑树的每个节点上的属性除了有一个key、3个指针:parent、lchild、rchild以外,还多了一个属性:color。它只能是两种颜色:红或黑。而红黑树除了具有二叉搜索树的所有性质之外,还具有以下4点性质:(为什么只要这些性质就能解决这个问题,其实还是一个问题)
1. 根节点是黑色的。
2. 空节点是黑色的(红黑树中,根节点的parent以及所有叶节点lchild、rchild都不指向NULL,而是指向一个定义好的空节点)。
3. 红色节点的父、左子、右子节点都是黑色。
4. 在任何一棵子树中,每一条从根节点向下走到空节点的路径上包含的黑色节点数量都相同。
如下图就是一棵红黑树:
有了这几条规则,就可以保证整棵树的平衡,也就等于保证了搜索的时间为O(log N)。
但是在插入、删除节点后,就有可能破坏了红黑树的性质。所以我们要做一些操作来把整棵树修补好。下面我就来介绍一下。
首先有一个预备知识,那就是节点的Left-Rotate和Right-Rotate操作。所谓Left-Rotate(x)就是把节点x向左下方向移动一格,然后让x原来的右子节点代替它的位置。而Right-Rotate当然就是把Left-Rotate左、右互反一下。如下图:
注意,Left-Rotate(x)后,x的右子树变成了原来y的左子树,Right-Rotate反之。思考一下,这样一次变换后,仍然满足二叉搜索树的性质(中序遍历并没有改变)。在红黑树的插入、删除中,要用到很多Left-Rotate和Right-Rotate操作。
//把一个节点向左下方移一格,并让他原来的右子节点代替它的位置。
void leftRotate(RBTNode* node)
{
RBTNode* right = node->rchild;
node->rchild = right->lchild;
node->rcount = right->lcount;
node->rchild->parent = node;
right->parent = node->parent;
if (right->parent == m_null) {
m_root = right;
}
else if (node == node->parent->lchild) {
node->parent->lchild = right;
}
else {
node->parent->rchild = right;
}
right->lchild = node;
right->lcount += node->lcount + 1;
node->parent = right;
}
//把一个节点向右下方移一格,并让他原来的左子节点代替它的位置。
inline void rightRotate(RBTNode* node) {
RBTNode* left = node->lchild;
node->lchild = left->rchild;
node->lcount = left->rcount;
node->lchild->parent = node;
left->parent = node->parent;
if (left->parent == m_null) {
m_root = left;
}
else if (node == node->parent->lchild) {
node->parent->lchild = left;
}
else {
node->parent->rchild = left;
}
left->rchild = node;
left->rcount += node->rcount + 1;
node->parent = left;
}
一、 插入
插入首先是按部就班二叉搜索树的插入步骤,把新节点z插入到某一个叶节点的位置上。
接下来把z的颜色设成红色。为什么?还记得红黑树的性质吗,从根节点向下到空节点的每一条路径上的黑色节点数要相同。如果新插入的是黑色节点,那么它所在的路径上就多出了一个黑色的节点了。所以新插入的节点一定要设成红色。但是这样可能又有一个矛盾,如果z的父节点也是红色,怎么办,前面说过红色节点的子节点必须是黑色。因此我们要执行下面一个迭代的过程,称为Insert-Fixup,来修补这棵红黑树。
在Insert-Fixup中,每一次迭代的开始,指针z一定都指向一个红色的节点。如果z->parent是黑色,那我们就大功告成了;如果z->parent是红色,显然这就违返了红黑的树性质,那么我们要想办法把z或者z->parent变成黑色,但这要建立在不破坏红黑树的其他性质的基础上。
这里再引入两个指针:grandfather,指向z->parent->parent,也就是z的爷爷(显然由于z->parent为红色,grandfather一定是黑色);uncle,指向grandfather除了z->parent之外的另一个子节点,也就是z的父亲的兄弟,所以叫uncle。
(为了说话方便,我们这里都假设z->parent是grandfather的左子节点,而uncle是grandfather的右子节点。如果遇到的实际情况不是这样,那也只要把所有操作中的左、右互反就可以了。)
在每一次迭代中,我们可能遇到以下三种情况。
Case 1. uncle也是红色。这时只要把z->parent和uncle都设成黑色,并把grandfather设成红色。这样仍然确保了每一条路径上的黑色节点数不变。然后把z指向grandfather,并开始新一轮的迭代。如下图:
注1:我们可以看出左边的图,各条路径包含黑颜色的数目是正确的,只是颜色不对而已,我们把它分成两边来看,即到节点D应该包含N+1个黑色节点,其中这个1是C,而N是C以上的黑色节点个数。同理A也应该是N+1,B也是N+1,调整以后,看看我们确实没有改变到A、B、D的所包含的黑色节点数。下面的情况也可以同样的方法来分析。
Case 2. uncle是黑色,并且z是z->parent的右子节点。这时我们只要把z指向z->parent,然后做一次Left-Rotate(z)。就可以把情况转化成Case 3。
Case 3. uncle是黑色,并且z是z->parent的左子节点。到了这一步,我们就剩最后一步了。只要把z->parent设成黑色,把grandfather设成红色,再做一次Right-Rotate(grandfather),整棵树就修补完毕了。可以思考一下,这样一次操作之后,确实满足了所有红黑树的性质。Case 2和Case 3如下图:
反复进行迭代,直到某一次迭代开始时z->parent为黑色而告终,也就是当遇到Case 3后,做完它而告终。
void insertFixup(RBTNode* insertNode) {
RBTNode* p = insertNode;
while (p->parent->color == RED) {
//z->parent是grandfather的左子节点,下面是三种情况
if (p->parent == p->parent->parent->lchild) {
RBTNode* parentRight = p->parent->parent->rchild;
if (parentRight->color == RED) {
p->parent->color = BLACK;
parentRight->color = BLACK;
p->parent->parent->color = RED;
p = p->parent->parent;
}
else {
if (p == p->parent->rchild) {
p = p->parent;
leftRotate(p);
}
p->parent->color = BLACK;
p->parent->parent->color = RED;
rightRotate(p->parent->parent);
}
}
else {
RBTNode* parentLeft = p->parent->parent->lchild;
if (parentLeft->color == RED) {
p->parent->color = BLACK;
parentLeft->color = BLACK;
p->parent->parent->color = RED;
p = p->parent->parent;
}
else {
if (p == p->parent->lchild) {
p = p->parent;
rightRotate(p);
}
p->parent->color = BLACK;
p->parent->parent->color = RED;
leftRotate(p->parent->parent);
}
}
}
m_root->color = BLACK;
}
二、删除
让我们来回顾一下二叉搜索树的删除节点z的过程:如果z没有子节点,那么直接删除即可;如果z只有一个子节点,那么让这个子节点来代替z的位置,然后把z删除即可;如果z有两个子节点,那么找到z在中序遍历中的后继节点s(也就是从z->rchild开始向左下方一直走到底的那一个节点),把s的key赋值给z的key,然后删除s。
红黑树中删除一个节点z的方法也是首先按部就班以上的过程。
按照二叉搜索树的删除方法删除节点,如果删除节点是红色的,那并不会改变红黑树的性质。如果删除的节点是黑色的,那么显然它所在的路径上就少一个黑色节点,那么红黑树的性质就被破坏了。这时我们就要执行一个称为Delete-Fixup的过程,来修补这棵树。下面我就来讲解一下。
一个节点被删除之后,一定有一个它的子节点代替了它的位置(即使是叶节点被删除后,也会有一个空节点来代替它的位置。前面说过,在红黑树中,空节点是一个实际存在的节点。)。我们就设指针x指向这个代替位置的节点。
显然,如果x是红色的,那么我们只要把它设成黑色,它所在的路径上就重新多出了一个黑色节点,那么红黑树的性质就满足了。
然而,如果x是黑色的,那我们就要假想x上背负了2个单位的黑色。那么红黑树的性质也同样不破坏,但是我们要找到某一个红色的节点,把x上“超载”的这1个单位的黑色丢给它,这样才算完成。Delete-Fixup做的就是这个工作。
注:删除了一个黑色节点以后,遍历到节点一下的叶子节点比遍历其他分支的叶子节点的黑色节点数就少了一个,这就要是找到一个红色,把这个节点换成黑色来拟补这个删除的黑色节点,使得遍历到叶子节点经过黑色节点的数目一样。
Delete-Fixup同样是一个循环迭代的过程。每一次迭代开始时,如果指针x指向一个红色节点,那么大功告成,把它设成黑色即告终。相反如果x黑色,那么我们就会面对以下4种情况。
这里引入另一个指针w,指向x的兄弟。这里我们都默认x是x->parent的左子节点,则w是x->parent的右子节点。(如果实际遇到相反的情况,只要把所有操作中的左、右互反一下就可以了。)
Case 1. w是红色。这时我们根据红黑树的性质可以肯定x->parent是黑色、w->lchild是黑色。我们把x->parent与w的颜色互换,然后做一次Left-Rotate(x->parent)。做完之后x就有了一个新的兄弟:原w->lchild,前面说过它一定是黑色的。那么我们就在不破坏红黑树性质的前提下,把Case 1转换成了Case2、3、4中的一个,也就是w是黑色的情况。思考一下,这样做不会改变每条路径上黑色节点的个数,如下图:
注:可以看出这样变化以后就变成了Case2了。
Case 2. w是黑色,并且w的两个子节点都是黑色。这时我们只要把w设成红色。然后把x移到x->parent,开始下一轮迭代(注意,那“超载”的1单位的黑色始终是跟着指针x走的,直到x走到了一个红色节点上才能把它“卸下”)。思考一下,这一次操作不会破坏红黑树的性质。如下图(图中节点B不一定是红色,也可能是黑色):
注:这里只要把B变成红色就大功告成了。
Case 3. w是黑色,并且w的两个子节点左红右黑。这时我们把w与w->lchild的颜色互换,然后做Right-Rotate(w)。思考一下,这样做之后不会破坏红黑树的性质。这时x的新的兄弟就是原w->lchild。而Case 3被转化成了Case 4。
Case 4. w是黑色,并且w的右子节点是红色。一但遇到Case 4,就胜利在望了。我看下面一张图。先把w与x->parent的颜色互换,再做Left-Rotate(x->parent)。这时图中节点E(也就是原w->rchild)所在的路径就肯定少了一个黑色,而x所在的路径则多了一个黑色。那么我们就把x上多余的1个单位的黑色丢给E就可以了。至此,Delete-Fixup就顺利完成了。
注:通过注1我们可以看出问题在Case4后已经解决了。
void delFixup(RBTNode* delNode) {
RBTNode* p = delNode;
while (p != m_root && p->color == BLACK) {
if (p == p->parent->lchild) {//左边情况,以下是四种不同的Case
RBTNode* sibling = p->parent->rchild;
if (sibling->color == RED) {
sibling->color = BLACK;
p->parent->color = RED;
leftRotate(p->parent);
sibling = p->parent->rchild;
}
if (sibling->lchild->color == BLACK
&& sibling->rchild->color == BLACK
) {
sibling->color = RED;
p = p->parent;
}
else {
if (sibling->rchild->color == BLACK) {
sibling->lchild->color = BLACK;
sibling->color = RED;
rightRotate(sibling);
sibling = sibling->parent;
}
sibling->color = sibling->parent->color;
sibling->parent->color = BLACK;
sibling->rchild->color = BLACK;
leftRotate(sibling->parent);
p = m_root;
}
}
else {//右边情况
RBTNode* sibling = p->parent->lchild;
if (sibling->color == RED) {
sibling->color = BLACK;
p->parent->color = RED;
rightRotate(p->parent);
sibling = p->parent->lchild;
}
if (sibling->lchild->color == BLACK
&& sibling->rchild->color == BLACK
) {
sibling->color = RED;
p = p->parent;
}
else {
if (sibling->lchild->color == BLACK) {
sibling->rchild->color = BLACK;
sibling->color = RED;
leftRotate(sibling);
sibling = sibling->parent;
}
sibling->color = sibling->parent->color;
sibling->parent->color = BLACK;
sibling->lchild->color = BLACK;
rightRotate(sibling->parent);
p = m_root;
}
}
}
p->color = BLACK;
}
posted on 2008-11-22 14:16
漂漂 阅读(501)
评论(0) 编辑 收藏 引用 所属分类:
算法