zoukankan      html  css  js  c++  java
  • 红黑树及其插入与删除操作

    红黑树及其插入与删除操作[未完待续]

    红黑树的定义

    ​ 红黑树是一种自平衡二叉查找树。它相比于平衡二叉树的优点在于,其的特性可以让其在任何条件下保持树的高度小于等于log n,所以其即使在最坏条件下对于增删查改这样的基本操作也能保持O(log n)的时间复杂度;另外,相比于严格平衡的AVL树,红黑树是一种不严格的平衡二叉树,这样就减少了在实际使用过程中为了保持树的平衡性而在增删节点时不断进行的平衡化操作,效率更高。这些特性使得它得到了相比平衡二叉树更加广泛的应用,例如C++的STL中的map、set以及Java中的TreeMap都是利用它实现的

    红黑树有五条性质:

    1. 每一个节点要么是红色,要么是黑色

    2. 根节点是黑色的

    3. 每个终节点是黑色的

      注意,这里的终节点和我们平常理解的叶子节点是不一样的。《Algorithms》中用sentinel node来表示,它指的是我们所说的叶子节点的子节点,其parent、left、right属性值都是null。很显然,它也是需要实例化的,为了节省不必要的开销,书中将所有的终节点指针都指向同一个对象,当然这样就没有办法用该节点的parent属性了

      图片1

    4. 如果一个节点是红色的,则它的两个孩子节点一定是黑色的

    5. 对于每一个节点,从该节点到终节点的每一条路径上有相同数量的黑色节点

    证明:有n个非终节点的红黑树的高度最高为2log(n+1)

    引理:以节点x为根的子树包括至少2^(bh(x))-1个非终节点。其中bh(x)叫黑色高度,指的是从这个点(不包括该点)到终结点(包括终结点)的黑色点个数

    证明引理:使用数学归纳法

    1. 如果子树x的高度为0,则也就是说这个子树只有x这一个节点,所以内部有2^(0)-1=0个非终节点。

    2. 如果子树高度不为0。由于其为黑色节点(为什么一定是黑色节点?因为红黑树的根必须是黑色的),其子节点可以说红色,也可以是黑色,所以对于其两个子节点来说,黑色高度可能是bh(x)[子节点为红色的情况],也可能是bh(x)-1[子节点为黑色的情况,也是能构成子树的情况,下面讨论的就是这种]。如果假设该引理正确,则对于其子节点来说,由其为根的子树至少有2(bh(x)-1)-1个非终节点,那么对于节点x,其非终结点数量就是2*2(bh(x)-1)-2+1=2^(bh(x))-1。(注意最后加的那个1是节点x自己)

    3. 引理得证

    证明原命题

    由于性质4,从根节点到终结点的每一条路径上,至少有一半的节点是黑色的(因为出现红色节点必须跟着一对黑色子节点,而黑色节点则是没有要求的),所以我们假设一棵树高度为h,那么这样的一条支路上至少有h/2个黑色节点,也就是说这棵树的黑色高度至少为h/2,由引理可知,该树至少有2^(h/2)-1个非终节点,方程化简一下就能得到命题结果。

    由这个命题我们可以看出来,节点的高度一定是log级别的,那么由于其是有序的,在增删查改时采用的是二叉搜索树通用的方法,所以遍历的高度不会高于树的高度,这就是红黑树能保持O(logn)的原因。


    红黑树的插入

    拖更了~


    红黑树的删除

    红黑树的删除由于情况之多(有六种不同情况),所以是它最难的地方,这里我就自己掌握的情况尝试进行讲解,许多资料当然还是参考其他博主和书籍的。

    还记得我们在AVL树中删除节点时的操作吗?对于有两个子节点的节点,我们不能直接删除,而是需要

    1. 找到该节点的前驱或者后继
    2. 将前驱或者后继的值赋给该节点
    3. 删除该前驱或者后继

    由于前驱或者后继一定是叶子节点或者有一个子节点的节点,所以最终的删除问题可以归结为两类:

    1. 删除的节点是叶子节点
    2. 删除的节点只有一个子节点

    我们要搞清楚一个问题,对于红黑树,我们关注的不是节点是怎样被删掉的,而是关注在删除节点时如何保证红黑树的性质不被破坏。这里的逻辑是:

    1. 确定要删除的节点
    2. 对树进行变换(其实涉及到的范围主要还是在要删除的节点附件)
    3. 删除该节点

    第三部分很简单,我们先来看一下:如果要删除的节点是叶子节点,直接删除就好;如果有一个子节点,则用它来代替该节点原来的位置。

    好了,第三部分回答完了,现在我们主要来看第二部分。

    我们要删除的节点要么是红色的,要么是黑色的。如果是红色的,就没有什么影响,第2步是不需要的,直接删除就好了。关键我们要分析如果它是黑色的应该怎么办。

    先上一张来自博主(https://www.cnblogs.com/nullllun/p/8214599.html)的图

    根据图片我们可以看到,这里把删除时候的情况分为了六种,为了之后叙述的简单,我们做一些符号上的约定:

    1. N表示待删除节点
    2. P表示N的双亲节点
    3. S表示兄弟节点,即P的另外一个子节点
    4. SL表示S的左子节点
    5. SR表示S的右子节点

    现在我们来看六种情况:

    1. 树只有一个节点(当然就是N了)
    2. N、P、SL、SR为黑色,S为红色
    3. N、S、SL、SR为黑色,P为红色
    4. N、S、SR为黑色,P哪种颜色均可,SL为红色
    5. N、S为黑色,SL、P哪种颜色均可,SR为黑色
    6. N、S、P、SL、SR均为黑色

    注意,我这里的分类没有按照图片中的顺序来,是为了尽可能显示分类的完备性,之后讲解还是会按图中顺序来的。


    分析分类的完备性

    1. 没有红色节点:这种情况包括了

    2. 有一个红色节点:这种情况包括了

    3. 有两个红色节点:根据红黑树的性质,能出现同时两个为红色节点的情况只有:

      1. PSL
      2. PSR
      3. SLSR

      这三种情况也包括了

    4. 有三个红色节点:只有PSLSR这一种,也包括了

    5. 不可能有四个及以上的红色节点

    也就是说,对于每一种可能的情况,我们都可以给出要达到红黑树平衡条件应该进行的变换,只要我们分析得知每一种情况的变换都是正确的,那删除的问题就解决了!剩下的就是编程问题了


    下面正式进行分析,具体的操作请见图片

    对于情况一不解释,只有一个节点,删了就删了

    情况二:原来1-6节点从子树根节点开始经过的黑色节点数为2,2,2,2,2,2。对R节点进行左旋并交换P和S的颜色。问题转换到了P为红色,其子节点N和SL为黑色的问题,即情况四,此时按情况四的做法交换P和SL的颜色,变换之后1-6经过的黑色节点数为2,2,2,2,2,2,所以没有变化。

    情况三:将S变成红色即可,删除N后两叉的黑色高度与一开始依然保持了一致

    情况四:交换P和S的颜色,变成和情况三一样的状态

    情况五:将S节点右旋并交换SL和S的颜色,此时问题转换为了情况六,操作方法看情况六

    情况六:将P节点左旋并与S交换颜色,再将SR变为黑色。假设P和SL是红色(是红是黑都无妨,这里只是选了一种情况来计数),原来1,2,3,4,5,6节点从子树根节点开始经过的黑色节点数为1,1,1,1,1,1。变换之后经过的黑色节点数依然是1,1,1,1,1,1(注意N是被删了的,1或者2在删除后是直接和P相联接的),所以没有变化.

    这里很重要的一点是我们考虑的应该是删除对整个树的影响,这里分析的1-6在片段中经过的黑色个数,可以理解为这一段树和其余段之间的”接口“就是1-6,这一段树下面的节点的黑色高度当然不会变,而我们证明了上面的节点黑色高度也不变,就能证明它没有影响到各个支路上黑色节点的总数量,也就是从”最上面“的根节点通过各个支路到终结点的黑色节点数量。这个可以好好理解下,很容易理解的。


    程序实现

    拖更了~


    Reference

    1. 《Algorithms》
    2. https://www.cnblogs.com/nullllun/p/8214599.html
    3. https://zhuanlan.zhihu.com/p/25402654
  • 相关阅读:
    找到数组或整数列表中连续子序列的最大和
    编写一个调用的函数,该函数接受一个括号字符串,并确定括号的顺序是否有效
    SRS流媒体服务器搭建及拉取摄像头视频流经opencv处理后再推流至SRS
    (pymysql.err.OperationalError) (1055, "Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column
    微信商户转帐到个人零钱
    双色球1千万,等你来拿!
    python后端开发面试总结
    alipay接入步骤
    Mongodb简单操作
    flask基础
  • 原文地址:https://www.cnblogs.com/jiading/p/11508828.html
Copyright © 2011-2022 走看看