Árbol rojo-negro

18
Árbol rojo-negro 1 Árbol rojo-negro Un árbol rojo negro es un tipo abstracto de datos, concretamente es un árbol binario de búsqueda equilibrado, una estructura de datos utilizada en informática y ciencias de la computación. 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. Es complejo, pero tiene un buen peor caso de tiempo de ejecución para sus operaciones y es eficiente en la práctica. Puede buscar, insertar y borrar en un tiempo O(log n), donde n es el número de elementos del árbol. Sería ideal exponer la especificación algebraica completa de este tipo abstracto de datos (TAD) escrita en algún lenguaje de especificación de TADs como podría ser Maude; sin embargo, la complejidad de la estructura hace que la especificación quede bastante ilegible, y no aportaría nada. Por tanto, explicaremos su funcionamiento con palabras, esquemas e implementaciones de funciones en el lenguaje de programación C. Terminología Un árbol rojo-negro es un tipo especial de árbol binario usado en informática para organizar información compuesta por datos comparables (como por ejemplo números). En los árboles rojo-negro las hojas no son relevantes y no contienen datos. A la hora de implementarlo en un lenguaje de programación, para ahorrar memoria, un único nodo (nodo-centinela) hace de nodo hoja para todas las ramas. Así,todas las referencias de los nodos internos a las hojas van a parar al nodo centinela. En los árboles rojo-negro, como en todos los árboles binarios de búsqueda, es posible moverse ordenadamente a través de los elementos de forma eficiente si hay forma de localizar el padre de cualquier nodo. El tiempo de desplazarse desde la raíz hasta una hoja a través de un árbol equilibrado que tiene la mínima altura posible es de O(log n). Propiedades Un ejemplo de árbol rojo-negro Un árbol rojo-negro es un árbol binario de búsqueda en el que cada nodo tiene un atributo de color cuyo valor es rojo o negro. Además de los requisitos impuestos a los árboles binarios de búsqueda convencionales, se deben satisfacer los siguientes para tener un árbol rojo-negro válido: 1. Todo nodo es o bien rojo o bien negro. 2. La raíz es negra. 3. Todas las hojas son negras (las hojas son los hijos nulos). 4. 4. Los hijos de todo nodo rojo son negros (también llamada "Propiedad del rojo"). 5. Cada camino simple desde un nodo a una hoja descendiente contiene el mismo número de nodos negros, ya sea contando siempre los nodos negros nulos, o bien no contándolos nunca (el resultado es equivalente). También es llamada "Propiedad del camino", y al número de nodos negros de cada camino, que es constante para todos los caminos, se le denomina "Altura negra del árbol", y por tanto el cámino no puede tener dos rojos seguidos. 6. El camino más 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 a una hoja en dicho árbol. El resultado es que dicho árbol está aproximadamente equilibrado.

Transcript of Árbol rojo-negro

Page 1: Árbol rojo-negro

Árbol rojo-negro 1

Árbol rojo-negroUn árbol rojo negro es un tipo abstracto de datos, concretamente es un árbol binario de búsqueda equilibrado, unaestructura de datos utilizada en informática y ciencias de la computación. La estructura original fue creada porRudolf Bayer en 1972, que le dio el nombre de “árboles-B binarios simétricos”, pero tomó su nombre moderno en untrabajo de Leo J. Guibas y Robert Sedgewick realizado en 1978. Es complejo, pero tiene un buen peor caso detiempo de ejecución para sus operaciones y es eficiente en la práctica. Puede buscar, insertar y borrar en un tiempoO(log n), donde n es el número de elementos del árbol.Sería ideal exponer la especificación algebraica completa de este tipo abstracto de datos (TAD) escrita en algúnlenguaje de especificación de TADs como podría ser Maude; sin embargo, la complejidad de la estructura hace quela especificación quede bastante ilegible, y no aportaría nada. Por tanto, explicaremos su funcionamiento conpalabras, esquemas e implementaciones de funciones en el lenguaje de programación C.

TerminologíaUn árbol rojo-negro es un tipo especial de árbol binario usado en informática para organizar información compuestapor datos comparables (como por ejemplo números).En los árboles rojo-negro las hojas no son relevantes y no contienen datos. A la hora de implementarlo en unlenguaje de programación, para ahorrar memoria, un único nodo (nodo-centinela) hace de nodo hoja para todas lasramas. Así,todas las referencias de los nodos internos a las hojas van a parar al nodo centinela.En los árboles rojo-negro, como en todos los árboles binarios de búsqueda, es posible moverse ordenadamente através de los elementos de forma eficiente si hay forma de localizar el padre de cualquier nodo. El tiempo dedesplazarse desde la raíz hasta una hoja a través de un árbol equilibrado que tiene la mínima altura posible es deO(log n).

