arbol rojo negro (1)

31
ARBOL ROJO NEGRO 1) CONCEPTOS: A. Un árbol rojo negro es un tipo abstracto de datos, concretamente es un árbol binario de búsqueda equilibrado. B. Un árbol binario de búsqueda auto- balanceable o equilibrado es un árbol binario de búsqueda que intenta mantener su altura, o el número de niveles de nodos bajo la raíz, tan pequeños como sea posible en todo momento. C. Podemos recordar que: Los árboles AVL están siempre equilibrados de tal modo que para todos los nodos, la altura de la rama izquierda no difiere en más de una unidad de la altura de la rama derecha o viceversa. Gracias a esta forma de equilibrio (o balanceo), la complejidad de una búsqueda en uno de estos árboles se mantiene siempre en orden de complejidad O(log n). esto permite que al insertar o eliminar algún nodo, el árbol siga siendo balanceado o equilibrado. D. Un árbol binario de búsqueda es un árbol con una restricción adicional - que mantiene los elementos en el árbol en un orden determinado. Oficialmente, cada nodo en el ABB tiene dos hijos (si falta alguno consideramos que es un nodo cero) a la izquierda, un niño y un niño de la derecha. Los nodos están enraizados en el lugar sobre la base de sus valores, con la más pequeña a la izquierda y el más grande de la derecha. E. Un árbol rojo-negro es un árbol binario de búsqueda en el que cada nodo almacena un bit adicional de información llamado color, el cual puede ser rojo o negro. Sobre este atributo de color se aplican restricciones que resultan en un árbol en el que ningún camino de la raíz a una hoja es más de dos 1

Upload: marylaura-carolina-limay-oliva

Post on 09-Dec-2014

126 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Arbol Rojo Negro (1)

ARBOL ROJO NEGRO

1) CONCEPTOS:

A. Un árbol rojo negro es un tipo abstracto de datos, concretamente es un árbol binario de búsqueda equilibrado.

B. Un árbol binario de búsqueda auto- balanceable o equilibrado es un árbol binario de búsqueda que intenta mantener su altura, o el número de niveles de nodos bajo la raíz, tan pequeños como sea posible en todo momento.

C. Podemos recordar que: Los árboles AVL están siempre equilibrados de tal modo que para todos los nodos, la altura de la rama izquierda no difiere en más de una unidad de la altura de la rama derecha o viceversa. Gracias a esta forma de equilibrio (o balanceo), la complejidad de una búsqueda en uno de estos árboles se mantiene siempre en orden de complejidad O(log n). esto permite que al insertar o eliminar algún nodo, el árbol siga siendo balanceado o equilibrado.

D. Un árbol binario de búsqueda es un árbol con una restricción adicional - que mantiene los elementos en el árbol en un orden determinado. Oficialmente, cada nodo en el ABB tiene dos hijos (si falta alguno consideramos que es un nodo cero) a la izquierda, un niño y un niño de la derecha. Los nodos están enraizados en el lugar sobre la base de sus valores, con la más pequeña a la izquierda y el más grande de la derecha.

E. Un árbol rojo-negro es un árbol binario de búsqueda en el que cada nodo almacena un bit adicional de información llamado color, el cual puede ser rojo o negro. Sobre este atributo de color se aplican restricciones que resultan en un árbol en el que ningún camino de la raíz a una hoja es más de dos veces más largo que cualquier otro, lo cual significa que el árbol es balanceado.

F. Cada nodo de un árbol rojo negro contiene la siguiente información: color, dato, hijo izquierdo, hijo derecho y padre. Si un hijo o el padre de un nodo no existe, el apuntador correspondiente contiene el valor NULO, el cual consideraremos como un nodo cuyo color es negro. En lo sucesivo nos referiremos a los nodos distintos a las hojas (NULO)

1

Page 2: Arbol Rojo Negro (1)

como nodos internos del árbol y a las hojas y al padre de la raíz como nodos externos.

G. Es una representación en árbol binario de un árbol 2-3-4.H. Árboles rojo-negro son una evolución de los árboles binarios de

