Este artículo lo llevará a través del árbol rojo-negro --- el árbol rojo-negro es tan simple

Tabla de contenido

Tabla de contenido

Tabla de contenido

El autor tiene algo que decir.

árbol de búsqueda binaria

operación común

        Buscar (común para árboles rojo-negros): para encontrar cada nodo, comenzamos desde el nodo raíz

        insertar:

        Encuentre el valor mínimo (común para árboles rojo-negros):

        Encuentre el valor máximo (común para árboles rojo-negros):

        Encuentre nodos predecesores (común para árboles rojo-negro):

        Encuentre nodos sucesores (común para árboles rojo-negros):

        Traversal (común para árboles rojo-negros):

        borrar:

Continuación de la pregunta BST

    

árbol AVL

El árbol AVL logra el equilibrio

Con el árbol AVL, ¿por qué necesitamos un árbol rojo-negro?

Una ilustración de la construcción de un árbol AVL

zurdo

Diestro

2-3-4 árbol

Proceso de construcción del árbol 2-3-4

Reglas de conversión de árbol 2-3-4 a árbol rojo-negro

árbol negro rojo

      definición

      5 principios del árbol rojo-negro contra el árbol 2-3-4:

        operación común

        Descoloramiento:

        Mano izquierda:

        Rotación derecha:

        Agregado:

Eliminar (dificultad):

Ajuste después de la eliminación       

        Descripción resumida:

Referencia de video del árbol rojo-negro: 14-Operación del núcleo del árbol rojo-negro: el ajuste después de la eliminación de los nodos hermanos se puede realizar tomando prestada la situación y el código_哔哩哔哩_bilibili

Árbol rojo-negro escrito a mano (código)

El autor tiene algo que decir.

        Si quieres jugar con árboles rojos y negros, debes tener cierta comprensión del juego de imitación. Cuando jugamos al Cubo de Rubik, siempre y cuando sigamos las reglas, no importa cuán complicado o simple sea su proceso y pasos, finalmente puede hacer que cada cara solo necesite un color para resolverse con éxito. (Por favor, deje el palo: tome el Cubo de Rubik 3*3 como ejemplo). Lo mismo es cierto para jugar árboles rojo-negro, siempre que siga las reglas (zurdo, diestro, cambio de color), no importa cuál sea el proceso en su medio, finalmente puede escribir un rojo-negro. árbol (el orden de los pasos para cada persona puede ser diferente, pero el principio es el mismo).

        Es demasiado laborioso dibujar imágenes por computadora, por lo que dibujaré algunas imágenes a continuación en papel, espero que pueda entender. Si tiene una buena recomendación de herramienta de dibujo, puede comentar o enviar un mensaje privado.

        He estado buscando un código de árbol rojo-negro durante mucho tiempo, pero no he encontrado uno completo. . . Después de la impotencia, escriba un código de árbol rojo-negro usted mismo ==> el código de árbol rojo-negro más completo de c ++ en toda la red

árbol de búsqueda binaria

         Hablando de árbol rojo-negro, primero debemos conocer su antepasado antiguo: árbol de búsqueda binaria

        Definición: Es un árbol binario cuyo nodo izquierdo es más pequeño que el nodo padre y cuyo nodo derecho es más grande que el nodo padre. Su altura determina la eficacia de la búsqueda.

         Los árboles de búsqueda binarios están ordenados Cabe decir que todos los árboles de búsqueda binarios y sus variantes están ordenados. ¿Cómo decir que está ordenado? Lo siguiente puede explicar: el árbol de búsqueda binario se basa en la idea de binario, el propósito es acelerar la eficiencia de búsqueda. Lo que se suele hacer antes del binario es ordenar primero, y es imposible binar una secuencia sin orden.

Veamos las reglas de clasificación del árbol de búsqueda binario: cuando establecemos un sistema de coordenadas en la periferia de un árbol de búsqueda binario, encontraremos un fenómeno mágico.

¿No es así, de hecho, se ordena en la coordenada X y luego se divide en dos? Incluyendo lo que llamamos árboles rojo-negros.

Extensión: pensar en el recorrido previo al pedido, el recorrido en orden y el recorrido posterior al pedido de árboles. Independientemente de la conveniencia, todas las secuencias de acceso son las mismas pero el tiempo de impresión es diferente. Compruébalo tú mismo, ja. 

operación común

        Buscar (común para árboles rojo-negros): para encontrar cada nodo, comenzamos desde el nodo raíz

        1. Si el valor de búsqueda es mayor que el valor actual, busque el subárbol derecho

        2. Si el valor de búsqueda es igual al valor actual, deje de buscar y regrese al nodo actual

        3. Si el valor de búsqueda es menor que el valor actual, busque en el subárbol izquierdo

        insertar :

        Para insertar un nodo, primero se debe encontrar la posición del nodo de inserción. La comparación sigue siendo desde el nodo raíz. Si es más pequeño que el nodo raíz, se comparará con el subárbol izquierdo. De lo contrario, se comparará con el subárbol derecho. Hasta que el subárbol izquierdo esté vacío o el subárbol derecho esté vacío. , se insertará en la posición vacía correspondiente.

       Encuentre el valor mínimo (común para árboles rojo-negros):

        Busque a lo largo del subárbol izquierdo del nodo raíz hasta el último nodo que no esté vacío, que es el nodo más grande del árbol actual.

        Encuentre el valor máximo (común para árboles rojo-negros):

        Busque a lo largo del subárbol derecho del nodo raíz hasta el último nodo que no esté vacío, que es el nodo más grande del árbol actual.

       Encuentre nodos predecesores (común para árboles rojo-negro):

        menor que el valor máximo del nodo actual

        Encuentre nodos sucesores (común para árboles rojo-negros):

        mayor que el valor mínimo del nodo actual

        Traversal (común para árboles rojo-negros):

        Visite todo el árbol de acuerdo con un cierto orden, los comunes son el recorrido previo al pedido, el recorrido en orden, el recorrido posterior al pedido

        eliminar :

        En esencia, es encontrar un nodo predecesor o un nodo sucesor para reemplazar

        Hay tres casos:

        1. Los nodos hoja se eliminan directamente

                      2. Si solo hay un nodo secundario, reemplácelo con un nodo secundario

          3. Si hay dos nodos secundarios, debe encontrar un nodo de reemplazo (nodo predecesor o nodo sucesor)

        En cuanto a por qué necesitamos encontrar un nodo predecesor o un nodo sucesor, volvemos a la definición de un árbol binario de búsqueda: es un árbol binario cuyo nodo izquierdo es más pequeño que el nodo padre y cuyo nodo derecho es más grande que el nodo padre. . La propiedad se puede mantener sin cambios a través del nodo predecesor y el nodo sucesor. Más tarde, cuando se elimina el árbol rojo-negro, también se usa este método. La diferencia es que el árbol rojo-negro necesita asegurar el equilibrio negro, por lo que necesita ser ajustado.

