Árbol rojo-negro

March 23, 2018 | Author: jbormey1976 | Category: Computing, Technology, Areas Of Computer Science, Computer Programming, Mathematics


Comments



Description

Árbol rojo-negro1 Á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 á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. 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. Un ejemplo de árbol rojo-negro Como consecuencia de esto todos los nodos internos tienen dos hijos. pero cambian algunas de las propiedades y se complican los algoritmos. este artículo utilizan “hojas nulas”.Árbol rojo-negro Dado que las operaciones básicas como insertar. de forma contraria a lo que sucede en los árboles binarios de búsqueda. En muchas presentaciones de estructuras arbóreas de datos. 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. excepto la raíz. esta cota superior de la altura permite a los árboles rojo-negro ser eficientes en el peor caso. es posible para un nodo tener solo un hijo y las hojas contienen información. 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 uno o ambos nodos podrían ser una hoja nula. y el más largo alterna entre nodos rojos y negros. que no contienen información y simplemente sirven para indicar dónde el árbol acaba. 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. esto muestra que no hay ningún camino que pueda tener el doble de longitud que otro camino. como se mostró antes. No es esto únicamente lo que los hace valiosos en aplicaciones sensibles al tiempo como las aplicaciones en tiempo real. por la propiedad 5. pero que realmente no los contradice. el borrado y la búsqueda. Por esta razón. Otra explicación que se da del árbol rojo-negro es la de tratarlo como un árbol binario de búsqueda cuyas aristas. borrar y encontrar valores tienen un peor tiempo de búsqueda proporcional a la altura del árbol. . Es posible presentar los árboles rojo-negro en este paradigma. para cada árbol 2-3-4. sino que además son apreciados para la construcción de bloques en otras estructuras de datos que garantizan un peor caso. basta ver que ningún camino puede tener 2 nodos rojos seguidos debido a la propiedad 4. El camino más corto posible tiene todos sus nodos negros. existe un árbol correspondiente rojo-negro con los datos en el mismo orden. Está equilibrado de forma más rígida que los árboles rojo-negro. Los árboles rojo-negro son particularmente valiosos en programación funcional. inserción y borrado. aunque frecuentemente no sean utilizados en la práctica. muchas estructuras de datos usadas en geometría computacional pueden basarse en árboles rojo-negro. En otras palabras. La versión persistente del árbol rojo-negro requiere un espacio O(log n) para cada inserción o borrado. lo cual da como resultado un árbol que parece contradecir los principios expuestos antes. en lugar de nodos. 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. El árbol AVL es otro tipo de estructura con O(log n) tiempo de búsqueda. Para ver que estas propiedades garantizan lo dicho. pero esto no produce ninguna diferencia. 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. 2 Usos y ventajas Los árboles rojo-negro ofrecen un peor caso con tiempo garantizado para la inserción. Por ejemplo. son coloreadas de color rojo o negro. además del tiempo. Como todos los caminos máximos tienen el mismo número de nodos negros. Habitualmente estos nodos son omitidos en las representaciones. Los árboles rojo-negro son isométricos a los árboles 2-3-4. que es siempre negra (por la propiedad 2) donde la correspondiente arista no existe. else { // Aqui aux->padre->dcho == aux aux->padre->dcho = p. sus tiempos de ejecución siguen siendo O(log n). void rotar_izda(struct node *p) { struct node *aux. que no son más que reestructuraciones en las relaciones padre-hijo-tío-nieto. en ciertos casos de la inserción y la eliminación será necesario reestructurar el árbol. Para ello.Árbol rojo-negro 3 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. 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). . se llevan a cabo una o varias rotaciones. sin embargo. también se dan las rotaciones dobles. aux-> dcho = p->izdo. p->izdo = aux. si bien no debe perderse la ordenación relativa de los nodos. 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. 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. Rotación Para conservar las propiedades que debe cumplir todo árbol rojo-negro. Las rotaciones que se consideran a continuación son simples. Sin embargo. en particular en cualquier árbol rojo-negro. aux = p. Podemos ver también la implementación en C de dichas operaciones. p = p->dcho. //reenraizar subarbol if(aux->padre->izdo == aux) aux->padre->izdo = p. A pesar de que las operaciones de inserción y borrado son complicadas. // No hacer si usamos una sola hoja nula y aux->dcho es nulo } 4 void rotar_dcha(struct node *p) { struct node *aux. aux->izdo = p->dcho. aux->dcho->padre = aux. aux->izdo->padre = aux. aux->padre = p. p = p->izdo. aux = p.Árbol rojo-negro } // actualizar los padres de los nodos modificados p->padre = aux->padre. aux->padre = p. } // actualizar los padres de los nodos modificados p->padre = aux->padre. p->dcho = aux. // No hacer si usamos una sola hoja nula y aux->izdo es nulo } . else { // aqui aux->padre->dcho == aux aux->padre->dcho = p. // reenraizar subarbol if(aux->padre->izdo == aux) aux->padre->izdo = p. else if (a->clave > elem) return buscar(a->hIzquierdo. suponiendo que estamos buscando una clave alojada en un nodo donde está el correspondiente "dato" que precisamos encontrar: data Buscar_ABB(abb t. } Véase ahora la versión recursiva en ese mismo lenguaje: int buscar(tArbol *a. elem). elem). Cabe destacar que la búsqueda en este tipo de árboles es muy eficiente. si el elemento es menor se busca en el subárbol izquierdo y si es mayor en el derecho. p=t. } } if (!estaVacio(p) &&(p->d!=NULL) ) { e=copiaDato(p->d). Si se alcanza un nodo hoja y el elemento no ha sido encontrado se supone que no existe en el árbol. if (!estaVacio(p)) { while (!estaVacio(p) && (p->k!=k) ) { if (k < p->k) { p=p->l. int elem) { if (a == NULL) return 0. se puede realizar de dos formas. Ejemplo de versión iterativa en el lenguaje de programación C. e=NULL. iterativa o recursiva. y en un árbol rojo negro en particular. else . } if (p->k < k) { p=p->r.Árbol rojo-negro 5 Búsqueda La búsqueda consiste acceder a la raíz del árbol.clave k) { abb p. else if (a->clave < elem) return buscar(a->hDerecho. La búsqueda de un elemento en un ABB (Árbol Binario de Búsqueda) en general. } } return e. si el elemento a localizar coincide con éste la búsqueda ha concluido con éxito. dato e. representa una función logarítmica. • 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. • La propiedad 4 (Ambos hijos de cada nodo rojo son negros) está amenazada solo por añadir un nodo rojo. Al contrario de lo que sucede en otros árboles como puede ser el Árbol AVL. Conviene notar que: • La propiedad 3 (Todas las hojas. en cada inserción se realiza un máximo de una rotación. incluyendo las nulas. } 6 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. pero en cada caso. la etiqueta N será utilizada por el nodo que está siendo insertado. 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. 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í: . es repintado a color negro para satisfacer la propiedad 2 (la raíz es negra). Caso 1: El nuevo nodo N es la raíz de del árbol. } struct node * tio(struct node *n) { struct node *a = abuelo(n). son negras) siempre se cumple. P para los padres del nodo N. toda etiqueta continúa representando el mismo nodo que representaba al comienzo del caso. G para los abuelos del nodo N. } Estudiemos ahora cada caso de entre los posibles que nos podemos encontrar al insertar un nuevo nodo. como en los árboles familiares humanos.Árbol rojo-negro return 1. por repintar un nodo negro de color rojo o por una rotación. Como esto añade un nodo negro a cada camino. Por otra parte. Notamos que los roles y etiquetas de los nodos están intercambiados entre algunos casos. se asegura un tiempo de recoloración máximo de por cada inserción. else return a->izdo. ya sea simple o doble. else return NULL. Nota: En los esquemas que acompañan a los algoritmos. En este caso. El término tío nodo será usado para referenciar al hermano del padre de un nodo. Cualquier color mostrado en el diagrama está o bien supuesto en el caso o implicado por dichas suposiciones. y U para los tíos del nodo N. if (n->padre == a->izdo) return a->dcho. El código en C quedaría de la siguiente forma: void insercion_caso3(struct node *n) { struct node *t = tio(n). else insercion_caso2(n). if ((t != NULL) && (t->color == ROJO)) { n->padre->color = NEGRO. el nuevo nodo rojo N tiene un padre negro. Ahora. y así esta propiedad se mantiene satisfecha. 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ó. /* Árbol válido. Como cualquier camino a través del padre o el tío debe pasar a través del abuelo. el nodo P) es negro. En este caso. y si fuese la raíz. Sin embargo.Árbol rojo-negro void insercion_caso1(struct node *n) { if (n->padre == NULL) n->color = NEGRO. así que la propiedad 4 (ambos hijos de cada nodo rojo son negros) se mantiene. La propiedad 5 (todos los caminos desde cualquier nodo dado a sus hojas contiene igual número de nodos negros) se mantiene. } Caso 2: El padre del nuevo nodo (esto es. en el caso de la 4 porque G podría tener un padre rojo. el procedimiento completo se realizará de forma recursiva hacia arriba hasta alcanzar el caso 1. } Nota: En los siguientes casos se puede asumir que N tiene un abuelo. . Consecuentemente. el número de nodos negros en esos caminos no ha cambiado. pero como N es rojo. 7 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). 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). el árbol es aun válido. porque el nuevo nodo N tiene dos hojas negras como hijos. Para solucionar este problema. Su implementación: void insercion_caso2(struct node *n) { if (n->padre->color == NEGRO) return. N tiene también un nodo tío U a pesar de que podría ser una hoja en los casos 4 y 5. que era negra. *a. el nodo G. porque su padre P es rojo. */ else insercion_caso3(n). sería negro. En este caso. if ((n == n->padre->dcho) && (n->padre == a->izdo)) { rotar_izda(n->padre). el primer nodo padre P se ve 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) se mantiene aún incumplida. una rotación a la izquierda que cambia los roles del nuevo nodo N y su padre P puede ser realizada. El código del ejemplo toma esto en consideración. insercion_caso1(a). izquierda y derecha deberían ser invertidas a partir de los casos 4 y 5.Árbol rojo-negro t->color = NEGRO. 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. a->color = ROJO. n = n->dcho. el nuevo nodo N es el hijo derecho de P. entonces. también. 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 violada por la rotación. se asume que el nodo padre P es el hijo izquierdo de su padre. } insercion_caso5(n). } } Nota: En los casos restantes. 8 Caso 4: El nodo padre P es rojo pero el tío U es negro. y P es el hijo izquierdo de su padre G. n = n->izdo. Aquí tenemos una posible implementación: void insercion_caso4(struct node *n) { struct node *a = abuelo(n). } . } else { insercion_caso4(n). Si es el hijo derecho. a = abuelo(n). } else if ((n == n->padre->izdo) && (n->padre == a->dcho)) { rotar_dcha(n->padre). pero ambos nodos son rojos. Una posible implementación en C es la siguiente: void insercion_caso5(struct node *n) { struct node *a = abuelo(n). Otro caso simple es cuando el nodo borrado es negro y su hijo es rojo. así que las propiedades 3 (todas las hojas. y ambos nodos. este es el único nodo negro de los tres. y movemos su valor al nodo que es borrado (como se muestra aquí). Resumiendo. y ahora entran por P. el nuevo nodo N es el hijo izquierdo de P. son negras) y 4 (los dos hijos de cada nodo rojo son negros) se mantienen. Si borramos un nodo rojo. se realiza una rotación a la derecha sobre el padre P. ya que todos los caminos que iban a través de esos tres nodos entraban por G antes. } } Nótese que la inserción se realiza sobre el propio árbol y que los códigos del ejemplo utilizan recursión de cola. } else { /* * En este caso. Simplemente eliminar un nodo negro podría romper las . Eliminación En un árbol binario de búsqueda normal. a->color = ROJO. han de ser negros. tomamos el máximo elemento del subárbol izquierdo o el mínimo del subárbol derecho. En cada caso. incluyendo las nulas. podemos simplemente reemplazarlo con su hijo.Árbol rojo-negro 9 Caso 5: El padre P es rojo pero el tío U es negro. podemos asumir que borramos un nodo con como mucho un hijo no hoja (si solo tiene nodos hojas por hijos. (n == n->padre->dcho) && (n->padre == a->dcho). el padre del borrado y el hijo. if ((n == n->padre->izdo) && (n->padre == a->izdo)) { rotar_dcha(a). No importa si este nodo es el nodo que queríamos originalmente borrar o el nodo del que copiamos el valor. 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. En este caso. Este nodo G ha de ser negro. y P es el hijo izquierdo de su padre G. tomaremos uno de ellos como su hijo). Borramos entonces el nodo del que copiábamos el valor que debe tener menos de dos nodos no hojas por hijos. el resultado es un árbol donde el padre P es ahora el padre del nuevo nodo N y del inicial abuelo G. 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. Todos los caminos hasta el nodo borrado simplemente pasarán a través de un nodo rojo menos. Se intercambian los colores de ambos y el resultado satisface la propiedad 4 (ambos hijos de un nodo rojo son negros). que debe ser negro. */ rotar_izda(a). n->padre->color = NEGRO. así como su hijo P rojo. cuando se borra un nodo con dos nodos internos como hijos. El blanco representa un color desconocido (o bien rojo o bien negro). SL para el hijo izquierdo de S. toda etiqueta sigue representando al mismo nodo que representaba al comienzo del caso. hijo). ambas propiedades quedan preservadas. por otra parte N fuese una hoja nula. 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). if (n->color == NEGRO) { if (hijo->color == ROJO) hijo->color = NEGRO. pero si repintamos su hijo de negro. Si el nodo que estamos borrando tiene un hijo no hoja N. else eliminar_caso1(hijo). else return n->padre->izdo. Empezamos por reemplazar el nodo que va a ser borrado con su hijo. Por facilitar la comprensión del ejemplo. donde la función reemplazar_nodo sustituye hijo en el lugar de n en el árbol. Nota: Entre algunos casos cambiamos roles y etiquetas de los nodos. Podemos realizar los pasos resaltados arriba con el siguiente código.Árbol rojo-negro 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). y SR para el nuevo hijo derecho de S (se puede mostrar que S no puede ser una hoja). El caso complejo es cuando el nodo que va a ser borrado y su hijo son negros. usaremos P para el nuevo padre de N. Encontraremos el hermano usando esta función: struct node * hermano(struct node *n) { if (n == n->padre->izdo) return n->padre->dcho. necesitamos que toda hoja nula siga siendo una hoja nula tras todas las transformaciones (que toda hoja nula no tendrá ningún hijo). Si. asegura un máximo de tres rotaciones y hasta recoloraciones. Cualquier color mostrado en el diagrama es o bien supuesto en su caso o bien implicado por dichas suposiciones. void elimina_un_hijo(struct node *n) { /* * Precondición: n tiene al menos un hijo no nulo. es fácil ver que la propiedad se satisface. reemplazar_nodo(n. y su hermano (el otro hijo de su nuevo padre) S. Llamaremos a este hijo (en su nueva posición) N. } 10 . En los diagramas de debajo. se verifica por los diagramas o el código que para todos los casos la propiedad se satisface también. El cumplimiento de estas reglas en un árbol con n nodos. } Nota: Con el fin de preservar la buena definición del árbol. */ struct node *hijo = es_hoja(n->dcho) ? n->izdo : n->dcho. pero en cada caso. 11 Caso 2: S es rojo. hm->color = NEGRO. pasando S a ser el abuelo de N. así las propiedades se cumplen. Aunque todos los caminos tienen todavía el mismo número de nodos negros. de tal forma que n seguirá siendo una hoja tras todas las operaciones. } Nota: Si N es una hoja nula y no queremos representar hojas nulas como nodos reales. En este caso invertimos los colores de P y S. En este caso. 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). Y podemos borrarla con seguridad. ahora N tiene un hermano negro y un padre rojo. así que podemos proceder a al paso 4. podemos modificar el algoritmo llamando primero a eliminar_caso1() en su padre (el nodo que borramos. if (n == n->padre->izdo) rotar_izda(n->padre). Si N y su padre original son negros. por lo que rotamos a la izquierda P. De nuevo. Si éste fuese el hijo derecho. hemos acabado. Borramos un nodo negro de cada camino y la nueva raíz es negra. que es rojo). Caso 1: N es la nueva raíz. el árbol debe ser reequilibrado. así que se comporta de la misma forma que una hoja nula (y a veces es llamada hoja “fantasma”). if (hm->color == ROJO) { n->padre->color = ROJO. Nótese que P tiene que ser negro al tener un hijo rojo. asumimos que N es el hijo izquierdo de su padre P.Árbol rojo-negro free(n). la izquierda y la derecha deberían ser invertidas en todos estos casos. else . n en el código anterior) y borrándolo después. En casos posteriores. el código del ejemplo toma ambos casos en cuenta. } Nota: En los casos 2. 5 y 6. Aquí podemos ver una implementación: void eliminar_caso2(struct node *n) { struct node *hm = hermano(n). entonces borrar este padre original causa caminos que pasan por N y tienen un nodo negro menos que los caminos que no. como se muestra arriba. Podemos hacer esto porque el padre es negro. 5 o 6 (este nuevo hermano es negro porque éste era uno de los hijos de S. 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). Hay varios casos a considerar. reetiquetaremos el nuevo hermano de N como S. simplemente cambiamos S a rojo. if ((n->padre->color == NEGRO) && (hm->color == NEGRO) && (hm->izdo->color == NEGRO) && (hm->dcho->color == NEGRO)) { hm->color = ROJO. } else eliminar_caso4(n). precisamente aquellos que no pasan por N. Esto no afecta al número de nodos negros en los caminos que no van a través de S. hacemos el proceso de reequilibrio en P.Árbol rojo-negro rotar_dcha(n->padre). Para corregir esto. 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). compensando así el borrado del nodo negro en dichos caminos. eliminar_caso1(n->padre). Si lo implementamos en C. quedaría: void eliminar_caso4(struct node *n) { struct node *hm = hermano_menor(n). tienen un nodo negro menos. } eliminar_caso3(n). En este caso. El resultado es que todos los caminos a través de S. pero P es rojo. pero añade uno al número de nodos negros a los caminos que van a través de N. } 12 Caso 3: P. } Caso 4: S y los hijos de éste son negros. simplemente intercambiamos los colores de S y P. En este caso. 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. todos los caminos a través de P tienen ahora un nodo negro menos que los caminos que no pasan por P. Sin embargo. . empezando en el caso 1. Su implementación en C: void eliminar_caso3(struct node *n) { struct node *hm = hermano_menor(n). S y los hijos de S son negros. Ni N ni su padre son afectados por esta transformación (de nuevo. reetiquetamos el nuevo hermano de N como S). Entonces intercambiamos los colores de S y su nuevo padre. su hijo izquierdo es rojo. } 13 Caso 5: S es negro. así que caemos en el caso 6.Árbol rojo-negro if ((n->padre->color == ROJO) && (hm->color == NEGRO) && (hm->izdo->color == NEGRO) && (hm->dcho->color == NEGRO)) { hm->color = ROJO. He aquí la implementación en C: void eliminar_caso5(struct node *n) { struct node *hm = hermano(n). hm->izdo->color = NEGRO. } eliminar_caso6(n). así su hijo izquierdo se convierte en su padre y en el hermano de N. Todos los caminos tienen aún el mismo número de nodos negros. por el caso 6. rotar_dcha(hm). pero ahora N tiene un hermano negro cuyo hijo derecho es rojo. if ((n == n->padre->izdo) && (hm->color == NEGRO) && (hm->izdo->color == ROJO) && (hm->dcho->color == NEGRO)) { hm->color = ROJO. } else eliminar_caso5(n). } . hm->dcho->color = NEGRO. n->padre->color = NEGRO. En este caso rotamos a la derecha S. y N es el hijo izquierdo de su padre. el derecho es negro. rotar_izda(hm). } else if ((n == n->padre->dcho) && (hm->color == NEGRO) && (hm->dcho->color == ROJO) && (hm->izdo->color == NEGRO)) { hm->color = ROJO. entonces hay dos posibilidades: • • Éste pasa a través del nuevo hermano de N. De este modo. hm->izdo->color == ROJO. De este modo. los caminos que pasan por N pasan por un nodo negro mas. así que S se convierte en el padre de P y éste en el hijo derecho de S. Sin embargo. hm->dcho->color == ROJO. su hijo derecho es rojo. el cual ha tomado el color de su anterior padre. al igual que antes. } else { /* * Aquí. su padre. Entonces intercambiamos los colores de P y S. n->padre->color = NEGRO. luego se harán un número de rotaciones (más de 3) que será constante. todas las llamadas de la función usan recursión de cola así que el algoritmo realiza sus operaciones sobre el propio árbol. 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. De cualquier forma. y tienen sólo que intercambiar los colores. y por su hijo derecho. */ hm->izdo->color = NEGRO. pero ahora sólo pasa por S. 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 subárbol aún tiene el mismo color que su raíz. éste debe pasar por S y P. Adjuntamos el último algoritmo: void eliminar_caso6(struct node *n) { struct node *hm = hermano(n). rotar_izda(n->padre). if (n == n->padre->izdo) { /* * Aquí. o bien era negro y S se ha añadido como un abuelo negro. Así los caminos contienen el mismo número de nodos negros. */ hm->dcho->color = NEGRO. } } De nuevo. y ponemos el hijo derecho de S en negro. el hijo derecho de S. Mientras tanto. . pero debe tener el mismo color tanto antes como después de la transformación. las llamadas no recursivas se harán después de una rotación. El efecto final es que este camino va por el mismo número de nodos negros. Éste anteriormente pasaba por S. el cual ha cambiado de rojo a negro. y N es el hijo izquierdo de P.Árbol rojo-negro 14 Caso 6: S es negro. Éste pasa por el nuevo tío de N. En este caso rotamos a la izquierda P. El nodo blanco en diagrama puede ser rojo o negro. N tiene ahora un antecesor negro mas: o bien P se ha convertido en negro. Además. rotar_dcha(n->padre). el número de nodos negros en dichos caminos no cambia. hm->color = n->padre->color. su padre y su hijo derecho. Entonces. si un camino no pasa por N. Rodríguez. Luego: nodos internos.273–301. la altura-negra de la raíz es al menos h(raíz)/2. J. • bh(v) = número de nodos negros (sin contar v si es negro) desde v hasta cualquier hoja del subárbol (llamado altura-negra). Rodríguez. Referencias • Hernández. • Thomas H. Usando este lema podemos mostrar que la altura del árbol es algorítmica. «Red-Black Trees in a Functional Setting [4]» (PS). Por el lema tenemos que: Por tanto. Charles E. Por la hipótesis de inducción cada hijo tiene al menos nodos internos. González. Leiserson.. Chris. • Pfaff.Daniel. Díaz. Ben (June de 2004). Gustavo. 84-9732-358-0. Madrid: Thomson Editores Spain. Second Edition. «Performance Analysis of BSTs in System Software [3]» (PDF).Árbol rojo-negro 15 Demostración de cotas Un árbol rojo-negro que contiene n nodos internos tiene una altura de O(log(n)). Fundamentos de Estructuras de Datos. puede ascender por el árbol un nivel en cada iteración Como la altura original del árbol es O(log n). Lema: Un subárbol enraizado al nodo v tiene al menos 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. MIT Press and McGraw-Hill. hay O(log n) iteraciones. J. Ronald L. by Roger Whitney. • Mathworld: Red-Black Tree [1]. Como éste tiene dos hijos que tienen altura-negra. (2005). 2001. • San Diego State University: CS 660: Red-Black tree notes [2].. Margarita. Rivest. José. pp. • Okasaki. Cormen.. o bh( bh( )-1 (dependiendo si es rojo o negro). Stanford_university. Soluciones en Ada. Zenón. Hagamos los siguientes apuntes sobre notación: • H(v) = altura del árbol cuya raíz es el nodo v. Hipótesis de Inducción: si v es tal que h(v) = k y contiene nodos internos. veamos que esto implica que )o tal que h( ) = k+1 contiene nodos internos. Si tiene h( ) > 0 entonces es un nodo interno. Introduction to Algorithms. así que tiene : nodos internos. ISBN 0-262-03293-7 .Carlos. Java y C++. x. 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 de la raíz es O(log(n)). and Clifford Stein. 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. Chapter 13: Red-Black Trees. . por tanto bh(v)=0. Pérez. Así que en general la inserción tiene una complejidad de O(log n). Árbol rojo-negro 16 Enlaces externos Demos y simuladores • Simulador de árboles Rojo-Negro [5]. ps [5] https:/ / www. • AVL Tree Applet [7]. javaresearch. pdf [4] http:/ / www. de/ lehre/ ss98/ audii/ applets/ BST/ RedBlackTree-Example. eecs. com/ foros/ ver. en código Java. una demo de los árboles rojo-negro y otros muchos más árboles de búsqueda. [14] • libredblack: Una biblioteca de árboles rojo-negro en C. wolfram. htm [15] http:/ / libredblack. edu/ courses/ fall95/ cs660/ notes/ RedBlackTree/ RedBlack. php?foroid=674005& temaid=3112672 [13] http:/ / eternallyconfuzzled. ulpgc. edu/ ~emin/ www/ source_code/ red_black_tree/ index. por David M. es/ docencia/ ed_ii/ simulaciones/ arboles_binarios/ applet. • En la librería de C++. ibr. net/ archive/ durian/ red_black_tree. edu/ ~blp/ papers/ libavl. atspace. • Red Black Tree Applet [6]. html [9] http:/ / www. los containers std::set<Value> y std::map<Key. com/ tuts/ datastructures/ jsw_tut_rbtree.Value> están implementados a menudo con árboles rojo-negro. edu/ webs/ people/ okasaki/ jfp99. una demostración de la inserción en el peor caso. net/ [16] http:/ / web. • Implementación eficiente de á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. rotaciones y mucho más. html [12] http:/ / miarroba. es/ users/ jriera/ Docencia/ AVL/ AVL%20tree%20applet. html [2] http:/ / www. edu/ ~franco/ C321/ html/ RedBlack/ redblack. com/ Red-BlackTree. una demo de los árboles rojo-negro. java. Standard Template Library. html [10] http:/ / www. htm [8] http:/ / www. eli. usma. sourceforge. GIF animado que muestra la inserción (200KB). aspx [14] http:/ / efsa. html [17] http:/ / www. ull.util. [15] • Código en C de árboles rojo-negro. • Red-Black Tree Animation [9]. com . gedlc. stanford. Howard. ececs. html [6] http:/ / people. splay y rojo-negro. aisee. mit. htm [11] http:/ / geocities. ksp. una demo acerca de la inserción. • Red-Black Tree Demonstration [11]. called Completely Fair Scheduler.6. com/ dmh2000/ articles/ code/ red-blacktree. [13] • RBT: Una biblioteca de árboles rojo-negro en SmallEiffel. cs. sdsu. AVL. html [18] http:/ / dragonflybsd. org [19] http:/ / scctoolkit. • aiSee: Visualization of a Sorting Algorithm [10]. is implemented via a Red-Black tree [22] Referencias [1] http:/ / mathworld. una demo interactiva acercad de la inserción y la eliminaicón en árboles AVL. html [7] http:/ / webpages. • Red Black Tree Applet [7]. [16] • Implementación en Java de os árboles rojo-negro en java. tu-bs. por Kubo Kovac. Implementaciones • Descargar Programa Red-black [12] en java. org/ source/ jdk142/ java/ util/ TreeMap. uc. com/ anim/ maple.TreeMap [17] • Los subsistemas de DragonFlyBSD VM utilizan árboles rojo-negro. sk/ ~kuko/ bak/ index. html#RTFToC2 [3] http:/ / www.23. una demo interactiva acerca de la inserción y eliminación con una implementación en Java. sourceforge. • Red/Black Tree Demonstration [8]. com/ developerworks/ linux/ library/ l-cfs/ ?ca=dgr-lnxw04CFC4Linux 17 . watson. ibm.Árbol rojo-negro [20] http:/ / www. h [22] http:/ / www. codeplex. com/ NGenerics [21] http:/ / fxr. org/ fxr/ source/ / sys/ tree. svg  Licencia: Creative Commons Attribution-ShareAlike 3.org/licenses/by-sa/3.org/w/index.png  Licencia: Public Domain  Contribuyentes: User Deco on en. Periku.wikipedia Archivo:Red-black_tree_delete_case_3.php?title=Archivo:Red-black_tree_delete_case_4.wikipedia Archivo:Red-black_tree_delete_case_2.org/w/index.wikipedia Archivo:Red-black_tree_delete_case_4.png  Fuente: http://es. Porao.Fuentes y contribuyentes del artículo 18 Fuentes y contribuyentes del artículo Árbol rojo-negro  Fuente: http://es.png  Fuente: http://es. 44 ediciones anónimas Fuentes de imagen. Regnaron.png  Licencia: Public Domain  Contribuyentes: Users Cintrom.png  Fuente: http://es.png  Licencia: Public Domain  Contribuyentes: User Deco on en.org/w/index.wikipedia.wikipedia. Deco on en.png  Fuente: http://es.JPG  Fuente: http://es.org/w/index.png  Fuente: http://es.org/w/index.wikipedia.0/ . Carutsu.php?title=Archivo:Red-black_tree_delete_case_6.JPG  Licencia: Creative Commons Attribution-Share Alike  Contribuyentes: Jose María García Alaminos Archivo:Rotación_Simple_derecha.wikipedia Licencia Creative Commons Attribution-Share Alike 3.php?oldid=58487543  Contribuyentes: Amanuense.org/w/index.org/w/index.wikipedia.wikipedia.wikipedia.0 Unported  Contribuyentes: en:User:Cburnett Archivo:Rotación_Simple_izquierda. Deco. Vcarceler.png  Fuente: http://es.php?title=Archivo:Red-black_tree_insert_case_5.png  Licencia: Public Domain  Contribuyentes: Users Deelkar. Matdrodes.php?title=Archivo:Red-black_tree_example.wikipedia.php?title=Archivo:Red-black_tree_delete_case_2. Bermudob.org/w/index.png  Fuente: http://es.org/w/index. Lasneyx.php?title=Archivo:Rotación_Simple_derecha.php?title=Archivo:Rotación_Simple_izquierda.php?title=Archivo:Red-black_tree_insert_case_3.wikipedia. Licencias y contribuyentes Archivo:Red-black tree example. U.wikipedia. Isha.png  Licencia: Public Domain  Contribuyentes: User Deco on en.. Wmtnez. Tomatejc.php?title=Archivo:Red-black_tree_delete_case_3. Ender.org/w/index.org/w/index.JPG  Licencia: Public Domain  Contribuyentes: Jose María García Alaminos Archivo:Red-black_tree_insert_case_3.0 Unported //creativecommons.png  Licencia: Public Domain  Contribuyentes: User Deco on en.wikipedia. Mariols15.JPG  Fuente: http://es.wikipedia Archivo:Red-black_tree_delete_case_5.wikipedia Archivo:Red-black_tree_delete_case_6.png  Fuente: http://es.php?title=Archivo:Red-black_tree_delete_case_5. Deelkar on en.svg  Fuente: http://es.png  Licencia: Public Domain  Contribuyentes: User Deco on en.png  Licencia: Public Domain  Contribuyentes: User Deco on en. Sabbut. Ascánder. Gramito.wikipedia.org/w/index.wikipedia. Cburnett.gonzalez. Petnapet.wikipedia Archivo:Red-black_tree_insert_case_4.php?title=Archivo:Red-black_tree_insert_case_4.wikipedia Archivo:Red-black_tree_insert_case_5.
Copyright © 2024 DOKUMEN.SITE Inc.