búsqueda que tienen como objetivo mantener el árbol balanceado sin afectar a la complejidad de las operaciones primitivas. Esto se hace mediante la coloración de cada nodo en el árbol con rojo o negro y la preservación de un conjunto de propiedades que garantizan que el más profundo camino en el árbol no es más largo que el doble de la más corta.

De su creación podemos decir:

La estructura original fue creada por Rudolf Bayer en 1972, que le dio el nombre de “árboles-B binarios simétricos”, pero tomó su nombre moderno en un trabajo de Leo J. Guibas y Robert Sedgewick realizado en 1978.

B) Terminología:

El árbol rojo negro es un tipo especial de árbol binario, utilizado en informática para organizar información compuesta por datos comparables, como son los números.

C) Características y propiedades:

Todo nodo es o bien rojo o bien negro. La raíz es de color negra Un nodo rojo puede tener solo hijos negros. Todas las hojas son negras (las hojas son los hijos nulos). Cada camino simple desde un nodo a un nodo hijo descendiente

contiene el mismo número de nodos negros, ya sea contando siempre los nodos negros nulos o bien no contándolos nunca. También es llamada “propiedad del camino” y por tanto el camino no puede tener dos rojos seguidos.

2

Page 3: Arbol Rojo Negro (1)

El camino largo desde la raíz hasta una hoja no es más largo que 2 veces el camino más corto desde la raíz del árbol hasta una hija en dicho árbol. El resultado es que dicho árbol esta aproximadamente equilibrado.

En muchas presentaciones de estructuras arbóreas de datos, es posible para un nodo tener solo un hijo y las hojas contienen información. Es posible presentar los árboles rojo-negros en este paradigma, pero cambian algunas de las propiedades y se complican los algoritmos. Se utiliza el término “hojas nulas”, porque no contienen información y simplemente sirven para indicar dónde el árbol acaba, como se mostró antes. Habitualmente estos nodos son omitidos en las representaciones, lo cual no lo hace ver como realmente se describe teóricamente. Como consecuencia de esto todos los nodos internos tienen dos hijos, aunque uno o ambos nodos podrían ser una hoja nula.

Otra explicación que se da del árbol rojo-negro es la de tratarlo como un árbol binario de búsqueda cuyas aristas, en lugar de nodos, son coloreadas de color rojo o negro.

2) IMPORTANCIA DEL USO DE ÁRBOLES ROJO NEGROS:

3

Page 4: Arbol Rojo Negro (1)

Los árboles rojo-negros ofrecen un peor caso con tiempo garantizado para la inserción, el borrado y la búsqueda. No es esto únicamente lo que los hace valiosos en aplicaciones sensibles al tiempo como las aplicaciones en tiempo real, sino que además son apreciados para la construcción de bloques en otras estructuras de datos que garantizan un peor caso. Por ejemplo, muchas estructuras de datos usadas en geometría computacional pueden basarse en árboles rojo-negro.

Los árboles rojo-negro son particularmente valiosos en programación funcional, donde son una de las estructuras de datos persistentes más comúnmente utilizadas en la construcción de arrays asociativos y conjuntos que pueden retener versiones previas tras mutaciones. La versión persistente del árbol rojo-negro requiere un espacio O(log n) para cada inserción o borrado, además del tiempo.

Los árboles rojo-negro son isométricos a los árboles 2-3-4. En otras palabras, para cada árbol 2-3-4, existe un árbol correspondiente rojo-negro con los datos en el mismo orden. La inserción y el borrado en árboles 2-3-4 son también equivalentes a los cambios de colores y las rotaciones en los árboles rojo-negro. Esto los hace ser una herramienta útil para la comprensión del funcionamiento de los árboles rojo-negro y por esto muchos textos introductorios sobre algoritmos presentan los árboles 2-3-4 justo antes que los árboles rojo-negro, aunque frecuentemente no sean utilizados en la práctica.

3. OPERACIONES

A. Rotación. B. Búsqueda (similar a los árboles binarios de búsqueda. Los colores de

los hijos no se usan)C. Inserción (se utilizarán las transformaciones de los 4-nodos descritas

para los árboles 2-3-4)D. Eliminación