Continuación de la pregunta BST

        Volvamos y hablemos de un caso especial del siguiente árbol de búsqueda binario: un árbol de búsqueda binario sesgado, la altura de este árbol es N

    

   Cuando insertamos un conjunto de elementos secuencialmente, el árbol binario degenera en una lista enlazada, y cuando degenera en una lista enlazada, la eficiencia de búsqueda se convierte en O(n).

        Ampliemos aquí para decir qué tan grande es la diferencia entre O(n) y O(logN), dibujemos una curva de función:

        

        En otras palabras, la notación Big O expresa la relación en función de N. Entonces, cuando lo pensamos, debemos considerarlo de acuerdo con el tamaño de los datos. Cuando hay muy pocos datos, es más rápido usar una lista enlazada.

        Volviendo a BST, basado en los problemas de BST, se produjo un árbol de búsqueda binario balanceado. Al insertar y eliminar un árbol equilibrado, la altura se mantendrá en logN a través de la operación de rotación. Dos árboles equilibrados representativos son árboles AVL (el equilibrio de altura tiene todas las propiedades de un árbol de búsqueda binaria y la diferencia de altura entre los subárboles izquierdo y derecho es lo mismo sobre 1) y árboles rojo-negros.

        Cómo elegir el árbol AVL o el árbol rojo-negro:

        Cuando hay muchas operaciones de búsqueda y casi ninguna operación de inserción y eliminación, es más eficiente elegir un árbol AVL que un árbol rojo-negro.

        Cuando hay muchas operaciones de inserción y eliminación, es más apropiado elegir un árbol rojo-negro. (Puede entenderlo como: el árbol rojo-negro es una solución de compromiso de bajo perfil)

árbol AVL

        En particular, no quería hablar sobre el árbol AVL. Ahora que lo he escrito aquí, hablemos de eso por cierto y exploremos el costo del árbol AVL para mantener el equilibrio. El balance de altura tiene todas las propiedades de un árbol de búsqueda binaria, y la diferencia de altura entre los subárboles izquierdo y derecho no excede 1

El árbol AVL logra el equilibrio

        A través de zurdos y diestros (bajo la descripción: zurdos y diestros no deben romper las reglas de búsqueda del árbol de búsqueda binaria)

Con el árbol AVL, ¿por qué necesitamos un árbol rojo-negro?

        El árbol AVL no es tan bueno como el árbol rojo-negro (el árbol rojo-negro solo mantiene los nodos negros equilibrados) debido a su implementación compleja y al bajo rendimiento de inserción y eliminación.

Una ilustración de la construcción de un árbol AVL

     

         Parece que olvidé mencionar de antemano la operación de rotación a la izquierda y rotación a la derecha (muy simple, y el código también es simple de implementar)

zurdo

        Cómo expresarlo, cómo hacer animaciones. Déjame dibujarlo a mano, escribe un ejemplo.

        

         ¿No conoces la onda de imagen? Imagina que alguien tira hacia abajo a lo largo de la línea 3-5-6 (queriendo colocar la polea), luego 5 baja y 6 sube. En este momento, 5 se convierte en un nodo secundario de 6, y 6 tiene tres nodos secundarios que obviamente no se ajustan al árbol binario. Para mantener las propiedades básicas del árbol de búsqueda binaria, lo más adecuado es que 5.5 se convierta en el hijo derecho de 5.

Diestro

        Al contrario de la rotación a la izquierda, puedes hacer un dibujo tú mismo para experimentarlo.

2-3-4 árbol

        Permítanme hablar de eso primero, ¿por qué escribir un árbol 2-3-4 primero? Debido a que la naturaleza del árbol 2-3-4 puede deducir la naturaleza del árbol rojo-negro, escuché que el árbol rojo-negro es un árbol 2-3-4, no sé si es cierto. De todos modos, es conveniente que entendamos que el árbol rojo-negro es real, por supuesto, debemos entender que no podemos memorizarlo a la fuerza, por lo que no es conveniente optimizar de acuerdo con la situación real, y es fácil de olvidar

        El árbol 2-3-4 es un árbol B (Balance Tree) de cuarto orden, que pertenece a un árbol de búsqueda multidireccional, y su estructura tiene las siguientes restricciones.

        1. Todos los nodos de hoja tienen la misma altura (profundidad)

        2. El nodo solo puede ser uno de 2 nodos, 3 nodos y 4 nodos

        Nota complementaria: 

        2 nodos: se pueden montar 2 niños debajo de este nodo

        3 nodos: se pueden montar 3 niños debajo de este nodo

        4 nodos: se pueden montar 4 niños debajo de este nodo

        Dibujamos un gráfico para representar: 

   

    3. Los elementos siempre mantienen el orden de clasificación y mantienen la naturaleza del árbol binario como un todo, es decir, el nodo principal es mayor que el nodo secundario izquierdo y más pequeño que el nodo secundario derecho, y cuando un nodo tiene varios elementos , cada elemento debe ser mayor que el elemento de su subárbol izquierdo. (Corrija el error en la imagen de arriba: 2 nodos, 3 nodos, 4 nodos son un todo)

