数据结构——红黑树

本文详细介绍了红黑树的插入和删除操作,涉及各种情况的处理,如首次插入、红节点插入、父叔节点为红等,以及删除时的节点调整规则,包括不同兄弟节点状态下的处理。通过案例分析和代码片段展示了红黑树维护平衡的关键步骤。
摘要由CSDN通过智能技术生成

前提了解

红黑树

红黑树是自平衡的二叉搜索树,具有以下特性

  • 结点是红色或黑色
  • 根结点是黑色
  • 所有叶子都是黑色(叶子是NIL结点)
  • 每个红色结点的两个子结点都是黑色(从每个叶子到根的所有路径上不能有两个连续的红色结点)
  • 从任一节结点到其每个叶子的所有路径都包含相同数目的黑色结点

红黑树的优势

红黑树吸收了2-3-4树和AVL树的优点,放弃了完美平衡的特性,改为局部平衡和完美黑色平衡

放弃2-3-4树的多节点,改为使用颜色来区分不同的节点类型,这样就降低了维护的成本和时间复杂度

红黑树链接方式讨论

  • 以下对应2-3-4树为2-节点、左倾3-节点、右倾3-节点、4-节点
    在这里插入图片描述
  • 以下对应2-3-4树为插入4-节点分裂的过程,分裂到根节点黑色加一
    在这里插入图片描述
  • 一颗平衡的红黑树只会出现上述7种链接方式

插入情况讨论

  • 每次插入的节点都是红节点

case0:第一次插入

  • 操作:将根置黑
  • 简单理解:根结点必须是黑色
  • 对应2-3-4树:相当于第一次生成2-节点
    在这里插入图片描述

case1:红节点插入到黑节点

  • 操作:无需调整
  • 对应2-3-4树:相当于把2-节点变成3-节点或把3-节点变成4-节点
  • 故红节点插入到红节点才需要调整
    在这里插入图片描述

case2:插入结点的父、叔节点为红节点

  • 操作:父、叔置黑,祖置红,向上递归,若递归到根节点,则将根置黑,黑色加1
  • 简单理解:插入两边为红
  • 对应2-3-4树:插入4-节点并分裂,将中间值传给父节点,若递归到根,树高加1
    在这里插入图片描述

case3:插入红节点左边,并且父在祖父左边

  • 操作:父置黑,祖置红,对祖右旋
  • 简单理解:产生左左连续红节点
  • 对应2-3-4树:插入左倾3-节点的左边,生成4-节点
    在这里插入图片描述

case4:插入红节点右边,并且父在祖父左边

  • 操作:对父左旋,变成case3
  • 简单理解:产生左右连续红节点,将其变成左左连续红节点
  • 对应2-3-4树:插入左倾3-节点的中间,生成4-节点
    在这里插入图片描述

case5:插入红节点右边,并且父在祖父右边

  • 操作:父置黑,祖置红,对祖左旋
  • 简单理解:产生右右连续红节点
  • 对应2-3-4树:插入右倾3-节点的右边,生成4-节点
    在这里插入图片描述

case6:插入红节点左边,并且父在祖父右边

  • 操作:对父右旋,变成case5
  • 简单理解:产生右左连续红节点,变成右右连续红节点
  • 对应2-3-4树:插入右倾3-节点的中间,生成4-节点
    在这里插入图片描述

删除情况判断

  • 删除首先要找到节点,判断其是叶节点还是非叶节点
  • 对应2-3-4树,将删除内部节点转为删除叶节点

case1:若待删除结点为叶节点,若为黑则先调整再删除,为红直接删除

在这里插入图片描述

case2:若待删除结点有一个子结点,用子结点替换待删除结点,再调整替换的子节点

这种情况只会出现在左倾3-节点或右倾3-节点
在这里插入图片描述
实际上case2是可以转为case1的,如下图
在这里插入图片描述
但TreeMap中代码将case1和case2分开进行了讨论,为什么要这样?

  • 如果case2转为case1,会多了一个旋转操作和fixAfterDeletion()中的兄左右为黑的循环操作(即下面删除情况讨论中的case3)
  • 调整的本质是调整待删除节点所在的不平衡子树,即调整其父和兄弟节点

case3:若待删除结点有两个子结点,用后继结点替换待删除结点,转为case1或case2

在这里插入图片描述

删除情况讨论

  • 每次删除的都是叶节点,删除黑叶节点才需要调整,下面只讨论删除左边黑叶节点的情况,删除右边黑叶节点对称理解
  • 对应2-3-4树:删除2-节点才需要调整