Las operaciones que veremos a continuación son las que se aplican de una forma particular a los arboles rojo negros; sin embargo las operaciones de

4

Page 5: Arbol Rojo Negro (1)

lectura (las más simples), son las mismas que en los arboles binarios de búsqueda y no tienen alteración; ya que recordemos que los arboles rojo negros son arboles ABB; por lo tanto no se tendrán en cuenta en este texto.

NOTA IMPORTANTE: Sin embargo, el resultado inmediato de una inserción o la eliminación de un nodo utilizando los algoritmos de un árbol binario de búsqueda normal podría violar las propiedades de un árbol rojo-negro. Restaurar las propiedades rojo-negro requiere un pequeño número (O(log n))de cambios de color (que son muy rápidos en la práctica) y no más de 3 rotaciones (2 por inserción). A pesar de que las operaciones de inserción y borrado son complicadas, sus tiempos de ejecución siguen siendo O(log n).

A) ROTACIÓN:

Para conservar las propiedades que debe cumplir todo árbol rojo-negro, en ciertos casos de la inserción y la eliminación será necesario reestructurar el árbol, si bien no debe perderse la ordenación relativa de los nodos. Para ello, se llevan a cabo una o varias rotaciones, que no son más que reestructuraciones en las relaciones padre-hijo-tío-nieto.

Las rotaciones que se consideran a continuación son simples; sin embargo, también se dan las rotaciones dobles.

En las imágenes pueden verse de forma simplificada cómo se llevan a cabo las rotaciones simples hacia la izquierda y hacia la derecha en

5

Page 6: Arbol Rojo Negro (1)

cualquier árbol binario de búsqueda, en particular en cualquier árbol rojo-negro. Podemos ver también la implementación en C de dichas operaciones.

Void rotar_izda(struct node *p)

{

struct node *aux;

aux = p;

p = p->dcho;

aux-> dcho = p->izdo;

p->izdo = aux;

//reenraizar subarbol

if(aux->padre->izdo == aux)

aux->padre->izdo = p;

else {

// Aqui aux->padre->dcho == aux

aux->padre->dcho = p;

}

// actualizar los padres de los nodos modificados

p->padre = aux->padre;

aux->padre = p;

aux->dcho->padre = aux; // No hacer si usamos una sola hoja nula y aux->dcho es nulo

}

6

Page 7: Arbol Rojo Negro (1)

void

rotar_dcha(struct node *p)

{

struct node *aux;

aux = p;

p = p->izdo;

aux->izdo = p->dcho;

p->dcho = aux;

// reenraizar subarbol

if(aux->padre->izdo == aux)

aux->padre->izdo = p;

else {

// aqui aux->padre->dcho == aux

aux->padre->dcho = p;

}

// actualizar los padres de los nodos modificados

p->padre = aux->padre;

7

Page 8: Arbol Rojo Negro (1)

aux->padre = p;

aux->izdo->padre = aux; // No hacer si usamos una sola hoja nula y

aux->izdo es nulo

}

B) BÚSQUEDA

La búsqueda consiste acceder a la raíz del árbol, si el elemento a localizar coincide con éste la búsqueda ha concluido con éxito, si el elemento es menor se busca en el subárbol izquierdo y si es mayor se buscará en el derecho. Si llegamos a un nodo hoja y el elemento no ha sido encontrado se supone que no existe en el árbol. Cabe destacar que la búsqueda en este tipo de árboles es muy eficiente, representa una “función logarítmica”.

NOTA PARA RECORDAR: La búsqueda de un elemento en un ABB (Árbol Binario de Búsqueda) en general, y en un árbol rojo negro en particular, se puede realizar de dos formas, iterativa o recursiva.

Ejemplo de versión iterativa en el lenguaje de programación C, suponiendo que estamos buscando una clave alojada en un nodo donde está el correspondiente "dato" que precisamos encontrar:

data Buscar_ABB(abb t,clave k)

{

abb p;

dato e;

e=NULL;

p=t;

if (!estaVacio(p))

{

while (!estaVacio(p) && (p->k!=k) )

{

8

Page 9: Arbol Rojo Negro (1)

if (k < p->k)

{

p=p->l;

}

if (p->k < k)

{

p=p->r;

}

}

if (!estaVacio(p) &&(p->d!=NULL) )

{

e=copiaDato(p->d);

}

}

return e;

}

