数据结构---红黑树

红黑树定义:

红黑树是一种含有红黑结点并能自平衡的二叉查找树。它必须满足下面性质

  • 性质1:每个节点要么是黑色,要么是红色。

  • 性质2:根节点是黑色。

  • 性质3:每个叶子节点是黑色。

  • 性质4:每个红色结点的两个子结点一定都是黑色。

  • 性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点。(保持平衡)

总结:红色节点不可能相连,黑色节点可能相连。

红黑树自平衡基本操作:

  1. 变色:在不违反上述红黑树规则特点情况下,将红黑树某个node节点颜色由红变黑,或者由黑变红;

  2. 左旋:逆时针旋转两个节点,让一个节点被其右子节点取代,而该节点成为右子节点的左子节点

  3. 右旋:顺时针旋转两个节点,让一个节点被其左子节点取代,而该节点成为左子节点的右子节点

HashMap中红黑树节点的结构:

需要三个指针就够了,基本的左右指针,指向父节点的指针。然后颜色标识符:red

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // red-black tree links  父节点
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;
...
}

对于红黑树来说,查找是比较简单的,和普通二叉树没有区别,难点在于构建红黑树,删除节点,会有很多情况。

1.查找节点

final Entry<K,V> getEntry(Object key) {
    Entry<K,V> p = root;
    while (p != null) {
        int cmp = k.compareTo(p.key);
        if (cmp < 0)
            p = p.left;
        else if (cmp > 0)
            p = p.right;
        else
            return p;
    }
    return null;
}

2.添加元素

    第一步插入,和普通的排序二叉树插入一样

    第二步则是一个调整的过程。因为红黑树不一样,当我们添加一个新的元素之后可能会破坏它固有的属性。主要在于两个地方,一个是要保证新加入元素后,到所有叶节点的黑色节点还是一样的。另外也要保证红色节点的子节点为黑色节点。

    还有一个就是,结合TreeMap的map特性,我们添加元素的时候也可能会出现新加入的元素key已经在数中间存在了,那么这个时候就不是新加入元素,而是要更新原有元素的值。

public V put(K key, V value) {
    Entry<K,V> t = root;
    if (t == null) {// 根节点为空,直接放入根节点
        compare(key, key); // type (and possibly null) check

        root = new Entry<>(key, value, null);
        size = 1;
        modCount++;
        return null;
    }
    int cmp;
    Entry<K,V> parent;
    do {
        parent = t;        //父节点,保存
        cmp = cpr.compare(key, t.key);
        if (cmp < 0)
            t = t.left;
        else if (cmp > 0)
            t = t.right;
        else    //已经存在,直接重新赋值
            return t.setValue(value);
    } while (t != null);    //t为空,就退出,找到存放位置
    
    Entry<K,V> e = new Entry<>(key, value, parent);    //根据父节点,创建新的节点。
    if (cmp < 0)        //根据cmp的值,设置parent的子节点,此处就成功插入了
        parent.left = e;
    else
        parent.right = e;
    fixAfterInsertion(e);    //调整,【重点】
    size++;
    modCount++;
    return null;
}

2.1 调整红黑树

private void fixAfterInsertion(Entry<K,V> x) {
    x.color = RED;        //先把新插入的节点,设置为红色
    //如果是根节点,或者父节点颜色是黑色的,那么新节点也直接设置为黑色。
        //如果父节点是红色,那么根据原则一定要调整
    while (x != null && x != root && x.parent.color == RED) {  
    // 父节点 是否是 父节点的父节点的左子节点
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
            //取得父的父节点的右子节点
            Entry<K,V> y = rightOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) { //如果是红色    --------------情况1
                setColor(parentOf(x), BLACK);    //新插入节点的上层设置为黑色
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));    //取到父父节点
            } else {                        //--------------------情况2
                if (x == rightOf(parentOf(x))) {    //如果新插入的节点是右节点,需要调整到左节点
                    x = parentOf(x);
                    rotateLeft(x);    //左选,可见下面图片,情况2
                }
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateRight(parentOf(parentOf(x))); //对父父节点,进行右旋
            }
        } else {
            Entry<K,V> y = leftOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {    //-----------------------情况3
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {                //---------------------------情况4
                if (x == leftOf(parentOf(x))) {
                    x = parentOf(x);
                    rotateRight(x);
                }
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateLeft(parentOf(parentOf(x)));    //左选
            }
        }
    }
    root.color = BLACK;
}

情况1:插入的新节点是左节点,父节点是红色,父节点的兄弟节点也是红色,那么将父父节点设置为红色,第二层全部设置为黑色

 

情况2 :里面实则有两种情况,但是可以进行简单的化归

然后对G进行右旋,右旋之后,重新上色

情况3:也就是情况1的反向操作,新插入节点的父节点是右节点

情况4:自然和情况2相对,这里就不展示了

颜色设置好之后,需要进行左选,右旋操作

左选:注意传入的参数:一个节点

两个节点,进行旋转,他们的排版其实存在两种情况,传入节点为:左节点 or 右节点 