Mira un árbol completo 2-3-4:

La siguiente es una queja, escribir un blog realmente no es fácil. Esperamos ver y apoyar lo siguiente. . . . .

        Digresión: la operación de consulta del árbol 2-3-4 es muy simple como el árbol de búsqueda binario común, pero debido al número incierto de elementos de nodo, no es conveniente implementarlo en algunos lenguajes de programación Equivalencia - árbol rojo-negro. Un árbol 2-3-4 puede corresponder a varios árboles rojo-negro, y un árbol rojo-negro solo puede corresponder a un árbol 2-3-4.

Proceso de construcción del árbol 2-3-4

        Mirando la imagen, vale la pena señalar que el proceso de división es ascendente . Es decir, la construcción de este árbol es diferente a lo que imaginábamos, todo el árbol crece hacia arriba.

Reglas de conversión de árbol 2-3-4 a árbol rojo-negro

        Escuché que el árbol rojo-negro se originó en el árbol 2-3-4, y la esencia del árbol rojo-negro es un árbol 2-3-4. No sé si es correcto, pero las 5 reglas del árbol rojo-negro sí se pueden obtener a través del árbol 2-3-4.

        Puntos clave: Las reglas de nivel de 2-3-4 árboles a árboles rojo-negro son los puntos clave, que recorren todo el contenido siguiente. Recuerda estas cuatro relaciones de equivalencia, y las usaremos para deducir los cinco principios de los árboles rojo-negro a través de 2-3-4 árboles dentro de un rato.

        Hay 4 relaciones de equivalencia:

        2 nodos:

         

        3 nodos:

         

        4 nodos:

        

        Relación de fisión:

        

        Convierta un árbol 2-3-4 en un árbol rojo-negro según el principio jerárquico anterior:

        Conclusión: un árbol 2-3-4 corresponde a múltiples árboles rojo-negro, y un árbol rojo-negro corresponde a un solo árbol 2-3-4