Véase ahora la versión recursiva en ese mismo lenguaje:

int buscar(tArbol *a, int elem)

{

if (a == NULL)

return 0;

else if (a->clave < elem)

return buscar(a->hDerecho, elem);

else if (a->clave > elem)

return buscar(a->hIzquierdo, elem);

else

return 1;

9

Page 10: Arbol Rojo Negro (1)

}

C) INSERCIÓN:

La inserción comienza añadiendo el nodo como lo haríamos en un árbol binario de búsqueda convencional y pintándolo de rojo. Lo que sucede después depende del color de otros nodos cercanos. El término tío nodo será usado para referenciar al hermano del padre de un nodo, como en los árboles familiares humanos. Conviene notar que:

La propiedad “Todas las hojas, incluyendo las nulas, son negras” siempre se cumple”.

La propiedad “Ambos hijos de cada nodo rojo son negros” está amenazada solo por añadir un nodo rojo, por repintar un nodo negro de color rojo o por una rotación”.

La propiedad “Todos los caminos desde un nodo dado hasta sus nodos hojas contiene el mismo número de nodos negros” está amenazada solo por repintar un nodo negro de color rojo o por una rotación”.

Al contrario de lo que sucede en otros árboles como puede ser el Árbol AVL, en cada inserción se realiza un máximo de una rotación, ya sea simple o doble. Por otra parte, se asegura un tiempo de recoloración máximo de   por cada inserción.

NOTA IMPORTANTE: En los esquemas que acompañan a los algoritmos, la etiqueta N será utilizada por el nodo que está siendo insertado, P  para los padres del nodo N, G para los abuelos del nodo N, y U para los tíos del nodo N. Notamos que los roles y etiquetas de los nodos están intercambiados entre algunos casos, pero en cada caso, toda etiqueta continúa representando el mismo nodo que representaba al comienzo del

10

Page 11: Arbol Rojo Negro (1)

caso. Cualquier color mostrado en el diagrama está o bien supuesto en el caso o implicado por dichas suposiciones.

Los nodos tío y abuelo pueden ser encontrados por las siguientes funciones:

struct node *abuelo(struct node *n)

{

if ((n != NULL) && (n->padre != NULL))

return n->padre->padre;

else

return NULL;

}

struct node * tio(struct node *n)

{

struct node *a = abuelo(n);

if (n->padre == a->izdo)

return a->dcho;

else

return a->izdo;

}

Estudiemos ahora cada caso de entre los posibles que nos podemos encontrar al insertar un nuevo nodo.

Caso 1: El nuevo nodo N es la raíz de del árbol. En este caso, es repintado a color negro para satisfacer la propiedad (la raíz es negra). Como esto añade un nodo negro a cada camino, la propiedad “todos los caminos desde un nodo dado a sus hojas contiene el mismo número de nodos negros” se mantiene. En C quedaría así:

11

Page 12: Arbol Rojo Negro (1)

Void insercion_caso1(struct node *n)

{

if (n->padre == NULL)

n->color = NEGRO;

else

insercion_caso2(n);

}

Caso 2: El padre del nuevo nodo (esto es, el nodo P) es negro, así que por propiedad (ambos hijos de cada nodo rojo son negros) se mantiene. En este caso, el árbol es aun válido. La propiedad “todos los caminos desde cualquier nodo dado a sus hojas contiene igual número de nodos negros” se mantiene, porque el nuevo nodo N tiene dos hojas negras como hijos, pero como N es rojo, los caminos a través de cada uno de sus hijos tienen el mismo número de nodos negros que el camino hasta la hoja que reemplazó, que era negra, y así esta propiedad se mantiene satisfecha. Su implementación:

Void insercion_caso2(struct node *n)

{

if (n->padre->color == NEGRO)

return; /* Árbol válido. */

else

insercion_caso3(n);

}

