红黑树

红黑树的由来

二叉搜索树的操作依赖于其树高 h h ,在相对平衡的状态下,基本操作的平均时间复杂度是 θ(lgn)。然而在极端的情况下,严重倾斜的二叉搜索树会退化成链表,基本操作的时间复杂度为 θ(n) θ ( n ) 。于是在二叉搜索树基础上,发展出一些自平衡二叉树,这些树会自动调整自身的指针结构(父子关系),以达到相对平衡。红黑树是一种自平衡二叉树。

红黑树的性质

红黑树的节点基本与BST的节点相同,不同在于每个节点多了一个属性“颜色”。
红黑树有五大性质:

  1. 每个节点是黑色或红色。
  2. 根节点是黑色。
  3. 叶节点(NIL)为黑色。
  4. 红色节点的两个子节点必须是红色。
  5. 对于任意一个节点,所以从这个节点开始,到一片叶子(NIL)的任意简单路径上的黑色节点数目相同。
    性质5所定义的数目,也称作 blackheight b l a c k − h e i g h t (简称bh),注意的是,一个节点本身不计入这个节点的bh。

CLRS中的红黑树
引理 13.1: 带有n个节点的红黑树的高度最多为 2lg(n+1) 2 l g ( n + 1 )


旋转

旋转(rorate)是红黑树区别于BST的一个特点,由于红黑树节点本身有颜色属性,所以当插入或删除一个节点时候,红黑树的性质可能被破坏,所以要进行修复工作,而这个过程需要旋转这种操作。旋转又分为左旋和右旋,此二者分别为镜像。图示:
这里写图片描述
左旋伪代码如下:
这里写图片描述

右旋和左旋为镜像,按照图示,把 y 作为传入的参数,x 是函数中声明的指针,并把代码中的 left 和 right 互换就可以了。
注意的是:

  1. 左旋和右旋并非可逆操作。 对同一节点 x 先后进行左旋和右旋,x附近的结构并非原来的结构,先对 x 进行左旋,然后对 x 的父节点进行右旋才恢复原来的结构。
  2. 旋转对bh的影响。
    这里写图片描述

插入

完整的插入过程包含两部分:

  1. 按照BST的插入方式,通过一系列关键字比较将节点插入正确的位置。在插入时,我们将新插入的节点的颜色涂为红色,方便下面的讨论。
  2. 判断新插入的节点是否会破坏红黑树性质,如果会,那么进行修复(fixup)。
    插入的伪代码如下:
    这里写图片描述
    那么问题就来了,什么情况会引起红黑树性质被破坏?又有哪些性质会被破坏?如何修复被破坏了的性质?我们假设:在插入之前,红黑树是完好的,没有一条性质被破坏。在分类讨论前,先回顾红黑树的5条性质,看看哪一些有可能被破坏。

  3. 性质1:这个不会被破坏,节点不会有第三种颜色。

  4. 性质2:我们假设插入的点是树的root,因为这个节点被涂成了红色,那么性质1就被破坏了。
  5. 性质3:在插入过程中,不会修改叶子(NIL)的属性,故不会破坏。
  6. 性质4:当被插入点x的父亲是红色,那么就会破坏
  7. 性质5:因为添加的点是红色的,对bh没有任何影响。所以性质5不会破坏。

那么会同时破坏哪些性质呢?答案是最多只有一条被破坏。被破坏的性质分别是2和4,若x是新的root,那么它的父节点是叶子(NIL),不会破坏性质4。同样,当x的父亲是红色的,那么x也不会是root。

插入之后,进行fixup操作。同样,有z的父节点是祖父节点的左或右孩子两种情况。下面的讨论是前者,后者只需把left和right互换。
当z是root时,只需要把root涂为红色即可。当z不是root且z的父节点是黑色时,更简单了,什么都不用做。当x的父节点是红色时,分如下三种情况(y是z的叔节点):

  • case 1:z.p 和 y 都是红色
  • case 2:z.p 是红色, y是黑色,且 z 是右节点。
  • case 3:z.p 是红色, y是黑色,且 z 是左节点。

伪代码如下:
这里写图片描述
这里写图片描述

这里写图片描述

对insert-fixup后红黑树性质保持的证明如下:
初始化:在进行插入操作前,红黑树性质完好。新增加一个红色节点z。
终止:当z.p是黑色时,循环终止(包含z是root的情况)。
维持:分别对6种case(包括镜像)讨论:
令 G = z.p.p

  • case 1:进行操作后,G的两棵子树bh不变,而G被涂为了红色。G.p的颜色在这次不会改变。
    • 1.在下一次循环开始时,G是root,那么循环停止,G被涂为黑色,done。
    • 2.下一次循环时,G不是root,G.p的颜色为黑色,done。
    • 3.下一次循环时,G不是root,G.p为红色,那么违背了性质4,继续递归讨论。
  • case 2 :对 z.p 用一个左旋把case 2 变为case 3。此时z为z.p,左旋没有改变颜色。
  • case 3:对G用右旋,并且把z.p变为黑色。可知,G的颜色一点是黑色。G进行了右旋,那么(z.p, G)这课右子树的bh会加上1,比(z, z.p)这课左子树bh大1,

