Hable sobre el principio de realización del TreeMap de JDK1.8

La implementación de TreeMap es el algoritmo de árbol rojo-negro. Para comprender la implementación de TreeMap, primero debe comprender el árbol rojo-negro.

Introducción al árbol rojo-negro

RB Tree, el nombre completo es Red-Black Tree, también conocido como "Red-Black Tree", es un árbol de búsqueda binario especial. Cada nodo del árbol rojo-negro tiene un bit de almacenamiento para indicar el color del nodo, que puede ser rojo o negro.

Las características del árbol rojo-negro (la complejidad temporal del árbol rojo-negro es: O (lgn)):

  • Cada nodo es negro o rojo.
  • El nodo raíz es negro.
  • Cada nodo hoja (NIL) es negro. [Nota: ¡El nodo hoja aquí se refiere al nodo hoja que está vacío (NIL o NULL)! ]
  • Si un nodo es rojo, sus nodos secundarios deben ser negros. Si un nodo es negro, sus nodos secundarios pueden ser rojos o negros
  • Todas las rutas de un nodo a los descendientes de ese nodo contienen el mismo número de nodos negros.
  • Los puntos recién agregados son todos rojos

 Diagrama esquemático del árbol rojo-negro

 El funcionamiento básico del árbol rojo-negro: zurdo, diestro

La operación básica del árbol rojo-negro es agregar y eliminar . Sin embargo, agregar y eliminar operaciones puede cambiar la estructura del árbol rojo-negro (puede que no cumpla con las características del árbol rojo-negro). En este momento, es necesario rotarlo (para zurdos, diestros) para transformar en un árbol rojo-negro. En pocas palabras, el propósito de la rotación es mantener el árbol con las características de un árbol rojo-negro.

Mano izquierda

Descripción de rotación:

前提:这里假设x的右孩子为y。下面开始正式操作
将 “y的左孩子” 设为 “x的右孩子”,即 将β设为x的右孩子
将 “x” 设为 “y的左孩子的父亲”,即 将β的父亲设为x

将 “x的父亲” 设为 “y的父亲”
情况1:如果 “x的父亲” 是空节点,则将y设为根节点
情况2:如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
情况3:如果 x是它父节点的右孩子, 将y设为“x的父节点的右孩子”

Mano derecha

 Descripción de rotación:

前提:这里假设y的左孩子为x。下面开始正式操作
将 “x的右孩子” 设为 “y的左孩子”,即 将β设为y的左孩子
将 “y” 设为 “x的右孩子的父亲”,即 将β的父亲设为y
将 “y的父亲” 设为 “x的父亲”
情况1:如果 “y的父亲” 是空节点,则将x设为根节点
情况2:如果 y是它父节点的右孩子,则将x设为“y的父节点的左孩子”
情况3:如果 y是它父节点的左孩子,则将x设为“y的父节点的左孩子”

Resumen: Zurdo y diestro son dos conceptos relativos, con principios similares. Entender uno también comprende al otro.

Análisis de realización del método TreeMap put ()

       El método de implementación de TreeMap put () se divide principalmente en dos pasos, primero: construir un árbol binario de clasificación y segundo: equilibrar un árbol binario.

Para la creación de un árbol binario ordenado, el proceso de agregar nodos es el siguiente:

       1. Busque con el nodo raíz como nodo inicial.

       2. Compare con el nodo actual Si el valor del nuevo nodo es mayor, el nodo hijo derecho del nodo actual se utilizará como el nuevo nodo actual. De lo contrario, el nodo hijo izquierdo del nodo actual se toma como el nuevo nodo actual.

       3. Realice un bucle de forma recursiva en 2 pasos hasta que se recupere el nodo hoja apropiado.

       4. Compare el nuevo nodo con el nodo encontrado en el paso 3. Si el nuevo nodo es más grande, agréguelo como un nodo secundario derecho, de lo contrario, agréguelo como un nodo secundario izquierdo.

De acuerdo con las reglas anteriores, el nodo se puede colocar en la posición más adecuada. El código fuente se implementa de la siguiente manera:

public V put(K key, V value) {  
           //用t表示二叉树的当前节点  
            Entry<K,V> t = root;  
            //t为null表示一个空树,即TreeMap中没有任何元素,直接插入  
            if (t == null) {  
                //比较key值,个人觉得这句代码没有任何意义,空树还需要比较、排序?  
                compare(key, key); // type (and possibly null) check  
                //将新的key-value键值对创建为一个Entry节点,并将该节点赋予给root  
                root = new Entry<>(key, value, null);  
                //容器的size = 1,表示TreeMap集合中存在一个元素  
                size = 1;  
                //修改次数 + 1  
                modCount++;  
                return null;  
            }  
            int cmp;     //cmp表示key排序的返回结果  
            Entry<K,V> parent;   //父节点  
            // split comparator and comparable paths  
            Comparator<? super K> cpr = comparator;    //指定的排序算法  
            //如果cpr不为空,则采用既定的排序算法进行创建TreeMap集合  
            // 该判断主要是为了循环找出当前子节点存放位置的父节点
            if (cpr != null) {  
                do {  
                    parent = t;      //parent指向上次循环后的t  
                    //比较新增节点的key和当前节点key的大小  
                    cmp = cpr.compare(key, t.key);  
                    //cmp返回值小于0,表示新增节点的key小于当前节点的key,则以当前节点的左子节点作为新的当前节点  
                    if (cmp < 0)  
                        t = t.left;  
                    //cmp返回值大于0,表示新增节点的key大于当前节点的key,则以当前节点的右子节点作为新的当前节点  
                    else if (cmp > 0)  
                        t = t.right;  
                    //cmp返回值等于0,表示两个key值相等,则新值覆盖旧值,并返回新值  
                    else  
                        return t.setValue(value);  
                } while (t != null);  
            }  
            //如果cpr为空,则采用默认的排序算法进行创建TreeMap集合  
            else {  
                if (key == null)     //key值为空抛出异常  
                    throw new NullPointerException();  
                /* 下面处理过程和上面一样 */  
                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);  
            }  
            //将新增节点当做parent的子节点  
            Entry<K,V> e = new Entry<>(key, value, parent);  
            //如果新增节点的key小于parent的key,则当做左子节点  
            if (cmp < 0)  
                parent.left = e;  
          //如果新增节点的key大于parent的key,则当做右子节点  
            else  
                parent.right = e;  
            /*  
             *  上面已经完成了排序二叉树的的构建,将新增节点插入该树中的合适位置  
             *  下面fixAfterInsertion()方法就是对这棵树进行调整、平衡,具体过程参考上面的五种情况  
             */  
            fixAfterInsertion(e);  
            //TreeMap元素数量 + 1  
            size++;  
            //TreeMap容器修改次数 + 1  
            modCount++;  
            return null;  
        }  

El algoritmo anterior es para implementar el algoritmo de clasificación del árbol binario de clasificación, pero el árbol binario de clasificación puede estar desequilibrado, lo que hará que la consulta sea complicada para O (N), por lo que también debemos saltar a un árbol binario equilibrado ( árbol rojo-negro), y el equilibrio es posible.Necesita operaciones para zurdos, diestros, colorear y otras.

El contenido del algoritmo de clasificación está en fixAfterInsertion (e);,  analizamos más a fondo:

     private void fixAfterInsertion(Entry<K,V> x) {  
            x.color = RED;    //新增节点的颜色为红色  
  
            //循环 直到 x不是根节点,且x的父节点不为红色  
            while (x != null && x != root && x.parent.color == RED) {  
                //如果X的父节点(P)是其父节点的父节点(G)的左节点  
                if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {  
                    //获取X的叔节点(U)  
                    Entry<K,V> y = rightOf(parentOf(parentOf(x)));  
                    //如果X的叔节点(U) 为红色
                    if (colorOf(y) == RED) {       
                        //将X的父节点(P)设置为黑色  
                        setColor(parentOf(x), BLACK);  
                        //将X的叔节点(U)设置为黑色  
                        setColor(y, BLACK);  
                        //将X的父节点的父节点(G)设置红色  
                        setColor(parentOf(parentOf(x)), RED);  
                        x = parentOf(parentOf(x));  
                    }  
                    //如果X的叔节点(U为黑色);这里会存在两种情况 
                    else {     
                        //如果X节点为其父节点(P)的右子树,则进行左旋转
                        if (x == rightOf(parentOf(x))) {  
                            //将X的父节点作为X  
                            x = parentOf(x);  
                            //右旋转  
                            rotateLeft(x);  
                        }  
                        //将X的父节点(P)设置为黑色  
                        setColor(parentOf(x), BLACK);  
                        //将X的父节点的父节点(G)设置红色  
                        setColor(parentOf(parentOf(x)), RED);  
                        //以X的父节点的父节点(G)为中心右旋转  
                        rotateRight(parentOf(parentOf(x)));  
                    }  
                }  
                //如果X的父节点(P)是其父节点的父节点(G)的右节点  
                else {  
                    //获取X的叔节点(U)  
                    Entry<K,V> y = leftOf(parentOf(parentOf(x)));  
                  //如果X的叔节点(U) 为红色 
                    if (colorOf(y) == RED) {  
                        //将X的父节点(P)设置为黑色  
                        setColor(parentOf(x), BLACK);  
                        //将X的叔节点(U)设置为黑色  
                        setColor(y, BLACK);  
                        //将X的父节点的父节点(G)设置红色  
                        setColor(parentOf(parentOf(x)), RED);  
                        x = parentOf(parentOf(x));  
                    }  
                  //如果X的叔节点(U为黑色);这里会存在两种情况
                    else {  
                        //如果X节点为其父节点(P)的左子树,则先进行右旋转  
                        if (x == leftOf(parentOf(x))) {  
                            //将X的父节点作为X  
                            x = parentOf(x);  
                           //右旋转  
                            rotateRight(x);  
                        }  
                        //将X的父节点(P)设置为黑色  
                        setColor(parentOf(x), BLACK);  
                        //将X的父节点的父节点(G)设置红色  
                        setColor(parentOf(parentOf(x)), RED);  
                        //以X的父节点的父节点(G)为中心右旋转  (左旋)
                        rotateLeft(parentOf(parentOf(x)));  
                    }  
                }  
            }  
            //将根节点G强制设置为黑色  
            root.color = BLACK;  
        }  