private void rotateLeft(Entry<K,V> p) {
    if (p != null) {
        Entry<K,V> r = p.right;    //暂时保存自己的右子节点的引用
        p.right = r.left;    
        if (r.left != null)    //如果不为空,则r的左子节点,成功转到p节点的右子节点。
            r.left.parent = p;
        r.parent = p.parent;    //右子节点 变成传入节点同级
        if (p.parent == null)        
            root = r;
        else if (p.parent.left == p)    //如果传入节点是左节点,那么左子节点变成传入节点的右子节点
            p.parent.left = r;
        else                    //传入节点是右节点
            p.parent.right = r;
        r.left = p;    //
        p.parent = r;
    }
}

我们这里传入的是P节点,注意旋转,不是简单的交换子节点的位置,要保证二叉树的有序性,所以左中右的顺序不能改变。

1.传入节点是左节点

2.传入节点是右节点

左旋第一个节点:PP,

总结:传入节点的左子节点不会发生变化,右节点接收右子节点的左子节点,右子节点提升一级,传入节点变成左子节点。

右旋

private void rotateRight(Entry<K,V> p) {
    if (p != null) {
        Entry<K,V> l = p.left;
        p.left = l.right;
        if (l.right != null) l.right.parent = p;
        l.parent = p.parent;
        if (p.parent == null)
            root = l;
        else if (p.parent.right == p)
            p.parent.right = l;
        else p.parent.left = l;
        l.right = p;
        p.parent = l;
    }
}

右旋:

此处就不分析P为左节点情况,同理可得。

 

参考链接:

1.https://www.jianshu.com/p/e136ec79235c

2.https://www.iteye.com/blog/shmilyaw-hotmail-com-1836431

3.https://www.cnblogs.com/LiaHon/p/11221634.html

### Java 中红黑树数据结构实现与应用 #### 红黑树简介 红黑树是一种自平衡二叉查找树,在计算机科学中用于高效地存储和检索键值对。该数据结构的特点在于其能够自动调整节点位置来维持一定的平衡条件,从而保证最坏情况下的时间复杂度为 O(log n)[^1]。 #### 平衡特性对比 相较于 AVL 树而言,尽管两者都致力于维护二叉查找树的高度接近于最小可能高度,但是由于红黑树对于旋转次数的要求较低,因此在频繁发生插入或删除的情况下表现更优;而像 `TreeMap` 和 `TreeSet` 这样的集合类正是利用这一点实现了高效的增删改查功能[^2]。 #### 关键属性定义 为了满足上述提到的性质,每棵红黑树中的结点除了拥有指向左右子树以及父辈指针外还需要额外记录颜色信息(红色/黑色),具体如下所示: ```java private static final boolean RED = false; private static final boolean BLACK = true; static class Node<K,V> { K key; // 存储的关键字 V value; // 对应关键字所映射的对象 Node<K,V> left; // 左孩子引用 Node<K,V> right; // 右孩子引用 Node<K,V> parent;// 父亲引用 boolean color; // 颜色标记 public Node(K key, V value, Node<K,V> parent){ this.key=key; this.value=value; this.parent=parent; this.color=RED; // 新加入的节点默认设为红色 } } ``` #### 插入操作逻辑 当向一棵已有的红黑树内添加新元素时,会先按照普通的 BST 方式找到合适的位置并创建新的叶子节点。之后再依据一系列规则判断是否需要执行重涂色或是旋转变换以恢复整棵树应有的形态特征。 - 如果当前新增加的是根,则直接将其变为黑色; - 若祖父存在且叔叔也为红色,则将父亲、叔叔均改为黑色并将祖父变更为红色继续向上处理; - 当仅有单侧兄弟呈红色状态时可通过一次左(右)转使得双亲成为临时性的“假”根以便后续统一调整; - 剩余情形下只需简单交换父子间染色即可完成修复工作。 以上过程可以概括成一段伪代码形式表示出来: ```pseudo function insertFixup(node) if node is root then set its color to black and return. while (node != null && node's parent is red) // 处理违反红黑树特性的几种场景... end while ``` 实际上完整的算法较为复杂,涉及到多个分支路径的选择问题,这里仅给出大致思路供理解参考之用。 #### 删除操作概述 移除某个指定项的过程同样遵循着相似的原则——即先依照普通二叉搜索树的方式定位目标对象所在位置,并考虑三种不同类型的状况分别采取相应的措施加以应对:如果待消除的目标恰好处于末端处那么可以直接摘掉它而不影响其他部分;反之则需寻找合适的替代品填补空缺后再做进一步讨论。 值得注意的是在整个过程中可能会引入一些暂时不符合标准的情况,这就要求我们及时作出必要的修正动作直至最终达到稳定为止。 #### 应用实例分析 正如前面提及的一样,红黑树广泛应用于各种编程语言的标准库当中作为内部机制支撑起诸如有序表之类的抽象容器类型。例如在 JDK8 版本以后版本里头,`HashMap` 的链地址法解决哈希冲突方案便采用了基于红黑树优化后的链表结构,以此提高极端情况下访问效率的同时兼顾了空间利用率方面的需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值