文章目录
1. 红黑树性质
红黑树是一种自平衡的二叉搜索树,以前也叫平衡二叉B树(Symmetric Binary B-tree)
红黑树必须满足以下五条性质:
-
结点是
RED
或者BLACK
-
根结点是
BLACK
-
叶子结点(外部结点,为空结点)都是
BLACK
-
RED
结点的子结点都是BLACK
- red结点的parent都是black
- 从根结点到叶子结点的所有路径上不能有两个连续的red结点
- red结点的parent都是black
-
从任意结点到
叶子结点
的所有路径都包含相同数目的BLACK
结点
可能刚看完,会感觉完全摸不到头脑,这?这啥玩意!?此时我们的表情可能是这样的,没有关系,好好学一定能学会
在我们分析红黑树之前需要先看一下下面这棵树满不满足红黑树的性质:
我们重点看性质五,从任意结点到
叶子结点
的所有路径都包含相同数目的BLACK
结点这里我们要注意,在红黑树中,叶子结点并不是图中的像
25
、74
的结点,在红黑树中,叶子结点是虚构出来的结点,且全部为black结点,我们看位置1
,显然不满足红黑树中的性质五,所以这五条性质我们应该牢记清除!
2. 红黑树与B树的等价变换
我们先来看一棵红黑树
接下来我们对这棵红黑树做出一些调整,我们让所有的红色结点
都上升一层,和其父结点同一层排列
我们仔细看一下,是不是很眼熟😏,这不就是B树
吗?而且还是一棵四阶B树
结论:
- 红黑树 和 4阶B树(2-3-4树)具有等价性
BLACK
节点与它的RED
子节点融合在一起,形成1个B树节点
- 红黑树的 BLACK 节点个数 与 4阶B树的节点总个数相等
其他常见变换有:
3. 添加
在完成红黑树添加编码之前,我们先在原有的代码中添加一些辅助函数
红黑树具有BST的性质,所以我们让红黑树继承BST,并且我们添加一些辅助函数,便于之后我们的操作
/**
* @Description: 红黑树
* @date: 2022/5/17 15:47
* @author: 梁峰源
*/
public class RedBlackTree<E> extends BinarySearchTree<E> {
private static final boolean RED = false;
private static final boolean BLACK = true;
public RedBlackTree() {
}
public RedBlackTree(Comparator<E> comparator) {
super(comparator);
}
/**
* 红黑树结点
*
* @param <E>
*/
private static class RBNode<E> extends Node<E> {
boolean color;
public RBNode(E element, Node<E> parent) {
super(element, parent);
}
}
@Override
protected void afterAdd(Node<E> node) {
super.afterAdd(node);
}
@Override
protected void afterRemove(Node<E> node) {
super.afterRemove(node);
}
/**
* 将元素染色
* @param node 带染色的结点
* @param color 需要染的颜色
* @return 将染色的结点返回
*/
private Node<E> color(Node<E> node,boolean color){
if(node == null) return node;
((RBNode<E>)node).color = color;
return node;
}
//染成红色
private Node<E> red(Node<E> node){
return color(node,RED);
}
//染成黑色
private Node<E> black(Node<E> node){
return color(node,BLACK);
}
//查看当前结点颜色
private boolean colorOf(Node<E> node){
return node == null ? BLACK : ((RBNode<E>)node).color;
}
private boolean isBlack(Node<E> node){
return colorOf(node) == BLACK;
}
private boolean isRea(Node<E> node){
return colorOf(node) == RED;
}
}
在BST的结点中我们需要再添加一些方法
// 返回当前结点的兄弟结点
public Node<E> sibling(){
if(isLeftChild()){
return parent.right;
}
if(isRightChild()){
return parent.left;
}
//没有兄弟结点
return null;
}
接下来我们来完成红黑树的添加代码
在红黑树的添加中我们要时刻牢记
B树
的一些基本性质,这样我们才能更好的理解红黑树的性质
我们已经知道了很多B树
的性质
- B树中,新元素必定是添加到叶子节点中
- 4阶B树所有节点的元素个数 x 都符合
1 ≤ x ≤ 3
建议新添加的节点默认为 RED,这样能够让红黑树的性质尽快满足(性质 1、2、3、5 都满足,性质 4 不一定)
如果添加的是根节点,染成 BLACK 即可
我们以下面的红黑树为例来看分析添加元素的所有情况
我们在此棵红黑树上添加元素一共会有12种情况(结合B树
的性质我们知道添加的元素一定会到叶子结点上)
其中在四个地方添加结点可以满足红黑树所有的性质(parent 为 BLACK
),因此不用做任何额外处理
剩下8种情况的添加都不能满足红黑树的性质(不满足性质四),所以我们要分别讨论并解决
我们可以看到下面的情况就是parent为red(double red)
的情况
3.1 double red - LL/RR-右旋/左旋
当我们添加的结点出现以下的两种情况的时候(看52
和60
两个结点):
-
我们的做法是将
50
结点染成黑色并且将46
结点染成红色,并对46
结点进行左旋将parent染成黑色,再将grand染成红色,最后对parent进行左旋
-
其实很有规律,黑色结点的RR两个结点都为红色,我们就需要进行
RR-左旋
,这点和AVL树非常的像,只是红黑树需要将结点重新染色
同理我们对76
结点采取的方式一样,因为黑结点的LL
两个结点不满足红黑树性质四,我们就需要完成LL-右旋
,并将72
和76
两个结点重新染色
🎈🎈我们来看一下具体步骤:
判定条件:uncle 不是 RED
(uncle 为其父结点的兄弟结点)
- parent 染成 BLACK,grand 染成 RED
- grand 进行单旋操作(LL:右旋转、RR:左旋转)
完成后的效果图为:
3.2 double red - LR/RL-双旋
下面的两种double red产生的情况是因为黑色结点的RL和LR结点都为红色结点而导致异常
我们在AVL树中已经知道需要进行双旋来解决这种情况:
- RL:LL右旋转,RR左旋转(对结点
50
进行右旋转接着对结点46
进行左旋转) - LR:RR左旋转,LL右旋转(对结点
72
进行左旋转接着对结点76
进行右旋转)
调整后的结点为:
🎈🎈我们来看一下具体步骤:
判定条件:uncle 不是 RED
(uncle 为其父结点的兄弟结点)
- 自己染成
BLACK
,grand 染成RED
- 进行双旋操作
- LR:parent 左旋转, grand 右旋转
- RL:parent 右旋转, grand 左旋转
3.3 添加 – 修复性质4 –上溢 – LL
上面的红黑树中因为元素10
,导致四阶B树结点超过三个,这个时候我们会进行上溢,上溢就是跳出中间元素并向上合并,左右分裂
判定条件:uncle 是 RED
解决方案:
- parent、uncle 染成 BLACK(将17和33两个结点染成黑色并分裂)
- grand 向上合并(将结点25当做是新插入的结点并进行修复即可)
这时候结点25
会上溢,但是又会造成,我们的做法是将结点25
当做是一个新插入的结点,继续上面的流程,递归处理
- 染成 RED,当做是新添加的节点进行处理(递归操作)
- grand 向上合并时,可能继续发生上溢
- 若上溢持续到根节点,只需将根节点染成 BLACK
可以看出其实出现LL上溢的情况,我们并没有对结点进行旋转,只进行了染色
3.4 添加 – 修复性质4 –上溢 – RR
判定条件:uncle 是 RED
- parent、uncle 染成 BLACK
- grand 向上合并
染成 RED,当做是新添加的节点进行处理
解决后:
3.5 添加 – 修复性质4 –上溢 – LR
判定条件:uncle 是 RED
- parent、uncle 染成 BLACK
- grand 向上合并
染成 RED,当做是新添加的节点进行处理
解决后:
3.6 添加 – 修复性质4 –上溢 – RL
判定条件:uncle 是 RED
- parent、uncle 染成 BLACK
- grand 向上合并
染成 RED,当做是新添加的节点进行处理
3.7 添加总结LL/RR
判定条件:uncle不是RED
- parent染成BLACK,grand染成RED
- grand进行单旋操作
LL右旋转、RR左旋转
3.8 添加总结LR/RL
3.9 添加所有情况小结
3.10 编码实现添加
/**
* @param node 新添加的结点
*/
@Override
protected void afterAdd(Node<E> node) {
// 先取出父结点
Node<E> parent = node.parent;
// 添加的是根结点(将其染成黑色并返回)或者上溢到根结点
if(parent == null){
black(node);
return;
}
// 如果是前四种情况,即父结点为黑色结点,不用处理
if(isBlack(parent)) return;
// 取出uncle结点
// 取出祖父结点
Node<E> grand = parent.parent;
Node<E> uncle = parent.sibling();
// 如果叔父结点是红色【B树结点上溢,只需要染色】
if(isRed(uncle)){
black(parent);
black(uncle);
// 把祖父结点当做是新添加的结点
// 递归调用
afterAdd(red(grand));
return;
}
/*
* 叔父结点不是红色,有四种情况
* LL/RR: parent染成BLACK,grand染成RED - grand进行单旋操作
* LR/RL: 自己染成black,grand染成red,再双旋
*/
if(parent.isLeftChild()){ // L
red(grand);
if(node.isLeftChild()){ // LL
black(parent);
}else { // LR
black(node);
rotateLeft(parent);
}
rotateRight(grand);
}else { //R
red(grand);
if(node.isLeftChild()){ // RL
black(node);
rotateRight(parent);
}else { // RR
black(parent);
}
rotateLeft(grand);
}
}
测试:
@Test
public void test01(){
Integer[] data = {
55, 42, 96, 62, 24, 29, 18, 2, 89, 40, 23, 3
};
RedBlackTree<Integer> rb = new RedBlackTree<>();
for(int i : data){
rb.add(i);
}
BinaryTrees.println(rb);
}
结果:
┌─────29─────┐
│ │
┌──R_18──┐ ┌─R_55─┐
│ │ │ │
2─┐ ┌─24 ┌─42 ┌─89─┐
│ │ │ │ │
R_3 R_23 R_40 R_62 R_96
4. 删除
首先我们应该知道:B树中,最后真正被删除的元素都在叶子节点中
叶子结点我们可以分类成删除red
结点和删除black
结点两种情况
4.1 删除红色结点
如果删除的是红色结点,由于删除后依旧满足红黑树的性质,所以不需要做任何的调整
4.2 删除黑色非叶子 结点
一共有三种情况:
第一种:删除拥有两个红色子结点的black结点(如图25结点)
- 不可能被直接删除,因为会找它的子结点替代删除
- 这种情况不用删除任何结点
由BST性质可知,删除一个度为2的结点,其实是删除其前驱或者后继结点,但是25
结点前驱和后继都是红色的,所以不用删除
第二种:拥有一个RED
子结点的BLACK
结点
如何判断 ?看用以取代他的子结点是不是红色结点
我们按照BST的做法应该是让其子结点取代要删除的结点,但是这时候出现了double red
的情况
那么我们现在需要修复性质。这种情况下只需要将替代结点染黑即可
首先我们要在父类BST里面重载一个方法,因为红黑树是靠颜色来维持性质的,我们需要将用以替换的结点传入进去
/**
* 删除结点时后进行平衡【父类重载的方法】
* @param node 删除的结点
* @param replaceNode 用来替换的结点
*/
protected void afterRemove(Node<E> node,Node<E> replaceNode){
}
这里只处理第一次情况的情况
@Override
protected void afterRemove(Node<E> node, Node<E> replaceNode) {
// 如果删除的结点是红色,不需要做任何处理
if(isRed(node)) return;
// 用以取代的node结点是红色
if(isRed(replaceNode)){
// 直接染色就可以了
black(replaceNode);
return;
}
// 删除的是黑色叶子结点
}
4.3 🎈删除黑色叶子结点– sibling为BLACK
4.3.1 兄弟结点有红色子结点
BLACK 叶子节点被删除后,会导致B树节点下溢(比如删除88),在B树中的做法为:父结点下来,挑一个兄弟结点的孩子做父亲
如果 sibling 至少有 1 个 RED 子节点
口诀:删除黑色结点,如果黑色的兄弟有红孩子,借取之当爹,爹替换自己
- 进行旋转操作
- 旋转之后的中心节点继承 parent 的颜色
- 旋转之后的左右节点染为 BLACK
平衡后:
4.3.2 兄弟结点没有红色子结点
判定条件:sibling 没有 1 个 RED 子节点
将 sibling 染成 RED、parent 染成 BLACK 即可修复红黑树性质
如果 parent 是 BLACK,会导致 parent 也下溢,这时只需要把 parent 当做被删除的节点处理即可
4.3.3 小结
- 如果被删除结点的黑兄弟可以借(有红色结点),通过选择借取结点
- 如果黑兄弟不能借,如果父结点为红(不会导致失衡)就让父结点下来和我的黑兄弟合并到一起,如果父结点为黑,将parent 当做被删除的节点处理即可(递归一次)
4.4 删除黑色叶子节点 – sibling为RED
判断条件: sibling 是 RED
解决方案:
4.5 编码
@Override
protected void afterRemove(Node<E> node, Node<E> replaceNode) {
// 如果删除的结点是红色,不需要做任何处理
if (isRed(node)) return;
// 用以取代的node结点是红色
if (isRed(replaceNode)) {
// 直接染色就可以了
black(replaceNode);
return;
}
// 删除的是黑色叶子结点
// 拿到parent
Node<E> parent = node.parent;
// 如果是根结点不做处理
if (parent == null) return;
// 这里判断node结点是左还是右,需要看父结点清空了谁
boolean left = parent.left == null || node.isLeftChild();
Node<E> sibling = left ? parent.right : parent.left;
if (left) { // 被删除的结点在左边,兄弟结点在右边
/*
* sibling 染成 BLACK,parent 染成 RED,进行旋转
* 于是又回到 sibling 是 BLACK 的情况
*/
if (isRed(sibling)) {
black(sibling);
red(parent);
rotateLeft(parent);
// 更换兄弟
sibling = parent.right;
}
// 兄弟结点必然是黑色
if (isBlack(sibling.left) && isBlack(sibling.right)) {
// 兄弟结点没有红色子结点,父结点要向下跟兄弟结点合并,其实就是父结点染红,兄弟结点染黑
boolean parentIsBlack = isBlack(parent);
red(parent);
black(sibling);
// 如果下溢会导致父结点空,可以当做父结点被删除来看待,递归调用即可
if (parentIsBlack) afterRemove(parent, null);
} else { // 兄弟结点一定有红色的子结点
// 兄弟结点左边是黑色,兄弟要先旋转
if (isBlack(sibling.right)) {
rotateRight(sibling);
// 兄弟结点需要重新赋值
sibling = parent.right;
}
// 兄弟结点染成父结点的颜色
color(sibling, colorOf(parent));
// 将父结点的左右子结点都染成黑色
black(sibling.right);
black(parent);
// 对父结点进行右旋转
rotateLeft(parent);
}
} else { // 被删除的结点在右边,兄弟结点在左边
/*
* sibling 染成 BLACK,parent 染成 RED,进行旋转
* 于是又回到 sibling 是 BLACK 的情况
*/
if (isRed(sibling)) {
black(sibling);
red(parent);
rotateRight(parent);
// 更换兄弟
sibling = parent.left;
}
// 兄弟结点必然是黑色
if (isBlack(sibling.left) && isBlack(sibling.right)) {
// 兄弟结点没有红色子结点,父结点要向下跟兄弟结点合并,其实就是父结点染红,兄弟结点染黑
boolean parentIsBlack = isBlack(parent);
red(parent);
black(sibling);
// 如果下溢会导致父结点空,可以当做父结点被删除来看待,递归调用即可
if (parentIsBlack) afterRemove(parent, null);
} else { // 兄弟结点一定有红色的子结点
// 兄弟结点左边是黑色,兄弟要先旋转
if (isBlack(sibling.left)) {
rotateLeft(sibling);
// 兄弟结点需要重新赋值
sibling = parent.left;
}
// 兄弟结点染成父结点的颜色
color(sibling, colorOf(parent));
// 将父结点的左右子结点都染成黑色
black(sibling.left);
black(parent);
// 对父结点进行右旋转
rotateRight(parent);
}
}
}
5. 红黑树完整代码
BST中修改的部分
/**
* 对外暴露的删除方法
*/
public void remove(E element) {
remove(node(element));
}
/**
* 根据结点删除该结点
*/
private void remove(Node<E> node) {
if (node == null) return;
//优先处理度为2的结点
if (node.hasTwoChildren()) {
//找到其后继结点
Node<E> successor = successor(node);
//用后继结点的值覆盖度为2的结点的值
node.element = successor.element;
//因为度为2的结点的后继或者前驱结点一定是度为1或0,所以将删除结点交给后面的代码来做
node = successor;
}
//删除度为1或者度为0的结点
Node<E> replaceNode = node.left != null ? node.left : node.right;
/*
* 这里有三种情况,需要分类讨论
* 1. node是度为1的结点
* 2. node是叶子结点并且是根结点
* 3. node是叶子结点
*/
if (replaceNode != null) {
//先修改node.parent的指向
replaceNode.parent = node.parent;
//修改parent的left、right指向
if (node.parent == null) { //node是度为1的结点且是根结点
root = replaceNode;
} else if (node == node.parent.left) {
node.parent.left = replaceNode;
} else {
node.parent.right = replaceNode;
}
//删除结点之后的处理
afterRemove(replaceNode);
} else if (node.parent == null) {
//node是叶子结点并且是根结点,直接让该结点为null
root = null;
//删除结点之后的处理,这里不需要替代
afterRemove(node);
} else {
//叶子结点
//父结点的左子树
if (node == node.parent.left) {
node.parent.left = null;
} else {
//父结点右子树
node.parent.right = null;
}
//删除结点之后的处理,这里也不需要替代
afterRemove(node);
}
size--;
}
红黑树代码
package com.fx.RBTree;
import java.util.Comparator;
public class RBTree<E> extends BBST<E> {
private static final boolean RED = false;
private static final boolean BLACK = true;
public RBTree() {
this(null);
}
public RBTree(Comparator<E> comparator) {
super(comparator);
}
@Override
protected void afterAdd(Node<E> node) {
Node<E> parent = node.parent;
// 添加的是根节点 或者 上溢到达了根节点
if (parent == null) {
black(node);
return;
}
// 如果父节点是黑色,直接返回
if (isBlack(parent)) return;
// 叔父节点
Node<E> uncle = parent.sibling();
// 祖父节点
Node<E> grand = red(parent.parent);
if (isRed(uncle)) { // 叔父节点是红色【B树节点上溢】
black(parent);
black(uncle);
// 把祖父节点当做是新添加的节点
afterAdd(grand);
return;
}
// 叔父节点不是红色
if (parent.isLeftChild()) { // L
if (node.isLeftChild()) { // LL
black(parent);
} else { // LR
black(node);
rotateLeft(parent);
}
rotateRight(grand);
} else { // R
if (node.isLeftChild()) { // RL
black(node);
rotateRight(parent);
} else { // RR
black(parent);
}
rotateLeft(grand);
}
}
@Override
protected void afterRemove(Node<E> node) {
// 如果删除的节点是红色
// 或者 用以取代删除节点的子节点是红色
if (isRed(node)) {
black(node);
return;
}
Node<E> parent = node.parent;
// 删除的是根节点
if (parent == null) return;
// 删除的是黑色叶子节点【下溢】
// 判断被删除的node是左还是右
boolean left = parent.left == null || node.isLeftChild();
Node<E> sibling = left ? parent.right : parent.left;
if (left) { // 被删除的节点在左边,兄弟节点在右边
if (isRed(sibling)) { // 兄弟节点是红色
black(sibling);
red(parent);
rotateLeft(parent);
// 更换兄弟
sibling = parent.right;
}
// 兄弟节点必然是黑色
if (isBlack(sibling.left) && isBlack(sibling.right)) {
// 兄弟节点没有1个红色子节点,父节点要向下跟兄弟节点合并
boolean parentBlack = isBlack(parent);
black(parent);
red(sibling);
if (parentBlack) {
afterRemove(parent);
}
} else { // 兄弟节点至少有1个红色子节点,向兄弟节点借元素
// 兄弟节点的左边是黑色,兄弟要先旋转
if (isBlack(sibling.right)) {
rotateRight(sibling);
sibling = parent.right;
}
color(sibling, colorOf(parent));
black(sibling.right);
black(parent);
rotateLeft(parent);
}
} else { // 被删除的节点在右边,兄弟节点在左边
if (isRed(sibling)) { // 兄弟节点是红色
black(sibling);
red(parent);
rotateRight(parent);
// 更换兄弟
sibling = parent.left;
}
// 兄弟节点必然是黑色
if (isBlack(sibling.left) && isBlack(sibling.right)) {
// 兄弟节点没有1个红色子节点,父节点要向下跟兄弟节点合并
boolean parentBlack = isBlack(parent);
black(parent);
red(sibling);
if (parentBlack) {
afterRemove(parent);
}
} else { // 兄弟节点至少有1个红色子节点,向兄弟节点借元素
// 兄弟节点的左边是黑色,兄弟要先旋转
if (isBlack(sibling.left)) {
rotateLeft(sibling);
sibling = parent.left;
}
color(sibling, colorOf(parent));
black(sibling.left);
black(parent);
rotateRight(parent);
}
}
}
private Node<E> color(Node<E> node, boolean color) {
if (node == null) return node;
((RBNode<E>)node).color = color;
return node;
}
private Node<E> red(Node<E> node) {
return color(node, RED);
}
private Node<E> black(Node<E> node) {
return color(node, BLACK);
}
private boolean colorOf(Node<E> node) {
return node == null ? BLACK : ((RBNode<E>)node).color;
}
private boolean isBlack(Node<E> node) {
return colorOf(node) == BLACK;
}
private boolean isRed(Node<E> node) {
return colorOf(node) == RED;
}
@Override
protected Node<E> createNode(E element, Node<E> parent) {
return new RBNode<>(element, parent);
}
private static class RBNode<E> extends Node<E> {
boolean color = RED;
public RBNode(E element, Node<E> parent) {
super(element, parent);
}
@Override
public String toString() {
String str = "";
if (color == RED) {
str = "R_";
}
return str + element.toString();
}
}
}
测试:
@Test
public void test01(){
Integer[] data = {
55, 42, 96, 62, 24, 29, 18, 2, 89, 40, 23, 3
};
RedBlackTree<Integer> rb = new RedBlackTree<>();
for(int i : data){
rb.add(i);
}
BinaryTrees.println(rb);
// 删除结点
for (Integer datum : data) {
rb.remove(datum);
System.out.println("删除元素:【" + datum + "】");
BinaryTrees.println(rb);
System.out.println("---------------------------------");
}
}
结果:
┌─────29─────┐
│ │
┌──R_18──┐ ┌─R_55─┐
│ │ │ │
2─┐ ┌─24 ┌─42 ┌─89─┐
│ │ │ │ │
R_3 R_23 R_40 R_62 R_96
删除元素:【55】
┌─────29─────┐
│ │
┌──R_18──┐ ┌─R_62─┐
│ │ │ │
2─┐ ┌─24 ┌─42 89─┐
│ │ │ │
R_3 R_23 R_40 R_96
---------------------------------
删除元素:【42】
┌────29────┐
│ │
┌──R_18──┐ ┌─R_62─┐
│ │ │ │
2─┐ ┌─24 40 89─┐
│ │ │
R_3 R_23 R_96
---------------------------------
删除元素:【96】
┌────29────┐
│ │
┌──R_18──┐ ┌─R_62─┐
│ │ │ │
2─┐ ┌─24 40 89
│ │
R_3 R_23
---------------------------------
删除元素:【62】
┌────29────┐
│ │
┌──R_18──┐ ┌─89
│ │ │
2─┐ ┌─24 R_40
│ │
R_3 R_23
---------------------------------
删除元素:【24】
┌────29───┐
│ │
┌─R_18─┐ ┌─89
│ │ │
2─┐ 23 R_40
│
R_3
---------------------------------
删除元素:【29】
┌─40─┐
│ │
┌─R_18─┐ 89
│ │
2─┐ 23
│
R_3
---------------------------------
删除元素:【18】
┌─40─┐
│ │
┌─R_3─┐ 89
│ │
2 23
---------------------------------
删除元素:【2】
┌─40─┐
│ │
3─┐ 89
│
R_23
---------------------------------
删除元素:【89】
┌─23─┐
│ │
3 40
---------------------------------
删除元素:【40】
┌─23
│
R_3
---------------------------------
删除元素:【23】
3
---------------------------------
删除元素:【3】
---------------------------------
Process finished with exit code 0
6. 红黑树平衡的奥秘
我们在学习AVL树的时候,有明确的平衡因子的概念,我们在添加删除的时候都要计算平衡因子,防止结点失衡
但是我们在红黑树中,好像并没有去刻意的维护平衡,也没有平衡因子的概念,而是一直在维护那五条性质,那么为什么维护了这五条性质就能维护性质呢?
那5条性质,可以保证 红黑树 等价于 4阶B树
什么是平衡?即树的高度越矮越平衡
- 相比AVL树,红黑树的平衡标准比较宽松:没有一条路径会大于其他路径的2倍
- 是一种弱平衡、黑高度平衡(只算黑结点的高度都一样)
- 红黑树的最大高度是 2 ∗ log2(n + 1) ,依然是 O(logn) 级别
7. 红黑树与AVL树性能对比
7.1 平均时间复杂度
◼ 搜索:O(logn)
◼ 添加:O(logn),O(1) 次的旋转操作
◼ 删除:O(logn),O(1) 次的旋转操作
显然添加删除情况比AVL树更好,AVL树都是O(logn)级别的
7.2 AVL树
- 平衡标准比较严格:每个左右子树的高度差不超过1
- 最大高度是 1.44 ∗ log2 n + 2 − 1.328(100W个节点,AVL树最大树高28)
- 搜索、添加、删除都是 O(logn) 复杂度,其中添加仅需 O(1) 次旋转调整、删除最多需要 O(logn) 次旋转调整
7.3 红黑树
- 平衡标准比较宽松:没有一条路径会大于其他路径的2倍
- 最大高度是 2 ∗ log2(n + 1)( 100W个节点,红黑树最大树高40)
- 搜索、添加、删除都是 O(logn) 复杂度,其中添加、删除都仅需 O(1) 次旋转调整
7.4 小结
- 搜索的次数远远大于插入和删除,选择AVL树;搜索、插入、删除次数几乎差不多,选择红黑树
- 相对于AVL树来说,红黑树牺牲了部分平衡性以换取插入/删除操作时少量的旋转操作,整体来说性能要优于AVL树
- 红黑树的平均统计性能优于AVL树,实际应用中更多选择使用红黑树