红黑树相关(待补充)
红黑树的定义与性质
- 红黑树是一种含有红黑结点,并能自平衡的二叉查找树
具有以下性质
- 每个结点要么是黑色,要么是红色
- 根结点是黑色
- 每个叶子结点(NIL)是黑色
- 每个红色结点的两个子结点一定都是黑色
- 任意一个结点到每个叶子结点的路径都包含相同数量的黑结点
其他相似性质定义
- 红链接均为左链接(左子结点是红色的)
- 两个红链接不能相连在一起(根结点是黑色)
- 红黑树是完美黑色平衡
- 空结点的链接颜色是黑色
在实际代码中,可以用常量来表示红黑,调用一个方法可判定是黑是红。代码示例:
private boolean isRed(Node node){
if(node == null) return false;
return node.color == RED;
}
红黑树自平衡最小单元
红黑树的自平衡每次只考虑 CPGU 三代即可,其余部分无需考虑
- 祖父 G
- 父母 P
- 叔叔 U
- 兄弟 B
- 根 R
当前新增结点 C
红黑树自平衡的原子操作
红黑树的自平衡操作包括:变色、旋转。
其中旋转又可以分为左旋和右旋。旋转要有圆心,方向。
左旋转
右旋转
旋转过程的分解分析可以参考 AVL 树的旋转
AVL 树的旋转过程
重点:红黑树的增加与删除,都得遵循最基础的几个性质!!即所有的 “变色”、“旋转” 操作都是为了使得该红黑树满足其性质:
- 根结点是黑色的
- 红色结点的两个子结点是黑色的
- 每个叶子结点 Nil 是黑色的
- 任意一个结点到叶子结点路径中黑色结点的个数相同
红黑树的新增操作(图例)
时间复杂度:O(nlog(n))
需要注意的情况
新增结点需要考虑父结点的情况
新增结点的各种情况
- 情况1:C = root
- 情况2:C.parent = black
- 情况3:C.parent = red && C.uncle = red
- 情况4:C.parent = red && (C.uncle = black || C.uncle is Nil)
对于以上四种情况的新增
图例
该图例仅帮助了解 变色 与 旋转 操作过程,不一定是最优方法。只需要懂得变色与旋转在平衡一颗红黑树是怎样操作即可。
-
初始红黑树(叶子结点都是 NIL)
-
向红黑树中插入 结点 21(新插入的结点都是红色)
从图可发现,该树违反了红黑树的性质:红色结点(22)的两个子结点应该都是黑色的 -
调整红黑树——变色(截取红黑树部分,由前文提到的红黑树最小平衡单元——只考虑三代)
其实可以发现,该红黑树又违反了红黑树性质之——任一结点到叶子结点路径中的黑色结点个数相同
如 25 到 22 右子结点,跟 25 到 27 左子结点路径个数不同:3,2。 -
继续将结点 25 的颜色变成红色
此处违反红黑树性质之——每个红色结点的两个子结点都是黑色:结点 25 、27 都是红色 -
将结点 27 变成黑色
-
此时整个红黑树图
可以看到结点 17 、25 是连续的两个红色结点。此时不应该再通过 变色 调整红黑树(如果继续变色,即会让结点17变成黑色,根结点 13 变成红色,且13红色结点的左子结点为红色右子结点为黑色,违反两个性质),而是应该通过 旋转 -
从根结点开始左旋转
-
将根结点变成黑色
此时看起来像是调整好了,实际上根结点的左子树中仍违反了性质——任一结点到叶子结点路径中黑色结点个数一样
17->8->6->NIL:4
17->15->NIL:3 -
在根结点的左子树中发生 右旋转
-
最后观察整个红黑树,再进行一次 变色 既可
观察发现,符合红黑树的所有性质 -
根结点是黑色的
-
红色结点的两个子结点是黑色的
-
每个叶子结点 Nil 是黑色的
-
任意一个结点到叶子结点路径中黑色结点的个数相同
该例子操作过程:
变
色
−
>
左
旋
转
−
>
变
色
−
>
右
旋
转
−
>
变
色
变色 -> 左旋转 -> 变色 -> 右旋转 -> 变色
变色−>左旋转−>变色−>右旋转−>变色
PS:该案例目的不是最佳调整方案,主要了解变色与旋转操作。
红黑树的代码实现
了解即可…
参考文章
参考实现代码
Java代码实现源码
红黑树与平衡二叉树的对比
- 平衡树(AVL)更平衡,结构上更加直观,时间效能针对读取而言更高,但维护较难,即每次插入、删除之后都需要重新维护树的平衡。
- 红黑树通过规则的设定,确保了插入、删除的最坏的时间复杂度是O(log n)
- 红黑树解决了平衡树(AVL)维护成本高的问题,放弃了追求完美平衡。红黑树的读取略逊于 AVL,维护性比平衡树强,每次插入、删除的平均旋转次数远小于平衡树(最多只需要三次旋转)
- 平衡二叉树追求绝对平衡,条件比较苛刻,实现起来比较麻烦,每次插入新结点之后需要旋转的次数不能预知
红黑树与 B+ 树用途的区别
- 红黑树多用在内部排序,即全放在内存中,STL的map和set的内部实现就是红黑树
- B+树多用在外存上,B+也被称为一个磁盘友好的数据结构
为什么 B+ 树磁盘友好??
- 磁盘读写代价更低
- 查询效率更加稳定
- 遍历所有的数据更方便
为什么 MySql 索引使用 B+ 树而不使用红黑树
- B+ 树就是为文件存储而生的。
- 查询新能上比较
数据库文件存储在磁盘中,定位一行信息需要查找该行文件所在柱面号,磁盘号,扇区号,页号,这个阶段十分耗时。每一次的定位请求意味着做一次 IO 操作,也意味着成倍的时间消耗。因此减少 IO 查询时提高性能查询的关键。而 IO查询的次数就是索引树的高度,高度越低查询的次数越少。同样的结点次数 红黑树的高度最多为 2log(n+1),而 B+ 树的高度最多为 (log t(n+1)/2)+1,随着 t 增大高度会更小,IO 次数也会减少
B+树的查询时间大概是 log(n)
hash 存储索引查询时间 o(1)
为什么不用hash?
- 这与业务场景有关。如果只选一个数据,hash更快,但数据库中经常会选择多条,这时候由于B+树索引有序,并且又有链表相连,它的查询效率比hash就快很多了。
- 而且数据库中的索引一般是在磁盘上,数据量大的情况可能无法一次装入内存,B+树的设计可以允许数据分批加载,同时树的高度较低,提高查找效率。
小结
-
红黑树是平衡二叉树的一种,他保证在最坏情况下基本动态集合操作的时间复杂度为 O(log n)
-
具有以下性质
- 每个结点要么是黑色,要么是红色
- 根结点是黑色
- 每个叶子结点(NIL)是黑色
- 每个红色结点的两个子结点一定都是黑色
- 任意一个结点到每个叶子结点的路径都包含相同数量的黑结点
-
可通过 变色 与 旋转 调整红黑树完成插入、删除操作