Java实现红黑树的插入和删除

前言

红黑树定义

红黑树类的准备工作

红黑树的验证

红黑树的插入

红黑树的删除

后记


前言

        红黑树算是最有名的一种平衡二叉搜索树了,几乎只要涉及平衡搜索树的应用,就有它的身影。关于它与AVL树效率之争,也是人们津津乐道的话题。总而言之,红黑树是一种非常优秀的平衡树,网上关于它的博客真的浩如云烟,标题也都是彻底弄懂或者深入理解这样的字眼,不过通读以后,发现大多数描述也都是算法书上的解释,并不说不好,只是因为书上的东西都是别人成功的路,在研究过程所经历的挫折和失败,你几乎看不到,为什么要如此旋转、为什么要如此着色,很多时候我们也说不上来,只能指着书,说是书上讲的。

        虽然我们不能如发明红黑树的作者一般,知道红黑树的所有前因后果,但是我们可以通过它总结的性质,推导出属于自己的红黑树算法。所以我的这篇博文的侧重点为通过五条基本红黑树性质,推导出红黑树的插入和删除的算法。虽然红黑树是由2、3树衍生而来,但是我却是借鉴AVL树,按照传统的平衡二叉搜索树的思路进行算法推导,本文与AVL树的关系过于紧密,如果AVL树的核心(左、右旋转)不是很清楚的,可以参考我的这篇博文:Java实现AVL树的添加和删除,希望本博文能给大家另一种实现红黑树的思路。

红黑树定义

        (1)每个节点或者是黑色,或者是红色。
        (2)根节点是黑色。(红色也可以,只要逻辑完备。)
        (3)每个叶子节点(NIL)是黑色。 (这里叶子节点,是指为空(NIL或NULL)的叶子节点!)
        (4)如果一个节点是红色的,则它的子节点必须是黑色的。
        (5)从给定节点到其任何子代零节点(空节点)的每条路径都包含相同数量的黑色节点。

        简单分析上面五条:1、节点颜色要么是黑或者红色,如同AVL树的平衡因子一般,让节点多一个属性,为平衡做准备。

                                        2、根节点为黑色,这个只是为了维稳方便的设置,后面插入和删除再细说。

                                        3、每个空节点为黑色,与2、3树节点一致,保证节点的完满,纵使空的叶节点也有颜色。

                                        4、红节点的子节点必须为黑色,也就是说从每个叶子到根的所有路径上不能有两个连续的红色节点,最短                                                       的路径可能都是黑色节点,最长的路径可能有交替的红色和黑色节点。这个性质主要是为第五条性质服                                                         务,下面再细说。

                                       5、所有的路径都有相同数目的黑色节点,说明最长路径和最短路径都具有相同数量的黑色节点,这就表明                                                       了没有路径能多于任何其他路径的两倍长,并且树的高度不超过2log_{2}(n + 1),因此节点数为n的红黑                                                           树,其高度为O(log_{n}),由此,红黑树才满足一个平衡树的性质。

        具体的证明:一、一棵有 n个内部节点的红黑树的高度至多为2log_{2}(n + 1)

                             二、 最长路径至多是最短路径的两倍

        可以参考这篇博客:红黑树相关定理及其证明。这些数学性质,能很好地保证了红黑树的搜索效率。对于后面红黑树的插入和删除操作,我们只需要保证算法满足前面的五条性质,即可。

红黑树类的准备工作

        因为红黑树主要使用红黑两种颜色来保证树的平衡,所以我们定义的红黑树节点类,需要在普通的二叉搜索树节点的基础上,添加一个颜色节点。又因为红黑树只有红黑两种颜色,因此我们可以使用boolean类型,来存储这两种颜色。并添加一些获取属性的函数,故红黑树的类定义,基本代码如下所示:

public class RedBlackTree<E extends Comparable<E>>{
    private Node<E> root;
    private int size;
    private static final boolean BLACK = true;
    private static final boolean RED = false;
 
    .
    .
    .    

    private boolean colorOf(Node<E> node){
        return node == null ? BLACK : node.color;
    }
    
    private Node<E> parentOf(Node<E> node){
        return node == null ? null : node.parent;
    }

    private Node<E> leftOf(Node<E> node){
        return node == null ? null : node.left;
    }

    private Node<E> rightOf(Node<E> node){
        return node == null ? null : node.right;
    }

