数据结构---红黑树

红黑树定义:

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

  • 性质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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值