Rotación izquierda: rotateLeft ()

private void rotateLeft(Entry<K,V> p) {  
        if (p != null) {  
            //获取P的右子节点,其实这里就相当于新增节点N(情况四而言)  
            Entry<K,V> r = p.right;  
            //将R的左子树设置为P的右子树  
            p.right = r.left;  
            //若R的左子树不为空,则将P设置为R左子树的父亲  
            if (r.left != null)  
                r.left.parent = p;  
            //将P的父亲设置R的父亲  
            r.parent = p.parent;  
            //如果P的父亲为空,则将R设置为跟节点  
            if (p.parent == null)  
                root = r;  
            //如果P为其父节点(G)的左子树,则将R设置为P父节点(G)左子树  
            else if (p.parent.left == p)  
                p.parent.left = r;  
            //否则R设置为P的父节点(G)的右子树  
            else  
                p.parent.right = r;  
            //将P设置为R的左子树  
            r.left = p;  
            //将R设置为P的父节点  
            p.parent = r;  
        }  
    } 

Rotación derecha: rotateRight ()

private void rotateRight(Entry<K,V> p) {  
        if (p != null) {  
            //将L设置为P的左子树  
            Entry<K,V> l = p.left;  
            //将L的右子树设置为P的左子树  
            p.left = l.right;  
            //若L的右子树不为空,则将P设置L的右子树的父节点  
            if (l.right != null)   
                l.right.parent = p;  
            //将P的父节点设置为L的父节点  
            l.parent = p.parent;  
            //如果P的父节点为空,则将L设置根节点  
            if (p.parent == null)  
                root = l;  
            //若P为其父节点的右子树,则将L设置为P的父节点的右子树  
            else if (p.parent.right == p)  
                p.parent.right = l;  
            //否则将L设置为P的父节点的左子树  
            else   
                p.parent.left = l;  
            //将P设置为L的右子树  
            l.right = p;  
            //将L设置为P的父节点  
            p.parent = l;  
        }  
    }  

Colorear: setColor ()

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

El diagrama de zurdos y diestros es el siguiente:

                                   Zurdo diestro

Método de eliminación de TreeMap ()

Eliminación de nodo de árbol rojo-negro

Para los nodos agregados del árbol rojo-negro, la eliminación es más complicada, lo que hace que el árbol rojo-negro originalmente complicado sea más complicado. Eliminar un nodo al mismo tiempo es lo mismo que agregar un nodo, lo mismo es encontrar el nodo eliminado y ajustar el árbol rojo-negro después de la eliminación. Pero el nodo de eliminación aquí no se elimina directamente, sino tomando un "desvío" para eliminarlo a través de un atajo: busque el nodo secundario C del nodo eliminado D y reemplace D con C en lugar de eliminar directamente D, porque D Reemplazado por C, simplemente elimine C directamente. Entonces, aquí, la cuestión de eliminar el nodo padre D se transforma en la cuestión de eliminar el nodo hijo C, de modo que el procesamiento simplifica el complicado evento de eliminación. La regla para el nodo hijo C es: la rama más a la izquierda de la rama derecha o la rama más a la derecha de la rama izquierda.

      El nodo de eliminación del árbol rojo-negro también se dividirá en varias situaciones, aquí está la clasificación según el número de hijos del nodo a eliminar:

       1. Si no hay hijo, es Ye Node. Establezca directamente el puntero hijo correspondiente del nodo principal en NULL, elimine el nodo hijo y está bien.

       2. Solo un hijo. Luego apunte el puntero hijo correspondiente del nodo padre al único hijo del hijo y elimine el nodo hijo.

       3. Hay dos hijos. Esta situación es más complicada, pero aún relativamente simple. Como se mencionó anteriormente, reemplace el nodo D que se eliminará con el nodo secundario C y luego elimine el nodo secundario C.

 

