fbpx
Wikipedia

Árbol rojo-negro

Un árbol rojo-negro 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 deLeo 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.


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 (por ejemplo, números). En los árboles rojo-negro las hojas no son relevantes y no contienen datos.

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

Al implementar esta estructura es posible utilizar un único nodo centinela. Este cumple la función de hoja para todas las ramas del árbol. Así, todos los nodos internos que finalicen en una hoja tienen referencia a este único nodo centinela. Esto no es necesario, ya que puede hacerse una referencia nula (NIL) en el final de cada rama.

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. En adelante, se dice que un nodo es rojo o negro haciendo referencia a dicho atributo.

Además de los requisitos impuestos a los árboles binarios de búsqueda convencionales, se deben satisfacer las siguientes reglas 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 (NULL) son negras.
  4. Todo nodo rojo debe tener dos nodos hijos negros.
  5. Cada camino desde un nodo dado a sus hojas descendientes contiene el mismo número de nodos negros.

Estas reglas producen una regla crucial para los árboles rojo-negro: el camino más largo desde la raíz hasta una hoja no es más largo que dos veces el camino más corto desde la raíz a una hoja. El resultado es que dicho árbol está aproximadamente equilibrado.

Dado que las operaciones básicas como insertar, borrar y encontrar valores tienen un peor tiempo de ejecución proporcional a la altura del árbol, esta cota superior de la altura permite a los árboles rojo-negro ser eficientes en el peor caso, a diferencia de los árboles binarios de búsqueda.

Para comprobarlo basta ver que ningún camino puede tener dos nodos rojos seguidos debido a la propiedad 4. El camino más corto posible tiene todos sus nodos negros, y el más largo alterna entre nodos rojos y negros. Dado que todos los caminos máximos tienen el mismo número de nodos negros por la propiedad 5, no hay ningún camino que pueda tener longitud mayor que el doble de la longitud de otro camino.

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-negro en este paradigma, pero cambian algunas de las propiedades y se complican los algoritmos. Por esta razón, este artículo utiliza “hojas nulas”.

Una variante que se da al á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, pero esto no produce ninguna diferencia. El color de cada nodo en 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 es siempre negra (por la propiedad 2) donde la correspondiente arista no existe.

Al número de nodos negros de un camino se le denomina "altura negra".

Usos y ventajas

Los árboles rojo-negro 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.

El árbol AVL es otro tipo de estructura con O(log n) tiempo de búsqueda, inserción y borrado. Está equilibrado de forma más rígida que los árboles rojo-negro, lo que provoca que la inserción y el borrado sean más lentos pero la bú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 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.

Operaciones

Las operaciones de sólo lectura en un árbol rojo-negro no requieren modificación alguna con respecto a las utilizadas en 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 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).

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 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=&raiz;  if(p->padre!=NULL && p->padre->dcho==p)  aux=&(p->padre->dcho);  else if(p->padre!=NULL && p->padre->izdo==p)  aux=&(p->padre->izdo);  *aux=p->dcho;  (*aux)->padre=p->padre;  p->padre=*aux;  p->dcho=(*aux)->izdo;  (*aux)->izdo=p;  if(p->dcho!=NULL) p->dcho->padre=p; } 
 
void rotar_dcha(struct node *p) {  struct node **aux=&raiz;  if(p->padre!=NULL && p->padre->dcho==p)  aux=&(p->padre->dcho);  else if(p->padre!=NULL && p->padre->izdo==p)  aux=&(p->padre->izdo);  *aux=p->izdo;  (*aux)->padre=p->padre;  p->padre=*aux;  p->izdo=(*aux)->dcho;  (*aux)->dcho=p;  if(p->izdo!=NULL) p->izdo->padre=p; } 

Búsqueda

La búsqueda consiste acceder a la raíz del árbol y comparar su valor con el valor buscado. Si el elemento a localizar coincide con el de la raíz, la búsqueda ha concluido con éxito. Si el elemento es menor, se busca en el subárbol izquierdo; si es mayor, en el derecho. Si se alcanza 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 y representa una función logarítmica. 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 y 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:

TipoDato buscar_abb_iterativo(Abb a, TipoValor buscado){  TipoDato e = NULL;  Abb p = a;  while (!estaVacio(p) && (p->valor != buscado) ){  //avanzar el puntero  if (buscado < p->valor)  p = p->izquierda;  if (p->valor < buscado)  p = p->derecha;  }  if (!estaVacio(p))  e = copiaDato(p->dato);  return e; } 

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

TipoDato buscar_abb_recursivo(Nodo a, TipoValor buscado){  if (a == NULL)  return NULL;  if (a->valor == buscado)  return copiaDato(a->valor);  if (a->valor < elem)  return buscar_abb_recursivo(a->derecha, buscado);  if (a->valor > elem)  return buscar_abb_recursivo(a->izquierda, buscado); } 

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 3 (Todas las hojas 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á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: 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 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 del árbol. En este caso, es repintado en color negro para satisfacer la propiedad 2 (la raíz es negra). Como esto añade un nodo negro a cada camino, la propiedad 5 (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í:

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 rojo son negros) se mantiene. En este caso, el árbol es aun válido. La propiedad 5 (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: 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 U a pesar de que podría ser una hoja 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 la propiedad 5 (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 2 (la raíz es negra) o la 4 (ambos hijos de cada nodo rojo son negros), en el caso de la 4 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);  } } 
Nota: 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 (re etiquetando N y P ) debido a que la propiedad 4 (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 5 (todos los caminos desde cualquier nodo dado a sus hojas contiene el mismo número de nodos negros) no es violado por la rotación, después de completado este caso, se puede notar que aún se incumple la propiedad número 4 (ambos hijos de cada nodo rojo son de color negro), esto se resuelve pasando al caso 5. 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); } 
 

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 la propiedad 4 (ambos hijos de 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 cada caso, 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 de cola.

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 (como se muestra aquí). 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 es el nodo que querí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 por hijos, tomaremos uno de ellos como su hijo). 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 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 es cuando el nodo borrado es negro y su hijo es rojo. Simplemente eliminar un nodo negro podría romper las propiedades 4 (los dos hijos de cada nodo rojo son negros) y 5 (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: 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.

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 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;  else  eliminar_caso1(hijo);  }  free(n); } 
Nota: 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 5 (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. Hay varios casos 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 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); } 
Nota: En los casos 2, 5 y 6, asumimos que N es el hijo izquierdo de su padre P. Si este 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 este 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  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:

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 este 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:

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

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

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 en el padre de P y este 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árbol aú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 nodo a sus nodos hoja contienen el mismo número de nodos negros) se verifican. Sin embargo, N tiene ahora un antecesor negro más: o bien P se ha convertido 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 nodo negro más. Mientras tanto, si un camino no pasa por N, entonces hay dos posibilidades:

  • Este pasa a través del nuevo hermano de N. Entonces, este 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.
  • Este pasa por el nuevo tío de N, el hijo derecho de S. Este anteriormente pasaba por S, su padre y su hijo derecho, pero ahora sólo pasa por S, el cual 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 por el 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 de todo 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 nodo blanco en diagrama puede ser rojo o negro, pero debe tener el mismo color tanto antes como después de la transformación. Adjuntamos el último algoritmo:

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

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

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 que   tal que h( ) = k+1 contiene   nodos internos.

Si   tiene h( ) > 0 entonces es un nodo interno. Como este 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)).

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

Véase también

Bibliografía

  • 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 Editores Spain. 84-9732-358-0. 
  • Weisstein, Eric W. «Red-Black Tree». En Weisstein, Eric W, ed. MathWorld (en inglés). Wolfram Research. .
  • San Diego State University: CS 660: Red-Black tree notes el 9 de mayo de 2008 en Wayback Machine., 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 (junio de 2004). «Performance Analysis of BSTs in System Software» (PDF). Stanford University. .
  • Okasaki, Chris. (PS). Archivado desde el original el 26 de septiembre de 2007. .

Enlaces externos

Demos y simuladores

  • Simulador de árboles Rojo-Negro.
  • Red Black Tree visualisation, una demo de los árboles rojo-negro y otros muchos más árboles de búsqueda, por Kubo Kovac.
  • , una demo de los árboles rojo-negro, AVL, rotaciones y mucho más.
  • , una demo interactiva acerca de la inserción y eliminación con una implementación en Java.
  • Red-Black Tree Animation, una demostración de la inserción en el peor caso.
  • , GIF animado que muestra la inserción (200KB).
  • , una demo acerca de la inserción, en código Java, por David M. Howard.

Implementaciones

  • Descargar Programa Red-black en java.
  • En la librería de C++, Standard Template Library, los contenedores std::set<Value> y std::map<Key,Value> están implementados a menudo con árboles rojo-negro.
  • RBT: Una biblioteca de árboles rojo-negro en SmallEiffel. el 7 de octubre de 2017 en Wayback Machine.
  • libredblack: Una biblioteca de árboles rojo-negro en C.
  • Los subsistemas de DragonFlyBSD VM utilizan árboles rojo-negro.
  • NATURAL/ADABAS implementación de Paul Macgowan
  • FreeBSD's single header file implementation
  •   Datos: Q506496
  •   Multimedia: Red-black trees / Q506496