NOTA IMPORTANTE: En los siguientes casos se puede asumir que N tiene un abuelo, el nodo G, porque su padre P es rojo, y si fuese la raíz, sería negro. Consecuentemente, N tiene también un nodo tío Ua pesar de que podría ser una hoja en los casos 4 y 5.

12

Page 13: Arbol Rojo Negro (1)

Caso 3: Si el padre P y el tío U son rojos, entonces ambos nodos pueden ser repintados de negro y el abuelo G se convierte en rojo para mantener la propiedad “todos los caminos desde cualquier nodo dado hasta sus hojas contiene el mismo número de nodos negros”. Ahora, el nuevo nodo rojo N tiene un padre negro. Como cualquier camino a través del padre o el tío debe pasar a través del abuelo, el número de nodos negros en esos caminos no ha cambiado. Sin embargo, el abuelo G podría ahora violar la propiedad “la raíz es negra” o la de “ambos hijos de cada nodo rojo son negros”, en este caso porque G podría tener un padre rojo. Para solucionar este problema, el procedimiento completo se realizará de forma recursiva hacia arriba hasta alcanzar el caso 1. El código en C quedaría de la siguiente forma:

Void insercion_caso3(struct node *n)

{

struct node *t = tio(n), *a;

if ((t != NULL) && (t->color == ROJO)) {

n->padre->color = NEGRO;

t->color = NEGRO;

a = abuelo(n);

a->color = ROJO;

insercion_caso1(a);

} else {

insercion_caso4(n);

13

Page 14: Arbol Rojo Negro (1)

}

}

NOTA IMPORTANTE: En los casos restantes, se asume que el nodo padre P es el hijo izquierdo de su padre. Si es el hijo derecho, izquierda y derecha deberían ser invertidas a partir de los casos 4 y 5. El código del ejemplo toma esto en

consideración.

Caso 4: El nodo padre P es rojo pero el tío U es negro; también, el nuevo nodo N es el hijo derecho de P, y P es el hijo izquierdo de su padre G. En este caso, una rotación a la izquierda que cambia los roles del nuevo nodo N y su padre P puede ser realizada; entonces, el primer nodo padre P se ve implicado al usar el caso 5 de inserción

14

Page 15: Arbol Rojo Negro (1)

(reetiquetando N y P ) debido a que la propiedad “ambos hijos de cada nodo rojo son negros” se mantiene aún incumplida. La rotación causa que algunos caminos (en el sub-árbol etiquetado como “1”) pasen a través del nuevo nodo donde no lo hacían antes, pero ambos nodos son rojos, así que la propiedad “todos los caminos desde cualquier nodo dado a sus hojas contiene el mismo número de nodos negros” no es violada por la rotación. Aquí tenemos una posible implementación:

Void insercion_caso4(struct node *n)

{

struct node *a = abuelo(n);

if ((n == n->padre->dcho) && (n->padre == a->izdo)) {

rotar_izda(n->padre);

n = n->izdo;

} else if ((n == n->padre->izdo) && (n->padre == a->dcho)) {

rotar_dcha(n->padre);

n = n->dcho;

}

insercion_caso5(n);

}

15

Page 16: Arbol Rojo Negro (1)

Caso 5: El padre P es rojo pero el tío U es negro, el nuevo nodo N es el hijo izquierdo de P, y P es el hijo izquierdo de su padre G. En este caso, se realiza una rotación a la derecha sobre el padre P; el resultado es un árbol donde el padre P es ahora el padre del nuevo nodo N y del inicial abuelo G. Este nodo G ha de ser negro, así como su hijo P rojo. Se intercambian los colores de ambos y el resultado satisface aquella propiedad de que “ambos hijos de un nodo rojo son negros”. Luego la propiedad “todos los caminos desde un nodo dado hasta sus hojas contienen el mismo número de nodos negros” también se mantiene satisfecha, ya que todos los caminos que iban a través de esos tres nodos entraban por G antes, y ahora entran por P. En cada caso, este es el único nodo negro de los tres. Una posible implementación en C es la siguiente:

16

Page 17: Arbol Rojo Negro (1)

Void insercion_caso5(struct node *n)