Propiedades

Un ejemplo de árbol rojo-negro

Un árbol rojo-negro es un árbol binariode búsqueda en el que cada nodo tieneun atributo de color cuyo valor es rojoo negro. Además de los requisitosimpuestos a los árboles binarios debúsqueda convencionales, se debensatisfacer los siguientes para tener unárbol rojo-negro válido:

1. Todo nodo es o bien rojo o biennegro.

2. La raíz es negra.3. Todas las hojas son negras (las hojas son los hijos nulos).4.4. Los hijos de todo nodo rojo son negros (también llamada "Propiedad del rojo").5. Cada camino simple desde un nodo a una hoja descendiente contiene el mismo número de nodos negros, ya sea

contando siempre los nodos negros nulos, o bien no contándolos nunca (el resultado es equivalente). También esllamada "Propiedad del camino", y al número de nodos negros de cada camino, que es constante para todos loscaminos, se le denomina "Altura negra del árbol", y por tanto el cámino no puede tener dos rojos seguidos.

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

Page 2: Árbol rojo-negro

Árbol rojo-negro 2

Dado que las operaciones básicas como insertar, borrar y encontrar valores tienen un peor tiempo de búsquedaproporcional a la altura del árbol, esta cota superior de la altura permite a los árboles rojo-negro ser eficientes en elpeor caso, de forma contraria a lo que sucede en los árboles binarios de búsqueda. Para ver que estas propiedadesgarantizan lo dicho, basta ver que ningún camino puede tener 2 nodos rojos seguidos debido a la propiedad 4. Elcamino más corto posible tiene todos sus nodos negros, y el más largo alterna entre nodos rojos y negros. Comotodos los caminos máximos tienen el mismo número de nodos negros, por la propiedad 5, esto muestra que no hayningún camino que pueda tener el doble de longitud que otro camino.En muchas presentaciones de estructuras arbóreas de datos, es posible para un nodo tener solo un hijo y las hojascontienen información. Es posible presentar los árboles rojo-negro en este paradigma, pero cambian algunas de laspropiedades y se complican los algoritmos. Por esta razón, este artículo utilizan “hojas nulas”, que no contieneninformación y simplemente sirven para indicar dónde el árbol acaba, como se mostró antes. Habitualmente estosnodos son omitidos en las representaciones, lo cual da como resultado un árbol que parece contradecir los principiosexpuestos antes, pero que realmente no los contradice. Como consecuencia de esto todos los nodos internos tienendos 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, enlugar de nodos, son coloreadas de color rojo o negro, pero esto no produce ninguna diferencia. El color de cada nodoen la terminología de este artículo corresponde al color de la arista que une el nodo a su padre, excepto la raíz, que essiempre negra (por la propiedad 2) donde la correspondiente arista no existe.

Usos y ventajasLos árboles rojo-negro ofrecen un peor caso con tiempo garantizado para la inserción, el borrado y la búsqueda. Noes 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 peorcaso. Por ejemplo, muchas estructuras de datos usadas en geometría computacional pueden basarse en árbolesrojo-negro.El árbol AVL es otro tipo de estructura con O(log n) tiempo de búsqueda, inserción y borrado. Está equilibrado deforma más rígida que los árboles rojo-negro, lo que provoca que la inserción y el borrado sean más lentos pero labúsqueda y la devolución del resultado de la misma más veloz.Los árboles rojo-negro son particularmente valiosos en programación funcional, donde son una de las estructuras dedatos persistentes más comúnmente utilizadas en la construcción de arrays asociativos y conjuntos que puedenretener 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 árbolcorrespondiente rojo-negro con los datos en el mismo orden. La inserción y el borrado en árboles 2-3-4 son tambiénequivalentes 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 sobrealgoritmos presentan los árboles 2-3-4 justo antes que los árboles rojo-negro, aunque frecuentemente no seanutilizados en la práctica.

Page 3: Árbol rojo-negro

Árbol rojo-negro 3