    private void setColor(Node<E> node){
        if(node != null){
            node.color = color;
        }
    }

    private class Node<E extends Comparable<E>>{
        public Node<E> parent;
        public Node<E> left;
        public Node<E> right;
        public boolean color;
        public E value;
        public Node(E value, Node<E> parent){
            this.value = value;
            this.parent = parent;
        }
        public String toString(){
            return node.value + "(" + color + ")";
        }
    }
}

红黑树的验证

        这里默认需要被验证的红黑树已经是二叉搜索树了,这里我主要是验证红黑树的平衡性。验证依据为五条红黑树性质,算法的分析思路为:1、如果根节点为空,则为红黑树,根节点颜色为红,则不为红黑树。

           2、记录任意一条路径的黑节点数量,作为红黑树每条路径黑节点数量的基准点。

           3、利用一个Map结构记录每个节点信息,键为节点对象,值为根节点到该节点的黑节点数量。

           4、如果该节点为非满结点,则从Map中获取根节点到该节点的黑节点数量,与第二条中的基准点进行比较,不相等则为非红黑树。

详细代码如下所示:

public boolean isValid(){
    Node<E> node = root;
    if(node == null){
        return true;
    }else if(node.color == RED){
        return false;
    }
    int blackNum = 0;
    while(node != null){
        if(node.color == BLACK){
            ++blackNum;
        }
        node = node.left;
    }
    ArrayDeque<Node<E>> nodeStack = new ArrayDeque<>();
    nodeStack.push(root);
    HashMap<Node<E>, Integer> nodeMap = new HashMap<>();
    Integer parentBlackNum = null;
    Node<E> parent = null;
    boolean color = false;
    while(!nodeStack.isEmpty()){
        node = nodeStack.pop();
        parent = node.parent;
        if((color = node.color) == parent.color && color == RED){
            return false;
        }
        parentBlackNum = nodeMap.get(parent);
        if(node.left == null || node.right == null){
            int blackCount = color == BLACK ? 1 : 0;
            if(parentBlackNum != null){
                blackCount += parentBlackNum;
            }
            if(blackCount != blackNum){
                return false;
            }
            continue;
        }
        if(parentBlackNum == null){
            nodeMap.put(node, 1);
        }else{
            nodeMap.put(node, parentBlackNum + (color == BLACK ? 1 : 0));
        }
        if(node.right != null){
            nodeStack.push(node.left);
        }
        if(node.left != null){
            nodeStack.push(node.right);
        }
    }
    return true;
}