{

struct node *a = abuelo(n);

n->padre->color = NEGRO;

a->color = ROJO;

if ((n == n->padre->izdo) && (n->padre == a->izdo)) {

rotar_dcha(a);

} else {

/*

* En este caso, (n == n->padre->dcho) && (n->padre == a->dcho).

*/

rotar_izda(a);

}

}

Nótese que la inserción se realiza sobre el propio árbol y que los códigos del ejemplo utilizan recursión de cola.

D) Eliminación

En un árbol binario de búsqueda normal, cuando se borra un nodo con dos nodos internos como hijos, tomamos el máximo elemento del subárbol izquierdo o el mínimo del subárbol derecho, y movemos su valor al nodo que es borrado .Borramos, entonces el nodo del que copiábamos el valor que debe tener menos de dos nodos no hojas por hijos. Copiar un valor no viola ninguna de las propiedades rojo-negro y reduce el problema de borrar en general al de borrar un nodo con como mucho un hijo no hoja. No importa si este nodo

17

Page 18: Arbol Rojo Negro (1)

es el nodo que queríamos originalmente borrar o el nodo del que copiamos el valor.

Si borramos un nodo rojo, podemos simplemente reemplazarlo con su hijo, que debe ser negro. Todos los caminos hasta el nodo borrado simplemente pasarán a través de un nodo rojo menos, y ambos nodos, el padre del borrado y el hijo, han de ser negros, así que las propiedades como “todas las hojas, incluyendo las nulas, son negras” y “los dos hijos de cada nodo rojo son negros” se mantienen. Otro caso simple es cuando el nodo borrado es negro y su hijo es rojo. Simplemente eliminar un nodo negro podría romper las propiedades “los dos hijos de cada nodo rojo son negros” y “todos los caminos desde un nodo dado hasta sus hojas contienen el mismo número de nodos negros”, pero si repintamos su hijo de negro, ambas propiedades quedan preservadas.

El caso complejo es cuando el nodo que va a ser borrado y su hijo son negros. Empezamos por reemplazar el nodo que va a ser borrado con su hijo. Llamaremos a este hijo (en su nueva posición) N, y su hermano (el otro hijo de su nuevo padre) S. En los diagramas de debajo, usaremos P para el nuevo padre de N, SL para el hijo izquierdo de S, y SR para el nuevo hijo derecho de S (se puede mostrar que S no puede ser una hoja).

NOTA IMPORTANTE: Entre algunos casos cambiamos roles y etiquetas de los nodos, pero en cada caso, toda etiqueta sigue representando al mismo nodo que representaba al comienzo del caso. Cualquier color mostrado en el diagrama es o bien supuesto en su caso o bien implicado por dichas suposiciones. El blanco representa un color desconocido (o bien rojo o bien negro).

El cumplimiento de estas reglas en un árbol con n nodos, asegura un máximo de tres rotaciones y hasta   recoloraciones.

struct node *hermano(struct node *n){

18

Page 19: Arbol Rojo Negro (1)

if (n == n->padre->izdo) return n->padre->dcho; else return n->padre->izdo;}

Nota: Con el fin de preservar la buena definición del árbol, necesitamos que toda hoja nula siga siendo una hoja nula tras todas las transformaciones (que toda hoja nula no tendrá ningún hijo). Si el nodo que estamos borrando tiene un hijo no hoja N, es fácil ver que la propiedad se satisface. Si, por otra parte N fuese una hoja nula, se verifica por los diagramas o el código que para todos los casos la propiedad se satisface también.

Podemos realizar los pasos resaltados arriba con el siguiente código, donde la función reemplazar_nodo sustituye hijo en el lugar de N en el árbol. Por facilitar la comprensión del ejemplo, en el código de esta sección supondremos que las hojas nulas están representadas por nodos reales en lugar de NULL (el código de la sección inserción trabaja con ambas representaciones).

Void elimina_un_hijo(struct node *n)

{

/*

* Precondición: n tiene al menos un hijo no nulo.

*/

struct node *hijo = es_hoja(n->dcho) ? n->izdo : n->dcho;

reemplazar_nodo(n, hijo);

if (n->color == NEGRO) {

if (hijo->color == ROJO)

hijo->color = NEGRO;

19

Page 20: Arbol Rojo Negro (1)

else

eliminar_caso1(hijo);

}

free(n);

}