OperacionesLas operaciones de sólo lectura en un árbol rojo-negro no requieren modificación alguna con respecto a las utilizadasen los árboles binarios de búsqueda, ya que cada árbol rojo-negro es un caso especial de árbol binario de búsqueda.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 propiedadesrojo-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ásde 3 rotaciones (2 por inserción). A pesar de que las operaciones de inserción y borrado son complicadas, sustiempos de ejecución siguen siendo O(log n).

RotaciónPara conservar las propiedades que debe cumplir todo árbol rojo-negro, en ciertos casos de la inserción y laeliminación será necesario reestructurar el árbol, si bien no debe perderse la ordenación relativa de los nodos. Paraello, se llevan a cabo una o varias rotaciones, que no son más que reestructuraciones en las relacionespadre-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 izquierday hacia la derecha en cualquier árbol binario de búsqueda, en particular en cualquier árbol rojo-negro. Podemos vertambié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;

Page 4: Árbol rojo-negro

Árbol rojo-negro 4

}

// 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

}

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;

aux->padre = p;

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

y aux->izdo es nulo

}

Page 5: Árbol rojo-negro

Árbol rojo-negro 5

BúsquedaLa búsqueda consiste acceder a la raíz del árbol, si el elemento a localizar coincide con éste la búsqueda haconcluido con éxito, si el elemento es menor se busca en el subárbol izquierdo y si es mayor en el derecho. Si sealcanza un nodo hoja y el elemento no ha sido encontrado se supone que no existe en el árbol. Cabe destacar que labúsqueda en este tipo de árboles es muy eficiente, representa una función logarítmica. La búsqueda de un elementoen un ABB (Árbol Binario de Búsqueda) en general, y en un árbol rojo negro en particular, se puede realizar de dosformas, iterativa o recursiva.Ejemplo de versión iterativa en el lenguaje de programación C, suponiendo que estamos buscando una clave alojadaen 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) )

{

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

Page 6: Árbol rojo-negro

Árbol rojo-negro 6

return 1;

}

InserciónLa inserción comienza añadiendo el nodo como lo haríamos en un árbol binario de búsqueda convencional ypintá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 notarque:•• La propiedad 3 (Todas las hojas, incluyendo las nulas, son negras) siempre se cumple.•• La propiedad 4 (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 5 (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áximode 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: 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 caso. Cualquier colormostrado 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 propiedad2 (la raíz es negra). Como esto añade un nodo negro a cada camino, la propiedad 5 (todos los caminos desde un nododado a sus hojas contiene el mismo número de nodos negros) se mantiene. En C quedaría así:

Page 7: Árbol rojo-negro

Árbol rojo-negro 7

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 la propiedad 4 (ambos hijos de cada nodo rojoson negros) se mantiene. En este caso, el árbol es aun válido. La propiedad 5 (todos los caminos desde cualquiernodo dado a sus hojas contiene igual número de nodos negros) se mantiene, porque el nuevo nodo N tiene dos hojasnegras como hijos, pero como N es rojo, los caminos a través de cada uno de sus hijos tienen el mismo número denodos 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: En los siguientes casos se puede asumir que N tiene un abuelo, el nodo G, porque su padre P es rojo, ysi fuese la raíz, sería negro. Consecuentemente, N tiene también un nodo tío U a pesar de que podría ser unahoja en los casos 4 y 5.

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 lapropiedad 5 (todos los caminos desde cualquier nodo dado hasta sus hojas contiene el mismo número de nodos negros). Ahora, el nuevo nodo rojoN 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 esoscaminos no ha cambiado. Sin embargo, el abuelo G podría ahora violar la propiedad 2 (la raíz es negra) o la 4 (ambos hijos de cada nodo rojo sonnegros), en el caso de la 4 porque G podría tener un padre rojo. Para solucionar este problema, el procedimiento completo se realizará de formarecursiva 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;

Page 8: Árbol rojo-negro

Árbol rojo-negro 8

t->color = NEGRO;

a = abuelo(n);

a->color = ROJO;

insercion_caso1(a);

} else {

insercion_caso4(n);

}

}

Nota: En los casos restantes, se asume que el nodo padre P es el hijo izquierdo de su padre. Si es el hijoderecho, izquierda y derecha deberían ser invertidas a partir de los casos 4 y 5. El código del ejemplo tomaesto 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. Eneste 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 seve implicado al usar el caso 5 de inserción (reetiquetando N y P ) debido a que la propiedad 4 (ambos hijos de cada nodo rojo son negros) semantiene 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 lohacían antes, pero ambos nodos son rojos, así que la propiedad 5 (todos los caminos desde cualquier nodo dado a sus hojas contiene el mismonú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);

}