árbol negro rojo

      definición

        Un árbol rojo-negro es un árbol de búsqueda binaria con atributos de color para los nodos, pero tiene las siguientes cinco propiedades además del árbol de búsqueda binaria:

        1. El nodo es rojo o negro

        2. La raíz es negra

        3. Todas las hojas son negras (las hojas son nodos NULL, dichos nodos no se pueden ignorar)

        4. Cada nodo rojo debe tener dos nodos secundarios negros. (No puede haber dos nodos rojos consecutivos en todos los caminos desde cada hoja hasta la raíz)

        5. Todos los caminos simples desde cualquier nodo hasta cada una de sus hojas contienen el mismo número de nodos negros (balance de negros)

        La siguiente figura es un árbol rojo-negro típico:

        

         Muchos artículos introducen árboles rojo-negros y terminan aquí, de hecho, esto no es conveniente para que entendamos realmente los árboles rojo-negros. Necesitamos entender para asegurarnos de que podamos integrarnos más tarde.

      5 principios del árbol rojo-negro contra el árbol 2-3-4:

        1. El nodo es rojo o negro ===> evidente

        2. La raíz es negra

                Aquí tenemos que dividir en 3 situaciones: la raíz tiene 2 nodos, la raíz tiene 3 nodos y la raíz tiene 4 nodos

                2.1 La raíz es de 3 nodos

                Este es el caso en 2.1.0   . Revisemos las reglas para convertir los 3 nodos del árbol 2-3-4 en un árbol rojo-negro.

                2.1.1 Los 3 nodos del árbol 2-3-4 corresponden a la conversión del árbol rojo-negro: (arriba negro y abajo rojo)

                

                 2.1.2 Mediante transformación, podemos saber que cuando la raíz del árbol 2-3-4 es de 3 nodos, la raíz del árbol rojo-negro correspondiente debe ser negra

                 2.2 raíz es de 2 nodos

                 Este es el caso en 2.2.0   , revisemos las reglas para convertir 2 nodos de un árbol 2-3-4 en un árbol rojo-negro

                 2.2.1 2 nodos del árbol 2-3-4 corresponden a la conversión del árbol rojo-negro: (directamente convertido a negro)

                 

                 2.2.2 Mediante conversión, podemos saber que cuando la raíz del árbol 2-3-4 tiene 2 nodos, la raíz del árbol rojo-negro correspondiente siempre debe ser negra

                 2.3 La raíz tiene 4 nodos

                 Este es el caso en 2.3.0   . Repasemos las reglas para convertir los 4 nodos del árbol 2-3-4 en un árbol rojo-negro.

                 2.3.1 4 nodos del árbol 2-3-4 corresponden a la conversión del árbol rojo-negro: (negro superior y rojo inferior)

                 

                 2.3.2 Mediante transformación, podemos saber que cuando la raíz del árbol 2-3-4 es de 4 nodos, la raíz del árbol rojo-negro correspondiente siempre debe ser negra

        3. Todos los nodos de hoja son negros ===> los 2 nodos se convierten en árboles rojo-negros son negros

        4. Cada nodo rojo debe tener dos nodos secundarios negros ===> para mantener el equilibrio negro (como la regla 5)

                Derivamos esta conclusión del árbol 2-3-4

                4.1 Cuando el nodo rojo es un nodo pseudo-hoja (aquí lo llamamos así para facilitar su comprensión)

                 

                 Esta situación es fácil de entender. Revisemos la regla 3 del árbol rojo-negro [Todas las hojas son negras (las hojas son nodos NULOS, dichos nodos no se pueden ignorar)]

                4.2 Primero explique la situación en la que el árbol 2-3-4 sufrirá fisión. Solo cuando ya hay 3 elementos en un cierto nodo del árbol 2-3-4 y se necesita insertar un nuevo elemento en este nodo, entonces se dividirá hacia arriba.

                Todo el proceso de fisión se divide en: conversión de 3 nodos --> insertar un nuevo nodo (el nodo recién insertado debe ser rojo) --> juzgar si es el nodo raíz (el nodo raíz debe volverse negro).  

                

        5. Todos los caminos simples desde cualquier nodo hasta cada una de sus hojas contienen el mismo número de nodos negros

                Esta regla también se llama balance de negros, y es una propiedad importante para que los árboles rojo-negro mantengan el balance de negros . A través de algunas de las reglas y convenciones anteriores, podemos encontrar que el árbol rojo-negro que construimos ha satisfecho el balance negro. En otras palabras, puede ser más preciso: el árbol rojo-negro solo mantiene el balance de negros y no presta atención a los nodos rojos (esta propiedad se realizará completamente durante la operación de eliminación). Es precisamente debido a la naturaleza de balance de negros del árbol rojo-negro que evita la degeneración en una lista enlazada como un árbol de búsqueda binaria y también reduce el costo de ajuste como un árbol AVL para mantener un balance alto. Por lo tanto, puede entenderse como un compromiso del árbol AVL.

                En el proceso real de desarrollo del proyecto, ¿debe ser razonable elegir ciegamente el árbol rojo-negro como la estructura de datos subyacente? Obviamente, la respuesta es no, cuando el número de nuestras búsquedas es mucho mayor que las operaciones de inserción y eliminación. Podemos elegir el árbol AVL.El balance de altura del árbol AVL es más bajo que el del árbol rojo-negro hasta cierto punto, y la eficiencia de búsqueda es mayor. Si hay operaciones frecuentes de inserción y eliminación en el proyecto, es necesario seleccionar el árbol post-rojo-negro , y AVL pagará un alto precio para mantener un saldo alto.

        operación común

       Hemos presentado las cinco reglas principales del árbol rojo-negro, y luego veremos las operaciones básicas del árbol rojo-negro (los zurdos y los diestros se han presentado antes, aquí lo presentaremos con más detalle). ).

        Decoloración :

        El color del nodo cambia de negro a rojo o de rojo a negro

        Mano izquierda :

         Tomando un determinado nodo como punto de rotación, su nodo secundario derecho se convierte en el nodo principal del nodo de rotación, el nodo secundario izquierdo del nodo secundario derecho se convierte en el nodo secundario derecho del nodo de rotación y el nodo secundario izquierdo permanece sin cambios.

        Rotación a la derecha :

        Con un determinado nodo como punto de rotación, su nodo secundario izquierdo se convierte en el nodo principal del nodo de rotación, el nodo secundario derecho del nodo secundario izquierdo se convierte en el nodo secundario izquierdo del nodo de rotación y el nodo secundario derecho permanece sin cambios.

       Agregado:

        Discuta de acuerdo con la situación, principalmente para encontrar la posición de inserción, y luego autoequilibrio (zurdo o diestro) y el nodo insertado es rojo (si es rojo, habrá un nodo negro adicional en el actual rama, destruyendo así el balance negro).

                Dé un ejemplo: tome el subárbol izquierdo como ejemplo, la situación del subárbol derecho es la opuesta.                                                           

                a.Si se inserta el primer nodo (nodo raíz), el rojo se vuelve negro.                                                      

                B. Si el nodo principal es negro, insértelo directamente sin cambiar el color.                                                           

                c. Si el nodo padre es rojo y el nodo tío también es rojo (en este momento, el nodo abuelo debe ser negro), entonces el nodo padre y el nodo tío se vuelven negros, y el nodo abuelo se vuelve rojo (si el nodo abuelo es el nodo raíz, luego se vuelve negro nuevamente), el nodo abuelo debe ser recursivo en este momento (compare el nodo abuelo como un nodo recién insertado nuevamente)                                                                                        

                Aquí hay una explicación, por qué en este caso, el abuelo debe ser negro. Es imposible tener dos rojos consecutivos en un árbol rojo-negro, porque un nodo rojo debe estar conectado a dos nodos secundarios negros.

  Inserte 3 porque 5 es el nodo raíz y debe volver a ser negro

Inserte 2, 3 y 4 se vuelve negro, 4 se vuelve rojo. Balance de negros satisfecho, la recursión se detiene.

                d. Si el nodo padre es rojo, no hay ningún nodo tío o el nodo tío es negro (solo puede ser un nodo NULO en este momento), en este momento, el nodo abuelo se usa como punto de apoyo para rotar a la derecha. la rotación, el nodo abuelo original se vuelve rojo y el nodo padre original se vuelve negro.                   

         insertar 1   

        No es difícil ver que todo el proceso se divide en dos partes: agregar nodos y ajustar después de agregar (ver código)       

        Debido a que la adición y la eliminación son relativamente simples y similares a la operación de eliminación, no las presentaremos en detalle aquí. Centrémonos en la operación de eliminación. Si comprende la operación de eliminación, comprenderá naturalmente la nueva operación (amigos que aún no entienden, pueden dejar un mensaje o un mensaje privado para discutir juntos). 