case1:兄弟节点为黑,并且有红右节点

操作:兄置父颜色,父置黑,兄右置黑,对父左旋
对应2-3-4树:兄弟节点是3或4-节点
在这里插入图片描述
其可能有4种情况,这里列出,下面为删除20,父为黑的情况
在这里插入图片描述
下面为删除70,父为红的情况
在这里插入图片描述

case2:兄弟节点为黑,并且无右节点、有红左节点

操作:兄左置黑,兄置红,对兄右旋,兄左成新兄,转为case1
对应2-3-4树:兄弟节点是3-节点(先将左倾3-节点转为右倾3-节点)
在这里插入图片描述
下面是删除20父为黑,删除70父为红两种情况,转为case1处理,剩下的处理步骤看上面
在这里插入图片描述

case3:兄弟节点为黑,左右节点不存在

操作:兄置红,父置黑(这种情况的红黑树只能通过删除构造)
对应2-3-4树:兄弟节点是2-节点
在这里插入图片描述
下面是删除20父为黑的情况
在这里插入图片描述
在这里插入图片描述
下面是删除70父为红的情况
在这里插入图片描述在这里插入图片描述

case4:兄弟节点为红,转为黑再判断上述3种情况

操作:兄置黑,父置红,对父左旋,兄左成新兄,将兄转为黑
在这里插入图片描述
如下转为case1
在这里插入图片描述
如下转为case2
在这里插入图片描述
如下转为case3
在这里插入图片描述
至此,红黑树的左黑叶节点删除就介绍完了,右边对称同理就不再赘述

红黑树完整代码

以下代码是从TreeMap中抽取出的红黑树代码,上面的插入和删除情况讨论也是由TreeMap代码推导而来

class RbTree<K, V> {
    private static final boolean RED = false;
    private static final boolean BLACK = true;
    private transient TreeMapEntry<K, V> root;

    static final class TreeMapEntry<K, V> {
        K key;
        V value;
        TreeMapEntry<K, V> left;
        TreeMapEntry<K, V> right;
        TreeMapEntry<K, V> parent;
        boolean color = BLACK;

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

