数据结构之红黑树

概述

  1. 在之前的博客《数据结构之二叉树》我提到过二叉排序树,然而二叉排序树的性能取决于二叉树是否平衡:
    最好的情况是 O(logn),存在于完全二叉排序树情况下,其访问性能近似于折半查找;
    最差时候会是 O(n),比如插入的元素是有序的,生成的二叉排序树就是一个链表,这种情况下,需要遍历全部元素才行。如下图
    在这里插入图片描述
  2. 为了改变排序二叉树存在的不足,Rudolf Bayer 在 1972 年发明了另一种改进后的排序二叉树:红黑树
  3. 红黑树是一种 自平衡 的二叉树,所谓的自平衡是指在插入和删除的过程中,红黑树会采取一定的策略对树的组织形式进行调整,以尽可能的减少树的高度,从而节省查找的时间。
  4. 红黑树本质上是一种二叉查找树,但它在二叉查找树的基础上额外添加了一个标记(颜色),同时具有一定的规则。这些规则使红黑树保证了一种平衡,插入、删除、查找的最坏时间复杂度都为 O(logn)
  5. AVL(平衡二叉树)树是一棵严格的平衡树,它所有的子树都满足二叉平衡树的定义。因此AVL树的查找比较高效。但AVL树插入、删除结点后旋转的次数比红黑树多。红黑树用非严格的平衡来降低插入删除时旋转的次数。因此,如果你的业务中查找远远多于插入、删除,那选AVL树; 如果查找、插入、删除频率差不多,那么选择红黑树。
  6. 红黑树常被用于需求查找效率稳定的场景,如 Linux 中内核使用它管理内存区域对象、Java8 中 HashMap 的实现等。

性质

红黑树在原有的二叉查找树基础上增加了如下几个性质:
1. 每个节点要么是红色,要么是黑色
2. 根节点永远是黑色的
3. 所有的叶节点(红黑树中叶子节点其实是 NIL 节点,即空节点)都是是黑色的
4. 每个红色节点的两个子节点一定都是黑色
5. 从任一节点到其子树中每个叶子节点(NIL)的路径都包含相同数量的黑色节点

如下图:
在这里插入图片描述
以上性质保证了红黑树可以做到 从根到叶子的最长路径最多不会超过最短路径的两倍 ,这主要是考虑两个极端的情况,由性质 4 和 5 我们可以知道在一棵红黑树上从根到叶子的最短路径全部由黑色结点构成,而最长结点则由红黑结点相间构成,又因为最短路径和最长路径的黑色结点数目是一致的,所以最长路径上的结点数是最短路径的两倍
因此一棵有 n 个结点的红黑树高度至多为 2log(n+1),查找效率最坏为 O(log(n))

自平衡策略

对于一棵红黑树的操作最基本的无外乎增删改查,其中查和改都不会改变树的结构,所以与普通平衡二叉树操作无异。剩下的就是增删操作,插入和删除都会破坏树的结构,不过借助一定的平衡策略能够让树重新满足定义。平衡策略可以简单概括为三种: 左旋转右旋转 ,以及 变色 。在插入或删除结点之后,只要我们沿着结点到根的路径上执行这三种操作,就可以最终让树重新满足定义。

左旋:左旋就是将节点的右支往左拉,右子节点变成父节点,并把晋升之后多余的左子节点出让给降级节点的右子节点
在这里插入图片描述
右旋:右旋就是反过来,将节点的左支往右拉,左子节点变成了父节点,并把晋升之后多余的右子节点出让给降级节点的左子节点。
在这里插入图片描述
变色: 即改变节点的颜色

红黑树的操作

在讲操作前,先明确一下各节点的叫法
在这里插入图片描述
兄弟节点的子节点也称为“侄子节点”。

插入操作

默认插入的结点为红色。 因为红黑树中黑节点至少是红节点的两倍,因此插入节点的父节点为黑色的概率较大,而此时并不需要作任何调整,因此效率较高。

1. 父为黑
在这里插入图片描述
这种情况插入后无需任何操作。由于黑节点个数至少为红节点的两倍,因此父为黑的情况较多,而这种情况在插入后无需任何调整,这就是红黑树比AVL树插入效率高的原因!

2. 父为红
父为红的情况破坏了红黑树的性质(性质3),此时需要根据叔叔的颜色来做不同的处理。
父节点为红,由红黑树性质可知,祖父节点必定为黑
在这里插入图片描述
(1) 叔叔为红
在这里插入图片描述
此时很简单,只需执行变色操作,即交换父亲、叔叔和祖父的颜色即可。
此时若祖父节点和祖父节点的父节点颜色相同,再以祖父节点为新插入节点,进行刚才相同的操作即可。

(2) 叔叔为黑
此时较为复杂,分如下四种情况(左左,左右,右左,右右):