Eliminar (dificultad):

        En términos sencillos, hay tres oraciones (hay tres tipos de casos importantes) --> Si puedes manejarlo tú mismo, si no puedes manejarlo, pide ayuda a tu hermano y a tu padre. (Padre y hermano se autolesionan los nodos )

        De hecho, eliminar y agregar nodos es similar: eliminar nodos, ajustar después de la eliminación (mantener las reglas del árbol rojo-negro)

        ¿Hablemos detalladamente, cómo borrarlo es razonable?

        Hay tres situaciones en la operación de eliminación: estas tres situaciones se dividen según la situación del nodo del árbol rojo-negro

        1. El nodo de hoja se elimina, elimínelo directamente (por ejemplo: elimine 2, 6, 13, 18 ) -> después de la eliminación, sigue siendo un árbol rojo-negro

        2. El nodo eliminado tiene un nodo secundario, luego use el nodo secundario para reemplazar el nodo eliminado (por ejemplo: eliminar 4) --> Después de reemplazar el nodo secundario, sigue siendo un árbol rojo-negro

        3. El nodo eliminado tiene 2 nodos secundarios. En este momento, es necesario encontrar un nodo predecesor o un nodo sucesor para reemplazarlo.

                Esta situación es más complicada: generalmente cubrimos el nodo a eliminar a través del nodo predecesor o el nodo sucesor, y luego eliminamos el nodo predecesor o el nodo sucesor.

                ¿Por qué haces esto?

                Por ejemplo, en la imagen de arriba: ahora necesitamos eliminar el nodo con la clave = 10. Si elimina la clave = 10 directamente, debe desconectar 10 y luego usar la clave = 6 o la clave = 13 para reemplazar la posición de la clave = 10, y luego debe eliminar la clave = 6 o la clave = 13 se eliminan los nodos. Tal precio es obviamente relativamente alto.

                Nota complementaria: el nodo predecesor es el valor máximo menor que el nodo actual, y el nodo sucesor es el valor mínimo mayor que el nodo actual

                Solo hay 2 casos del nodo predecesor eliminado o del nodo sucesor: (volver a los casos 1 y 2)

                a. El nodo eliminado es un nodo hoja

 b. El nodo eliminado solo tiene un hijo (como se muestra en la figura a continuación, ya sea el hijo izquierdo o el hijo derecho)

                      C. En un caso especial, cuando solo hay un nodo raíz, se puede eliminar directamente (raíz = NULL)

Ajuste después de la eliminación       

La operación de borrado corresponde a la relación del árbol 2-3-4

        Según la explicación anterior, sabemos que el nodo eliminado solo puede ser un nodo hoja o un nodo no hoja con un nodo secundario.

        Luego analizamos: los nodos de hoja del árbol rojo-negro y la capa superior de los nodos de hoja deben corresponder a los nodos de hoja del árbol 2-3-4, por lo que la eliminación del árbol 2-3-4 correspondiente debe eliminar las hojas del nudo 2-3-4. [Nota complementaria: el árbol 2-3-4 se divide hacia arriba, por lo que el árbol 2-3-4 es un árbol binario completo, de acuerdo con la conversión del árbol 2-3-4, los nodos sin hoja del árbol rojo-negro y el capas superiores de nodos que no son hojas Otros nodos deben tener dos hijos]

De hecho, no nos cuesta ver que el propio árbol 2-3-4 mantiene un balance de negros (los árboles 2-3-4 no tienen color, no se malinterpreten)

 Miremos la imagen de arriba, es legal para nosotros eliminar 0, 1, 7, 7.5, 9, 10 y 11. En términos humanos, en el árbol 2-3-4, es legal que eliminemos un elemento en el nodo 3 y el nodo 4, y la eliminación aún satisface el árbol 2-3-4.

¿Qué sucede si elimino la clave = 3, 4, 5? Cuando eliminamos la clave = 3, el nodo con la clave = 2 necesita tener dos hijos, que ya no es un árbol 2-3-4. En este momento, es necesario tomar prestado del hermano a través del nodo padre. Si el hermano no puede pedir prestado, el nodo hermano o padre puede tomarlo por sí mismo.

        Descripción resumida:

        a. Haz lo que puedas hacer por ti mismo

  1. Si el nodo eliminado corresponde al nodo 3 o al nodo 4 del árbol 2-3-4, elimínelo directamente sin tomar prestado de los nodos hermanos y padre.

     Correspondiente al árbol rojo-negro: si se elimina el nodo rojo, elimínelo directamente. Si elimina un nodo negro, reemplácelo con un nodo rojo y simplemente póngalo en negro, no se requiere ningún ajuste. [1, 2 de la situación de eliminación del árbol rojo-negro]

        b. Si no puedes resolverlo, pídelo prestado a tu hermano. Si tu hermano no quiere tomarlo prestado, pídelo prestado a tu padre. Cuando el padre baja, el hermano encuentra un elemento para reemplazar al padre [clasificación garantizada en el eje x]

 b.1 La premisa es encontrar el nodo hermano "real"

 b.2 Los hermanos tienen que pedir prestado (el nodo hermano debe ser negro en este momento, si es rojo, significa que este nodo no es un nodo hermano real, debe volver al paso anterior para encontrar el nodo hermano real )

        Si el nodo hermano tiene dos nodos hijos (los dos nodos hijos deben ser rojos, si es negro, significa que el nodo hermano corresponde a 2 nodos en el árbol 2-3-4 en este momento, y es imposible tener elementos redundantes para tomar prestados), en este momento es necesario Girar para cambiar de color.

        Cuando el nodo hermano tiene solo un nodo secundario, debe rotar y cambiar de color

        C. Padre y hermano dañan los nodos

        Los nodos hermanos no tienen elementos adicionales para tomar prestados (en este momento, los nodos hermanos deben ser 2 nodos negros), y en este momento, la rama donde se encuentran los nodos hermanos también debe dañar un nodo negro para lograr el equilibrio negro.La forma más rápida es para convertir directamente el nodo hermano en rojo (muy parecido a reducir un nodo negro), y en este momento el subárbol cuyo nodo padre es la raíz ha alcanzado un equilibrio nuevamente (ambos lados tienen uno menos negro que antes). Sin embargo, el árbol enraizado en el nodo abuelo todavía está desequilibrado y en este momento se requiere un procesamiento recursivo.