        public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }
    }

    public V put(K key, V value) {  //插入,从根节点开始对比找到待插入的位置
        TreeMapEntry<K, V> t = root;
        if (t == null) {
            root = new TreeMapEntry<>(key, value, null);
            return null;
        }
        int cmp;
        TreeMapEntry<K, V> parent;
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
        Comparable<? super K> k = (Comparable<? super K>) key;
        do {
            parent = t;
            cmp = k.compareTo(t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
        TreeMapEntry<K, V> e = new TreeMapEntry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        return null;
    }

    private static <K, V> boolean colorOf(TreeMapEntry<K, V> p) {
        return (p == null ? BLACK : p.color);
    }

    private static <K, V> TreeMapEntry<K, V> parentOf(TreeMapEntry<K, V> p) {
        return (p == null ? null : p.parent);
    }

    private static <K, V> void setColor(TreeMapEntry<K, V> p, boolean c) {
        if (p != null) p.color = c;
    }

    private static <K, V> TreeMapEntry<K, V> leftOf(TreeMapEntry<K, V> p) {
        return (p == null) ? null : p.left;
    }

    private static <K, V> TreeMapEntry<K, V> rightOf(TreeMapEntry<K, V> p) {
        return (p == null) ? null : p.right;
    }

    private void rotateLeft(TreeMapEntry<K, V> p) {
        if (p != null) {
            TreeMapEntry<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;
        }
    }

    private void rotateRight(TreeMapEntry<K, V> p) {
        if (p != null) {
            TreeMapEntry<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;
        }
    }

    private void fixAfterInsertion(TreeMapEntry<K, V> x) {  //插入后调整
        x.color = RED;      //每次插入的都为红节点

        while (x != null && x != root && x.parent.color == RED) {   //若不为根且父为红
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {         //父在祖左边
                TreeMapEntry<K, V> y = rightOf(parentOf(parentOf(x)));  //y为叔
                if (colorOf(y) == RED) {                //叔为红,插入4-节点
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {                //叔为黑,插入3-节点
                    if (x == rightOf(parentOf(x))) {    //插入右边,右左连续红节点
                        x = parentOf(x);
                        rotateLeft(x);      //对父左旋调整为左左连续红节点
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {    //父在祖右边
                TreeMapEntry<K, V> y = leftOf(parentOf(parentOf(x)));   //y为叔
                if (colorOf(y) == RED) {        //叔为红,插入4-节点
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {    //叔为黑,插入3-节点
                    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; //根始终为黑
    }

    public V remove(Object key) {   //删除首先找到键值对
        TreeMapEntry<K, V> p = getEntry(key);
        if (p == null)
            return null;

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

    final TreeMapEntry<K, V> getEntry(Object key) { //通过比较键,获取键值对
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
        Comparable<? super K> k = (Comparable<? super K>) key;
        TreeMapEntry<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;
    }

    static <K, V> TreeMapEntry<K, V> successor(TreeMapEntry<K, V> t) {  //获取后继节点
        if (t == null)
            return null;
        else if (t.right != null) {     //是右子树的最小值
            TreeMapEntry<K, V> p = t.right;
            while (p.left != null)
                p = p.left;
            return p;
        } else {    //若无右子树,则是第一个在左边的父节点
            TreeMapEntry<K, V> p = t.parent;
            TreeMapEntry<K, V> ch = t;
            while (p != null && ch == p.right) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }

    private void deleteEntry(TreeMapEntry<K, V> p) {
        if (p.left != null && p.right != null) {    //若存在左右节点,则用后继节点替代,问题转为删除后继节点
            TreeMapEntry<K, V> s = successor(p);
            p.key = s.key;
            p.value = s.value;
            p = s;
        }
        //若未进入上面if说明: 1.只有左节点  2.只有右节点  3.当前为叶节点
        //若进入上面if说明: 1.后继节点为叶节点  2.后继节点只有右节点
        //总结起来就是替换的节点可能:1.只有一个子节点   2.为叶节点
        TreeMapEntry<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) {  //没有子节点且没有父节点,说明当前删除根节点
            root = null;
        } else {        //没有子节点,但有父节点,说明为叶节点
            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;
            }
        }
    }

    private void fixAfterDeletion(TreeMapEntry<K, V> x) {
        while (x != root && colorOf(x) == BLACK) {      //待删除节点为黑且未递归到根
            if (x == leftOf(parentOf(x))) {                 //删除左边的黑节点
                TreeMapEntry<K, V> sib = rightOf(parentOf(x));  //sib为兄
                if (colorOf(sib) == RED) {      //这个if中兄为红,转为兄为黑的情况
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateLeft(parentOf(x));
                    sib = rightOf(parentOf(x));
                }
                if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) {//兄左右不存在
                    setColor(sib, RED);
                    x = parentOf(x);        //将兄变红,向上递归,若父为红或为根则跳出循环置黑
                } else {
                    if (colorOf(rightOf(sib)) == BLACK) {   //这个if中兄右为黑(兄左为红)转为兄右为红
                        setColor(leftOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateRight(sib);
                        sib = rightOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));    //兄右为红
                    setColor(parentOf(x), BLACK);
                    setColor(rightOf(sib), BLACK);
                    rotateLeft(parentOf(x));
                    x = root;       //结束循环,根置黑
                }
            } else { //对称情况
                TreeMapEntry<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;
                }
            }
        }
        setColor(x, BLACK);     //x为根或3-节点的父节点
    }

    private void innerMidOrderTraversal(TreeMapEntry<K, V> node) {
        if (node == null) {
            return;
        }
        innerMidOrderTraversal(node.left);
        System.out.print("[" + node.key + "-" + node.value + "]");
        if (node.color == BLACK) {
            System.out.print("b" + " ");
        } else {
            System.out.print("r" + " ");
        }
        innerMidOrderTraversal(node.right);
    }

    @NonNull
    @Override
    public String toString() {
        System.out.print("中序遍历: ");
        innerMidOrderTraversal(root);
        return "";
    }
}

代码是标准库拿的,可以完美运行,各种情况已在上面讨论,下面只进行一下简单测试

RbTree tree = new RbTree();
tree.put(50, "a");
System.out.println(tree);
tree.put(20, "b");
System.out.println(tree);
tree.put(80, "c");
System.out.println(tree);
tree.put(70, "d");
System.out.println(tree);
tree.remove(20);
System.out.println(tree);

打印如下

中序遍历: [50-a]b 
中序遍历: [20-b]r [50-a]b 
中序遍历: [20-b]r [50-a]b [80-c]r 
中序遍历: [20-b]b [50-a]b [70-d]r [80-c]b 
中序遍历: [50-a]b [70-d]b [80-c]b 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值