红黑树¶
约 2648 个字 141 行代码 15 张图片 预计阅读时间 11 分钟
红黑树介绍¶
红黑树是第二种平衡二叉搜索树,为了解决普通二叉搜索树的效率问题,红黑树在控制平衡时选择近似平衡而不是AVL树的绝对平衡,每一个节点都会存储一个表示颜色的属性,包括红色和黑色,通过对颜色的控制,红黑树可以保证没有一条路径会比其他路径的长度长出两倍
红黑树的性质
- 根节点一定是黑色
- 一条简单路径下不会出现连续的红色节点,如果父亲节点为红色,则其孩子节点一定为黑色,如果父亲节点为黑色,则没有限制
- 对于每个节点来说,从该节点开始到其后代所有节点的简单路径上均包含相同数量的黑色节点
- (了解)每个空叶子结点都是黑色的
Note
在上图中,NIL表示空叶子节点,以NIL作为结束条件,一共有11条路径,需要注意不是以叶子节点为结束条件(即不是7条路径)
满足上面的四个性质,红黑树就可以保证其相对平衡的条件:红黑树可以保证最长路径会比最短路径的长度长出两倍。
Info
最短路径:节点颜色为全黑的路径,此时黑色节点的个数即为路径的长度 最长路径:节点颜色为红色和黑色交替出现的路径
红黑树的性质与平衡控制关系¶
因为黑色节点的个数可以决定一条路径的长度,假设黑色节点的个数为h,则最短路径的长度也为h,满足第二条规则时,红色节点的个数不会超过黑色节点(1个或者h个),从而最长路径的最大长度为2h,因为红色节点是在黑色节点出现后才会出现。
如果按照下图的插入方式导致红色节点连续出现:
违反了规则二,此时最短路径的长度的两倍会小于最长路径的长度,从而打破了红黑树的平衡。
如果插入的27为黑色节点,则最长路径的长度增加,并且黑色节点的个数也增加,违反了规则三,此时对于其他路径来说也需要增加一个黑色节点。
所以为了维持平衡,不可以插入黑色节点,此时最长路径的长度刚好为2h,如下图所示:
综上所述,在满足第二条规则和第三条规则下可以保证红黑树的最长路径始终不会超过最短路径的2倍
红黑树节点的插入¶
根据红黑树的性质可以看出红黑树如何控制高度近似平衡,但是如果插入了节点,可能会破坏原有的平衡,此时需要通过重新填色或者旋转调整使红黑树重新达到平衡
在前面的分析中可以得知,如果插入的节点是黑色节点,可能会导致每一条路径都需要多一个黑色节点,为了更加方便处理,规定插入的节点是红色节点
情况1:不需要调整¶
如果插入的节点是红色节点,并且其父亲节点是黑色节点,此时不需要进行任何处理,当父亲节点是黑色节点时,保证了规则三没有违背,因为黑色节点的个数决定了高度,插入前如果保证原树是红黑树,那么高度一定满足红黑树的近似平衡,并且此时插入红色节点也不会违背规则二,如下图的一种情况所示:
情况2:uncle
节点为红色¶
如果cur
的节点是红色,并且其父亲节点(假设为parent
)是红色节点,此时说明父亲的兄弟节点(假设为uncle
)也一定为红色,因为插入前一定是红黑树(插入前不是红黑树那么插入前就已经出现了不平衡,需要进行调整),当父亲节点时红色,说明父亲节点所在的路径缺少黑色节点,违反了规则三。父亲节点的父亲节点(假设为grandfather
)也一定为黑色,如果为红色则违反了第二条规则所以插入前的状态应该为:
Note
cur
节点可能为新增的红色节点,也可能为上一次调整变为的红色节点
-
假设a、b、c、d和e为黑色节点个数为0的红黑树,此时
cur
为新插入节点如下图所示:因为出现了连续的红色节点,为了恢复红黑树的平衡,此时需要进行调整,因为
uncle
节点为红色,为了保证每条路径上都有一个黑色节点并且保证没有连续的红色节点出现,将parent
节点的颜色改为黑色,将uncle
节点的颜色改为黑色,将grandfather
节点改为红色(如果grandfather
节点为根节点则再处理为黑色),处理完后,cur = grandfather
继续向上调整直到遇到根节点:Note
对于
uncle
为红色的情况来说,不需要考虑插入位置在parent
左或者右、parent
在grandfather
的左或者右已经uncle
在grandfather
的左或者右,因为不论是哪种情况,本质都是将uncle
和parent
变为黑色增加两边路径的黑色节点使其满足规则二和规则三 -
假设a、b、c、d和e为黑色节点个数大于0,此时
cur
为上一次调整变成的红色节点,如下图所示:对于当前情况来说,只有
cur
位置的节点是红色,其余几棵子树已经通过调整变成了符合规则的红黑树,所以也可以归类为上面的情形,处理方式与上面相同
情况3:uncle
节点为黑色¶
uncle
节点为黑色时一共有两种情况:
uncle
节点不存在uncle
节点存在且为黑
因为红黑树规定下空节点是黑色的,所以uncle
节点不存在与存在且为黑可以视为一种情况,下面主要以uncle
节点不存在进行分析,对于uncle
节点存在且为黑的情况给出一种分析,剩下与uncle
节点不存在的情况类似
Note
下面分析中,uncle
节点不存在时将不展示NIL
节点
当cur
节点在parent
的右子树并且parent
在grandfather
的右子树时,并且uncle
不存在时,此时需要进行左单旋,将parent
的颜色更新为黑色,grandfather
的颜色更新为红色,因为如果仅仅是将parent
变成黑色,则依旧不满足规则三,其余路径还是少一个黑色节点,通过左单旋使得当前子树的根节点变为黑色时可以使当前根节点出发的所有路径都至少有一个黑色节点,如下图所示:
当cur
节点在parent
的左子树并且parent
在grandfather
的左子树时,此时需要进行右单旋,将parent
的颜色更新为黑色,grandfather
的颜色更新为红色,原因类比左单旋,过程如下图所示:
当cur
节点在parent
的左子树并且parent
在grandfather
的右子树时,此时需要进行右左双旋,将cur
的颜色更新为黑色,将grandfather
的颜色更新为红色,过程如下:
当cur
节点在parent
的右子树并且parent
在grandfather
的左子树时,此时需要进行左右双旋,将cur
的颜色更新为黑色,将grandfather
的颜色更新为红色,过程如下:
如果uncle
节点本身存在,那么经过uncle
节点的路径下方的两个子树为红色,而parent
插入节点前至少会有一个黑色节点,如下图所示:
此时在parent
的右侧插入一个cur
节点(本身就是红色节点cur
节点也是如此)如下:
此时如果只是对parent
节点的颜色进行改变,则会出现parent
所在路径比uncle
所在路径多一个黑色节点,所以为了解决这个问题,单单改变颜色无法解决,当cur
在parent
的右边时,只需要一次左单旋即可,而cur
在parent
的左边时,需要先进行右旋再进行左旋,以右左双旋为例,如下图所示:
对于其他两种情况也是一样的道理,此处不再赘述
总结与代码实现¶
红黑树的插入过程一共有三种情况:
- 当插入节点的父亲节点为黑色时,直接插入节点即可,不需要进行任何调整
- 当uncle节点为红色时,
cur
节点的parent
节点和uncle
节点均变为黑色,将所在子树的根节点变为红色,如果子树的根节点为整棵树的根节点,则再处理为黑色 - 当uncle节点为黑色(包括空节点的黑色和存在且为黑色节点)时,需要进行旋转
parent
和cur
节点是同方向时,进行一次旋转(左旋或者右旋),将parent
节点变为黑色,将grandfather
节点变为红色,此时因为parent是本棵子树的根,所以更新完后当前子树也是一棵红黑树,颜色是黑色不需要再进行调整parent
和cur
节点不是同方向时,进行两次旋转(先左后右或者先右后左),再将cur
节点变为黑色,grandfather
节点变为红色,更新完后当前子树也是一棵红黑树,并且因为cur
的颜色是黑色不需要再进行调整
代码实现:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
|
红黑树的删除(待实现)¶
红黑树的效率¶
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(\(log_{2}{N}\)),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多