árbol negro rojo escrito a mano

main.cpp ==> para probar

#include <iostream>
#include "RBTree.h"

using namespace std;


int main()
{

	RBTree* tree1 = new RBTree();

	int key = 0;
	int value = 0;

	// 插入
	for(int i = 0; i < 10; i++)
	{
		key = i + 1;
		value = i + 1;
		tree1->put(key, value);
		/*tree1->printTree();*/
	}

	// 删除
	tree1->printTree();
	while (1)
	{
		cin >> key;
		tree1->remove(key);
		tree1->printTree();
	}


	delete tree1;

	return 0;
}

RBÁrbol.h

#pragma once

#define BLACK true
#define RED   false

struct RBNode
{
	RBNode* parent;
	RBNode* left;
	RBNode* right;

	bool color;
	int  key;
	int  value;

	RBNode()
	{

	}

	RBNode(int key, int value, RBNode* parent)
	{
		this->key = key;
		this->parent = parent;
		this->value = value;
		this->left = nullptr;
		this->right = nullptr;
		this->color = BLACK;
	}

	// 比较器
};

class RBTree
{
public:
	RBTree():root(nullptr)
	{
	}

	~RBTree()
	{

	}


	// 新增
	void put(int k, int v);

	// 删除
	int remove(int key);

	// 打印
	void printTree();

private:

	// 左旋
	void leftRotate(RBNode* p);

	// 右旋
	void rightRotate(RBNode* p);


	// 新增调整
	void fixAfterPut(RBNode* x);

	// 寻找前驱节点
	RBNode* predecessor(RBNode* node);

	// 寻找后继节点
	RBNode* sucessor(RBNode* node);

    // 找到key对应的位置
	RBNode* getNode(int key);

	// 删除
	void deleteNode(RBNode* node);

	// 删除后调整
	void fixAfterRemove(RBNode* x);

	// 父节点
	RBNode* parentOf(RBNode* node);

	// 右孩子
	RBNode* rightOf(RBNode* node);

	// 左孩子
	RBNode* leftOf(RBNode* node);

	// 改变颜色
	void setColor(RBNode* node, bool color);

	// 获取颜色
	bool colorOf(RBNode* node);

	// 销毁红黑树
	void deleteTree();

private:

	RBNode* root;

};

RBTree.cpp

#include "RBTree.h"
#include <queue>
#include <iostream>
#include <stack>

/*
-----------------------------------------
	围绕p左旋:
			p                       r
		   / \                    /  \
		 pl   r         ===>   p     rr
			 /  \              / \
			rl   rr          pl  rl
-----------------------------------------
*/
void RBTree::leftRotate(RBNode* p)
{

	if (p != nullptr)
	{
		RBNode* r = p->right;
		p->right = r->left;
		
		if (r->left != nullptr)
		{
			r->left->parent = p;
		}
		r->parent = p->parent;

		// 如果p是根节点
		if (p->parent == nullptr)
		{
			root = r;
		}
		else if(p->parent->left == p)  // p是左孩子
		{
			p->parent->left = r;
		}
		else  // p是右孩子
		{
			p->parent->right = r;
		}
		r->left = p;
		p->parent = r;
	}
}

/*
------------------------------------------
	围绕p右旋:
			p                      pl
		   / \                    /  \
		 pl   r         ===>   p     p
	    /  \                  /     / \    
	   rl   rr               rl    rr  r
-----------------------------------------
*/
void RBTree::rightRotate(RBNode* p)
{
	if (p != nullptr)
	{
		RBNode* l = p->left;
		p->left = l->right;

		if (l->right != nullptr)
		{
			l->right->parent = p;
		}
		l->parent = p->parent;

		// 如果p是根节点
		if (p->parent == nullptr)
		{
			root = l;
		}
		else if (p->parent->right == p)  // p是左孩子
		{
			p->parent->right = l;
		}
		else  // p是右孩子
		{
			p->parent->left = l;
		}

		l->right = p;
		p->parent = l;
	}
}

void RBTree::put(int k, int v)
{
	RBNode* t = root;

	// 如果是根节点
	if (t == nullptr)
	{
		root = new RBNode(k, v, nullptr);
		return;
	}

	// 需要插入位置
	// 定义一个双亲指针
	RBNode* parent = nullptr;
	// 沿着跟节点寻找插入位置
	do
	{
		parent = t;

		if(k < parent->key)
		{
			t = t->left;
		}
		else if(k > parent->key)
		{
			t = t->right;
		}
		else
		{
			t->value = v;
			return;
		}

	} while (t != nullptr);

	// t == nullptr 说明没有找到相同的节点

	RBNode* e = new RBNode(k, v, parent);

	// 挂在左孩子上
	if (k < parent->key)
	{
		parent->left = e;
	}
	else
	{
		parent->right = e;
	}

	// 调整
	fixAfterPut(e);

}