A través del análisis anterior, tenemos una comprensión preliminar de la eliminación de nodos en el árbol rojo-negro. En comparación con la adición de nodos, de hecho es más complicado de seleccionar. A continuación, veré cómo eliminar árboles rojos y negros en Java TreeMap.

    public V remove(Object key) {
        Entry<K,V> p = getEntry(key);// 获取该当前要删除的节点信息
        if (p == null)
            return null;

        V oldValue = p.value; // 记录当前节点值
        deleteEntry(p); //具体删除逻辑 
        return oldValue;
    }

implementación del método deleteEntry ()

    private void deleteEntry(Entry<K,V> p) {
        modCount++;  // 修改次数+1
        size--;  // 元素个数-1
        
        // 如果当前节点有左子树跟右子树
        if (p.left != null && p.right != null) {
            // 查找p的替代节点,规则为当前节点的右节点的最左边或左节点的最右边
            Entry<K,V> s = successor(p);
            // 替换p节点
            p.key = s.key;
            p.value = s.value;
            p = s;
        } // p has 2 children

        // replacement为替代节点,如果P的左子树存在那么就用左子树替代,否则用右子树替代 
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);

        // 如果替代节点存在
        if (replacement != null) {
            // 
            replacement.parent = p.parent;
            //若P没有父节点,则根节点直接变成replacement  
            if (p.parent == null)
                root = replacement;
            //如果P为左节点,则用replacement来替代为左节点  
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            //如果P为右节点,则用replacement来替代为右节点  
            else
                p.parent.right = replacement;

            //同时将P节点从这棵树中剔除掉  
            p.left = p.right = p.parent = null;

            /* 
             * 若P为红色直接删除,红黑树保持平衡 
             * 但是若P为黑色,则需要调整红黑树使其保持平衡 
             */   
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } 
          // p没有子节点,
          // 如果p没有父节点,表示为P根节点,直接删除即可  
          else if (p.parent == null) { 
            root = null;
        } 
          // 如果p没有子节点,说明是叶子节点
          else {
            // 如果当前节点颜色为黑色
            if (p.color == BLACK)
                fixAfterDeletion(p);
            // 如果当前节点父节点不为null
            if (p.parent != null) {
                // 如果当前节点是其父节点的左子树,将左子树直接置为null(删除)
                if (p == p.parent.left)
                    p.parent.left = null;
                // 如果当前节点是其父节点的右子树,将右子树直接置为null(删除)
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }

Si el nodo actual es negro, llame al método fixAfterDeletion () para lograr

    private void fixAfterDeletion(Entry<K,V> x) {
        // 删除节点需要一直迭代,直到 x 不是根节点,且 x 的颜色是黑色
        while (x != root && colorOf(x) == BLACK) {
            if (x == leftOf(parentOf(x))) {
                // 获取当前节点的兄弟节点(父节点的右节点)
                Entry<K,V> sib = rightOf(parentOf(x));

                // 如果sib节点为红色,则交换当前节点的父节点跟sib节点颜色,并进行一次左旋
                if (colorOf(sib) == RED) {
                    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) {
                        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 { // symmetric
                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;
                }
            }
        }

        setColor(x, BLACK);
    }

Este es el código fuente de eliminación del TreeMap, que incluye la conversión de color del árbol rojo-negro y la rotación izquierda (derecha) del árbol para mantener el equilibrio del árbol rojo-negro. Puede ser un poco difícil de Mire la lógica directamente. Puede detenerse y mirarla paso a paso. ¡Cómo cambia la estructura de datos, fácil de entender!

 

 

 

Supongo que te gusta

Origin blog.csdn.net/qq_43037478/article/details/112680880
Recomendado
Clasificación