Page 9: Árbol rojo-negro

Árbol rojo-negro 9

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, serealiza 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 abueloG. Este nodo G ha de ser negro, así como su hijo P rojo. Se intercambian los colores de ambos y el resultado satisface la propiedad 4 (ambos hijosde un nodo rojo son negros). La propiedad 5 (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 cadacaso, este es el único nodo negro de los tres. Una posible implementación en C es la siguiente:

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 decola.

EliminaciónEn un árbol binario de búsqueda normal, cuando se borra un nodo con dos nodos internos como hijos, tomamos elmáximo elemento del subárbol izquierdo o el mínimo del subárbol derecho, y movemos su valor al nodo que esborrado (como se muestra aquí). Borramos entonces el nodo del que copiábamos el valor que debe tener menos dedos nodos no hojas por hijos. Copiar un valor no viola ninguna de las propiedades rojo-negro y reduce el problemade borrar en general al de borrar un nodo con como mucho un hijo no hoja. No importa si este nodo es el nodo quequeríamos originalmente borrar o el nodo del que copiamos el valor.Resumiendo, podemos asumir que borramos un nodo con como mucho un hijo no hoja (si solo tiene nodos hojas porhijos, tomaremos uno de ellos como su hijo). Si borramos un nodo rojo, podemos simplemente reemplazarlo con suhijo, que debe ser negro. Todos los caminos hasta el nodo borrado simplemente pasarán a través de un nodo rojomenos, y ambos nodos, el padre del borrado y el hijo, han de ser negros, así que las propiedades 3 (todas las hojas,incluyendo las nulas, son negras) y 4 (los dos hijos de cada nodo rojo son negros) se mantienen. Otro caso simple escuando el nodo borrado es negro y su hijo es rojo. Simplemente eliminar un nodo negro podría romper las

Page 10: Árbol rojo-negro

Árbol rojo-negro 10

propiedades 4 (los dos hijos de cada nodo rojo son negros) y 5 (todos los caminos desde un nodo dado hasta sushojas contienen el mismo número de nodos negros), pero si repintamos su hijo de negro, ambas propiedades quedanpreservadas.El caso complejo es cuando el nodo que va a ser borrado y su hijo son negros. Empezamos por reemplazar el nodoque va a ser borrado con su hijo. Llamaremos a este hijo (en su nueva posición) N, y su hermano (el otro hijo de sunuevo padre) S. En los diagramas de debajo, usaremos P para el nuevo padre de N, S

L 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: Entre algunos casos cambiamos roles y etiquetas de los nodos, pero en cada caso, toda etiqueta siguerepresentando al mismo nodo que representaba al comienzo del caso. Cualquier color mostrado en el diagramaes o bien supuesto en su caso o bien implicado por dichas suposiciones. El blanco representa un colordesconocido (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.Encontraremos el hermano usando esta función:

struct node *

hermano(struct node *n)

{

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 unahoja nula tras todas las transformaciones (que toda hoja nula no tendrá ningún hijo). Si el nodo que estamosborrando tiene un hijo no hoja N, es fácil ver que la propiedad se satisface. Si, por otra parte N fuese una hojanula, 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_nodosustituye hijo en el lugar de n en el árbol. Por facilitar la comprensión del ejemplo, en el código de esta secciónsupondremos que las hojas nulas están representadas por nodos reales en lugar de NULL (el código de la seccióninserció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;

else

eliminar_caso1(hijo);

}

Page 11: Árbol rojo-negro

Árbol rojo-negro 11

free(n);

}

Nota: Si N es una hoja nula y no queremos representar hojas nulas como nodos reales, podemos modificar elalgoritmo llamando primero a eliminar_caso1() en su padre (el nodo que borramos, n en el código anterior) yborrándolo después. Podemos hacer esto porque el padre es negro, así que se comporta de la misma forma queuna hoja nula (y a veces es llamada hoja “fantasma”). Y podemos borrarla con seguridad, de tal forma que nseguirá 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 unnodo negro menos que los caminos que no. Como esto viola la propiedad 5 (todos los caminos desde un nodo dadohasta su nodos hojas deben contener el mismo número de nodos negros), el árbol debe ser reequilibrado. Hay varioscasos a considerar.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 esnegra, así las propiedades se cumplen. Una posible implementación en el lenguaje de programación C sería lasiguiente:

void

eliminar_caso1(struct node *n)

{

if (n->padre!= NULL)

eliminar_caso2(n);

}

Nota: En los casos 2, 5 y 6, asumimos que N es el hijo izquierdo de su padre P. Si éste fuese el hijo derecho, laizquierda y la derecha deberían ser invertidas en todos estos casos. De nuevo, el código del ejemplo tomaambos 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 Ptiene 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 hermanonegro 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, reetiquetaremos el nuevo hermano de N como S. Aquí podemos ver una implementación:

void

eliminar_caso2(struct node *n)

{

struct node *hm = hermano(n);

if (hm->color == ROJO) {

n->padre->color = ROJO;

hm->color = NEGRO;

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

rotar_izda(n->padre);

else

Page 12: Árbol rojo-negro

Árbol rojo-negro 12

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 caminosque 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 quelos 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 elmismo 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:

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úmerode 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:

void

eliminar_caso4(struct node *n)

{

struct node *hm = hermano_menor(n);

Page 13: Árbol rojo-negro

Árbol rojo-negro 13

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);

}

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í suhijo 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 tienenaú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 supadre 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 enC:

void

eliminar_caso5(struct node *n)

{

struct node *hm = hermano(n);

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

(hm->color == NEGRO) &&

(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);

}

Page 14: Árbol rojo-negro

Árbol rojo-negro 14

Caso 6: S es negro, su hijo derecho es rojo, y N es el hijo izquierdo de P, su padre. En este caso rotamos a la izquierda P, así que S se convierte enel padre de P y éste en el hijo derecho de S. Entonces intercambiamos los colores de P y S, y ponemos el hijo derecho de S en negro. El subárbolaún tiene el mismo color que su raíz, así que las propiedades 4 (los hijos de todo nodo rojo son negros) y 5 (todos los caminos desde cualquier nodoa sus nodos hoja contienen el mismo número de nodos negros) se verifican. Sin embargo, N tiene ahora un antecesor negro mas: o bien P se haconvertido en negro, o bien era negro y S se ha añadido como un abuelo negro. De este modo, los caminos que pasan por N pasan por un nodonegro mas. Mientras tanto, si un camino no pasa por N, entonces hay dos posibilidades:

• Éste pasa a través del nuevo hermano de N. Entonces, éste debe pasar por S y P, al igual que antes, y tienen sólo que intercambiar los colores.Así los caminos contienen el mismo número de nodos negros.

• Éste pasa por el nuevo tío de N, el hijo derecho de S. Éste anteriormente pasaba por S, su padre y su hijo derecho, pero ahora sólo pasa por S, elcual ha tomado el color de su anterior padre, y por su hijo derecho, el cual ha cambiado de rojo a negro. El efecto final es que este camino va porel mismo número de nodos negros.

De cualquier forma, el número de nodos negros en dichos caminos no cambia. De este modo, hemos restablecido las propiedades 4 (los hijos detodo nodo rojo son negros) y 5 (todos los caminos desde cualquier nodo a sus nodos hoja contienen el mismo número de nodos negros). El nodoblanco en diagrama puede ser rojo o negro, pero debe tener el mismo color tanto antes como después de la transformación. Adjuntamos el últimoalgoritmo:

void

eliminar_caso6(struct node *n)

{

struct node *hm = hermano(n);

hm->color = n->padre->color;

n->padre->color = NEGRO;

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

/*

* Aquí, hm->dcho->color == ROJO.

*/

hm->dcho->color = NEGRO;

rotar_izda(n->padre);

} else {

/*

* Aquí, hm->izdo->color == ROJO.

*/

hm->izdo->color = NEGRO;

rotar_dcha(n->padre);

}

}

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

Page 15: Árbol rojo-negro

Árbol rojo-negro 15

Demostración de cotasUn á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).

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:

Hipótesis de Inducción: si v es tal que h(v) = k y contiene nodos internos, veamos que esto implica quetal 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( ) obh( )-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 nodosen cualquier camino desde la raíz hasta una hoja negra (propiedad 4 de un árbol rojo-negro), la altura-negra de la raízes al menos h(raíz)/2. Por el lema tenemos que:

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

ComplejidadEn 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 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).

