8.3 红黑树

本文详细介绍了红黑树的插入、删除操作及其可能导致的双红和双黑修正。在插入节点时,新插入的节点首先标记为红色,然后通过双红修正确保树的平衡。删除节点时,先查找节点,然后删除并替换,最后进行双黑修正以保持树的平衡。文章涵盖了不同情况下的修正策略,如BB-1、BB-2R、BB-2B、BB-3等,并提供了相应的代码实现片段。
摘要由CSDN通过智能技术生成

参考博客:高级搜索树(伸展树,B-树,红黑树)
参考博客:红黑树的原理分析与(C++)实现
参考博客:红黑树
参考博客:数据结构算法 - 红黑树
参考博客:红黑树删除修正说明
参考博客:红黑树插入修正说明
参考博客:红黑树

本章问题

1.4阶B-树与红黑树等价,那么他们是如何转换的

(2,4)-树

自顶而下逐层考查红黑树各节点。每遇到一个红节点,都将对应的子树整体提升一层,从而与其父节点(必黑)水平对齐,二者之间的联边则相应地调整为横向。此时,若将原红黑树的节点视作关键码,沿水平方向相邻的每一组(父子至多三个)节点即恰好构成4阶B-树
的一个节点。

注释:在完全彩色版尚未出版时,本书约定分别以圆形正方形和八角形表示红黑树的红节点,黑节点和颜色未定节点,
以长方形表示B-节点
以下将不再严格区分红黑树中的节点及其在(2,4)-树中对应的关键码。当然,照此理解,此时的关键码也被赋予了对应的颜色。对照红黑树的条件,(2,4)-树中的每个节点应包含且仅包含一个黑关键码,同时红关键码不得超过两个。而且,若某个节点果真包含两个红关键码,则黑关键码的位置必然居中。

2.在测试时发现无法成功输出结果

1.有两行代码没有注释掉,导致运行时报错(无效)
2.调用父类的函数,要声明父类的作用域,这里没有声明导致报错(无效)
3.猜测可能删除函数有问题,导致旋转有问题,经过排除,不是删除的问题,
4.可能是双黑修正的问题,删除函数确实调用了,将修正注释,程序运行到了下一个函数,猜测是指针指向了不该指向的地方
5.查看源代码发现,可能是未重载BinNode的判等器导致无法判断是否相等(无效)
6.可能是FromParentTo ( *p ) = BST<T>::rotateAt ( t );这里出现问题,未找到明显原因,需要改正
7.发现max函数未能定义,前面与Vector函数冲突时,直接删除了宏定义(无效)
8.发现rotateAt ( t )这里有一个测试函数输出节点的data成员,如果是空指针就会程序崩溃
9.发现rotate函数都是用的数据成员比较左右子树
10.不能重载判等器,两个节点比较的引用相同,而不是数值相同,否则无法判断左右子树(无效)
11.测试双红修正,删除函数,插入函数可以运行,双黑修正停止
12.删除节点12时出错,发现父亲没有祖父,但是访问,导致报错,访问节点后继的父亲报错
13.判断是空指针颜色未判定为黑色
14.确认是删除操作导致bug,注释掉删除操作后程序不报错
15.使用前序遍历时发现插入节点6报错,可能错误不只一个
16.发现代码判断左节点出错,改成右节点了