红黑树的插入

        红黑树的插入方式和普通的二叉搜索树插入一般,具体可以参照这篇博文:Java实现二叉搜索树的插入。当我们插入一个新的节点时,就会产生二个问题:一是,新节点的颜色如何破坏红黑树规则,二是,红黑树规则被破坏后如何平衡。

        第一个问题,红黑树节点的颜色只能是红、黑色,在插入新节点时,原先的红黑树是平衡的,各路径的黑色节点数量相同。当新节点颜色为黑色时,被它插入的子树黑节点数量将会加一,会破坏规则五。最痛苦的是,除了根节点插入时,不需要平衡,接下来,每次插入都需要平衡。当新节点颜色为红色时,被它插入的子树黑节点数量不变,因为父节点可能是红色节点,所以它可能破坏规则四,但是相对于节点为黑色,它不需要每次插入都进行平衡,只有父节点为红色时,才进行平衡操作,为了提高效率,所有新插入的节点颜色都为红。

        第二个问题,当插入的节点是红色时,在其父节点也为红色时,会打破规则四。如何平衡呢?假设我们把父节点“弄”为黑色,且满足红黑树所有规则,即可,但是将一个红色节点直接设置为黑色,一定会破坏规则五。我们可以看看下面的这个图示,找找灵感。

        上图虽然理论上有点不严谨,且节点的左右关系并不全面,但是上图的颜色分布却满足所有需要平衡的插入情况。插入节点2,其颜色为红色,父节点3也为红色,叔叔节点5可能为红色也可能为黑色,祖父节点4一定为黑色。我们希望将节点3变成黑色,单纯的将3颜色置黑,前面就说了,行不通。祖父节点4为黑色,咦,黑色!黑色不正是节点3需要的颜色,如果把节点3的红色给祖父节点4,再把祖父节点4的黑色给节点3,不就大功告成了吗。怎么交换节点3和节点4呢?如果大家对AVL树比较了解,就知道我们交换子节点和父节点,都是通过左右旋转实现的,然后重新设置子节点和父节点的高度值即可。那么对于红黑树呢,我们同样可以使用左右旋转来交换节点3和节点4,再设置好节点3和节点4的颜色,不就解决问题了吗。下面是旋转上色后的图示:

        对于上图,我们可以看到,旋转后的几乎满足红黑树的所有条件,哎,很可惜是几乎,为什么呢?因为当节点5是红色时,违反了规则四。此时,我们将5为红色节点的情况单独提取出来,如下图所示:

        此时,旋转上面已经告诉我们不行了,就算旋转后,将5换成黑色,规则5也会被破坏。既然节点3借不到颜色,那就只能把自己的颜色变黑了(如同上梁山一般,实在是被逼无奈。),节点4一看到节点3变黑了,为了大局(规则五)着想,它只能把自己的颜色弄成红色算了,节点5看到自己的老父亲节点四都变红了,为了避嫌(规则四),只能把自己的颜色变黑了。此时的节点3一看自己的兄弟和父亲都为自己变色了,只能含泪把自己儿子(节点3)闯的祸让自己的老父亲(节点4)来承担了(老父亲心里真的哔了狗了)。总结成算法思路为:当父节点和叔叔节点都为红色时,将父节点和叔叔节点都置黑,祖父节点置红,将祖父节点设置为当前节点,继续进行平衡操作

        我们现在知道了如何解决叔叔节点为红色的情况。叔叔节点为黑色时,我们只知道利用旋转和着色可以解决问题,所以接下来,我们开始全面考虑该问题的所有情况。在以前学习AVL树时,它的左右子树的平衡操作是镜像的,两者操作几乎差不多,其实红黑树也是如此,所以我主要分析左子树的情况,右子树与之对应即可。在分析之前,我们先把准备工作做好,就是解决红黑树旋转的问题。其实红黑树旋转和AVL树的旋转是一模一样的,具体代码完全来自于我的博文:Java实现AVL树的添加和删除。稍微修改,如下所示:

private void rotateRight(Node<E> node){
    assert node.left != null;
    Node<E> left = node.left;
    node.left = left.right;
    if(left.right != null){
        left.right.parent = node;
    }
    Node<E> parent = node.parent;
    node.parent = left;
    left.right = node;
    left.parent = parent;
    if(parent == null){
        root = left;
    }else if(node == parent.left){
        parent.left = left;
    }else{
        parent.right = left;
    }
}

private void rotateLeft(Node<E> node){
    assert node.right != null;
    Node<E> right = node.right;
    node.right = right.left;
    if(right.left != null){
        right.left.parent = node;
    }
    Node<E> parent = node.parent;
    node.parent = right;
    right.left = node;
    right.parent = parent;
    if(parent == null){
        root = right;
    }else if(node == parent.left){
        parent.left = right;
    }else{
        parent.right = right;
    }
}

public void rotateLeftRight(Node<E> node){
    rotateLeft(node.left);
    rotateRight(node);    
}

private void rotateRightLeft(Node<E> node){
    rotateRight(node.right);
    rotateLeft(node);
}

        接下来,我们就针对左子树的情况来进行分析,所谓左子树情况,其实就是父节点为祖父节点的左节点的情况。所以被插入的节点就有两种情况,一种是被插入的节点是父节点的左节点,一种是被插入的节点是父节点的右节点。

        当被插入的节点是父节点的左节点时,具体图示如下所示:

        此时只需要将祖父节点4置红,将父节点3置黑,最后对祖父节点进行右旋,即可。

        当被插入的节点是父节点的右节点时,具体图示如下所示:

        这里为什么不和上面一般操作呢,因为对祖父节点进行右旋后,红色节点3变成了红色节点4的左节点,违反了规则四,然后根据AVL树在这么情况的插入方式,如上图所示,可以成功解决该问题。具体操作是,先将祖父节点4置红,将被插入节点3置黑,最后对祖父节点进行左右旋,即可。

        因为这两种情况,其祖父节点处的节点颜色都不变,因此红黑树只需要一次维稳操作,即可实现整棵红黑树的平衡。具体的算法思路如下:

        1、当插入节点和其父节点都为红色时,才执行平衡操作。因为父节点为红色,则祖父节点一定不为空。

        2、叔叔节点为红色时(祖父节点左右节点颜色相同),将父节点和叔叔节点都置黑,祖父节点置红,将祖父节点设置为当前节点,继续进行平衡操作。因为下次平衡操作可能直接退出,且当前节点为红色,且等于根节点,所以需要在最后将根节点置黑,免得违反规则二。

        3、叔叔节点为黑色时,将祖父节点设置为红色,当父节点是祖父节点的左节点时,如果插入节点为父节点的左节点,将父节点设置为黑色,对祖父节点进行右旋;如果插入节点为父节点的右节点,将插入节点设置为黑色,对祖父节点进行左右旋。退出操作。

       4、叔叔节点为黑色时,将祖父节点设置为红色,当父节点是祖父节点的右节点时,如果插入节点为父节点的左节点,将插入节点设置为黑色,对祖父节点进行右左旋;如果插入节点为父节点的右节点,将父节点设置为黑色,对祖父节点进行左旋。退出操作。

        根据算法思路,红黑树的插入代码如下:

public boolean insert(E e){
    Node<E> node = root;
    if(node == null){
        root = new Node<E>(e, null);
        root.color = BLACK;
        ++size;
        return true;
    }
    Node<E> parent = null;
    int cmp = 0;
    do{
        parent = node;
        cmp = e.compareTo(node.value);
        if(cmp > 0){
            node = node.right;
        }else if(cmp < 0){
            node = node.left;
        }else{
            return false;
        }
    }while(node != null);
    Node<E> newNode = new Node<E>(e, parent);
    if(cmp > 0){
        parent.right = newNode;
    }else{
        parent.left = newNode;
    }
    fixAfterInsertion(newNode);
    ++size;
}

private void fixAfterInsertion(Node<E> node){
    while(colorOf(node) == RED && colorOf(node.parent) == RED){
        Node<E> parent = node.parent;
        Node<E> grandParent = parent.parent;
        if(colorOf(grandParent.left) == colorOf(grandParent.right)){
            setColor(grandParent.left, BLACK);  
            setColor(grandParent.right, BLACK);
            setColor(grandParent, RED);
            node = grandParent;
            continue;
        }
        setColor(grandParent, RED);
        if(parent == grandParent.left){
            if(node == parent.left){
                setColor(parent, BLACK);
                rotateRight(grandParent);
            }else{
                setColor(node, BLACK);
                rotateLeftRight(grandParent);
            }
        }else{
            if(node == parent.right){
                setColor(parent, BLACK);
                rotateLeft(grandParent);
            }else{
                setColor(node, BLACK);
                rotateRightLeft(grandParent);
            }
        }
        break;
    }
    setColor(root, BLACK);
}

        如果大家对AVL树的插入比较熟悉,会发现AVL树的插入和我上面的代码几乎一模一样,只是多了些着色操作。虽然上面的代码和算法书里面的红黑树代码不一样,但本质是相同的,都是为了满足红黑树五个性质,两者只是思考的方式不一样罢了。