Árbol, rojo, negro, árbol, rojo, negro, árbol, binario, búsqueda, equilibrado, estructura, datos, utilizada, informática, ciencias, computación, estructura, original, creada, rudolf, bayer, 1972, nombre, árboles, binarios, simétricos, pero, tomó, nombre, moder. Un arbol rojo negro es un arbol binario de busqueda equilibrado una estructura de datos utilizada en informatica y ciencias de la computacion La estructura original fue creada por Rudolf Bayer en 1972 que le dio el nombre de arboles B binarios simetricos pero tomo su nombre moderno en un trabajo deLeo J Guibas y Robert Sedgewick realizado en 1978 Es complejo pero tiene un buen peor caso de tiempo de ejecucion para sus operaciones y es eficiente en la practica Puede buscar insertar y borrar en un tiempo O log n donde n es el numero de elementos del arbol Indice 1 Terminologia 2 Propiedades 3 Usos y ventajas 4 Operaciones 4 1 Rotacion 4 2 Busqueda 4 3 Insercion 4 4 Eliminacion 5 Demostracion de cotas 6 Complejidad 7 Vease tambien 8 Bibliografia 9 Enlaces externos 9 1 Demos y simuladores 9 2 ImplementacionesTerminologia EditarUn arbol rojo negro es un tipo especial de arbol binario usado en informatica para organizar informacion compuesta por datos comparables por ejemplo numeros En los arboles rojo negro las hojas no son relevantes y no contienen datos En los arboles rojo negro como en todos los arboles binarios de busqueda es posible moverse ordenadamente a traves de los elementos de forma eficiente si hay forma de localizar el padre de cualquier nodo El tiempo de desplazarse desde la raiz hasta una hoja a traves de un arbol equilibrado que tiene la minima altura posible es de O log n Al implementar esta estructura es posible utilizar un unico nodo centinela Este cumple la funcion de hoja para todas las ramas del arbol Asi todos los nodos internos que finalicen en una hoja tienen referencia a este unico nodo centinela Esto no es necesario ya que puede hacerse una referencia nula NIL en el final de cada rama Propiedades Editar Un ejemplo de arbol rojo negro Un arbol rojo negro es un arbol binario de busqueda en el que cada nodo tiene un atributo de color cuyo valor es rojo o negro En adelante se dice que un nodo es rojo o negro haciendo referencia a dicho atributo Ademas de los requisitos impuestos a los arboles binarios de busqueda convencionales se deben satisfacer las siguientes reglas para tener un arbol rojo negro valido Todo nodo es o bien rojo o bien negro La raiz es negra Todas las hojas NULL son negras Todo nodo rojo debe tener dos nodos hijos negros Cada camino desde un nodo dado a sus hojas descendientes contiene el mismo numero de nodos negros Estas reglas producen una regla crucial para los arboles rojo negro el camino mas largo desde la raiz hasta una hoja no es mas largo que dos veces el camino mas corto desde la raiz a una hoja El resultado es que dicho arbol esta aproximadamente equilibrado Dado que las operaciones basicas como insertar borrar y encontrar valores tienen un peor tiempo de ejecucion proporcional a la altura del arbol esta cota superior de la altura permite a los arboles rojo negro ser eficientes en el peor caso a diferencia de los arboles binarios de busqueda Para comprobarlo basta ver que ningun camino puede tener dos nodos rojos seguidos debido a la propiedad 4 El camino mas corto posible tiene todos sus nodos negros y el mas largo alterna entre nodos rojos y negros Dado que todos los caminos maximos tienen el mismo numero de nodos negros por la propiedad 5 no hay ningun camino que pueda tener longitud mayor que el doble de la longitud de otro camino En muchas presentaciones de estructuras arboreas de datos es posible para un nodo tener solo un hijo y las hojas contienen informacion Es posible presentar los arboles rojo negro en este paradigma pero cambian algunas de las propiedades y se complican los algoritmos Por esta razon este articulo utiliza hojas nulas Una variante que se da al arbol rojo negro es la de tratarlo como un arbol binario de busqueda cuyas aristas en lugar de nodos son coloreadas de color rojo o negro pero esto no produce ninguna diferencia El color de cada nodo en la terminologia de este articulo corresponde al color de la arista que une el nodo a su padre excepto la raiz que es siempre negra por la propiedad 2 donde la correspondiente arista no existe Al numero de nodos negros de un camino se le denomina altura negra Usos y ventajas EditarLos arboles rojo negro ofrecen un peor caso con tiempo garantizado para la insercion el borrado y la busqueda No es esto unicamente lo que los hace valiosos en aplicaciones sensibles al tiempo como las aplicaciones en tiempo real sino que ademas son apreciados para la construccion de bloques en otras estructuras de datos que garantizan un peor caso Por ejemplo muchas estructuras de datos usadas en geometria computacional pueden basarse en arboles rojo negro El arbol AVL es otro tipo de estructura con O log n tiempo de busqueda insercion y borrado Esta equilibrado de forma mas rigida que los arboles rojo negro lo que provoca que la insercion y el borrado sean mas lentos pero la busqueda y la devolucion del resultado de la misma mas veloz Los arboles rojo negro son particularmente valiosos en programacion funcional donde son una de las estructuras de datos persistentes mas comunmente utilizadas en la construccion de arrays asociativos y conjuntos que pueden retener versiones previas tras mutaciones La version persistente del arbol rojo negro requiere un espacio O log n para cada insercion o borrado ademas del tiempo Los arboles rojo negro son isometricos a los arboles 2 3 4 En otras palabras para cada arbol 2 3 4 existe un arbol correspondiente rojo negro con los datos en el mismo orden La insercion y el borrado en arboles 2 3 4 son tambien equivalentes a los cambios de colores y las rotaciones en los arboles rojo negro Esto los hace ser una herramienta util para la comprension del funcionamiento de los arboles rojo negro y por esto muchos textos introductorios sobre algoritmos presentan los arboles 2 3 4 justo antes que los arboles rojo negro aunque frecuentemente no sean utilizados en la practica Operaciones EditarLas operaciones de solo lectura en un arbol rojo negro no requieren modificacion alguna con respecto a las utilizadas en los arboles binarios de busqueda ya que cada arbol rojo negro es un caso especial de arbol binario de busqueda Sin embargo el resultado inmediato de una insercion o la eliminacion de un nodo utilizando los algoritmos de un arbol binario de busqueda normal podria violar las propiedades de un arbol rojo negro Restaurar las propiedades rojo negro requiere un pequeno numero O log n de cambios de color que son muy rapidos en la practica y no mas de 3 rotaciones 2 por insercion A pesar de que las operaciones de insercion y borrado son complicadas sus tiempos de ejecucion siguen siendo O log n Rotacion Editar Para conservar las propiedades que debe cumplir todo arbol rojo negro en ciertos casos de la insercion y la eliminacion sera necesario reestructurar el arbol si bien no debe perderse la ordenacion relativa de los nodos Para ello se llevan a cabo una o varias rotaciones que no son mas que reestructuraciones en las relaciones padre hijo tio nieto Las rotaciones que se consideran a continuacion son simples sin embargo tambien se dan las rotaciones dobles En las imagenes pueden verse de forma simplificada como se llevan a cabo las rotaciones simples hacia la izquierda y hacia la derecha en cualquier arbol binario de busqueda en particular en cualquier arbol rojo negro Podemos ver tambien la implementacion en C de dichas operaciones void rotar izda struct node p struct node aux amp raiz if p gt padre NULL amp amp p gt padre gt dcho p aux amp p gt padre gt dcho else if p gt padre NULL amp amp p gt padre gt izdo p aux amp p gt padre gt izdo aux p gt dcho aux gt padre p gt padre p gt padre aux p gt dcho aux gt izdo aux gt izdo p if p gt dcho NULL p gt dcho gt padre p void rotar dcha struct node p struct node aux amp raiz if p gt padre NULL amp amp p gt padre gt dcho p aux amp p gt padre gt dcho else if p gt padre NULL amp amp p gt padre gt izdo p aux amp p gt padre gt izdo aux p gt izdo aux gt padre p gt padre p gt padre aux p gt izdo aux gt dcho aux gt dcho p if p gt izdo NULL p gt izdo gt padre p Busqueda Editar La busqueda consiste acceder a la raiz del arbol y comparar su valor con el valor buscado Si el elemento a localizar coincide con el de la raiz la busqueda ha concluido con exito Si el elemento es menor se busca en el subarbol izquierdo si es mayor en el derecho Si se alcanza un nodo hoja y el elemento no ha sido encontrado se supone que no existe en el arbol Cabe destacar que la busqueda en este tipo de arboles es muy eficiente y representa una funcion logaritmica La busqueda de un elemento en un ABB Arbol Binario de Busqueda en general y en un arbol rojo negro en particular se puede realizar de dos formas iterativa y recursiva Ejemplo de version iterativa en el lenguaje de programacion C suponiendo que estamos buscando una clave alojada en un nodo donde esta el correspondiente dato que precisamos encontrar TipoDato buscar abb iterativo Abb a TipoValor buscado TipoDato e NULL Abb p a while estaVacio p amp amp p gt valor buscado avanzar el puntero if buscado lt p gt valor p p gt izquierda if p gt valor lt buscado p p gt derecha if estaVacio p e copiaDato p gt dato return e Vease ahora la version recursiva en ese mismo lenguaje TipoDato buscar abb recursivo Nodo a TipoValor buscado if a NULL return NULL if a gt valor buscado return copiaDato a gt valor if a gt valor lt elem return buscar abb recursivo a gt derecha buscado if a gt valor gt elem return buscar abb recursivo a gt izquierda buscado Insercion Editar La insercion comienza anadiendo el nodo como lo hariamos en un arbol binario de busqueda convencional y pintandolo de rojo Lo que sucede despues depende del color de otros nodos cercanos El termino tio nodo sera usado para referenciar al hermano del padre de un nodo como en los arboles familiares humanos Conviene notar que La propiedad 3 Todas las hojas nulas son negras siempre se cumple La propiedad 4 Ambos hijos de cada nodo rojo son negros esta amenazada solo por anadir un nodo rojo por repintar un nodo negro de color rojo o por una rotacion La propiedad 5 Todos los caminos desde un nodo dado hasta sus nodos hojas contiene el mismo numero de nodos negros esta amenazada solo por repintar un nodo negro de color rojo o por una rotacion Al contrario de lo que sucede en otros arboles como puede ser el Arbol AVL en cada insercion se realiza un maximo de una rotacion ya sea simple o doble Por otra parte se asegura un tiempo de recoloracion maximo de O log 2 n displaystyle O log 2 n por cada insercion Nota En los esquemas que acompanan a los algoritmos la etiqueta N sera utilizada por el nodo que esta siendo insertado P para los padres del nodo N G para los abuelos del nodo N y U para los tios del nodo N Notamos que los roles y etiquetas de los nodos estan intercambiados entre algunos casos pero en cada caso toda etiqueta continua representando el mismo nodo que representaba al comienzo del caso Cualquier color mostrado en el diagrama esta o bien supuesto en el caso o implicado por dichas suposiciones Los nodos tio y abuelo pueden ser encontrados por las siguientes funciones struct node abuelo struct node n if n NULL amp amp n gt padre NULL return n gt padre gt padre else return NULL struct node tio struct node n struct node a abuelo n if n gt padre a gt izdo return a gt dcho else return a gt 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 raiz del arbol En este caso es repintado en color negro para satisfacer la propiedad 2 la raiz es negra Como esto anade un nodo negro a cada camino la propiedad 5 todos los caminos desde un nodo dado a sus hojas contiene el mismo numero de nodos negros se mantiene En C quedaria asi void insercion caso1 struct node n if n gt padre NULL n gt color NEGRO else insercion caso2 n Caso 2 El padre del nuevo nodo esto es el nodo P es negro asi que la propiedad 4 ambos hijos de cada nodo rojo son negros se mantiene En este caso el arbol es aun valido La propiedad 5 todos los caminos desde cualquier nodo dado a sus hojas contiene igual numero de nodos negros se mantiene porque el nuevo nodo N tiene dos hojas negras como hijos pero como N es rojo los caminos a traves de cada uno de sus hijos tienen el mismo numero de nodos negros que el camino hasta la hoja que reemplazo que era negra y asi esta propiedad se mantiene satisfecha Su implementacion void insercion caso2 struct node n if n gt padre gt color NEGRO return Arbol valido 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 y si fuese la raiz seria negro Consecuentemente N tiene tambien un nodo tio U a pesar de que podria ser una hoja en los casos 4 y 5 Caso 3 Si el padre P y el tio U son rojos entonces ambos nodos pueden ser repintados de negro y el abuelo G se convierte en rojo para mantener la propiedad 5 todos los caminos desde cualquier nodo dado hasta sus hojas contiene el mismo numero de nodos negros Ahora el nuevo nodo rojo N tiene un padre negro Como cualquier camino a traves del padre o el tio debe pasar a traves del abuelo el numero de nodos negros en esos caminos no ha cambiado Sin embargo el abuelo G podria ahora violar la propiedad 2 la raiz es negra o la 4 ambos hijos de cada nodo rojo son negros en el caso de la 4 porque G podria tener un padre rojo Para solucionar este problema el procedimiento completo se realizara de forma recursiva hacia arriba hasta alcanzar el caso 1 El codigo en C quedaria de la siguiente forma void insercion caso3 struct node n struct node t tio n a if t NULL amp amp t gt color ROJO n gt padre gt color NEGRO t gt color NEGRO a abuelo n a gt 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 hijo derecho izquierda y derecha deberian ser invertidas a partir de los casos 4 y 5 El codigo del ejemplo toma esto en consideracion Caso 4 El nodo padre P es rojo pero el tio U es negro tambien el nuevo nodo N es el hijo derecho de P y P es el hijo izquierdo de su padre G En este caso una rotacion 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 insercion re etiquetando N y P debido a que la propiedad 4 ambos hijos de cada nodo rojo son negros se mantiene aun incumplida La rotacion causa que algunos caminos en el sub arbol etiquetado como 1 pasen a traves del nuevo nodo donde no lo hacian antes pero ambos nodos son rojos asi que la propiedad 5 todos los caminos desde cualquier nodo dado a sus hojas contiene el mismo numero de nodos negros no es violado por la rotacion despues de completado este caso se puede notar que aun se incumple la propiedad numero 4 ambos hijos de cada nodo rojo son de color negro esto se resuelve pasando al caso 5 Aqui tenemos una posible implementacion void insercion caso4 struct node n struct node a abuelo n if n n gt padre gt dcho amp amp n gt padre a gt izdo rotar izda n gt padre n n gt izdo else if n n gt padre gt izdo amp amp n gt padre a gt dcho rotar dcha n gt padre n n gt dcho insercion caso5 n Caso 5 El padre P es rojo pero el tio 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 rotacion a la derecha sobre el padre P el resultado es un arbol donde el padre P es ahora el padre del nuevo nodo N y del inicial abuelo G Este nodo G ha de ser negro asi como su hijo P rojo Se intercambian los colores de ambos y el resultado satisface la propiedad 4 ambos hijos de un nodo rojo son negros La propiedad 5 todos los caminos desde un nodo dado hasta sus hojas contienen el mismo numero de nodos negros tambien se mantiene satisfecha ya que todos los caminos que iban a traves de esos tres nodos entraban por G antes y ahora entran por P En cada caso este es el unico nodo negro de los tres Una posible implementacion en C es la siguiente void insercion caso5 struct node n struct node a abuelo n n gt padre gt color NEGRO a gt color ROJO if n n gt padre gt izdo amp amp n gt padre a gt izdo rotar dcha a else En este caso n n gt padre gt dcho amp amp n gt padre a gt dcho rotar izda a Notese que la insercion se realiza sobre el propio arbol y que los codigos del ejemplo utilizan recursion de cola Eliminacion Editar En un arbol binario de busqueda normal cuando se borra un nodo con dos nodos internos como hijos tomamos el maximo elemento del subarbol izquierdo o el minimo del subarbol derecho y movemos su valor al nodo que es borrado como se muestra aqui Borramos entonces el nodo del que copiabamos 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 es el nodo que queriamos 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 por hijos tomaremos uno de ellos como su hijo Si borramos un nodo rojo podemos simplemente reemplazarlo con su hijo que debe ser negro Todos los caminos hasta el nodo borrado simplemente pasaran a traves de un nodo rojo menos y ambos nodos el padre del borrado y el hijo han de ser negros asi 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 es cuando el nodo borrado es negro y su hijo es rojo Simplemente eliminar un nodo negro podria romper las propiedades 4 los dos hijos de cada nodo rojo son negros y 5 todos los caminos desde un nodo dado hasta sus hojas contienen el mismo numero 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 posicion 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 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 arbol con n nodos asegura un maximo de tres rotaciones y hasta O log 2 n displaystyle O log 2 n recoloraciones Encontraremos el hermano usando esta funcion struct node hermano struct node n if n n gt padre gt izdo return n gt padre gt dcho else return n gt padre gt izdo Nota Con el fin de preservar la buena definicion del arbol necesitamos que toda hoja nula siga siendo una hoja nula tras todas las transformaciones que toda hoja nula no tendra ningun hijo Si el nodo que estamos borrando tiene un hijo no hoja N es facil ver que la propiedad se satisface Si por otra parte N fuese una hoja nula se verifica por los diagramas o el codigo que para todos los casos la propiedad se satisface tambien Podemos realizar los pasos resaltados arriba con el siguiente codigo donde la funcion reemplazar nodo sustituye hijo en el lugar de n en el arbol Por facilitar la comprension del ejemplo en el codigo de esta seccion supondremos que las hojas nulas estan representadas por nodos reales en lugar de NULL el codigo de la seccion insercion trabaja con ambas representaciones void elimina un hijo struct node n Precondicion n tiene al menos un hijo no nulo struct node hijo es hoja n gt dcho n gt izdo n gt dcho reemplazar nodo n hijo if n gt color NEGRO if hijo gt color ROJO hijo gt color NEGRO else eliminar caso1 hijo free n Nota 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 codigo anterior y borrandolo despues Podemos hacer esto porque el padre es negro asi 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 seguira 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 5 todos los caminos desde un nodo dado hasta su nodos hojas deben contener el mismo numero de nodos negros el arbol debe ser reequilibrado Hay varios casos a considerar Caso 1 N es la nueva raiz En este caso hemos acabado Borramos un nodo negro de cada camino y la nueva raiz es negra asi las propiedades se cumplen Una posible implementacion en el lenguaje de programacion C seria la siguiente void eliminar caso1 struct node n if n gt 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 este fuese el hijo derecho la izquierda y la derecha deberian ser invertidas en todos estos casos De nuevo el codigo 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 Notese que P tiene que ser negro al tener un hijo rojo Aunque todos los caminos tienen todavia el mismo numero de nodos negros ahora N tiene un hermano negro y un padre rojo asi que podemos proceder a al paso 4 5 o 6 este nuevo hermano es negro porque este era uno de los hijos de S que es rojo En casos posteriores reetiquetaremos el nuevo hermano de N como S Aqui podemos ver una implementacion void eliminar caso2 struct node n struct node hm hermano n if hm gt color ROJO n gt padre gt color ROJO hm gt color NEGRO if n n gt padre gt izdo rotar izda n gt padre else rotar dcha n gt 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 traves 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 arbol Sin embargo todos los caminos a traves de P tienen ahora un nodo negro menos que los caminos que no pasan por P asi que la propiedad 5 aun no se cumple todos los caminos desde cualquier nodo a su nodo hijo contienen el mismo numero de nodos negros Para corregir esto hacemos el proceso de reequilibrio en P empezando en el caso 1 Su implementacion en C void eliminar caso3 struct node n struct node hm hermano menor n if n gt padre gt color NEGRO amp amp hm gt color NEGRO amp amp hm gt izdo gt color NEGRO amp amp hm gt dcho gt color NEGRO hm gt color ROJO eliminar caso1 n gt padre else eliminar caso4 n Caso 4 S y los hijos de este son negros pero P es rojo En este caso simplemente intercambiamos los colores de S y P Esto no afecta al numero de nodos negros en los caminos que no van a traves de S pero anade uno al numero de nodos negros a los caminos que van a traves de N compensando asi el borrado del nodo negro en dichos caminos Si lo implementamos en C quedaria void eliminar caso4 struct node n struct node hm hermano menor n if n gt padre gt color ROJO amp amp hm gt color NEGRO amp amp hm gt izdo gt color NEGRO amp amp hm gt dcho gt color NEGRO hm gt color ROJO n gt padre gt 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 asi 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 aun el mismo numero de nodos negros pero ahora N tiene un hermano negro cuyo hijo derecho es rojo asi que caemos en el caso 6 Ni N ni su padre son afectados por esta transformacion de nuevo por el caso 6 reetiquetamos el nuevo hermano de N como S He aqui la implementacion en C void eliminar caso5 struct node n struct node hm hermano n if n n gt padre gt izdo amp amp hm gt color NEGRO amp amp hm gt izdo gt color ROJO amp amp hm gt dcho gt color NEGRO hm gt color ROJO hm gt izdo gt color NEGRO rotar dcha hm else if n n gt padre gt dcho amp amp hm gt color NEGRO amp amp hm gt dcho gt color ROJO amp amp hm gt izdo gt color NEGRO hm gt color ROJO hm gt dcho gt color NEGRO rotar izda hm eliminar caso6 n 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 asi que S se convierte en el padre de P y este en el hijo derecho de S Entonces intercambiamos los colores de P y S y ponemos el hijo derecho de S en negro El subarbol aun tiene el mismo color que su raiz asi que las propiedades 4 los hijos de todo nodo rojo son negros y 5 todos los caminos desde cualquier nodo a sus nodos hoja contienen el mismo numero de nodos negros se verifican Sin embargo N tiene ahora un antecesor negro mas o bien P se ha convertido en negro o bien era negro y S se ha anadido como un abuelo negro De este modo los caminos que pasan por N pasan por un nodo negro mas Mientras tanto si un camino no pasa por N entonces hay dos posibilidades Este pasa a traves del nuevo hermano de N Entonces este debe pasar por S y P al igual que antes y tienen solo que intercambiar los colores Asi los caminos contienen el mismo numero de nodos negros Este pasa por el nuevo tio de N el hijo derecho de S Este anteriormente pasaba por S su padre y su hijo derecho pero ahora solo pasa por S el cual 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 por el mismo numero de nodos negros De cualquier forma el numero de nodos negros en dichos caminos no cambia De este modo hemos restablecido las propiedades 4 los hijos de todo nodo rojo son negros y 5 todos los caminos desde cualquier nodo a sus nodos hoja contienen el mismo numero de nodos negros El nodo blanco en diagrama puede ser rojo o negro pero debe tener el mismo color tanto antes como despues de la transformacion Adjuntamos el ultimo algoritmo void eliminar caso6 struct node n struct node hm hermano n hm gt color n gt padre gt color n gt padre gt color NEGRO if n n gt padre gt izdo Aqui hm gt dcho gt color ROJO hm gt dcho gt color NEGRO rotar izda n gt padre else Aqui hm gt izdo gt color ROJO hm gt izdo gt color NEGRO rotar dcha n gt padre De nuevo todas las llamadas de la funcion usan recursion de cola asi que el algoritmo realiza sus operaciones sobre el propio arbol Ademas las llamadas no recursivas se haran despues de una rotacion luego se haran un numero de rotaciones mas de 3 que sera constante Demostracion de cotas EditarUn arbol rojo negro que contiene n nodos internos tiene una altura de O log n Hagamos los siguientes apuntes sobre notacion H v altura del arbol cuya raiz es el nodo v bh v numero de nodos negros sin contar v si es negro desde v hasta cualquier hoja del subarbol llamado altura negra Lema Un subarbol enraizado al nodo v tiene al menos 2 b h v 1 displaystyle 2 bh v 1 nodos internos Demostracion del lema por induccion sobre la altura Caso base h v 0 Si v tiene altura cero entonces debe ser arbol vacio por tanto bh v 0 Luego 2 b h v 1 2 0 1 1 1 0 displaystyle 2 bh v 1 2 0 1 1 1 0 Hipotesis de Induccion si v es tal que h v k y contiene 2 b h v 1 displaystyle 2 bh v 1 nodos internos veamos que esto implica que v displaystyle v tal que h v displaystyle v k 1 contiene 2 b h v 1 displaystyle 2 bh v 1 nodos internos Si v displaystyle v tiene h v displaystyle v gt 0 entonces es un nodo interno Como este tiene dos hijos que tienen altura negra o bh v displaystyle v o bh v displaystyle v 1 dependiendo si es rojo o negro Por la hipotesis de induccion cada hijo tiene al menos 2 b h v 1 1 displaystyle 2 bh v 1 1 nodos internos asi que v displaystyle v tiene 2 b h v 1 1 2 b h v 1 1 1 2 b h v 1 displaystyle 2 bh v 1 1 2 bh v 1 1 1 2 bh v 1 nodos internos Usando este lema podemos mostrar que la altura del arbol es algoritmica Puesto que al menos la mitad de los nodos en cualquier camino desde la raiz hasta una hoja negra propiedad 4 de un arbol rojo negro la altura negra de la raiz es al menos h raiz 2 Por el lema tenemos que n 2 h raiz 2 1 log 2 n 1 h raiz 2 h raiz 2 log 2 n 1 displaystyle n geq 2 h text raiz over 2 1 leftrightarrow log 2 n 1 geq h text raiz over 2 leftrightarrow h text raiz leq 2 log 2 n 1 Por tanto la altura de la raiz es O log n Complejidad EditarEn el codigo del arbol hay un bucle donde la raiz de la propiedad rojo negro que hemos querido devolver a su lugar x puede ascender por el arbol un nivel en cada iteracion Como la altura original del arbol es O log n hay O log n iteraciones Asi que en general la insercion tiene una complejidad de O log n Vease tambien EditarLenguaje de especificacion de TADs Maude Lista enlazada Pila Cola Conjunto Arbol binario Arbol binario de busqueda Arbol AA Arbol AVL Arbol biselado Monticulo Monticulo binomial Monticulo de Fibonacci Arbol 2 3 Arbol 2 3 4 Arbol B Arbol splay GrafoBibliografia EditarHernandez Zenon Rodriguez J Carlos Gonzalez J Daniel Diaz Margarita Perez Jose Rodriguez Gustavo 2005 Fundamentos de Estructuras de Datos Soluciones en Ada Java y C Madrid Thomson Editores Spain 84 9732 358 0 Weisstein Eric W Red Black Tree En Weisstein Eric W ed MathWorld en ingles Wolfram Research San Diego State University CS 660 Red Black tree notes Archivado el 9 de mayo de 2008 en Wayback Machine 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 junio de 2004 Performance Analysis of BSTs in System Software PDF Stanford University Okasaki Chris Red Black Trees in a Functional Setting PS Archivado desde el original el 26 de septiembre de 2007 Enlaces externos EditarDemos y simuladores Editar Simulador de arboles Rojo Negro Red Black Tree visualisation una demo de los arboles rojo negro y otros muchos mas arboles de busqueda por Kubo Kovac Red Black Tree Applet una demo de los arboles rojo negro AVL rotaciones y mucho mas Red Black Tree Demonstration una demo interactiva acerca de la insercion y eliminacion con una implementacion en Java Red Black Tree Animation una demostracion de la insercion en el peor caso aiSee Visualization of a Sorting Algorithm GIF animado que muestra la insercion 200KB Red Black Tree Demonstration una demo acerca de la insercion en codigo Java por David M Howard Implementaciones Editar Descargar Programa Red black en java En la libreria de C Standard Template Library los contenedores std set lt Value gt y std map lt Key Value gt estan implementados a menudo con arboles rojo negro Implementacion eficiente de arboles rojo negro RBT Una biblioteca de arboles rojo negro en SmallEiffel Archivado el 7 de octubre de 2017 en Wayback Machine libredblack Una biblioteca de arboles rojo negro en C Codigo en C de arboles rojo negro Implementacion en Java de os arboles rojo negro en java util TreeMap Los subsistemas de DragonFlyBSD VM utilizan arboles rojo negro NATURAL ADABAS implementacion de Paul Macgowan NGenerics implementacion en C FreeBSD s single header file implementation The default scheduler of Linux 2 6 23 called Completely Fair Scheduler is implemented via a Red Black tree Datos Q506496 Multimedia Red black trees Q506496 Obtenido de https es wikipedia org w index php title Arbol rojo negro amp oldid 150860477, wikipedia, wiki, leyendo, leer, libro, biblioteca,

español

, española, descargar, gratis, descargar gratis, mp3, video, mp4, 3gp, jpg, jpeg, gif, png, imagen, música, canción, película, libro, juego, juegos