a) 父亲在左、我在左(简称“左左”)
在这里插入图片描述
以父亲为根节点,进行一次右旋转即可(同时交换父节点和祖父节点的颜色)。

b) 父亲在左、我在右(简称“左右”)
在这里插入图片描述
在这里插入图片描述
先以我为根节点,进行一次左旋转;
再以我为根节点,进行一次右旋转(同时交换新节点和祖父节点的颜色)。

c) 父亲在右、我在左(简称“右左”,与“左右”互为镜像)
在这里插入图片描述
先以我为根节点,进行一次右旋转;
再以我为根节点,进行一次左旋转(同时交换新节点和祖父节点的颜色)。

d) 父亲在右、我在右(简称“右右”,与“左左”互为镜像)
在这里插入图片描述
以父亲为根节点,进行一次左旋转即可(同时交换父节点和祖父节点的颜色)。

删除操作

红黑树的删除情景:

  • 情景1:待删除的节点无左右孩子(即为二叉树的叶子节点)。

  • 情景2:待删除的节点只有左孩子或者右孩子。

  • 情景3:待删除的节点既有左孩子又有右孩子。

对于情景1,直接删除即可;
对于情景2,则把节点删除后,把它的左孩子或者右孩子上移替换即可;
对于情景3,需要先找到待删节点的中序遍历后继,即其右子树的最小值,然后将其与待删除的节点互换,最后再删除该节点(根据二叉搜索树的特点可知,该节点要么为叶子节点,要么只有右孩子)。

删除操作后的平衡:

如果删除的节点是红色,无须做平衡调整。/如果删除的节点是黑色,会导致删除节点的父节点到左右子树路径的黑色节点不一致,需调整以达到平衡。
对于上面三种情景的删除操作,都可以看成是情景2操作后的结果。因为:
对于情景1,可以看成是情景2节点删除,上移替换后的结果;
对于情景3,由于实际删除的是节点的中序遍历后继,而该后继要么为叶子节点,要么只有右孩子)。因此最终删除操作和情景2相同。

因此删除平衡只需判断替换节点的颜色,有以下情况:

以下示意图中,D为要删除的节点,DR为替换节点,S为兄弟节点,SL为兄弟节点左孩子,SR为兄弟节点右孩子

(1) 替换节点为红色
此时,只需将替换节点改为黑色即可
在这里插入图片描述
(2)替换节点为黑色(以删除节点为父节点的左子树为例,右子树类似)
此时删除该节点会导致父节点的左子树路径上黑色节点减一,此时只能去借助右子树,从右子树中借一个红色节点过来即可,具体取决于右子树的情况:

a)兄弟节点为红色
兄弟节点是红色,则此时父节点是黑色,且兄弟节点肯定有两个孩子,且兄弟节点的左右子树路径上均有两个黑色节点,此时只需将兄弟节点与父节点颜色互换,然后将父节点左旋,左旋后,兄弟节点的左子树SL挂到了父节点p的右孩子位置,此时兄弟节点为SL,继续进行b中的判断。
在这里插入图片描述
b) 兄弟节点为黑色
兄弟节点是黑色,那么就只能打它孩子的主意了。

b1) 兄弟节点的左右孩子均为黑色
此时只需将兄弟节点与父节点颜色互换即可(如上图第二步操作,交换P和SL的颜色)

b2) 兄弟节点的右孩子为黑色
则先将兄弟节点S转为红色,然后右旋。此时兄弟节点为SL,兄弟节点的右孩子为S(红色),进入b3操作
在这里插入图片描述
b3) 兄弟节点的右孩子为红色
此时先将兄弟节点与父节点颜色互换,再将兄弟节点的右孩子S涂成黑色,再将父节点左旋即可(如上图第二步操作)。

红黑树实现

这里参考JDK1.8中TreeMap的实现

TreeMap和HashMap的区别:

  • HashMap
    1、基于哈希桶(数组)+链表/红黑树实现
    2、无序的
    3、理想状态下,插入、删除、查找时间复杂度为O(1)
  • TreeMap
    1、基于红黑树实现
    2、有序的,通过指定的comparator或者自然顺序
    3、插入、删除、查找时间复杂度为O(logn)
  1. 定义
public class MyTreeMap<K,V> {
    //红黑树的根节点
    private Entry<K, V> root;
    //红黑树的大小
    private int size = 0;
    //红色
    private static final boolean RED = false;
    //黑色
    private static final boolean BLACK = true;

    /**
     * 红黑树节点类
     *
     * @param <K>
     * @param <V>
     */
    static final class Entry<K, V> {
        K key;  //键
        V value;    //值
        Entry<K, V> left;   //左子树
        Entry<K, V> right;  //右子树
        Entry<K, V> parent; //父节点
        boolean color = BLACK;  //颜色