else/*zag*/
        if( IsRChild(*v)){/*zag-zag*/
            p->parent = g->parent;

17.发现使用父类connect函数中右孩子指针修改成功,但是运行时指针修改未成功,怀疑编译器出了问题
18.将c14转变为c11(无效)
19.在rotate函数中已经连接父节点了

r->parent = gg; //与原曾祖父联接,这里的函数多余了

20.FromParentTo ( *g )该函数出错了,导致无法连接
21.将代码修改为如下,原代码父节点无法连接,修改的结果有效,插入成功,遍历树与预期相同

      BinNodePosi(T) gg = g->parent; //曾祖父(great-grand parent)
       BinNodePosi(T) r;//临时变量
        if(gg==NULL){//是否为根
            //cout<<"herere"<<endl;
            r=BST<T>::_root=BST<T>::rotateAt ( x );
        }
        else if(g==gg->lc){
            r=gg->lc=BST<T>::rotateAt ( x );
        }
        else{
            r=gg->rc=BST<T>::rotateAt ( x );
        }
      //BinNodePosi(T) r =FromParentTo ( *g )=BST<T>::rotateAt ( x ); //调整后的子树根节点

      r->parent = gg; //与原曾祖父联接

22.怀疑节点非空指针值也为0,但是判断节点红黑成功,故不是值为0,而是自己忘记打取反符号
22.删除节点12仍然失败,判断代码仍然存在问题
23if ( !(–BST::_size ) ) return true; //实施删除,代码忘记加取反符号
24前序遍历时成环无限输出,推测是FromParentTo ( *g )函数出错导致
25.修改后程序依然错误
26 删除节点12时,双旋节点, 为空指针,无法访问数据成员
27.可以访问子成员,但是不成功
28.推测BinNodePosi(T) p = (r ? r->parent : this->_hot);//
29.p = (r ? r->parent : this->_hot);// 三目运算符会导致指针操作失效,不清楚什么原因

 if(R!=NULL){
    p=r->parent;
   }else{
     p=  this->_hot
   }

30.发现节点12的兄弟节点的颜色出错
31.问题在于插入节点,未调整好颜色,导致双黑修正出错
32.插入节点8时,节点颜色不对,发现宏定义出错,修改为如下代码时,代码成功

#define IsRoot(x) (!((x).parent))
#define IsLChild(x) (!IsRoot(x) && (&(x)==(x).parent->lc))
#define IsRChild(x) (!IsRoot(x) && (&(x)==(x).parent->rc))
#define HasParent(x) (!IsRoot(x))
#define HasLChild(x) ((x).lc)
#define HasRChild(x) ((x).rc)
#define HasChild(x) (HasLChild(x) || HasRChild(x))	//至少拥有一个孩子
#define HasBothChild(x) (HasLChild(x) && HasRChild(x))	//同时拥有两个孩子
#define IsLeaf(x) (!HasChild(x))

#define sibling(p) (IsLChild(*(p))?(p)->parent->rc:(p)->parent->lc)	//兄弟
#define uncle(x) sibling((x)->parent)	//叔叔33
  1. 删除节点5 出错 ,改正后成功
3.红黑树的特点

(1) 树根始终为黑色
(2) 每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(3) 其余节点若为红色,则其孩子节点必为黑色
(4) 从任一外部节点到根节点的沿途,黑节点的数目相等
从定义可推出:
由(1)(2)可推出红节点均为内部节点,且其父节点及左、右孩子必然存在。红节点之父必为黑色,因此树中任一通路都不含相邻的红节点。
从以上条件可得:红节点均为内部节点,且其父节点及左、右孩子必然存在。红节点之父必为黑色,因此树中任一通路都不含相邻的红节点。
在从根节点通往任一节点的沿途,除去根节点本身,沿途所经黑节点的总数称作该节点的黑深度(black depth)——根节点的黑深度为0,其余依此类推。
在从任一节点通往其任一后代外部节点的沿途,沿途所经黑节点的总数称作该节点的黑高度(black height)。
如此,所有外部节点的黑高度均为0,其余依此类推。

4.红黑树如何更新节点高度的

1.比较左右孩子的高度,此时高度为左右子树高度最大者
2.若本身为黑节点,则高度+1

5.红黑树怎么插入节点的

红黑树插入步骤:

根据插入数据的大小将数据插入到树中;
新插入的节点变成红色;
回忆下红黑树的条件:

1.根节点是黑色的
2.父子节点不能同时为红色
3.每一个节点机不是红色就是黑色,并且所有的叶子节点必须是黑色的
4.每个节点到它的叶子节点的所有路径上包含的黑色节点数目必须是一致的
根据以上条件,插入节点为什么是红色的就很好理解了,首先,条件1很好满足,如果是插入的是空树,那么直接将新插入的节点变成黑色就好了。条件2可不一定能满足,条件3也是满足的,条件4也是满足的,因为红色节点并不影响此节点到叶子节点路径上黑色节点的数目。

综上所述,红黑树插入修正其实只需要满足条件2就行了。

现在根据新插入节点的父节点颜色来分析:

情况1.新插入节点的父节点是黑色的,那么,新插入的节点对于这棵树是不影响平衡的;不需要做任何操作
情况2.新插入节点的父节点是红色的,那么,这种情况是违背父子节点不能同时是红色节点的情况。

我们再根据情况2(也就是父节点是红色节点的情况)做分析:(以下我们将新加入的节点称作N节点,父节点是F节点,叔叔节点是U节点)
根据情况2.有以下三种情况:

情况1: 父亲节点是红色,叔叔节点是红色(将父亲和叔叔节点变成黑色,将祖父节点(祖父几点之前一定是黑色的)变成红色,并将祖父节点设为当前节点接着判断)
情况2: 父亲节点是红色,叔叔节点是黑色,并且,新加入的节点是父亲节点是左孩子(以祖父节点为支点进行右旋转,然后交换祖父节点和父亲节点的颜色,此时当前节点还是新加入的节点)
情况3: 父亲节点是红色,叔叔节点是黑色,并且,新加入的节点是父亲节点的右孩子(以父节点为支点,进行左旋转,此时父节点变成子节点了,将父节点设为当前节点)

注意:修正节点的主要思想是将影响红黑树平衡的节点不断往往上移,最后如果移到了根节点就直接将根节点变成黑色就行了。
此处有一个隐藏条件,如果父亲节点是红色的,那么祖父节点就是黑色的

Case 1:如果是父亲和叔叔节点都是红色的,那么直接将他们两变成黑色的,此时,经过祖父节点的黑色节点数就多1了,此时将祖父节点变成红色(此时祖父节点可能会与他的父亲节点存在冲突,所以将祖父节点当成当前节点做进一步判断),并将祖父节点当成当前节点来处理;

Case2:如果父亲节点是红色的,而叔叔节点是黑色的,并且插入的节点是父亲节点的右孩子(时刻记住将多出来的红色往上移动),此时以父亲节点为支点进行左旋转,就将新插入的节点向上移动了(此时新插入的节点处在父亲节点的位置,而叔叔节点是不变的),但是,还是有同时为红色的父子节点,此时的情形是父亲节点是红色的,并且孩子节点是左孩子,叔叔节点还是黑色的。此时将最初的父亲节点、现在的孩子节点当成当前节点进行判断处理。而这种情形刚好是Case3的情形。

Case3:如果父亲节点是红色的,叔叔节点是黑色的,并且新插入的节点是左孩子。(这时候就要时刻记住要将多出来的红色节点往上移动,所以,可以将祖父节右旋转,但是旋转之后右边就会多出来一个黑色节点(祖父节点),并且右旋转后,原先的祖父节点变成此时的父亲节点,颜色由黑色变成红色,这种情况下,可以直接将祖父节点和父亲节点颜色交换,这样这两个问题就都解决了),此时将父亲节点和祖父节点交换颜色(就是父亲节点变成黑色,祖父节点变成红色)并以祖父节点为支点进行右旋转。

6.双红修正有那几种情况

1、如果没有出现双红现象,父亲是黑色的不需要修正;
2、叔叔是红色的 ,将叔叔和父亲染黑,然后爷爷染红;
3、叔叔是黑色的,父亲是爷爷的左节点,且当前节点是其父节点的左孩子,将“父节点”设为“黑色”, 将“祖父节点”设为“红色”,以“祖父节点”为支点进行右旋;
4、叔叔是黑色的,父亲是爷爷的右节点,且当前节点是其父节点的右孩子,将“父节点”设为“黑色”, 将“祖父节点”设为“红色”,以“祖父节点”为支点进行左旋;
5、叔叔是黑色的,父亲是爷爷的左节点,且当前节点是其父节点的右孩子,将“父节点”作为“新的当前节点”, 以“新的当前节点”为支点进行左旋。然后将“父节点”设为“黑色”,将“祖父节点”设为“红色”, 以“祖父节点”为支点进行右旋;
6、叔叔是黑色的,父亲是爷爷的右节点,且当前节点是其父节点的左孩子,将“父节点”作为“新的当前节点”, 以“新的当前节点”为支点进行右旋。然后将“父节点”设为“黑色”,将“祖父节点”设为“红色”, 以“祖父节点”为支点进行左旋;
将x的父亲和祖父分别记为p和g,x的兄弟及其两个孩子都为黑色(两个孩子是指空节点)
有两种情况:u节点为叔叔节点
情况1: 考查u节点为黑色的情况。此时,x的兄弟、两个孩子的黑高度,均与u节点 相等。

情况2: 考查u节点为红色的情况。此时,u的左、右孩子非空且均为黑色,其黑高度必与x的兄弟以及两个孩子相等。

7.怎么删除节点

删除
1.查找节点和后继
2.删除节点,将节点后继连接到被删除节点的父节点 ,或者使用替换删除法

8.双黑修正有几种情况

红黑树删除的步骤:

1.找到要删除的节点
2.删除节点
3.判断是否需要修正

红黑树的定义:

1.根节点是黑色的
2.每个节点要么是黑色的要么是红色的
3.不能出现同时为红色的父子节点
4.每个节点到它的叶子节点的所有路径上所经过的黑色节点的数量必须要保持一致

​ 仔细想一下,如果删除了一个节点,最有可能打破的是条件1、3、4。因为:1.如果删除的是根节点,而根节点的孩子节点有可能是红色的,删除根节点后,它的孩子节点替代根节点,此时根节点就是红色的。3.如果删除一个黑色节点,这个黑色节点有红色的父亲节点和红色的孩子节点,那么如果删除掉这个黑色节点后,它的红色父亲节点和红色 孩子节点就有可能相遇。4,其实这个条件是最容易遇到的,因为如果删除一个黑色节点的话,那么经过这个黑色节点到叶子节点的路上所经过的黑色节点数量必定就减少1。

其实,如果仔细看上面这三种情况,就会发现,如果删除的是一个黑色节点,那么久必定需要修正的,而如果是删除的是一个红色节点,那么就不用向那么多了,因为如果删除的是一个黑色节点,那么条件4不影响,而且,如果是红色节点,那么他的父亲和孩子节点就不可能是红色的,也不会影响到条件3和1。

删除红黑树节点也是和删除平衡树节点的情况差不多(多一个删除修正),其具体情况有以下几种:

1.删除节点只有一个孩子:(直接删除此节点,然后将孩子节点代替父亲节点)
2.删除节点没有孩子:(直接删除节点)
3.删除节点有两个孩子:(找到这个节点的后继节点,然后将后继节点代替这个节点)
以上是删除树节点的三种情况,如果考虑平衡修正的话:

1删除节点只有一个孩子:

1如果删除的节点是红色的,直接删除就行,不会影响平衡;
2.如果删除节点是黑色的,那么就要考虑孩子节点了(只有一个孩子节点,考虑红黑树平衡条件,这个孩子节点肯定是红色的,不然左右 孩子不平衡),所以,直接将孩子节点代替父亲节点后,将这这个节点染黑

2.删除节点没有孩子:

1.如果删除的节点是红色的,那么直接删除,平衡不受影响,如果删除的节点是黑色的,那么平衡表就此打破,赶紧想办法平衡吧。

3.删除节点有两个孩子节点:

1.找到此节点的后继节点,然后将孩子节点的值覆盖要删除的节点的值,然后删除后继节点。 这样问题就转变成考虑后继节点处是否平衡了。而这个要删除节点有两个孩子,那么,后继节点就一定是它的右孩子节点中最左边的节点了。这种情况下,后继节点不可能有两个孩子,也就是说后继节点要么只有一个右孩子,要么没有孩子,而这两种情况又刚好是以上讨论的两种情况。
综上所述,其实主要还是情况2,情况1很好修正,直接染黑孩子节点就行了,而情况3最终还是转换为情况1和情况2

注意:删除修正的核心思想是从别的 地方借一个红色节点过来拟补上丢失的黑色节点,或者将根节点变成红色的,然后染黑根节点。
所以:我们主要考虑情况2:

Case 1:被删除节点是黑色的,它的兄弟节点是红色的(父亲节点是黑色的)。(交换兄弟和父亲节点的颜色,然后左旋转父亲节 点,重新设置兄弟节点)

Case 2: 被删除节点是黑色的,它的兄弟节点是黑色的(父亲节点不确定),并且,它的孩子都是黑色的(染红兄弟节点,然后将父 亲节点设成当前节点,继续处理)

Case 3: 被删除节点是黑色的,它的兄弟节点是黑色的,它的左孩子是红色的,右孩子是黑的(染红兄弟节点,染黑兄弟的左孩 子节点,以兄弟节点为支点进行右旋转,重新设置兄弟节点)

Case 4: 被删除节点是黑色的,它的兄弟节点是黑色的,它的右孩子是红色的,左孩子随意(交换父亲和兄弟节点的颜色 ,染 黑 兄弟节点的右孩子然后以父亲节点为支点进行左旋转)

具体分析:

Case 1:如果被删除节点的兄弟节点是红色的(父亲节点必定是黑色的),那么其实可以从他那里借一个红色节点过来拟补丢失 的黑色节点的。具体处理就是染黑兄弟节点,然后以父亲节点左旋转,此时被删除节点这一支就拟补上删除的黑色节 点了,但是要注意一点:兄弟节点是红色的,然后被删除节点是黑色的,那么,兄弟节点就一定是两个非空的孩子的 ,而且,这两个孩子或者孩子的孩子中一定有一个黑色节点的,当父亲节点左旋转后,父亲节点的孩子节点中黑孩子个 数还是不平衡的(父亲节点的右孩子黑色节点数比左边多1)。这需要继续平衡。

Case 2:如果被删除节点有一个黑色兄弟,并且兄弟节点有两个黑色的孩子(这两个孩子一定是空节点)。这种情况就是:被删除 节点和兄弟节点都是黑色的,而父亲节点就不能确定了。此时就不能向兄弟以及他的孩子节点中去借了。而我们可以根据删除节点修复的核心思想:从别处借,或者染黑根节点;那么这种情况下,我们可以同时染红要被删除的节点和它的
兄弟节点(这样父亲节点的左右孩子中黑色节点数量一致了,但是经过父亲节点到叶子节点的路径上黑色节点就少一个了,
这种情况就很像是我们删除节点后,根据被删除节点颜色判断平衡的情况,此时我们也可以将父亲节点当成当前节点来判断)最后将父亲节点当成当前节点继续处理。

Case 3:如果被删除节点的兄弟节点是黑色的,兄弟节点的左孩子是红色的,右孩子是黑色的(右孩子是空的)。这种情况很简单, 我删除黑色节点,黑色节点数量少1了,而兄弟节点的孩子中不是刚好有红色节点嘛,我们借过来染黑不就行了嘛,具 体操作就是:交换兄弟节点和它的左孩子节点的颜色,然后以兄弟节点为支点进行右旋转,这种情况就刚好是Case 4:

Case 4:如果被删除节点是黑色的,兄弟节点也是黑色的,并且,兄弟节点的右孩子是红色的,左孩子随意:那么,还是一样 的,借红色节点过来,然后染黑。具体操作就是:染黑兄弟节点的右孩子,交换兄弟节点和父亲节点的颜色,然后以父 亲节点为支点进行左旋转此时就平衡了。

1、当我们移除的是一个红色节点,那么根本就不会影响我们的性质4和性质5,我们不需要调整
2、如果删除节点是黑色的,兄弟节点是红色的,把兄弟节点染黑,父节点染红,左/右旋父节点;
进行左旋转:
进行右旋转:
3、如果删除节点是黑色的,兄弟节点是黑色的,并且两个侄子节点都是黑色的,将兄弟节点染红,指针回溯至父亲节点;
4、如果删除节点是黑色的,兄弟节点是黑色,的并且远侄子是黑色的,近侄子是红色的,将近侄子染黑,兄弟染红, 左/右旋兄弟节点,进入下面情况 5 ;
5、如果删除节点是黑色的,兄弟节点是黑色的,并且远侄子是红色的,近侄子随意,将兄弟节点染成父亲节点的颜色, 父亲节点染黑,远侄子染黑,左/右旋父亲节点。

(a)或依然满足,(b)或经重染色后重新满足©或不在满足
如图(a)和(a’)所示,若x为红色,则在删除x并代之以r后,条件(3~4)依然满足;反之,若x为黑色,则要看其替代者r的颜色
如图(b)和(b’)所示,若r为红色,则只需将其翻转为黑色,即可使条件(3~4)重新满足。
然而如图©和(c’)所示,若x和r均为黑色,则为使条件(3~4)重新成立,还需要做略微复杂一些的处理。
因某一无红色孩子的黑节点被删除,而导致的此类复杂情况,称作“双黑” (double black)现象。此时,需从r出发调用solveDoubleBlack®算法予以修正。
自然,原黑节点x的兄弟必然非空,将其记作s;x的父亲记作p,其颜色不确定。以下视s和p颜色的不同组合,按四种情况分别处置。
BB-1:
节点x的另一孩子w = NULL,故从B-树角度可以等效地理解为:关键码x原所属的节点发生下溢;

(带问号的关键码可能存在且颜色不定)
BB-2-R:
节点s及其两个孩子均为黑色时,视节点p颜色的不同,又可进一步分为两种情况。
先考虑p为红色的情况。如图8.30(a)所示,即为一种典型的此类情况(与之对称的情况自行补充

(带问号的黑关键码可能但不会同时存在)

BB-2-B:
再考虑节点s、s的两个孩子以及节点p均为黑色的情况。如图8.31(a)所示,即为一种典型的此类情况(与之对称的情况,请自行补充

BB-3:
最后,考虑节点s为红色的情况。

8.3.1 红黑树ADT接口

操作功能
insert()插入节点
remove()删除节点
solveDoubleRed()双红修正
solveDoubleBlack()双黑修正
updateHeight(x )更新节点x的高度

8.3.2 红黑树类模板

#include "BST.h"
#define IsBlack( p ) ( ! (p) || ( RB_BLACK == (p)->color ))
#define IsRed(p) ( ! IsBlack(p) )
#define BlackHeightUpdated(x) (( stature( (x).lc ) == stature( (x).rc )) && ( (x).height == ( IsRed( &x ) ? stature( (x).lc ) : stature((x).lc) + 1)))
template <typename T> class RedBlack : public BST<T> { //RedBlack树模板类
protected:
   void solveDoubleRed ( BinNodePosi(T) x ); //双红修正
   void solveDoubleBlack ( BinNodePosi(T) x ); //双黑修正
   int updateHeight ( BinNodePosi(T) x ); //更新节点x的高度
public:
   BinNodePosi(T) insert ( const T& e ); //插入(重写)
   bool remove ( const T& e ); //删除(重写)
// BST::search()等其余接口可直接沿用
};

8.3.3updateHeight(更新节点高度)

template <typename T> int RedBlack<T>::updateHeight ( BinNodePosi(T) x ) { //更新节点高度
   x->height = max ( stature ( x->lc ), stature ( x->rc ) ); //孩子一般黑高度相等,除非出现双黑
   /*DSA*/// 红黑树中各节点左、右孩子的黑高度通常相等
   /*DSA*/// 这里之所以取更大值,是便于在删除节点后的平衡调整过程中,正确更新被删除节点父亲的黑高度
   /*DSA*/// 否则,rotateAt()会根据被删除节点的替代者(高度小一)设置父节点的黑高度
   return IsBlack ( x ) ? x->height++ : x->height; //若当前节点为黑,则计入黑深度
} //因统一定义stature(NULL) = -1,故height比黑高度少一,好在不致影响到各种算法中的比较判断

8.3.4 insert(插入节点)

template <typename T> BinNodePosi(T) RedBlack<T>::insert ( const T& e ) { //将e插入红黑树
   BinNodePosi(T) & x = search ( e ); if ( x ) return x; //确认目标不存在(留意对_hot的设置)
   x = new BinNode<T> ( e, this->_hot, NULL, NULL, -1 ); this->_size++; //创建红节点x:以_hot为父,黑高度-1
   solveDoubleRed ( x ); return x ? x : this->_hot->parent; //经双红修正后,即可返回
} //无论e是否存在于原树中,返回时总有x->data == e

8.3.5solveDoubleRed

/******************************************************************************************
 * RedBlack双红调整算法:解决节点x与其父均为红色的问题。分为两大类情况:
 *    RR-1:2次颜色翻转,2次黑高度更新,1~2次旋转,不再递归
 *    RR-2:3次颜色翻转,3次黑高度更新,0次旋转,需要递归
 ******************************************************************************************/
template <typename T> void RedBlack<T>::solveDoubleRed ( BinNodePosi(T) x ) { //x当前必为红
   if ( IsRoot ( *x ) ) //若已(递归)转至树根,则将其转黑,整树黑高度也随之递增
      {  this->_root->color = RB_BLACK; this->_root->height++; return;  } //否则,x的父亲p必存在
   BinNodePosi(T) p = x->parent; if ( IsBlack ( p ) ) return; //若p为黑,则可终止调整。否则
   BinNodePosi(T) g = p->parent; //既然p为红,则x的祖父必存在,且必为黑色
   BinNodePosi(T) u = uncle ( x ); //以下,视x叔父u的颜色分别处理
    
   if ( IsBlack ( u ) ) { //u为黑色(含NULL)时 //*DSA*/printf("  case RR-1:\n");
      if ( IsLChild ( *x ) == IsLChild ( *p ) ) //若x与p同侧(即zIg-zIg或zAg-zAg),则
         p->color = RB_BLACK; //p由红转黑,x保持红
      else //若x与p异侧(即zIg-zAg或zAg-zIg),则
         x->color = RB_BLACK; //x由红转黑,p保持红
      g->color = RB_RED; //g必定由黑转红
// 以上虽保证总共两次染色,但因增加了判断而得不偿失
// 在旋转后将根置黑、孩子置红,虽需三次染色但效率更高
      BinNodePosi(T) gg = g->parent; //曾祖父(great-grand parent)
      BinNodePosi(T) r = FromParentTo ( *g ) = rotateAt ( x ); //调整后的子树根节点
      r->parent = gg; //与原曾祖父联接
   } else { //若u为红色 //*DSA*/printf("  case RR-2:\n");
      p->color = RB_BLACK; p->height++; //p由红转黑
      u->color = RB_BLACK; u->height++; //u由红转黑
      if ( !IsRoot ( *g ) ) g->color = RB_RED; //g若非根,则转红
      solveDoubleRed ( g ); //继续调整g(类似于尾递归,可优化为迭代形式)
   }
}

8.3.6 remove

template <typename T> bool RedBlack<T>::remove ( const T& e ) { //从红黑树中删除关键码e
   BinNodePosi(T) & x = search ( e ); if ( !x ) return false; //确认目标存在(留意_hot的设置)
   BinNodePosi(T) r = removeAt ( x, this->_hot ); if ( ! ( --this->_size ) ) return true; //实施删除
// assert: _hot某一孩子刚被删除,且被r所指节点(可能是NULL)接替。以下检查是否失衡,并做必要调整
   if ( ! this->_hot ) //若刚被删除的是根节点,则将其置黑,并更新黑高度
      { this->_root->color = RB_BLACK; updateHeight ( this->_root ); return true; }
// assert: 以下,原x(现r)必非根,_hot必非空
   if ( BlackHeightUpdated ( *this->_hot ) ) return true; //若所有祖先的黑深度依然平衡,则无需调整
   if ( IsRed ( r ) ) //否则,若r为红,则只需令其转黑
      { r->color = RB_BLACK; r->height++; return true; }
// assert: 以下,原x(现r)均为黑色
   //*DSA*/printBinTree(_hot, 0, 0);
   solveDoubleBlack ( r ); return true; //经双黑调整后返回
} //若目标节点存在且被删除,返回true;否则返回false

8.3.7 solveDoubleBlack

/******************************************************************************************
 * RedBlack双黑调整算法:解决节点x与被其替代的节点均为黑色的问题
 * 分为三大类共四种情况:
 *    BB-1 :2次颜色翻转,2次黑高度更新,1~2次旋转,不再递归
 *    BB-2R:2次颜色翻转,2次黑高度更新,0次旋转,不再递归
 *    BB-2B:1次颜色翻转,1次黑高度更新,0次旋转,需要递归
 *    BB-3 :2次颜色翻转,2次黑高度更新,1次旋转,转为BB-1或BB2R
 ******************************************************************************************/
template <typename T> void RedBlack<T>::solveDoubleBlack ( BinNodePosi(T) r ) {
   BinNodePosi(T) p = r ? r->parent : this->_hot; if ( !p ) return; //r的父亲
   BinNodePosi(T) s = ( r == p->lc ) ? p->rc : p->lc; //r的兄弟
   if ( IsBlack ( s ) ) { //兄弟s为黑
      BinNodePosi(T) t = NULL; //s的红孩子(若左、右孩子皆红,左者优先;皆黑时为NULL)
      if ( IsRed ( s->rc ) ) t = s->rc; //右子
      if ( IsRed ( s->lc ) ) t = s->lc; //左子
      if ( t ) { //黑s有红孩子:BB-1
         //*DSA*/printf("  case BB-1: Child ("); print(s->lc); printf(") of BLACK sibling ("); print(s); printf(") is RED\n");
         RBColor oldColor = p->color; //备份原子树根节点p颜色,并对t及其父亲、祖父
      // 以下,通过旋转重平衡,并将新子树的左、右孩子染黑
         BinNodePosi(T) b = FromParentTo ( *p ) = rotateAt ( t ); //旋转
         if ( HasLChild ( *b ) ) { b->lc->color = RB_BLACK; updateHeight ( b->lc ); } //左子
         if ( HasRChild ( *b ) ) { b->rc->color = RB_BLACK; updateHeight ( b->rc ); } //右子
         b->color = oldColor; updateHeight ( b ); //新子树根节点继承原根节点的颜色
         //*DSA*/printBinTree(b, 0, 0);
      } else { //黑s无红孩子
         s->color = RB_RED; s->height--; //s转红
         if ( IsRed ( p ) ) { //BB-2R
            //*DSA*/printf("  case BB-2R: Both children ("); print(s->lc); printf(") and ("); print(s->rc); printf(") of BLACK sibling ("); print(s); printf(") are BLACK, and parent ("); print(p); printf(") is RED\n"); //s孩子均黑,p红
            p->color = RB_BLACK; //p转黑,但黑高度不变
            //*DSA*/printBinTree(p, 0, 0);
         } else { //BB-2B
            //*DSA*/printf("  case BB-2R: Both children ("); print(s->lc); printf(") and ("); print(s->rc); printf(") of BLACK sibling ("); print(s); printf(") are BLACK, and parent ("); print(p); printf(") is BLACK\n"); //s孩子均黑,p黑
            p->height--; //p保持黑,但黑高度下降
            //*DSA*/printBinTree(p, 0, 0);
            solveDoubleBlack ( p ); //递归上溯
         }
      }
   } else { //兄弟s为红:BB-3
      //*DSA*/printf("  case BB-3: sibling ("); print(s); printf(" is RED\n"); //s红(双子俱黑)
      s->color = RB_BLACK; p->color = RB_RED; //s转黑,p转红
      BinNodePosi(T) t = IsLChild ( *s ) ? s->lc : s->rc; //取t与其父s同侧
      this->_hot = p; FromParentTo ( *p ) = rotateAt ( t ); //对t及其父亲、祖父做平衡调整
      //*DSA*/printBinTree<T>(s, 0, 0);
      solveDoubleBlack ( r ); //继续修正r处双黑——此时的p已转红,故后续只能是BB-1或BB-2R
   }
}

8.3.8 RBTree.h

#include "BST.h"
#include<iostream>
#define IsBlack( p ) ( ! (p)|| ( RB_BLACK == (p)->color ))
#define IsRed(p) ( ! IsBlack(p) )
#define BlackHeightUpdated(x) (( stature( (x).lc ) == stature( (x).rc )) && ( (x).height == ( IsRed( &x ) ? stature( (x).lc ) : stature((x).lc) + 1)))
template <typename T> class RedBlack : public BST<T> { //RedBlack树模板类
protected:
   void solveDoubleRed ( BinNodePosi(T) x ); //双红修正
   void solveDoubleBlack ( BinNodePosi(T) x ); //双黑修正
   int updateHeight ( BinNodePosi(T) x ); //更新节点x的高度
public:
   BinNodePosi(T) insert ( const T& e ); //插入(重写)
   bool remove ( const T& e ); //删除(重写)
// BST::search()等其余接口可直接沿用
};

template <typename T> int RedBlack<T>::updateHeight ( BinNodePosi(T) x ) { //更新节点高度
   x->height = max ( stature ( x->lc ), stature ( x->rc ) ); //孩子一般黑高度相等,除非出现双黑
   /*DSA*/// 红黑树中各节点左、右孩子的黑高度通常相等
   /*DSA*/// 这里之所以取更大值,是便于在删除节点后的平衡调整过程中,正确更新被删除节点父亲的黑高度
   /*DSA*/// 否则,rotateAt()会根据被删除节点的替代者(高度小一)设置父节点的黑高度
   return IsBlack ( x ) ? x->height++ : x->height; //若当前节点为黑,则计入黑深度
} //因统一定义stature(NULL) = -1,故height比黑高度少一,好在不致影响到各种算法中的比较判断

template <typename T> BinNodePosi(T) RedBlack<T>::insert ( const T& e ) { //将e插入红黑树
   BinNodePosi(T) & x = BST<T>::search ( e ); if ( x ) return x; //确认目标不存在(留意对_hot的设置)
   x = new BinNode<T> ( e, this->_hot, NULL, NULL,-1  ); BST<T>::_size++; //创建红节点x:以_hot为父,黑高度-1
   //cout<<e<<"here  "<<x->color<<endl;
   solveDoubleRed ( x ); return x ? x : BST<T>::_hot->parent; //经双红修正后,即可返回
} //无论e是否存在于原树中,返回时总有x->data == e

/******************************************************************************************
 * RedBlack双红调整算法:解决节点x与其父均为红色的问题。分为两大类情况:
 *    RR-1:2次颜色翻转,2次黑高度更新,1~2次旋转,不再递归
 *    RR-2:3次颜色翻转,3次黑高度更新,0次旋转,需要递归
 ******************************************************************************************/
template <typename T> void RedBlack<T>::solveDoubleRed ( BinNodePosi(T) x ) { //x当前必为红
   if ( IsRoot ( *x ) ) //若已(递归)转至树根,则将其转黑,整树黑高度也随之递增
      {  this->_root->color = RB_BLACK; this->_root->height++; return;  } //否则,x的父亲p必存在
   BinNodePosi(T) p = x->parent; if ( IsBlack ( p ) ) return; //若p为黑,则可终止调整。否则
   BinNodePosi(T) g = p->parent; //既然p为红,则x的祖父必存在,且必为黑色
   BinNodePosi(T) u = uncle ( x ); //以下,视x叔父u的颜色分别处理

   if ( IsBlack ( u ) ) { //u为黑色(含NULL)时 //*DSA*/printf("  case RR-1:\n");
       //cout<<u->data<<"here"<<endl;
      if ( IsLChild ( *x ) == IsLChild ( *p ) ) {//若x与p同侧(即zIg-zIg或zAg-zAg),则
         p->color = RB_BLACK;
         } //p由红转黑,x保持红
      else{ //若x与p异侧(即zIg-zAg或zAg-zIg),则
         x->color = RB_BLACK;
         } //x由红转黑,p保持红
      g->color = RB_RED;
 //g必定由黑转红
// 以上虽保证总共两次染色,但因增加了判断而得不偿失
// 在旋转后将根置黑、孩子置红,虽需三次染色但效率更高
      BinNodePosi(T) gg = g->parent; //曾祖父(great-grand parent)
       BinNodePosi(T) r;//临时变量
        if(gg==NULL){//是否为根
            //cout<<"herere"<<endl;
            r=BST<T>::_root=BST<T>::rotateAt ( x );
        }
        else if(g==gg->lc){
            r=gg->lc=BST<T>::rotateAt ( x );
        }
        else{
            r=gg->rc=BST<T>::rotateAt ( x );
        }
      //BinNodePosi(T) r =FromParentTo ( *g )=BST<T>::rotateAt ( x ); //调整后的子树根节点

      r->parent = gg; //与原曾祖父联接
      //cout<<r->parent->data<<endl;
      //cout<<r->data<<endl;
      //cout<<r->lc->data<<endl;
     // cout<<r->rc->data<<endl;
   } else { //若u为红色 //*DSA*/printf("  case RR-2:\n");
      p->color = RB_BLACK; p->height++; //p由红转黑
      u->color = RB_BLACK; u->height++; //u由红转黑
      if ( !IsRoot ( *g ) ) g->color = RB_RED; //g若非根,则转红
      solveDoubleRed ( g ); //继续调整g(类似于尾递归,可优化为迭代形式)
   }
}
template <typename T> bool RedBlack<T>::remove ( const T& e ) { //从红黑树中删除关键码e
   BinNodePosi(T) & x = BST<T>::search ( e ); if ( !x ) return false; //确认目标存在(留意_hot的设置)
   //cout<<"here456"<<endl;
   BinNodePosi(T) r = removeAt ( x, this->_hot );//此时节点为空


   if ( !(--BST<T>::_size ) ) return true; //实施删除
// assert: _hot某一孩子刚被删除,且被r所指节点(可能是NULL)接替。以下检查是否失衡,并做必要调整
   if ( ! BST<T>::_hot ) //若刚被删除的是根节点,则将其置黑,并更新黑高度
      { BST<T>::_root->color = RB_BLACK; updateHeight ( BST<T>::_root ); return true; }
// assert: 以下,原x(现r)必非根,_hot必非空
   if ( BlackHeightUpdated ( *BST<T>::_hot ) )
    return true; //若所有祖先的黑深度依然平衡,则无需调整
   if ( IsRed ( r ) ) //否则,若r为红,则只需令其转黑
      { r->color = RB_BLACK; r->height++; return true; }
// assert: 以下,原x(现r)均为黑色
   //*DSA*/printBinTree(_hot, 0, 0);
   solveDoubleBlack ( r );
   return true; //经双黑调整后返回
} //若目标节点存在且被删除,返回true;否则返回false


/******************************************************************************************
 * RedBlack双黑调整算法:解决节点x与被其替代的节点均为黑色的问题
 * 分为三大类共四种情况:
 *    BB-1 :2次颜色翻转,2次黑高度更新,1~2次旋转,不再递归
 *    BB-2R:2次颜色翻转,2次黑高度更新,0次旋转,不再递归
 *    BB-2B:1次颜色翻转,1次黑高度更新,0次旋转,需要递归
 *    BB-3 :2次颜色翻转,2次黑高度更新,1次旋转,转为BB-1或BB2R
 ******************************************************************************************/
template <typename T> void RedBlack<T>::solveDoubleBlack ( BinNodePosi(T) r ) {

   BinNodePosi(T) p;
   if(r!=NULL){
    p=r->parent;
   }else{
     p=this->_hot;
   }

   //cout<<p->lc->lc->data<<"kkk"<<endl;
   if ( !p ) return; //r的父亲
    //cout<<"here852"<<endl;
    //cout<<p->lc->lc->data<<"kkk"<<endl;
   BinNodePosi(T) s;
   if(r == p->lc){
    s=p->rc;
   }
   else{
    s=p->lc;
   }
   //cout<<s->data<<"ksp"<<endl;
   //cout<<p->lc->lc->data<<"kkk"<<endl;
   //cout<<s->color<<endl;
   if ( IsBlack ( s ) ) { //兄弟s为黑
       //cout<<s->data<<"ksp100"<<endl;
      BinNodePosi(T) t = NULL; //s的红孩子(若左、右孩子皆红,左者优先;皆黑时为NULL)
      if ( IsRed ( s->rc ) ) t = s->rc; //右子
      if ( IsRed ( s->lc ) ) t = s->lc; //左子
      //cout<<t->data<<"ksp100"<<endl;
      if ( t!=NULL ) { //黑s有红孩子:BB-1
         //*DSA*/printf("  case BB-1: Child ("); print(s->lc); printf(") of BLACK sibling ("); print(s); printf(") is RED\n");
         RBColor oldColor = p->color; //备份原子树根节点p颜色,并对t及其父亲、祖父
      // 以下,通过旋转重平衡,并将新子树的左、右孩子染黑
        //cout<<t->data<<"ksp100"<<endl;
         BinNodePosi(T) b ;//定义临时变量
         if((p->parent)==NULL){
            b=BST<T>::_root=BST<T>::rotateAt ( t );
            //cout<<b->data<<"here417"<<endl;
         }
         else if(p==(p->parent->lc)){
            b=(p->parent)->lc=BST<T>::rotateAt ( t );
         }
         else{
            b=(p->parent)->rc=BST<T>::rotateAt ( t );
         }

         //FromParentTo ( *p ) = BST<T>::rotateAt ( t ); //旋转

         if ( HasLChild ( *b ) ) { b->lc->color = RB_BLACK; updateHeight ( b->lc ); } //左子
         if ( HasRChild ( *b ) ) { b->rc->color = RB_BLACK; updateHeight ( b->rc ); } //右子
         b->color = oldColor; updateHeight ( b ); //新子树根节点继承原根节点的颜色
         //*DSA*/printBinTree(b, 0, 0);
      } else { //黑s无红孩子
         s->color = RB_RED; s->height--; //s转红
         if ( IsRed ( p ) ) { //BB-2R
            //*DSA*/printf("  case BB-2R: Both children ("); print(s->lc); printf(") and ("); print(s->rc); printf(") of BLACK sibling ("); print(s); printf(") are BLACK, and parent ("); print(p); printf(") is RED\n"); //s孩子均黑,p红
            p->color = RB_BLACK; //p转黑,但黑高度不变
            //*DSA*/printBinTree(p, 0, 0);
         } else { //BB-2B
            //*DSA*/printf("  case BB-2R: Both children ("); print(s->lc); printf(") and ("); print(s->rc); printf(") of BLACK sibling ("); print(s); printf(") are BLACK, and parent ("); print(p); printf(") is BLACK\n"); //s孩子均黑,p黑
            p->height--; //p保持黑,但黑高度下降
            //*DSA*/printBinTree(p, 0, 0);
            solveDoubleBlack ( p ); //递归上溯
         }
      }
   } else { //兄弟s为红:BB-3
       //cout<<"hkkk"<<endl;
      //*DSA*/printf("  case BB-3: sibling ("); print(s); printf(" is RED\n"); //s红(双子俱黑)
      s->color = RB_BLACK; p->color = RB_RED; //s转黑,p转红
      BinNodePosi(T) t = IsLChild ( *s ) ? s->lc : s->rc; //取t与其父s同侧
      this->_hot = p;

       FromParentTo ( *p ) = BST<T>::rotateAt ( t ); //对t及其父亲、祖父做平衡调整
      //*DSA*/printBinTree<T>(s, 0, 0);
      solveDoubleBlack ( r ); //继续修正r处双黑——此时的p已转红,故后续只能是BB-1或BB-2R
   }
}

8.3.9 RBTree.h测试

#include"RBTree.h"
int main(){
    RedBlack<int> rb;
    cout << "----------------初始节点转换为根" << endl;
    rb.insert(10);
    cout<<rb.root()->data<<endl;//10
    cout << rb.root()->color << endl;//1,树根自动转为黑
    rb.insert( 5 );
    rb.insert( 12 );
    cout << rb.root()->lc->data<<endl;//5
    cout << rb.root()->lc->color << endl;//0
    cout << rb.root()->rc->data <<endl;//12
    cout << rb.root()->rc->color << endl;//0
    cout << "---------------- 插入节点8 RB-2" << endl;
    //RR-2
    rb.insert( 8 );
    cout << rb.root()->data<<endl;//10
    cout << rb.root()->color << endl;//1
    cout << rb.root()->lc->data<<endl;//5
    cout << rb.root()->lc->color << endl;//0
    cout << rb.root()->rc->data <<endl;//12
    cout << rb.root()->rc->color << endl;//0
    cout << rb.root()->lc->rc->data << endl;//8
    cout << rb.root()->lc->rc->color << endl;//0
    cout << "---------------- " << endl;
    /*
    *        10(1)
    *   5(1)        12(1)
    *     8(0)
    */

    //rb.traverse(rb.root(),visit);
    cout << "---------------- 插入节点6 RB-1" << endl;
    //RR-1
    rb.insert( 6 );
    cout << rb.root()->data<<endl;//10
    cout << rb.root()->color << endl;//1
    cout << rb.root()->lc->data<<endl;//6
    cout << rb.root()->lc->color << endl;//1
    cout << rb.root()->rc->data <<endl;//12
    cout << rb.root()->rc->color << endl;//1
    cout << rb.root()->lc->lc->data << endl;//5
    cout << rb.root()->lc->lc->color << endl;//0
    cout << rb.root()->lc->rc->data << endl;//8
    cout << rb.root()->lc->rc->color << endl;//0
    cout << "---------------- " << endl;

    /*初始状态
    *        10(1)
    *   5(1)        12(1)
    *     8(0)
    *       6(0)
    叔叔是黑色的,父亲是爷爷的右节点,且当前节点是其父节点的右孩子,
    将“父节点”设为“黑色”, 将“祖父节点”设为“红色”,
    *        10(1)
    *   5(0)        12(1)
    *     8(1)
    *       6(0)
    以“祖父节点”为支点进行左旋;
    *        10(1)
    *    6(1)          12(1)
    * 5(0)    8(0)
    */

    cout << "---------------- 删除节点 12 " << endl;
    rb.remove( 12 );//删除
    cout << rb.root()->data<<endl;//6
    cout << rb.root()->color << endl;//1
    cout << rb.root()->lc->data<<endl;//5
    cout << rb.root()->lc->color << endl;//1
    cout << rb.root()->rc->data <<endl;//10
    cout << rb.root()->rc->color << endl;//1
    cout << rb.root()->rc->lc->data << endl;//8
    cout << rb.root()->rc->lc->color << endl;//0

    cout << "---------------- " << endl;
    /*
     删除节点12过程描述
     12为黑节点
     查找的节点值等于12的节点
     发现未进行双黑旋转
     被删除节点是黑色的,它的兄弟节点是黑色的,
     它的右孩子是红色的,左孩子随意(交换父亲和兄弟节点的颜色 ,染黑
     兄弟节点的右孩子然后以父亲节点为支点进行左旋转)
    */
    /*
    *           6(1)
    *    5(1)          10(1)
    *               8(0)
    */
    cout << "---------------- 删除节点 10 " << endl;
    rb.remove(10 );
    cout << rb.root()->data<<endl;//6
    cout << rb.root()->color << endl;//1
    cout << rb.root()->lc->data<<endl;//5
    cout << rb.root()->lc->color << endl;//1
    cout << rb.root()->rc->data <<endl;//8
    cout << rb.root()->rc->color << endl;//1
    cout << "---------------- " << endl;

    /*
    *           6(1)
    *    5(1)          8(1)
    *
    */
    cout << "---------------- 删除节点 5 " << endl;
    //rb.remove( 5 );
    cout << rb.root()->data<<endl;//6
    cout << rb.root()->color << endl;//1
    cout << rb.root()->rc->data <<endl;//8
    cout << rb.root()->rc->color << endl;//0
    cout << "---------------- " << endl;

       /*
    *           6(1)
    *                   8(0)
    *
    */
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值