/*
    1. 2-3-4树:新增元素+2节点合并(节点中只有1个元素) = 3节点(节点中有2个元素)
	   红黑树:新增一个红色节点+黑色父节点 = 上黑下红  (不需要调整)
	2. 2-3-4树: 新增元素+3节点合并(节点中只有2个元素) = 4节点(节点中有3个元素)
	   这里有6种情况:(左3、右3需要调整 | 左2右1、右2左1需要调整 | 其他2种情况不需要调整)
	   红黑树:新增一个红色节点+上黑下红 = 排序后中间节点是黑色,两边是红色(3节点)
	3. 2-3-4树: 新增一个元素+4节点合并(节点中只有3个元素) = 原来的4节点分裂,中间元素升级为父节点,新增节点与剩下的节点合并
	   红黑树:新增一个红色节点+爷爷节点黑色,父节点和叔叔节点都是红色 = 爷爷节点变红,父亲和叔叔节点变黑,如果爷爷节点是根节点,则爷爷节点变黑
*/

void RBTree::fixAfterPut(RBNode* x)
{
	x->color = RED;

	// 如果父节点是黑色就不需要调整
	while (x != nullptr && x != root && x->parent->color == RED)
	{
		//1.左3: x的父节点是爷爷的左孩子
		if (parentOf(x) == leftOf(parentOf(parentOf(x)))) 
		{
			// 叔叔节点
			RBNode* y = rightOf(parentOf(parentOf(x)));
			// 如果叔叔节点是红色:情况3
			if (colorOf(y) == RED)
			{
				setColor(parentOf(x), BLACK);
				setColor(y, BLACK);
				setColor(parentOf(parentOf(x)), RED);
				x = parentOf(parentOf(x));
			}
			else  // 情况2
			{
				// 左2右1
				if (x == rightOf(parentOf(x)))
				{
					RBNode* p = parentOf(x);
					int tmpkey = p->key;
					int tmpvalue = p->value;
					p->key = x->key;
					p->value = y->value;
					x->key = tmpkey;
					y->value = tmpvalue;

					leftRotate(parentOf(x));
				}

				setColor(parentOf(x), BLACK);
				setColor(parentOf(parentOf(x)), RED);
				rightRotate(parentOf(parentOf(x)));
			}
		}
		else
		{
			// 叔叔节点
			RBNode* y = leftOf(parentOf(parentOf(x)));
			// 如果叔叔节点是红色:情况3
			if (colorOf(y) == RED)
			{
				setColor(parentOf(x), BLACK);
				setColor(y, BLACK);
				setColor(parentOf(parentOf(x)), RED);
				x = parentOf(parentOf(x));
			}
			else  // 情况2
			{
				// 左2右1
				if (x == leftOf(parentOf(x)))
				{
					RBNode* p = parentOf(x);
					int tmpkey = p->key;
					int tmpvalue = p->value;
					p->key = x->key;
					p->value = y->value;
					x->key = tmpkey;
					y->value = tmpvalue;

					rightRotate(parentOf(x));
				}

				setColor(parentOf(x), BLACK);
				setColor(parentOf(parentOf(x)), RED);
				leftRotate(parentOf(parentOf(x)));
			}
		}	
	}

	root->color = BLACK;
}

RBNode* RBTree::predecessor(RBNode* node)
{
	if (node == nullptr)
	{
		return nullptr;
	}
	
	if(node->left != nullptr)
	{
		RBNode* p = node->left;
		while (p->right != nullptr)
		{
			p = p->right;
		}

		return p;
	}
	else
	{
		RBNode* p = node->parent;
		RBNode* child = node;

		while (p != nullptr && child == p->left)
		{
			child = p;
			p = p->parent;
		}

		return p;
	}
}

RBNode* RBTree::sucessor(RBNode* node)
{
	if (node == nullptr)
	{
		return nullptr;
	}

	if (node->right != nullptr)
	{
		RBNode* p = node->right;
		while (p->left != nullptr)
		{
			p = p->left;
		}

		return p;
	}
	else
	{
		RBNode* p = node->parent;
		RBNode* child = node;

		while (p != nullptr && child == p->right)
		{
			child = p;
			p = p->parent;
		}

		return p;
	}
}

int RBTree::remove(int key)
{
	RBNode* node = getNode(key);
	if (node == nullptr)
	{
		return -1;
	}
    
	int val = node->value;

	deleteNode(node);

	return val;

}

RBNode* RBTree::getNode(int key)
{
	RBNode* node = root;

	while (root != nullptr)
	{
		if (key < node->key)
		{
			node = node->left;
		}
		else if(key > node->key)
		{
			node = node->right;
		}
		else
		{
			return node;
		}
	}

	return nullptr;
}

/*---------------------------------
删除操作:
1.删除叶子节点,直接删除
2.删除的节点有一个子节点,那么用子节点来替换
3.如果删除的节点有2个子节点,此时需要找到前驱节点或者后继节点来替代
-----------------------------------*/
void RBTree::deleteNode(RBNode* node)
{
	// 3.node节点有2个孩子
	if (node->left != nullptr && node->right != nullptr)
	{
		RBNode* sucessorNode = sucessor(node);
		node->key = sucessorNode->key;
		node->value = sucessorNode->value;
		node = sucessorNode;
	}

	RBNode* replacement = node->left != nullptr ? node->left : node->right;

	// 2.替代节点不为空
	if (replacement != nullptr)
	{
		// 替代者的父指针指向原来的node的父亲
		replacement->parent = node->parent;
		if (node->parent == nullptr)
		{
			root = replacement;
		}
		else if(node == node->parent->left)   // node是左孩子,所以替代者依然是左孩子
		{
			node->parent->left = replacement;
		}
		else // node是右孩子,所以替代者依然是右孩子
		{
			node->parent->right = replacement;
		}
        
		// 替换完之后需要调整平衡
		if (node->color == BLACK)
		{
            // 需要调整,此时替代节点一定是红色(替代节点一定是红色,此时只要变色)
			fixAfterRemove(replacement);
		}

		// 将node的左右孩子指针和父指针都指向null
		node->left = node->right = node->parent = nullptr;
		delete node;

	}
	else if(node->parent == nullptr)  // 根节点
	{
		node->left = node->right = node->parent = nullptr;
		delete node;
		root = nullptr;
	}
	else  // 1.叶子节点
	{
		
		// 调整
		if (node->color == BLACK)
		{
			fixAfterRemove(node);
		}
		
	    // 删除
		if (node->parent != nullptr)
		{
			if (node == node->parent->left)
			{
				node->parent->left = nullptr;
			}
			else if(node == node->parent->right)
			{
				node->parent->right = nullptr;
			}

			// node->parent = nullptr;
			node->left = node->right = node->parent = nullptr;
			delete node;
		}
	}
}