        Entry(K key, V value, Entry<K, V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }
    }

}
  1. 左旋
	 /**
     * 左旋
     * @param p p为降级节点
     */
    private void rotateLeft(Entry<K,V> p) {
        if (p != null) {
            //r为晋升节点
            Entry<K,V> r = p.right;
            //晋升之后多余的左子节点出让给降级节点的右子节点
            p.right = r.left;
            if (r.left != null)
                //修改晋升之后多余的左子节点的父节点
                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;
        }
    }
  1. 右旋
	 /**
     * 右旋
     * @param p p为降级节点
     */
    private void rotateRight(Entry<K,V> p) {
        if (p != null) {
            //l为晋升节点
            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;
        }
    }

  1. 基础操作
	//返回节点颜色
    private static <K,V> boolean colorOf(Entry<K,V> p) {
        return (p == null ? BLACK : p.color);
    }
    //返回父节点
    private static <K,V> Entry<K,V> parentOf(Entry<K,V> p) {
        return (p == null ? null: p.parent);
    }
    //设置节点颜色
    private static <K,V> void setColor(Entry<K,V> p, boolean c) {
        if (p != null)
            p.color = c;
    }
    //返回左子树
    private static <K,V> Entry<K,V> leftOf(Entry<K,V> p) { return (p == null) ? null: p.left; }
    //返回右子树
    private static <K,V> Entry<K,V> rightOf(Entry<K,V> p) {
        return (p == null) ? null: p.right;
    }
  1. 插入操作
	/**
     * 插入元素
     * @param key
     * @param value
     * @return 如果key存在,返回旧value
     */
    public V put(K key, V value) {
        Entry<K,V> t = root;
        //如果树为空
        if (t == null) {
            root = new Entry<>(key, value, null);
            size = 1;
            return null;
        }
        int cmp;
        Entry<K,V> parent;

        //如果key为空,抛出异常
        if (key == null)
            throw new NullPointerException();
        //将可以转换成可比较类型
        Comparable<? super K> k = (Comparable<? super K>) key;
        //循环比较,直到parent为叶子节点
        do {
            parent = t;
            cmp = k.compareTo(t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            //如果key存在,则替换value值,返回旧value
            else{
                V oldValue = t.value;
                t.value = value;
                return oldValue;
            }
        } while (t != null);
        //创建新节点,并插入节点
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        //插入后调整树,以达到平衡
        fixAfterInsertion(e);
        size++;
        return null;
    }
	
	 /**
     * 插入后平衡
     * @param x
     */
    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)));
                //1.1 如果叔叔节点为红色,则只需执行变色操作
                if (colorOf(y) == RED) {
                    //变色
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    //x指向祖父节点,递归执行
                    x = parentOf(parentOf(x));
                }
                //如果叔叔节点为黑色
                else {
                    //2.1 如果插入节点是父节点的右子树,即“左右”,则先左旋,后右旋
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        //左旋
                        rotateLeft(x);
                    }
                    //2.2 如果插入节点是父节点的左子树,即“左左”,则右旋即可
                    //交换父节点和祖父节点的颜色
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    //右旋
                    rotateRight(parentOf(parentOf(x)));
                }
            }
            //如果父节点为祖父节点的右子树,和左子树操作类似
            else {
                //获取叔叔节点
                Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                //如果叔叔节点颜色是红色,同1.1 操作
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                }
                //如果叔叔节点颜色为黑
                else {
                    //2.3 如果插入节点是父节点的左子树,即“右左”,则先右旋,后左旋
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateRight(x);
                    }
                    //2.4 如果插入节点是父节点的右子树,即“右右”,则左旋即可
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        //根节点永远是黑色
        root.color = BLACK;
    }
  1. 查找元素
 	/**
     * 根据key获取元素值
     * @param key
     * @return
     */
    public V get(Object key) {
        Entry<K,V> p = getEntry(key);
        return (p==null ? null : p.value);
    }

    /**
     * 根据key获取红黑树节点
     * @param key
     * @return
     */
    final Entry<K,V> getEntry(Object key) {
        if (key == null)
            throw new NullPointerException();
        Comparable<? super K> k = (Comparable<? super K>) 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
                //如果key相等,返回当前节点
                return p;
        }
        return null;
    }

  1. 删除元素
	/**
     * 删除元素
     * @param key
     * @return 返回删除的元素值
     */
    public V remove(Object key) {
        Entry<K,V> p = getEntry(key);
        if (p == null)
            return null;

        V oldValue = p.value;
        deleteEntry(p);
        return oldValue;
    }

    /**
     * 删除红黑树节点
     * @param p
     */
    private void deleteEntry(Entry<K,V> p) {
        size--;
        // 1.如果要删除的节点左右子树都不为空
        if (p.left != null && p.right != null) {
            //获取要删除节点的中序遍历后继节点,由于右子树不为空,则必定返回右子树中的最小值
            Entry<K,V> s = successor(p);
            //要删除节点的值替换为后继节点的值
            p.key = s.key;
            p.value = s.value;
            //删除后继节点(根据successor()可知,后继节点要么是叶子节点,要么只有右孩子)
            p = s;
        }

        // 2.如果要删除的节点只有一个子树,则获取该子树
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);
        if (replacement != null) {
            //将子树上移
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            else
                p.parent.right = replacement;

            //删除节点
            p.left = p.right = p.parent = null;

            // 如果删除的节点是黑色,会导致删除节点的父节点到左右子树路径的黑色节点不一致,需要调整以达到平衡
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { // 3.如果红黑树只有一个节点
            root = null;
        } else { // 4. 如果删除的节点是叶子节点
            //如果删除的节点是黑色,会导致删除节点的父节点到左右子树路径的黑色节点不一致,需调整以达到平衡
            if (p.color == BLACK)
                fixAfterDeletion(p);
            //断开与父节点的连接,删除节点
            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }

    /**
     * 删除后平衡
     * @param x x为替换节点
     */
    private void fixAfterDeletion(Entry<K,V> x) {
        //1 如果x为黑色,需要根据x的兄弟节点颜色来调整平衡
        while (x != root && colorOf(x) == BLACK) {
            //如果x为父节点的左孩子
            if (x == leftOf(parentOf(x))) {
                //获取兄弟节点
                Entry<K,V> sib = rightOf(parentOf(x));
                //1.1 如果兄弟节点是红色
                if (colorOf(sib) == RED) {
                    //兄弟节点与父节点互换颜色
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    //左旋
                    rotateLeft(parentOf(x));
                    //重新定位兄弟节点,此时兄弟节点必为黑色
                    sib = rightOf(parentOf(x));
                }
                //1.2 如果兄弟节点为黑色,则需兄弟节点的左右孩子的颜色情况
                //1.2.1 如果兄弟的左右孩子均为黑色
                if (colorOf(leftOf(sib))  == BLACK &&
                        colorOf(rightOf(sib)) == BLACK) {
                    //交换兄弟节点与父节点的颜色
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    //1.2.2 如果兄弟节点的右孩子为黑色
                    if (colorOf(rightOf(sib)) == BLACK) {
                        //交换兄弟节点与兄弟节点左孩子的颜色
                        setColor(leftOf(sib), BLACK);
                        setColor(sib, RED);
                        //右旋
                        rotateRight(sib);
                        //重新定位兄弟节点,此时兄弟节点的右孩子必定为红色
                        sib = rightOf(parentOf(x));
                    }
                    //1.2.3 兄弟节点的右孩子为红色
                    // 交换兄弟节点与父节点的颜色
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    //设置兄弟节点的右孩子为黑
                    setColor(rightOf(sib), BLACK);
                    //右旋
                    rotateLeft(parentOf(x));
                    x = root;
                }
            }
            //当x为父节点的右孩子,与左孩子操作类似
            else {
                Entry<K,V> sib = leftOf(parentOf(x));

                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateRight(parentOf(x));
                    sib = leftOf(parentOf(x));
                }

                if (colorOf(rightOf(sib)) == BLACK &&
                        colorOf(leftOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    if (colorOf(leftOf(sib)) == BLACK) {
                        setColor(rightOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateLeft(sib);
                        sib = leftOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(leftOf(sib), BLACK);
                    rotateRight(parentOf(x));
                    x = root;
                }
            }
        }
        //2 如果x为红色,只需将x改为黑色即可
        setColor(x, BLACK);
    }

    /**
     * 返回当前节点的中序遍历后继节点
     * @param t
     * @param <K>
     * @param <V>
     * @return
     */
    static <K,V> Entry<K,V> successor(Entry<K,V> t) {
        if (t == null)
            return null;
        //如果当前节点有右子树,则返回右子树中的最小值
        else if (t.right != null) {
            Entry<K,V> p = t.right;
            while (p.left != null)
                p = p.left;
            return p;
        }
        //如果当前节点没有右子树,父节点为祖父节点左子节点,则返回祖父节点
        else {
            Entry<K,V> p = t.parent;
            Entry<K,V> ch = t;
            while (p != null && ch == p.right) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }

参考链接:
https://blog.csdn.net/qq_25940921/article/details/82183093
https://blog.csdn.net/Sup_Heaven/article/details/39313731
https://blog.csdn.net/sun_tttt/article/details/65445754
https://blog.csdn.net/qq_34173549/article/details/79636764
https://blog.csdn.net/yangyutong0506/article/details/78204953
https://segmentfault.com/a/1190000014037447
https://blog.csdn.net/qq_42564846/article/details/81127576

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农先锋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值