NOTA IMPORTANTE: Si N es una hoja nula y no queremos representar hojas nulas como nodos reales, podemos modificar el algoritmo llamando primero a eliminar_caso1() en su padre (el nodo que borramos, n en el código anterior) y borrándolo después. Podemos hacer esto porque el padre es negro, así que se comporta de la misma forma que una hoja nula (y a veces es llamada hoja “fantasma”). Y podemos borrarla con seguridad, de tal forma que n seguirá siendo una hoja tras todas las operaciones, como se muestra arriba.

Si N y su padre original son negros, entonces borrar este padre original causa caminos que pasan por N y tienen un nodo negro menos que los caminos que no. Como esto viola la propiedad “todos los caminos desde un nodo dado hasta su nodos hojas deben contener el mismo número de nodos negros”, el árbol debe ser reequilibrado. Tenemos varios casos:

Caso 1: N es la nueva raíz. En este caso, hemos acabado. Borramos un nodo negro de cada camino y la nueva raíz es negra, así las propiedades se cumplen. Una posible implementación en el lenguaje de programación C sería la siguiente:

Void eliminar_caso1(struct node *n)

{

if (n->padre!= NULL)

eliminar_caso2(n);

}

20

Page 21: Arbol Rojo Negro (1)

NOTA IMPORTANTE: En los casos 2, 5 y 6, asumimos que N es el hijo izquierdo de su padre P. Si éste fuese el hijo derecho, la izquierda y la derecha deberían ser invertidas en todos estos casos. De nuevo, el código del ejemplo toma ambos casos en cuenta.

Caso 2: S es rojo. En este caso invertimos los colores de P y S, por lo que rotamos a la izquierda P, pasando S a ser el abuelo de N. Nótese que P tiene que ser negro al tener un hijo rojo. Aunque todos los caminos tienen todavía el mismo número de nodos negros, ahora N tiene un hermano negro y un padre rojo, así que podemos proceder a al paso 4, 5 o 6 (este nuevo hermano es negro porque éste era uno de los hijos de S, que es rojo). En casos posteriores, re etiquetaremos el nuevo hermano de N como S. Aquí podemos ver una implementación:

Void eliminar_caso2(struct node *n)

{

struct node *hm = hermano(n);

21

Page 22: Arbol Rojo Negro (1)

if (hm->color == ROJO) {

n->padre->color = ROJO;

hm->color = NEGRO;

if (n == n->padre->izdo)

rotar_izda(n->padre);

else

rotar_dcha(n->padre);

}

eliminar_caso3(n);

}

Caso 3: P, S y los hijos de S son negros. En este caso, simplemente cambiamos S a rojo. El resultado es que todos los caminos a través de S, precisamente aquellos que no pasan por N, tienen un nodo negro menos. El hecho de borrar el padre original de N haciendo que todos los caminos que pasan por N tengan un nodo negro menos nivela el árbol. Sin embargo, todos los caminos a través de P tienen ahora un nodo negro menos que los caminos que no pasan por P, así que la propiedad 5 aún no se cumple (todos los caminos desde cualquier nodo a su nodo hijo contienen el mismo número de nodos negros). Para corregir esto, hacemos el proceso de reequilibrio en P, empezando en el caso 1. Su implementación en C:

22

Page 23: Arbol Rojo Negro (1)

void eliminar_caso3(struct node *n)

{

struct node *hm = hermano_menor(n);

if ((n->padre->color == NEGRO) &&

(hm->color == NEGRO) &&

(hm->izdo->color == NEGRO) &&

(hm->dcho->color == NEGRO)) {

hm->color = ROJO;

eliminar_caso1(n->padre);

} else

eliminar_caso4(n);

}

Caso 4: S y los hijos de éste son negros, pero P es rojo. En este caso, simplemente intercambiamos los colores de S y P. Esto no afecta al número de nodos negros en los caminos que no van a través de S, pero añade uno al número de nodos negros a los caminos que van a través de N, compensando así el borrado del nodo negro en dichos caminos. Si lo implementamos en C, quedaría:

23

Page 24: Arbol Rojo Negro (1)

Void eliminar_caso4(struct node *n)

{

struct node *hm = hermano_menor(n);

if ((n->padre->color == ROJO) &&

(hm->color == NEGRO) &&

(hm->izdo->color == NEGRO) &&

(hm->dcho->color == NEGRO)) {

hm->color = ROJO;

n->padre->color = NEGRO;

} else

eliminar_caso5(n);

}

24

Page 25: Arbol Rojo Negro (1)

Caso 5: S es negro, su hijo izquierdo es rojo, el derecho es negro, y N es el hijo izquierdo de su padre. En este caso rotamos a la derecha S, así su hijo izquierdo se convierte en su padre y en el hermano de N. Entonces intercambiamos los colores de S y su nuevo padre. Todos los caminos tienen aún el mismo número de nodos negros, pero ahora N tiene un hermano negro cuyo hijo derecho es rojo, así que caemos en el caso 6. Ni N ni su padre son afectados por esta transformación (de nuevo, por el caso 6, reetiquetamos el nuevo hermano de N como S). He aquí la implementación en C:

Void eliminar_caso5(struct node *n)

{ struct node *hm = hermano(n);

if ((n == n->padre->izdo) &&

(hm->color == NEGRO) &&

25

Page 26: Arbol Rojo Negro (1)

(hm->izdo->color == ROJO) &&

(hm->dcho->color == NEGRO)) {

hm->color = ROJO;

hm->izdo->color = NEGRO;

rotar_dcha(hm);

} else if ((n == n->padre->dcho) &&

(hm->color == NEGRO) &&

(hm->dcho->color == ROJO) &&

(hm->izdo->color == NEGRO)) {

hm->color = ROJO;

hm->dcho->color = NEGRO;

rotar_izda(hm);

}

eliminar_caso6(n);}

De nuevo, todas las llamadas de la función usan recursión de cola así que el algoritmo realiza sus operaciones sobre el propio árbol. Además, las llamadas no recursivas se harán después de una rotación, luego se harán un número de rotaciones (más de 3) que será constante.

4) DEMOSTRACION DE COTAS

Un árbol rojo-negro que contiene n nodos internos tiene una altura de O(log(n)).

Hagamos los siguientes apuntes sobre notación:

H(v) = altura del árbol cuya raíz es el nodo v.

bh(v) = número de nodos negros (sin contar v si es negro) desde v hasta cualquier hoja del subárbol (llamado altura-negra).

26

Page 27: Arbol Rojo Negro (1)

Lema: Un subárbol enraizado al nodo v tiene al menos nodos internos.

Demostración del lema (por inducción sobre la altura):

Caso base: h(v)=0 Si v tiene altura cero entonces debe ser árbol vacío, por tanto bh(v)=0. Luego:

H

Hipótesis de Inducción: si v es tal que h(v) = k y contiene   nodos internos, veamos que esto implica que   tal que h( ) = k+1

contiene   nodos internos.

Si   tiene h( ) > 0 entonces es un nodo interno. Como éste tiene dos hijos que tienen altura-negra, o bh( ) o bh( )-1 (dependiendo si es rojo o negro). Por la hipótesis de inducción cada hijo tiene al

menos   nodos internos, así que   tiene :

 nodos internos.

Usando este lema podemos mostrar que la altura del árbol es algorítmica. Puesto que al menos la mitad de los nodos en cualquier camino desde la raíz hasta una hoja negra (propiedad 4 de un árbol rojo-negro), la altura-negra de la raíz es al menos h(raíz)/2. Por el lema tenemos que:

Por tanto, la altura de la raíz es O(log(n)).

5) COMPLEJIDAD

En el código del árbol hay un bucle donde la raíz de la propiedad rojo-negro que hemos querido devolver a su lugar, x, puede ascender por el árbol un nivel en cada iteración Como la

27

Page 28: Arbol Rojo Negro (1)

altura original del árbol es O(log n), hay O(log n) iteraciones. Así que en general la inserción tiene una complejidad de O(log n).

28