00001 #ifndef Rojinegro_h 00002 #define Rojinegro_h ///< Evita inclusión múltiple de \c "Arbol_Rojinegro.h" 00003 #include "Bin_Tree.h" 00004 00005 /** 00006 Clase Arbol_RojiNegro: 00007 -Un árbol rojo-negro resulta de la representación de un árbol 2-3-4 mediante 00008 un árbol binario. 00009 -Cada nodo de un árbol rojo negro contiene la siguiente información: 00010 color, valor, hijo izquierdo, hijo derecho y padre. 00011 -Si un hijo o el padre de un nodo no existe, el apuntador correspondiente 00012 contiene el valor NULL, el cual consideraremos como un nodo cuyo color es negro. 00013 -En lo sucesivo nos referiremos a los nodos distintos a las hojas (NULL) como 00014 nodos internos del árbol y a las hojas y al padre de la raíz como nodos externos. 00015 00016 Todo árbol Rojinegro satisface las siguientes propiedades: 00017 -Todo nodo es rojo o negro 00018 -La raíz es negra 00019 -Toda hoja (NULL) es negra. 00020 -Si un nodo es rojo, entonces sus dos hijos son negros. 00021 -Para cada nodo, todas las rutas de un nodo a las hojas (NULLs) contienen el mismo 00022 número de nodos negros. El número de nodos negros en esta ruta se conoce 00023 como altura-negra del nodo. 00024 */ 00025 template <class T> 00026 class Arbol_Rojinegro : public Bin_Tree <T> { 00027 public: 00028 Arbol_Rojinegro() : Bin_Tree() {}///Constructor. 00029 Arbol_Rojinegro( Bin_Tree & o) : Bin_Tree(o){} 00030 Arbol_Rojinegro( const T& val) : Bin_Tree (val) {}///Constructor a partir de un valor. 00031 ~Arbol_Rojinegro() {} ///Destructor. 00032 bool color; /// guarda el color. 00033 00034 }; 00035 00036 00037 /** 00038 Rotaciones de la clase Arbol_Rojinegro: 00039 -Las operaciones de inserción y eliminación de un árbol binario de búsqueda, 00040 si se aplican a un árbol rojinegro pueden modificar ls propiedades basicas 00041 que debe cumplir la clase Arbol_Rojinegro. 00042 -Para restaurar estas propiedades es necesario cambiar el color de algunos nodos 00043 así como también la estructura de los apuntadores. 00044 -La estructura de los apuntadores se cambia mediante rotación, la cual es una 00045 operación que preserva las propiedades de un árbol binario de búsqueda. 00046 -Existen dos tipos de rotaciones: a la izquierda y a la derecha. 00047 */ 00048 template <class T> 00049 void rotarIzq (Arbol_Rojinegro<T>& x , Arbol_Rojinegro<T>& raiz ){ 00050 Arbol_Rojinegro<T> nulo; /// para realizar comparaciones nulas 00051 Arbol_Rojinegro<T> y = x.right(); 00052 x.right() = y.left(); 00053 y.left().padre() = x; 00054 y.padre() = x.padre(); 00055 if (Comp(x.padre(),nulo)) { raiz = y; } 00056 else if ( Comp(x , x.padre().left())){ x.padre().left() = y; } 00057 else { x.padre().right() = y; } 00058 y.left() = x; 00059 x.padre() = y; 00060 }// fin de rotar izquierda 00061 00062 /** 00063 Rotaciones de la clase Arbol_Rojinegro: 00064 -Las operaciones de inserción y eliminación de un árbol binario de búsqueda, 00065 si se aplican a un árbol rojinegro pueden modificar ls propiedades basicas 00066 que debe cumplir la clase Arbol_Rojinegro. 00067 -Para restaurar estas propiedades es necesario cambiar el color de algunos nodos 00068 así como también la estructura de los apuntadores. 00069 -La estructura de los apuntadores se cambia mediante rotación, la cual es una 00070 operación que preserva las propiedades de un árbol binario de búsqueda. 00071 -Existen dos tipos de rotaciones: a la izquierda y a la derecha. 00072 */ 00073 template <class T> 00074 void rotarDer (Arbol_Rojinegro<T>& y , Arbol_Rojinegro<T>& raiz ){ 00075 Arbol_Rojinegro<T> nulo; /// para realizar comparaciones nulas 00076 Arbol_Rojinegro<T> x = y.right(); 00077 y.right() = x.left(); 00078 x.left().padre() = y; 00079 x.padre() = y.padre(); 00080 if (Comp(y.padre(),nulo)) { raiz = x; } 00081 else if ( Comp(y , y.padre().left())){ y.padre().left() = x; } 00082 else { y.padre().right() = x; } 00083 x.left() = y; 00084 y.padre() = x; 00085 }//fin de rotar derecha 00086 00087 00088 /** 00089 Inserción de un elemento en un arbol rojinegro: 00090 -La inserción de un nodo en un árbol rojo-negro con n elementos puede 00091 realizarse en un tiempo O(lg n). 00092 -Se usara una versión modificada de la rutina de inserción en un árbol binario de 00093 búsqueda ordinario (version de orderedInsert de la clase Bin_Tree). 00094 00095 Puede observarse que el código es casi igual a la rutina de inserción en 00096 un árbol binario de búsqueda normal, salvo por tres diferencias fundamentales: 00097 - 1=> En lugar de usar el null ordinario, utilizamos un booleano al cual vamos a 00098 definir como true (verdadero) cuando el nodo de color negro. 00099 - 2=> Se establece el color del nodo z a insertar como rojo, o sea false. 00100 - 3=> Se invoca a la rutina corregirInserción(z) para restaurar las propiedades de árbol 00101 rojinegro. 00102 00103 Nótese que una vez insertado y marcado como rojo el nodo z 00104 (justo antes de llamar a corregirInsercion)se presenta la siguiente situación: 00105 -Las propiedades 1 y 3 indudablemente se cumplen, ya que los hijos del nuevo 00106 nodo rojo insertado son true(negros) y por lo tanto son negros. 00107 -La propiedad 5 se cumple debido a que no se ha insertado ningún nodo negro 00108 y por lo tanto todas las rutas de la raíz a las hojas siguen teniendo el mismo 00109 número de hijos negros. 00110 -Las únicas propiedades que podrían ser violadas son la 2, que requiere que la 00111 raíz sea negra y la 4, que dice que un nodo rojo no puede tener nigún hijo rojo. 00112 Ambas violaciones se deben a que el nuevo nodo es rojo. 00113 -La propiedad 2 se viola si z es la raíz y la 4 si el padre de z es rojo. 00114 */ 00115 template <class T> 00116 void insercion( Arbol_Rojinegro<T> & root , const T& val ) { 00117 00118 if ( root.isEmpty() ) { 00119 root.changeRoot(val); 00120 root.color = true; //lo pinto de negro 00121 return;}//Si esta vacio entonces meto el arbol z de raiz general. 00122 Arbol_Rojinegro<T> raiz(root) ; 00123 Arbol_Rojinegro<T> nulo ; /// arbol vacio. 00124 //Arbol_Rojinegro<T> raiz (L) ; /// copia del arbol enviado 00125 Arbol_Rojinegro<T> z (val); 00126 00127 while ( ! raiz.isEmpty() ) { 00128 if ( val < raiz.dato() ) { 00129 if ( raiz.left().isEmpty() ) { 00130 raiz.makeLeftChild( z ); 00131 z.left() = z.right() = nulo; 00132 z.color = false; /// o sea es rojo. 00133 corregirInsercion(z , raiz); //corrige los colores. 00134 return; 00135 } 00136 else { 00137 raiz = raiz.right(); 00138 z.left() = z.right() = nulo; 00139 z.color = false; /// o sea es rojo. 00140 corregirInsercion(z , raiz); //corrige los colores. 00141 } 00142 } 00143 else if ( val > raiz.dato() ) { 00144 if ( raiz.right().isEmpty() ) { 00145 raiz.makeRightChild( z ); 00146 z.left() = z.right() = nulo; 00147 z.color = false; /// o sea es rojo. 00148 corregirInsercion(z , raiz); //corrige los colores. 00149 return; 00150 } 00151 else { 00152 raiz = raiz.right(); 00153 z.left() = z.right() = nulo; 00154 z.color = false; /// o sea es rojo. 00155 corregirInsercion(z , raiz); //corrige los colores. 00156 } 00157 } 00158 } 00159 }//fin de insercion 00160 00161 /** 00162 Para estudiar cómo funciona la funcion "corregirInsercion" que permite restaurar 00163 las propiedades de árbol rojo-negro examinaremos el código en tres partes: 00164 -inicialización. 00165 -terminación. 00166 -mantenimiento. 00167 00168 Inicialización: 00169 Antes de la primera iteración, comenzamos con un árbol rojo-negro sin violaciones y 00170 añadimos un nodo rojo z. 00171 Si hay una violación a la propiedad 2 (raíz negra), entonces la raíz roja tiene 00172 que ser el nodo recién añadido z, el cual sería el único nodo interno del árbol. 00173 Debido a que tanto el padre como los hijos de z son false, el cual es negro, 00174 noy hay violación de la propiedad 4. Así, esta sería la única violación en el árbol. 00175 Si hay una violación de la propiedad 4, entonces, considerando que los hijos 00176 del nodo z son true (negros) y que el árbol no tenía violaciones antes de la 00177 inserción de z, la violación tiene que ser porque tanto z como z:padre son rojos. 00178 Es imposible que haya alguna otra violación de las propiedades. 00179 00180 Terminación: 00181 Cuando el ciclo termina, lo hace porque z:padre es negro. Así, no hay violación 00182 de la propiedad 4 al terminar el ciclo. La única propiedad que podría fallar es 00183 la propiedad 2, la cual es restaurada en la ultima instruccion de la funcion. 00184 00185 Mantenimiento: 00186 Hay seis casos a considerar dentro del ciclo while, pero tres de ellos son simétricos; 00187 dependiendo de si z:padre es un hijo izquierdo o un hijo derecho del 00188 abuelo de z (z:padre:padre). Se explicara la primera posibilidad: 00189 Nótese que z:padre:padre existe, ya que la condición del ciclo es 00190 que z:padre sea rojo y por lo tanto z:padre no puede ser la raíz. 00191 El primero de los tres casos a considerar se diferencia de los casos 2 y 3 00192 por el color del tío de z (z:padre:padre:der). Si el tío (y) es rojo entonces 00193 se ejecuta el caso 1. De otro modo se transfiere el control a los casos 2 y 3. 00194 En los tres casos el abuelo de z (z:padre:padre) es negro, puesto que 00195 el padre z:padre es rojo y la propiedad 4 sólo puede ser violada entre z y z:padre. 00196 00197 -Caso 1: El tío de z (y) es rojo El caso 1 se ejecuta cuando tanto 00198 z:padre e y son rojos. Puesto que z:padre:padre es negro, se pueden colorear 00199 z:padre e y como negros, corrigiendo así el problema de que z y z:padre sean 00200 ambos rojos, y colorear z:padre:padre como rojo, manteniendo de esta manera 00201 la propiedad 5. Luego se repite el ciclo con z:padra:padre como el nuevo nodo z. 00202 Nótese que si al fnal del ciclo la raíz queda roja, la violación es corregida 00203 en la ultima instruccion; 00204 00205 -Caso 2: El tío de z (y) es negro y z es el hijo derecho de su padre. 00206 En el caso 2 z es el hijo derecho de z:padre. En este caso se procede simplemente 00207 a realizar una rotación a la izquierda para transformar el problema en el caso 3. 00208 Al terminar la rotación se considerará z al antiguo z:padre que fue rotado. 00209 00210 -Caso 3: El tío de z (y) es negro y z es el hijo izquierdo de su padre. 00211 En el caso 3 tanto z como z:padre son rojos y z:padre:padre es negro. 00212 La acción a realizar en este caso consiste en hacer negro a z:padre y rojo 00213 a z:padre:padre y realizar una rotación a la derecha de z:padre:padre. 00214 Como z y z:padre originalmente eran rojos, la rotación realizada no introduce 00215 una violación de la propiedad 5 ya que la altura-negra de los nodos no resulta 00216 afectada y como ya no quedan nodos rojos consecutivos el procedimiento está terminado. 00217 */ 00218 template <class T> 00219 void corregirInsercion (Arbol_Rojinegro<T>& z , Arbol_Rojinegro<T>& raiz ){ 00220 Arbol_Rojinegro<T> padre_uno = z.padre(); 00221 Arbol_Rojinegro<T> padre_dos = z.padre().padre() ; 00222 Arbol_Rojinegro<T> y; 00223 // Creo estos arboles porque no puedo usar el REP del Bin_Tree :( . 00224 00225 // true = negro y false = rojo. 00226 while ( padre_uno.color == false) { 00227 if ( Comp (z.padre() , z.padre().padre().left()) ){ 00228 y = z.padre().padre().right(); 00229 if (y.color == false){ // o sea; si y es rojo? entonces... 00230 padre_uno.color = true; // pinto de negro 00231 z.padre() = padre_uno; // asi le agrego el campo color y lo pinto negro 00232 y.color = true; // pinto de negro 00233 padre_dos.color = false; // pinto de rojo 00234 z.padre().padre() = padre_dos;//asi le agrego el campo color y lo pinto rojo 00235 z = z.padre().padre(); 00236 } 00237 else{ 00238 if (Comp (z , z.padre().right())){ 00239 z = z.padre(); 00240 rotarIzq(z,raiz); 00241 } 00242 padre_uno.color = true; // pinto de negro 00243 z.padre() = padre_uno; // asi le agrego el campo color y lo pinto negro 00244 padre_dos.color = false; // pinto de rojo 00245 z.padre().padre() = padre_dos; //asi le agrego el campo color y lo pinto rojo 00246 rotarDer(padre_dos,raiz); 00247 z.padre().padre() = padre_dos; 00248 } 00249 } 00250 else { 00251 y = z.padre().padre().left(); 00252 if (y.color == false){ // si es rojo entonces...... 00253 padre_uno.color = true; // pinto de negro 00254 z.padre() = padre_uno; 00255 y.color = true; // y es negro. 00256 padre_dos = z.padre().padre(); 00257 padre_dos.color = false; // z.padre().padre() es rojo. 00258 z.padre().padre() = padre_dos; 00259 z = z.padre().padre(); 00260 } 00261 else { 00262 if (Comp( z , z.padre().left())){ 00263 z = z.padre(); 00264 rotarDer(z,raiz); 00265 } 00266 padre_uno.color = true; // pinto de negro 00267 z.padre() = padre_uno; 00268 padre_dos = z.padre().padre(); 00269 padre_dos.color = false; // pinto de rojo 00270 z.padre().padre() = padre_dos; 00271 rotarIzq( padre_dos , raiz); 00272 z.padre().padre() = padre_dos; 00273 } 00274 } 00275 }//fin del ciclo while 00276 raiz.color = true; // pinto la raiz de negro 00277 }//fin de corregirInsercion 00278 00279 00280 ///Funcion para buscar un nodo en el arbol. 00281 template <class T> 00282 Bin_Tree<T> busca_nodo (Bin_Tree<T>& root ,const T& val ){ 00283 00284 Bin_Tree<T> raiz = root; 00285 if (raiz.dato() > val ){ raiz = raiz.left();} 00286 if (raiz.dato() < val ){ raiz = raiz.right();} 00287 if (raiz.dato() == val ){return * raiz;} 00288 else{busca_nodo(raiz,val);} 00289 } 00290 00291 /** 00292 00293 Eliminación de un elemento en el arbol rojinegro: 00294 La elminación de un nodo en un árbol rojo-negro con n elementos puede realizarse 00295 en un tiempo O(lg n). Puede observarse que el código es casi igual a la rutina de 00296 inserción en un árbol binario de búsqueda normal, salvo por las siguientes diferencias: 00297 - 1=> En lugar de usar el null ordinario, utilizamos un booleano definido como false 00298 cuando necesitemos que la hoja tenga color negro. 00299 - 2=> Se hace una asignación incondicional (en un árbol binario de búsqueda normal 00300 sólo se puede hacer esta asignación si x no es nulo). En caso de que x 00301 sea false, esta asignación facilitará la codificación de corregirEliminar(). 00302 - 3=> Se invoca a la rutina corregirEliminación(z)para restaurar las propiedades de 00303 árbol rojo-negro en caso de que el nodo desaparecido sea negro. 00304 00305 Nótese que si el nodo desaparecido es negro, pueden presentarse tres problemas: 00306 - 1=> Si y era la raíz y un hijo rojo de y se convierte en la nueva raíz, se viola 00307 la propiedad 2. 00308 - 2=> Si tanto x como y:padre (que también es x:padre) eran rojos, entonces se 00309 viola la propiedad 4. 00310 - 3=> La remoción de y hace que cualquier ruta que previamente contenía a y ahora 00311 tenga un nodo negro menos. Por lo tanto se viola la propiedad 5. Para evitar 00312 lidiar con este problema, asumiremos que la negrura del nodo desaparecido y 00313 se le transfiere a su hijo x. El problema ahora es que x no es ni negro ni rojo 00314 (violando la propiedad 1), sino que es doble negro o rojo-negro y 00315 contribuye 2 o 1, respectivamente, al conteo de nodos negros en las rutas que 00316 lo contengan. El atributo color de x seguirá siendo false (si es rojo-negro) 00317 o true (si es negro-negro). En otras palabras, el atributo negro extra de un 00318 nodo se refleja por el hecho de que x apunte a él y no en el atributo color. 00319 */ 00320 template <class T> 00321 void borrado (Arbol_Rojinegro<T>& root ,const T& val ){ 00322 if ( root.isEmpty() ) { return; } // porque esta vacio. 00323 Arbol_Rojinegro<T> raiz (root); 00324 Arbol_Rojinegro<T> z(busca_nodo(raiz,val)); 00325 Arbol_Rojinegro<T> y , x; 00326 if (z.left().isEmpty() && z.right().isEmpty()){ 00327 z.erase(); // no tiene hijos, solo tiene que borrarse. 00328 } 00329 else{ 00330 y = z; 00331 } 00332 if (y.left().isEmpty()){ 00333 x = y.left(); 00334 } 00335 else{ 00336 x = y.right(); 00337 } 00338 x.padre() = y.padre(); 00339 if ( y.padre().isEmpty()){ 00340 root = x; 00341 } 00342 else{ 00343 if( Comp (y , y.padre().left())){ 00344 y.padre().left() = x; 00345 } 00346 } 00347 if ( Comp (y,z) == false){ 00348 z.dato() = y.dato();//copiar datos adicionales si aplica 00349 } 00350 if ( y.color == true){ 00351 corregirEliminar(x, root); 00352 } 00353 } 00354 /** 00355 A continuación examinaremos cómo el procedimiento "corregirEliminar" restaura las 00356 propiedades de árbol rojo-negro: 00357 00358 Dentro del ciclo while, x siempre apunta a un nodo doble-negro distinto a la raíz. 00359 En la segunda línea determinamos si x es un hijo izquierdo de su padre x:padre. 00360 La situación cuando x es el hijo derecho es simétrica, así que nos concentraremos 00361 sólo en el primer caso. 00362 00363 Nótese que el propósito del ciclo while es mover el negro extra hasta que: 00364 -1=> x apunte a un nodo rojo-negro, en cuyo caso se colorea como negro (no doble) 00365 fuera del ciclo, en la última línea. 00366 -2=> x apunta a la raíz, en cuyo caso la negrura extra puede ser simplemente 00367 desaparecida o. 00368 -3=> se puedan ejecutar rotaciones y cambios de colores apropiados. 00369 00370 En primer lugar mantenemos un apuntador w al hermano de x. Puesto que x es doble negro, 00371 w no puede ser true; de otro modo el número de nodos negros en la ruta de x:padre 00372 a la hoja w sería menor que el número en la ruta de x:padre a x. 00373 En el código se indican cuatro casos. La idea clave en cada caso es observar que el 00374 número de nodos negros (incluyendo el negro extra de x) desde la raíz del subárbol a 00375 cada uno de sus subárboles es preservado por la transformación. Así, si la propiedad 5 00376 se cumple antes de la transformación, también se cumple después. 00377 Lo mismo ocurre con los otros subárboles y en los otros cosas. 00378 00379 Posibles casos: 00380 -1=> El hermano (w) de x es rojo Como los dos hijos de w tienen que ser negros, se pueden 00381 intercambiar una rotación a la izquierda de x:padre. Después de la rotación, el hermano de 00382 x será uno de los antiguos hijos negros de w, convirtiendose así el problema en 00383 uno de los casos 2, 3 o 4. 00384 -2=>El hermano (w) de x es negro, los dos hijos de w son negros En este caso quitamos 00385 negrura tanto de x (moviendo el apuntador) como de w (convirtiéndolo en negro) y movemos 00386 la negrura al padre de ambos (haciendo que x apunte ahora x:padre). Después de esto, el 00387 ciclo se repite, a menos que la negrura extra se haya movido a la raíz del árbol, de donde 00388 se puede desaparecer sin problemas. 00389 -3=> El hermano (w) de x es negro, el hijo izquierdo de w es rojo y el hijo derecho de 00390 w es negro En este caso se pueden intercambiar los colores de w y su hijo izquierdo w:izq 00391 y luego ejecutar un rotación a la derecha de w sin violar las propiedades. Esto no nos 00392 permite desaparecer la negrura extra de x, pero nos permite transformar el problema al 00393 Caso 4, que resolvimos previamente. 00394 -4=> El hermano (w) de x es negro y el hijo derecho de w es rojo En este caso 00395 intercambiando algunos colores (haciendo que w tenga el color del padre y que 00396 w:padre y w:der sean negros) y haciendo una rotación a la izquierda podemos eliminar 00397 la negrura extra de x sin violar ninguna de las propiedades de árbol rojo negro. 00398 Nótese que por la manera en que se asignaron los colores es imposible que se viole 00399 la propiedad 4 ya que aunque w podría convertirse en rojo, su hijo derecho se convierte 00400 en negro. El hijo izquierdo de w es potencialmente rojo, pero su nuevo 00401 padre después de la rotación será w:padre el cual previamente había se había coloreado 00402 de negro. 00403 Finalmente, podría ser que w quede rojo y que quede como raíz del árbol, pero fuera 00404 del ciclo se corrige esta situación. 00405 */ 00406 template <class T> 00407 void corregirEliminar(Arbol_Rojinegro<T>& x,Arbol_Rojinegro<T>& root) { 00408 Arbol_Rojinegro<T> w; 00409 Arbol_Rojinegro<T> temp; 00410 Arbol_Rojinegro<T> tmp; 00411 Arbol_Rojinegro<T> tempo; 00412 Arbol_Rojinegro<T> tempor; 00413 while ( Comp(x,root)==false && x.color == false) { 00414 if ( Comp(x,x.padre().left())) { 00415 w = x.padre().right(); 00416 if (w.color == false) { 00417 w.color = true; 00418 temp = x.padre(); 00419 temp.color = false; 00420 x.padre() = temp; 00421 rotarIzq(temp,root); 00422 x.padre() = temp; 00423 w = x.padre().right(); 00424 } 00425 tmp = w.left(); 00426 tempo = w.right(); 00427 if (tmp.color == true && tempo.color == true) { 00428 w.color = false; 00429 x = x.padre(); 00430 } 00431 else { 00432 if (tempo.color == true) { 00433 tmp.color = true; 00434 w.color = false; 00435 rotarDer(w,root); 00436 w = x.padre().right(); 00437 } 00438 tempor = x.padre(); 00439 w.color = tempor.color; 00440 tempor.color = true; 00441 tempo.color = true; 00442 rotarIzq(tempor,root); 00443 x.padre() = tempor; 00444 x = root; 00445 } 00446 } /// 00447 if ( Comp(x,x.padre().right())) { 00448 w = x.padre().left(); 00449 if (w.color == false) { 00450 w.color = true; 00451 temp = x.padre(); 00452 temp.color = false; 00453 x.padre() = temp; 00454 rotarIzq(temp,root); 00455 x.padre() = temp; 00456 w = x.padre().left(); 00457 } 00458 tmp = w.right(); 00459 tempo = w.left(); 00460 if (tmp.color == true && tempo.color == true) { 00461 w.color = false; 00462 x = x.padre(); 00463 } 00464 else { 00465 if (tempo.color == true) { 00466 tmp.color = true; 00467 w.color = false; 00468 rotarDer(w,root); 00469 w = x.padre().left(); 00470 } 00471 tempor = x.padre(); 00472 w.color = tempor.color; 00473 tempor.color = true; 00474 tempo.color = true; 00475 rotarIzq(tempor,root); 00476 x.padre() = tempor; 00477 x = root; 00478 } 00479 } 00480 } 00481 x.color = true; 00482 } 00483 00484 #endif
1.4.7