C++代码

我按照书上的伪代码码c++代码,程序一直在case 1 和case 3那里死循环,气死我了。后来把代码改一改,就成功了,而且经过验证,是完整的红黑树。

template <typename T1>
void RBTree<T1>::insert_fix(RBTree_node<T1> *z)
{           
    cout << "Insertion fixing up...\n";
    struct RBTree_node<T1> *y; 


    while(z->p->color == RED)
    {
        if (z->p->p->l == z->p)
        {
            y = z->p->p->r;
            if (y->color == RED)
            {
                z->p->color = BLACK;
                y->color= BLACK;
                y->p->color = RED;
                z = z->p->p;

                continue; 
            }   

            if (z == z->p->r)
            {   
                z = z->p;
                l_rotate(z);

            }


            z->p->color = BLACK;
            z->p->p->color = RED;
            r_rotate(z->p->p);
        }
        else //Exchange 'left' & 'right'.
        {
            y = z->p->p->l;
            if (y->color == RED)
            {           
                z->p->color = BLACK;
                y->color= BLACK;
                y->p->color = RED;
                z = z->p->p;

                continue;
            }   

            if (z == z->p->l)
            {
                z = z->p;
                r_rotate(z);


            }

            z->p->color = BLACK;
            z->p->p->color = RED;
            l_rotate(z->p->p);
        }

    }
    root->color = BLACK;
}

删除

删除操作分为删除节点和fixup两部分。删除一个节点时,就若它是红色的,那么它的存在对bh没有影响;如果它是黑色的,那么删除它将导致所有包含这个节点的简单路径的bh都减少1。因此fixup的目的在于:通过调整指针结构和节点颜色,使得bh恢复到删除操作前。RBT的删除节点与BST类似,把要删除的节点从树种剥离出,然后把它的直接前驱或直接后继“顶替”它,故这里涉及到移植操作。CLRS中采用用直接后继节点顶替。

移植

这里写图片描述
这里的移植和BST中的移植略有不同。直观地看,移植就是用节点v顶替u的位置,并且处理好指针关系。

删除节点

伪代码如下:
这里写图片描述
删除操作又分为3种情况:

  1. case 1:z的左子节点为叶子NIL。
    这里写图片描述
  2. case 2:z的右子节点为叶子NIL。
    这里写图片描述
  3. case 3:z有两个孩子,且它的直接后继y是z的右子节点的孩子。
    这里写图片描述
  4. case 4:z有两个孩子,且它的直接后继y并非z的子节点的子节点。
    这里写图片描述

根据y-origin-color来判断是否进行fixup操作。注意的是,case 1,2中的y是z的孩子,而case 3 中y是z的直接后继,x为y的右子节点(因为y是z的直接后继,不可能有左子节点)。伪代码的第12 - 13行,考虑了y是NIL(NIL.p = NIL,所以要做一下处理)。
14 - 16行是把y剥离出来,用来顶替z的位置。case 3 和 4 用y顶替了z,并且把y.c涂成z.c,这样,虽然实际上z被删除了,但是看起来像没被删除,那么它也没有违背性质。这样我们可以把精力放在x上。
x的父节点是y,y被剥离出来,这实际就是被删除,那么y可能违背了红黑树性质。根据y原本的情况,若它是黑色,那么包含y的简单路径bh减少1,需要-进行修复。

修复

x为y的子节点,x及其兄弟w,还有w的子节点的颜色会影响修复操作,情况分为四种(实际上还有镜像的四种,共八种):

  1. case 1:w是红色
  2. case 2:w是黑色,且w的两个子节点也是黑色。
  3. case 3:w是黑色,w的左子节点是红色,右子节点是黑色。
  4. case 4:w是黑色,w的右节点是红色(左节点或红或黑)。

这里写图片描述

分析:
可能路径有:
1 – 2 – out (new x.c = red)
1 – 2 – —继续讨论 — out
1–3 – 4 – out
4 — out

  1. case 1,那么通过对x.p进行左旋,并且x.p涂为黑色(w是红色,那么x.p一定为黑),令w = x.p.r(原来的w的左子节点),然后进入case 2 或者 3或者4。
  2. case 2, x的兄弟w为黑色,x与w的父亲颜色可红可黑。因为x子树相对于其兄弟w子树少一个黑色节点,可以将w置为红色,这样,x子树与w子树黑色节点一致,保持了平衡。new x为x与w的父亲。new x相对于它的兄弟节点new w少一个黑色节点。j进入下一个循环。如果new x为红色,则将new x置为黑,则整棵树平衡。
  3. case 3, 把 w.r 和 w 的颜色互换,对w进行右旋,进入case 4。
  4. case 4,x.p涂为黑色,w. r 涂为黑色,对x.p进行左旋,至此,树平衡,性质完好。

红黑树的应用

时间复杂度:
空间复杂度:
与其他数据结构的对比:
应用场景:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值