红黑树Java语言实现
红黑树的用途
用于快速查找元素,或者说快速根据关键字查询值。和散列表不同,散列表是利用散列函数建立单向映射实现快速查找。红黑树属于二叉查找树,它使得树的深度保持在O[ log(n) ],查找次数不多于最大深度,从而实现快速查找。
红黑树的定义
- 树中有两种颜色的节点,红色和黑色(顾名思义)
- 所有终端节点为黑色。终端节点(又称叶子节点)就是null节点
- 根节点是黑色
- 不能有两个连续的红色节点(即每个红色节点都有2个黑色节点)
- 从根节点到终端节点路径上的黑色节点个数相同
红黑树高效的原因
上述4、5最重要,找到终端可谓是最糟糕的情况了,即便的最坏的情况下,黑色节点都相同。那么路径最长的情况下是红黑相间的情况下,最短情况是全部是黑色。显然,到终端最长路径不超过最短路径的2倍。因此能够保证树的深度是O[ log(n) ]的(本人未证明),从而保证了效率。
红黑树的插入
首先要声明一下,除了插入根节点之外,新插入的节点初始的颜色都是红色的(后来可能会调整颜色)
1. 最简单的情况——插入根节点
只需保证根节点为黑色,非常简单。。。
2. 也很简单的情况——新节点的父亲是黑色的
没有任何负面影响,因为即保证了红色节点不连续,又不影响根节点到终端的黑色节点个数。原来是(黑)父亲+(黑)孩子,现在是(黑)父亲+(红)新节点+(黑色)新节点的儿子
3. 新节点的父亲是红色的
这时候就出现了红色节点连续的情况了,需要调整
3.1 新节点的叔叔是红色的
把爷爷变成黑色,父亲和叔叔变成红色。首先在从太爷爷到终端的黑色节点个数不变,其次这个局部没有红黑相间的情况。但是爷爷和太爷爷之间可能会出现2个连续红色的情况,把爷爷设为新节点(current),继续运行插入规律(上调)。
3.2 新节点的叔叔是黑色的
3.2.1 LL形式
这时候新的节点和父亲出现了红色相邻的情况。这时候对爷爷进行右单旋,并且爷爷和父亲交换颜色。注意的是可能会丢失父亲的右孩子,它本来就是介于父亲和爷爷的值,正好弥补了爷爷的左孩子。这样依旧保证了红黑树性质,因为父亲是黑色的,不需要上调。
3.2.2 LR形式
- 先说一种一步到位的方法,就是让父亲和爷爷成为新节点的左儿子和右儿子,同时新节点的左子树接到父亲的右子树,新节点的右子树接到爷爷的左子树。新节点和爷爷颜色交换,新节点设为黑色。
- 显然结果不存在连续的红色节点,根节点到终端的黑色节点数也保持不变。
- 新节点的左子树本身大于父亲小于新节点,新节点的右子树小于爷爷大于新节点,旋转后依然保持二叉搜索树的性质
- 再说一种两步到位的方法,先对父亲左单旋,注意新节点的左子树接到父亲的右子树上,否则会丢失节点!这样就转换成了3.2.1 LL形式(对爷爷右单旋)。从编程的角度来说还是一步到位更有效率!
3.2.3 RR形式
是LL的镜像对称模式,一切取反即可,不再累述
3.2.4 RL形式
是LR的镜像对称模式,一切取反即可,不再累述
红黑树 vs AVL树
- 总结来说,AVL树插入效率小于红黑树,AVL查询效率大于红黑树
- AVL可以保证左右子树高度差绝对值不大于1并且是一颗二叉搜索树
- 插入时,AVL树平衡性强,更容易引起不平衡触发耗时的旋转操作,红黑树只有在父亲是红色时才能触发旋转操作,“容纳\忍耐”能力强一些,因此损耗更低。
- 查询时候,AVL可以保证左右子树高度差绝对值不大于1,红黑树平衡性更差,所以深度略大,查询效率略低一些。
- 平均来说,红黑树性能>AVL性能
- AVL树介绍和实现,参考我以前的博客:AVL树Java语言实现
红黑树Java实现
参考我的实现:https://github.com/ghostorsoul/red_black_tree
代码解析未完待续。。。