Referencias• Hernández, Zenón; Rodríguez, J.Carlos; González, J.Daniel; Díaz, Margarita; Pérez, José; Rodríguez, Gustavo.

(2005). Fundamentos de Estructuras de Datos. Soluciones en Ada, Java y C++.. Madrid: Thomson EditoresSpain. 84-9732-358-0.

• Mathworld: Red-Black Tree [1].• San Diego State University: CS 660: Red-Black tree notes [2], by Roger Whitney.• Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein. Introduction to Algorithms,

Second Edition. MIT Press and McGraw-Hill, 2001. ISBN 0-262-03293-7 . Chapter 13: Red-Black Trees,pp.273–301.

• Pfaff, Ben (June de 2004). «Performance Analysis of BSTs in System Software [3]» (PDF). Stanford_university..• Okasaki, Chris. «Red-Black Trees in a Functional Setting [4]» (PS)..

Page 16: Árbol rojo-negro

Árbol rojo-negro 16

Enlaces externos

Demos y simuladores• Simulador de árboles Rojo-Negro [5].• Red Black Tree Applet [6], una demo de los árboles rojo-negro y otros muchos más árboles de búsqueda, por

Kubo Kovac.• Red Black Tree Applet [7], una demo de los árboles rojo-negro, AVL, rotaciones y mucho más.• Red/Black Tree Demonstration [8], una demo interactiva acerca de la inserción y eliminación con una