void RBTree::fixAfterRemove(RBNode* x)
{
	//情况2/3:
	while (x != root && colorOf(x) == BLACK) 
	{
		if (x == leftOf(parentOf(x)))
		{
			RBNode* rnode = rightOf(parentOf(x));
			
			// 判断此时兄弟节点是不是真正的兄弟几点
			if (colorOf(rnode) == RED)
			{
				setColor(rnode, BLACK);
				setColor(parentOf(x), RED);
				leftRotate(parentOf(x));

				// 找到真正的兄弟节点
				rnode = rightOf(parentOf(x));
			}
			
			// 情况3:兄弟没得借
			if (colorOf(leftOf(rnode)) == BLACK && colorOf(rightOf(rnode)))
			{
				setColor(rnode, RED);
				x = parentOf(x);
			}
		    // 情况2:自己搞不定,需要向兄弟借,兄弟有得借
			else
			{
				// 分两种小情况:兄弟节点本来是3节点或者4节点
				if (colorOf(rightOf(rnode)) == BLACK)
				{
					setColor(leftOf(rnode), BLACK);
					setColor(rnode, RED);
					rightRotate(rnode);
					rnode = rightOf(parentOf(x));
				}
				
				// 设置颜色
				setColor(rnode, colorOf(parentOf(x)));
				setColor(parentOf(x), BLACK);
				setColor(rightOf(rnode), BLACK);
				leftRotate(parentOf(x));
				x = root;
			}
		}
		else // x是右孩子
		{
			RBNode* lnode = leftOf(parentOf(x));

			// 判断此时兄弟节点是不是真正的兄弟几点
			if (colorOf(lnode) == RED)
			{
				setColor(lnode, BLACK);
				setColor(parentOf(x), RED);
				rightRotate(parentOf(x));

				// 找到真正的兄弟节点
				lnode = leftOf(parentOf(x));
			}

			// 情况3:兄弟没得借
			if (colorOf(rightOf(lnode)) == BLACK && colorOf(leftOf(lnode)))
			{
				setColor(lnode, RED);
				x = parentOf(x);
			}
			// 情况2:自己搞不定,需要向兄弟借,兄弟有得借
			else
			{
				// 分两种小情况:兄弟节点本来是3节点或者4节点
				if (colorOf(leftOf(lnode)) == BLACK)
				{
					setColor(rightOf(lnode), BLACK);
					setColor(lnode, RED);
					leftRotate(lnode);
					lnode = leftOf(parentOf(x));
				}

				// 设置颜色
				setColor(lnode, colorOf(parentOf(x)));
				setColor(parentOf(x), BLACK);
				setColor(leftOf(lnode), BLACK);
				rightRotate(parentOf(x));
				x = root;
			}
		}
	}

	//情况1:替代节点是红色,则直接染黑,补偿删除的黑色节点,这样红黑树依然保持平衡
	//递归补偿
	setColor(x, BLACK);
}

RBNode* RBTree::parentOf(RBNode* node)
{
	return node != nullptr ? node->parent : nullptr;
}


RBNode* RBTree::rightOf(RBNode* node)
{
	return node != nullptr ? node->right : nullptr;
}

RBNode* RBTree::leftOf(RBNode* node)
{
	return node != nullptr ? node->left : nullptr;
}

bool RBTree::colorOf(RBNode* node)
{
	// 如果为空,实际上是黑色
	return node == nullptr ? BLACK : node->color;
}

void RBTree::setColor(RBNode* node, bool color)
{
	if (node != nullptr)
	{
		node->color = color;
	}
}

// 底层遍历
void RBTree::printTree()
{

	std::queue<RBNode*> que;
	que.push(root);
	
	while (!que.empty())
	{
		RBNode* node = que.front();
		que.pop();

		if (node->color == RED)
		{
			std::cout << node->key << "(R)" << "  ";
		}
		else
		{
			std::cout << node->key << "(B)" << "  ";
		}
		
		if (node->left != nullptr)
		{
			que.push(node->left);
		}

		if (node->right != nullptr)
		{
			que.push(node->right);
		}
	}
	
}

void RBTree::deleteTree()
{
	std::stack<RBNode*> sta;
	std::queue<RBNode*> que;

	while (!que.empty())
	{
		RBNode* node = que.front();
		que.pop();

		sta.push(node);

		if (node->left != nullptr)
		{
			que.push(node->left);
		}

		if (node->right != nullptr)
		{
			que.push(node->right);
		}
	}

	while (!sta.empty())
	{

		RBNode* tmp = sta.top();
		sta.pop();

		tmp->left = tmp->right = tmp->parent = nullptr;
		delete tmp;
	}

}

Supongo que te gusta

Origin blog.csdn.net/weixin_46120107/article/details/128421692
Recomendado
Clasificación