红黑树学习,Java实现

1. 红黑树性质

红黑树是一种自平衡的二叉搜索树,以前也叫平衡二叉B树(Symmetric Binary B-tree)

红黑树必须满足以下五条性质:

  1. 结点是RED或者BLACK

  2. 根结点是BLACK

  3. 叶子结点(外部结点,为空结点)都是BLACK

  4. RED结点的子结点都是BLACK

    • red结点的parent都是black
      • 从根结点到叶子结点的所有路径上不能有两个连续的red结点
  5. 从任意结点到叶子结点的所有路径都包含相同数目的BLACK结点

image-20220419180531046

可能刚看完,会感觉完全摸不到头脑,这?这啥玩意!?此时我们的表情可能是这样的,没有关系,好好学一定能学会

image-20220419181105375

在我们分析红黑树之前需要先看一下下面这棵树满不满足红黑树的性质:

image-20220517152525868

我们重点看性质五,从任意结点到叶子结点的所有路径都包含相同数目的BLACK结点

这里我们要注意,在红黑树中,叶子结点并不是图中的像2574的结点,在红黑树中,叶子结点是虚构出来的结点,且全部为black结点,我们看位置1,显然不满足红黑树中的性质五,所以这五条性质我们应该牢记清除!

image-20220517152804712

2. 红黑树与B树的等价变换

我们先来看一棵红黑树

image-20220517153215382

接下来我们对这棵红黑树做出一些调整,我们让所有的红色结点都上升一层,和其父结点同一层排列

image-20220517153340973

我们仔细看一下,是不是很眼熟😏,这不就是B树吗?而且还是一棵四阶B树

image-20220517153508552

结论:

  • 红黑树 和 4阶B树(2-3-4树)具有等价性
  • BLACK 节点与它的 RED 子节点融合在一起,形成1个B树节点
  • 红黑树的 BLACK 节点个数 与 4阶B树的节点总个数相等

其他常见变换有:

image-20220517154150856

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 即可

我们以下面的红黑树为例来看分析添加元素的所有情况

image-20220517163922449

我们在此棵红黑树上添加元素一共会有12种情况(结合B树的性质我们知道添加的元素一定会到叶子结点上)

image-20220517164321529

其中在四个地方添加结点可以满足红黑树所有的性质(parent 为 BLACK),因此不用做任何额外处理

image-20220517164658153

剩下8种情况的添加都不能满足红黑树的性质(不满足性质四),所以我们要分别讨论并解决

我们可以看到下面的情况就是parent为red(double red)的情况

image-20220517165201462

3.1 double red - LL/RR-右旋/左旋

当我们添加的结点出现以下的两种情况的时候(看5260两个结点):

  • 我们的做法是将50结点染成黑色并且将46结点染成红色,并对46结点进行左旋

    将parent染成黑色,再将grand染成红色,最后对parent进行左旋

  • 其实很有规律,黑色结点的RR两个结点都为红色,我们就需要进行RR-左旋,这点和AVL树非常的像,只是红黑树需要将结点重新染色

同理我们对76结点采取的方式一样,因为黑结点的LL两个结点不满足红黑树性质四,我们就需要完成LL-右旋,并将7276两个结点重新染色

image-20220517170525137

🎈🎈我们来看一下具体步骤:

判定条件:uncle 不是 RED(uncle 为其父结点的兄弟结点)

  1. parent 染成 BLACK,grand 染成 RED
  2. grand 进行单旋操作(LL:右旋转、RR:左旋转)

完成后的效果图为:

image-20220517171246737

3.2 double red - LR/RL-双旋

下面的两种double red产生的情况是因为黑色结点的RLLR结点都为红色结点而导致异常

我们在AVL树中已经知道需要进行双旋来解决这种情况:

  • RL:LL右旋转,RR左旋转(对结点50进行右旋转接着对结点46进行左旋转)
  • LR:RR左旋转,LL右旋转(对结点72进行左旋转接着对结点76进行右旋转)

image-20220517171413653

调整后的结点为:

image-20220517172219148

🎈🎈我们来看一下具体步骤:

判定条件:uncle 不是 RED(uncle 为其父结点的兄弟结点)

  1. 自己染成 BLACK,grand 染成 RED
  2. 进行双旋操作
    • LR:parent 左旋转, grand 右旋转
    • RL:parent 右旋转, grand 左旋转

3.3 添加 – 修复性质4 –上溢 – LL

image-20220629125055598

上面的红黑树中因为元素10,导致四阶B树结点超过三个,这个时候我们会进行上溢,上溢就是跳出中间元素并向上合并,左右分裂

判定条件:uncle 是 RED

解决方案:

  • parent、uncle 染成 BLACK(将17和33两个结点染成黑色并分裂)
  • grand 向上合并(将结点25当做是新插入的结点并进行修复即可)

这时候结点25会上溢,但是又会造成,我们的做法是将结点25当做是一个新插入的结点,继续上面的流程,递归处理

  • 染成 RED,当做是新添加的节点进行处理(递归操作)

image-20220629130421438

  • grand 向上合并时,可能继续发生上溢
  • 若上溢持续到根节点,只需将根节点染成 BLACK

可以看出其实出现LL上溢的情况,我们并没有对结点进行旋转,只进行了染色

3.4 添加 – 修复性质4 –上溢 – RR

判定条件:uncle 是 RED

  • parent、uncle 染成 BLACK
  • grand 向上合并

染成 RED,当做是新添加的节点进行处理

image-20220629200535165

解决后:

image-20220629201140039

3.5 添加 – 修复性质4 –上溢 – LR

判定条件:uncle 是 RED

  • parent、uncle 染成 BLACK
  • grand 向上合并

染成 RED,当做是新添加的节点进行处理

image-20220629200759599

解决后:

image-20220629201111360

3.6 添加 – 修复性质4 –上溢 – RL

判定条件:uncle 是 RED

  • parent、uncle 染成 BLACK
  • grand 向上合并

染成 RED,当做是新添加的节点进行处理

image-20220629201450876

image-20220629201527944

3.7 添加总结LL/RR

判定条件:uncle不是RED

  • parent染成BLACK,grand染成RED
  • grand进行单旋操作

LL右旋转、RR左旋转

image-20220629202902831

3.8 添加总结LR/RL

image-20220629205155204

3.9 添加所有情况小结

image-20220629205453664

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 删除红色结点

如果删除的是红色结点,由于删除后依旧满足红黑树的性质,所以不需要做任何的调整

image-20220711090148171

4.2 删除黑色非叶子 结点

image-20220711090437889

一共有三种情况:

第一种:删除拥有两个红色子结点的black结点(如图25结点)

  • 不可能被直接删除,因为会找它的子结点替代删除
  • 这种情况不用删除任何结点

由BST性质可知,删除一个度为2的结点,其实是删除其前驱或者后继结点,但是25结点前驱和后继都是红色的,所以不用删除

第二种:拥有一个RED子结点的BLACK结点

如何判断 ?看用以取代他的子结点是不是红色结点

image-20220711090950509

我们按照BST的做法应该是让其子结点取代要删除的结点,但是这时候出现了double red的情况

image-20220711091544683

那么我们现在需要修复性质。这种情况下只需要将替代结点染黑即可

首先我们要在父类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

image-20220711102945605

平衡后:

image-20220711103839399

4.3.2 兄弟结点没有红色子结点

判定条件:sibling 没有 1 个 RED 子节点

将 sibling 染成 RED、parent 染成 BLACK 即可修复红黑树性质

如果 parent 是 BLACK,会导致 parent 也下溢,这时只需要把 parent 当做被删除的节点处理即可

image-20220711211527660

4.3.3 小结

image-20220711211302679

  • 如果被删除结点的黑兄弟可以借(有红色结点),通过选择借取结点
  • 如果黑兄弟不能借,如果父结点为红(不会导致失衡)就让父结点下来和我的黑兄弟合并到一起,如果父结点为黑,将parent 当做被删除的节点处理即可(递归一次)

4.4 删除黑色叶子节点 – sibling为RED

判断条件: sibling 是 RED

解决方案

image-20220711212233581

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
---------------------------------
删除元素:【233
---------------------------------
删除元素:【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树,实际应用中更多选择使用红黑树

8. BST VS AVL VS RedBlack Tree

image-20220712130218713

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值