implementación en Java.• Red-Black Tree Animation [9], una demostración de la inserción en el peor caso.• aiSee: Visualization of a Sorting Algorithm [10], GIF animado que muestra la inserción (200KB).• Red-Black Tree Demonstration [11], una demo acerca de la inserción, en código Java, por David M. Howard.• AVL Tree Applet [7], una demo interactiva acercad de la inserción y la eliminaicón en árboles AVL, splay y

rojo-negro.

Implementaciones• Descargar Programa Red-black [12] en java.• En la librería de C++, Standard Template Library, los containers std::set<Value> ystd::map<Key,Value> están implementados a menudo con árboles rojo-negro.

• Implementación eficiente de árboles rojo-negro. [13]

• RBT: Una biblioteca de árboles rojo-negro en SmallEiffel. [14]

• libredblack: Una biblioteca de árboles rojo-negro en C. [15]

• Código en C de árboles rojo-negro. [16]

• Implementación en Java de os árboles rojo-negro en java.util.TreeMap [17]

• Los subsistemas de DragonFlyBSD VM utilizan árboles rojo-negro. [18]

• NATURAL/ADABAS implementación de Paul Macgowan [19]

• NGenerics : implementación en C# [20]

• FreeBSD's single header file implementation [21]

• The default scheduler of Linux 2.6.23, called Completely Fair Scheduler, is implemented via a Red-Black tree [22]

Referencias[1] http:/ / mathworld. wolfram. com/ Red-BlackTree. html[2] http:/ / www. eli. sdsu. edu/ courses/ fall95/ cs660/ notes/ RedBlackTree/ RedBlack. html#RTFToC2[3] http:/ / www. stanford. edu/ ~blp/ papers/ libavl. pdf[4] http:/ / www. eecs. usma. edu/ webs/ people/ okasaki/ jfp99. ps[5] https:/ / www. gedlc. ulpgc. es/ docencia/ ed_ii/ simulaciones/ arboles_binarios/ applet. html[6] http:/ / people. ksp. sk/ ~kuko/ bak/ index. html[7] http:/ / webpages. ull. es/ users/ jriera/ Docencia/ AVL/ AVL%20tree%20applet. htm[8] http:/ / www. ececs. uc. edu/ ~franco/ C321/ html/ RedBlack/ redblack. html[9] http:/ / www. ibr. cs. tu-bs. de/ lehre/ ss98/ audii/ applets/ BST/ RedBlackTree-Example. html[10] http:/ / www. aisee. com/ anim/ maple. htm[11] http:/ / geocities. com/ dmh2000/ articles/ code/ red-blacktree. html[12] http:/ / miarroba. com/ foros/ ver. php?foroid=674005& temaid=3112672[13] http:/ / eternallyconfuzzled. com/ tuts/ datastructures/ jsw_tut_rbtree. aspx[14] http:/ / efsa. sourceforge. net/ archive/ durian/ red_black_tree. htm[15] http:/ / libredblack. sourceforge. net/[16] http:/ / web. mit. edu/ ~emin/ www/ source_code/ red_black_tree/ index. html[17] http:/ / www. javaresearch. org/ source/ jdk142/ java/ util/ TreeMap. java. html[18] http:/ / dragonflybsd. org[19] http:/ / scctoolkit. atspace. com