红黑树的删除

        红黑树删除节点的操作,和普通的二叉搜索树删除节点的操作一致,只是因为删除该节点后,破坏了红黑树中的规则,导致红黑树不平衡,需要我们删除后进行维衡处理。那到底删除节点后,红黑树的哪些规则被破坏了?

        对于任何一种二叉搜索树,删除一个节点,最终被删除的节点的度都是小于2。

        当被删除的节点是红色时,假设被删除节点含有一个子节点,那么这个子节点必为黑色节点,但是这种情况不满足规则五,所以此时被删除的节点必定是叶节点,删除它以后。不会破坏红黑树的任何性质,因此这种情况就无需平衡操作。

        当被删除的节点是黑色时,删除节点一定会破坏规则五。如果被删除节点的子节点为红色,只需要将这个子节点置黑,就能重新符合所有规则,即红黑树平衡了。但如果被删除节点的子节点是黑色,此时的子节点一定是零节点,即空节点,单纯的置色解决不了问题,它必须从其他地方拿一个黑色节点,具体怎么“拿”呢?

        在后续的说明中,我会把被删除节点的子节点记作“节点”,把被删除节点的父节点记作“父节点”。因为被删除节点为黑色,所以节点一定缺了一个黑色节点。我们想要拿一个黑节点,一定要保持其他子树的平衡,让自己这边的子树多一个黑色节点。想让自己子树多一个黑色节点,只能找自己的父节点,只有它离节点最近,最容易实现。此时,我们维稳思想核心就是:从兄弟节点那里(兄弟节点或者兄弟节点的子节点)“拿”一个红色节点,将父节点变黑,通过旋转,让这个红节点去替换父节点(红节点置父节点原先颜色),并保证兄弟节点部分的平衡。

        假设兄弟节点为红,处理方式如下图所示:

        通过观察,我们发现兄弟节点5是一个红节点,因此,我们可以按照维稳思想,进行方式二的操作。将父节点3置黑,节点5置父节点颜色,然后左旋,此时5、3、NIL这条路径的确是平衡了,但是5、3、4这条路径确多了一个黑色节点。如果我们直接将节点置红,因为节点4可能存在红色的子节点,免得违反规则四;此时我们换个思路,先不着急的就将父节点置黑,让旋转过来的节点4保持平衡,即将节点3置红,这样节点NIL虽然还是缺少一个黑节点,但叔叔节点确变成了黑色节点,这种是我们后面要处理的情况,还不如将它们归类在一起,统一处理,更为方便一点。因此,这种情况可以转换为兄弟节点为黑色的情况。因为方式一需要额外的维稳操作,因此,我选择方式二,和兄弟节点为黑色的情况一起处理。因为节点可以为左节点和右节点,两者其实处理方式一致,因此,我就选择节点为左节点的方式进行操作。

        由上可知,当叔叔节点为红色时,需将父节点置红,叔叔节点置黑(原先父节点的颜色),再对父节点左旋,节点不变,继续操作

        接下来我们来分析,叔叔节点为黑色的情况,此时父节点的颜色未知。因为叔叔节点为黑色,所以叔叔节点的两个子节点,有四种不同颜色情况。

        第一种,当叔叔节点的两个子节点都为黑色时,解决方案如下图所示:

        这种情况,兄弟节点那里一个红节点都没有,想“拿”都拿不了,既然如此,我们还不如寄希望于父节点,把整个父节点弄成缺一个黑色节点的情况,反正最终有根节点的帮助,不然规则二不是浪费了吗。我们将节点置于节点3,并将节点5置红。当节点3为红色时,退出操作,然后将其置黑,这样整棵树平衡了,就算节点3是黑色,我们还能继续向上维稳,直到根节点,当节点3为根节点时,根节点也就无所谓缺一个黑节点了,自然就平衡了,所以这种方式在理论上能保证这种情况的平衡。

        由上可知,当叔叔节点(黑色)的两个子节点都为黑色时,只需要将叔叔节点置红,节点指向父节点,继续操作

        第二种,当叔叔节点的左子节点为红色并且右子节点为黑色时,解决方案如下图所示:

        叔叔节点的左子节点为红色,如何用节点4来取代节点3呢?如果前面插入部分比较了解的话,可以发现,直接对父节点3进行右左旋,然后4的颜色涂成节点3的颜色,节点3涂成黑色就大功告成了。仔细观察,其实我们也可以发现,就算叔叔节点的右节点为红色,此操作也依然成立。

        由上可知,当叔叔节点(黑色)的左节点为红且右节点为黑时,将左节点涂为父节点颜色,父节点涂黑,对父节点右左旋,退出。

        第三种,当叔叔节点的左子节点为黑色并且右子节点为红色时,解决方案如下图所示:

        叔叔节点的右子节点为红色,如何用节点6来取代节点3呢?因为节点3不管怎么旋转,节点6和节点5的相对位置都是不变的,单纯的左右旋不足以用节点6取代节点3,那怎么办呢?如果我们把节点5和节点6的颜色交换,再把节点5涂成节点3的颜色,节点3涂成黑色,最后对节点3进行左旋,就大功告成了。仔细观察,其实我们也可以发现,就算叔叔节点的左节点为红色,此操作也依然成立。

        上面,两种情况都能处理左右节点都为红色的情况,因为左旋效率大于右左旋效率,所以叔叔节点的左子节点和右子节点都为红色的情况,就使用第三种情况的解决方式。

        由上可知,当叔叔节点(黑色)的右节点为红时,将右节点涂黑,叔叔节点涂成父节点颜色,对父节点左旋,退出。

        针对节点为其父节点的左节点的情况,详细的删除的总结如下:

        1、被删除节点颜色为黑时,执行维稳操作。

        2、节点为红色时,直接将其涂成黑色,结束操作。

        3、当节点为黑色且其父节点存在时,执行平衡操作。

                a、当兄弟节点为红色时,将父节点涂红,将兄弟节点涂黑,最后对父节点进行左旋,接着执行下面的操作。

                b、当兄弟节点为黑色,且它的两个子节点都是黑色时,将兄弟节点涂红,节点指向父节点,重新开始执行平衡操作。

                c、当兄弟节点为黑色,且它的右节点为黑时,将左节点涂成父节点颜色,父节点涂黑,对父节点右左旋,退出。

                d、当兄弟节点为黑色,且它的右节点为红时,将右节点涂黑,兄弟节点涂成父节点颜色,父节点涂黑,对父节点左旋,退出。

        根据上面的总结,转换为删除代码如下:

public boolean remove(Object e){
    Node<E> replaced = getNode(e);
    if(replaced == null){
        return false;
    }else{
        --size;
    }
    Node<E> removed = replaced;
    if(replaced.left != null && replaced.right != null){
        removed = successor(replaced);
    }
    Node<E> moved = removed.left != null ? removed.left : removed.right;
    Node<E> parent = removed.parent;
    if(moved != null){
        moved.parent = parent;
    }
    if(parent == null){
        root = moved;
    }else if(removed == parent.left){
        parent.left = moved;
    }else{
        parent.right = moved;
    }
    if(replaced != removed){
        replaced.value = removed.value;
    }
    if(removed.color == BLACK){
        fixAfterDeletion(moved, parent);
    }
    return true;
}

private void fixAfterDeletion(Node<E> node, Node<E> parent){
    while(colorOf(node) == BLACK && parent != null){
        Node<E> sibling = null;
        if(node == parent.left){
            sibling = parent.right;
            if(colorOf(sibling) == RED){
                setColor(sibling, BLACK);
                setColor(parent, RED);
                rotateLeft(parent);
                sibling = parent.right;
            }
            if(colorOf(leftOf(sibling)) == BLACK && colorOf(rightOf(sibling)) == BLACK){
                setColor(sibling, RED);
                node = parent;
                parent = node.parent;                    
            }else{
                if(colorOf(sibling.right) == BLACK){
                    setColor(sibling.left, parent.color);
                    rotateRightLeft(parent);
                }else{
                    setColor(sibling.right, BLACK);
                    setColor(sibling, parent.color);
                    rotateLeft(parent);
                }
                setColor(parent, BLACK);
                return;
            }
        }else{
            sibling = parent.left;
            if(colorOf(sibling) == RED){
                setColor(sibling, BLACK);
                setColor(parent, RED);
                rotateRight(parent);
                sibling = parent.left;
            }
            if(colorOf(leftOf(sibling)) == BLACK && colorOf(rightOf(sibling)) == BLACK){
                setColor(sibling, RED);
                node = parent;
                parent = node.parent;
            }else{
                if(colorOf(sibling.left) == BLACK){
                    setColor(sibling.right, parent.color);
                    rotateLeftRight(parent);
                }else{
                    setColor(sibling.left, BLACK);
                    setColor(sibling, parent.color);
                    rotateRight(parent);
                }
                setColor(parent, BLACK);
                return;
            }
        }
    }
    setColor(node, BLACK);
}

        这上面代码和算法书上的红黑树删除代码有一些出入,不过基本思路差不多,只是有一部分是参考的AVL树。

后记

        其实我推理这两个算法时,也不一帆风顺,虽然上面介绍一部分我错误的方式,但是还有一些并没有一一写出来,主要是用PPT画红黑树太麻烦了,不过最后推理出来以后,还是很开心的!!!

        红黑树的插入和删除,说起来难,其实并没想象中的困难。虽然我的插入和删除算法和一般的红黑树算法有所不同,但是其本质相同,都是为了满足红黑树的五点性质。我的这两个算法,灵感来源于AVL树的插入和删除,所以,大家可以参考AVL树的相关原理,来看这篇博文。其实大众版的红黑树算法,可以参考java.util.TreeMap类,里面有插入和删除算法的完整实现,可以对照起来学习。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值