Page 17: Árbol rojo-negro

Árbol rojo-negro 17

[20] http:/ / www. codeplex. com/ NGenerics[21] http:/ / fxr. watson. org/ fxr/ source/ / sys/ tree. h[22] http:/ / www. ibm. com/ developerworks/ linux/ library/ l-cfs/ ?ca=dgr-lnxw04CFC4Linux

Page 18: Árbol rojo-negro

Fuentes y contribuyentes del artículo 18

Fuentes y contribuyentes del artículoÁrbol rojo-negro  Fuente: http://es.wikipedia.org/w/index.php?oldid=58487543  Contribuyentes: Amanuense, Ascánder, Bermudob, Carutsu, Cburnett, Ender., Gramito, Isha, Lasneyx,Mariols15, Matdrodes, Periku, Petnapet, Porao, Regnaron, Sabbut, Tomatejc, U.gonzalez, Vcarceler, Wmtnez, 44 ediciones anónimas

Fuentes de imagen, Licencias y contribuyentesArchivo:Red-black tree example.svg  Fuente: http://es.wikipedia.org/w/index.php?title=Archivo:Red-black_tree_example.svg  Licencia: Creative Commons Attribution-ShareAlike 3.0Unported  Contribuyentes: en:User:CburnettArchivo:Rotación_Simple_izquierda.JPG  Fuente: http://es.wikipedia.org/w/index.php?title=Archivo:Rotación_Simple_izquierda.JPG  Licencia: Creative Commons Attribution-Share Alike Contribuyentes: Jose María García AlaminosArchivo:Rotación_Simple_derecha.JPG  Fuente: http://es.wikipedia.org/w/index.php?title=Archivo:Rotación_Simple_derecha.JPG  Licencia: Public Domain  Contribuyentes: Jose MaríaGarcía AlaminosArchivo:Red-black_tree_insert_case_3.png  Fuente: http://es.wikipedia.org/w/index.php?title=Archivo:Red-black_tree_insert_case_3.png  Licencia: Public Domain  Contribuyentes: UsersCintrom, Deco, Deelkar on en.wikipediaArchivo:Red-black_tree_insert_case_4.png  Fuente: http://es.wikipedia.org/w/index.php?title=Archivo:Red-black_tree_insert_case_4.png  Licencia: Public Domain  Contribuyentes: User Decoon en.wikipediaArchivo:Red-black_tree_insert_case_5.png  Fuente: http://es.wikipedia.org/w/index.php?title=Archivo:Red-black_tree_insert_case_5.png  Licencia: Public Domain  Contribuyentes: User Decoon en.wikipediaArchivo:Red-black_tree_delete_case_2.png  Fuente: http://es.wikipedia.org/w/index.php?title=Archivo:Red-black_tree_delete_case_2.png  Licencia: Public Domain  Contribuyentes: UsersDeelkar, Deco on en.wikipediaArchivo:Red-black_tree_delete_case_3.png  Fuente: http://es.wikipedia.org/w/index.php?title=Archivo:Red-black_tree_delete_case_3.png  Licencia: Public Domain  Contribuyentes: UserDeco on en.wikipediaArchivo:Red-black_tree_delete_case_4.png  Fuente: http://es.wikipedia.org/w/index.php?title=Archivo:Red-black_tree_delete_case_4.png  Licencia: Public Domain  Contribuyentes: UserDeco on en.wikipediaArchivo:Red-black_tree_delete_case_5.png  Fuente: http://es.wikipedia.org/w/index.php?title=Archivo:Red-black_tree_delete_case_5.png  Licencia: Public Domain  Contribuyentes: UserDeco on en.wikipediaArchivo:Red-black_tree_delete_case_6.png  Fuente: http://es.wikipedia.org/w/index.php?title=Archivo:Red-black_tree_delete_case_6.png  Licencia: Public Domain  Contribuyentes: UserDeco on en.wikipedia

LicenciaCreative Commons Attribution-Share Alike 3.0 Unported//creativecommons.org/licenses/by-sa/3.0/