Rojo-Negro
¿Qué representa este TAD?
Un árbol Rojo-Negro mantiene orden de búsqueda y usa reglas de color para limitar su altura. Inserciones y eliminaciones pueden cambiar colores o rotar enlaces para recuperar esas reglas.
Prueba este ejemplo
En un árbol vacío carga 10, 20, 30 paso a paso. Al terminar, la raíz debe ser negra y el recorrido Inorden debe mostrar 10, 20, 30. Ejecuta Validar para comprobar las reglas.
Qué observar · tres pasos
- Separa el valor almacenado del color del nodo.
- Observa si el paso recolorea o rota: no son la misma acción.
- Comprueba las reglas al finalizar la operación. Validar informa un resultado, pero no repara un árbol incorrecto.
Ir a la visualización e ingresar estos datos
De la idea al resultado
- Prepara una operación y predice qué cambiará.
- Activa Paso a paso antes de ejecutar si quieres ver la traza; sin ese modo obtienes el resultado final.
- Relaciona el dibujo con la línea C resaltada: acaba de ejecutarse. Una condición puede no cambiar ningún nodo.
- Lee las pistas de Comprender y las pestañas de Resultados.
Paso anterior / Siguiente paso revisan la misma llamada. Pausar no deshace; cerrar Paso a paso muestra su final sin ejecutarla otra vez. Restablecer TAD (o Reiniciar, según el módulo) borra estructura e historial, no sólo el cursor.
Usa −, +, 100%, Encajar vista y Centrar inicio para acomodar el gráfico. El zoom y el desplazamiento se conservan durante los pasos; la memoria auxiliar es opcional.
Agregar varios valores en esta estructura
Disponible en Pila, Cola, Lista enlazada, Lista circular, ABB, AVL, Rojo-Negro y Montículo binario. En las listas agrega al final. No está disponible para Grafos, Hash, Cola de prioridad ni Sublista; Ordenamiento conserva su propia entrada de arreglo.
- Abre Agregar varios en Preparar. Escribe hasta 50 enteros separados por comas, por ejemplo
8, -3, 12. Se permiten espacios y signos; no decimales, posiciones vacías ni una coma final. Rango: −2147483648 a 2147483647. - Revisa cantidad y orden. Se valida la lista completa antes de empezar. ABB, AVL y Rojo-Negro no aceptan repetidos, ni dentro del lote ni respecto al árbol actual. Pila, Cola, listas y Montículo sí los admiten.
- Elige Cargar valores para añadirlos en orden con resultado final, o Agregar paso a paso para estudiar cada inserción.
- En paso a paso, usa los controles del lote. Reproducir traza sólo reproduce el valor actual; Pausar detiene la reproducción. Al final, pulsa Siguiente valor para aceptar la siguiente inserción, o Finalizar lote al terminar la última.
Al terminar o detener un lote, edita la lista antes de iniciar otro. La vista previa conserva el orden solicitado; el resumen describe ese lote, no sustituye el estado actual tras otra operación o Restablecer TAD.
Detener lote conserva las operaciones ya aceptadas, incluida la actual, y descarta los valores pendientes. No es «deshacer todo». Si falla una operación durante el proceso, las anteriores no se revierten. Si la respuesta es incierta, usa Recargar estado e historial y comprueba qué se guardó antes de volver a cargar valores.
Palabras básicas para leer el dibujo
- TAD
- Tipo abstracto de dato: los datos y las operaciones permitidas sobre ellos.
- Nodo
- Elemento que guarda un valor y enlaces a otros elementos.
- Puntero o enlace
- Referencia que permite llegar a otro nodo. NULL indica que no apunta a un nodo.
- Raíz, padre e hijo
- La raíz inicia el árbol; un padre se conecta con sus hijos.
- Tope, frente y atrás
- Tope (TOP) en Pila; frente (FRONT) y atrás (BACK) en Cola son referencias a sus extremos.
Primera práctica de 5 minutos · Copiar, ampliar y exportar · Resolver dudas y errores · Atajos y referencia
Profundizar: invariantes, memoria y complejidad
Guía de aprendizaje
Objetivo: Justificar recoloreos y rotaciones con padre, abuelo y tío.
Estrategia: Insertar como ABB y ejecutar casos de reparación hasta la raíz.
Invariante: Raíz negra, sin rojo-rojo y black-height uniforme.
Memoria C: El C representa hojas negras con NULL, sin reservas NIL. El caller libera las reservas reales y asigna NULL a su raíz; el color no sustituye los enlaces.
Para leer el costo: n es la cantidad de nodos y h la altura. O(...) describe crecimiento del trabajo, no tiempo de reproducción.
Complejidad: Búsqueda, inserción y eliminación O(log n).
Errores frecuentes
- Evaluar solo el color del nodo.
- Usar color sin equivalente textual.
Glosario jerárquico
- Raíz
- Nodo sin padre.
- Hoja
- Nodo sin hijos.
- Altura
- En ABB, AVL y el auxiliar de Rojo-Negro se cuentan niveles: árbol vacío 0, hoja 1. Revisa la convención de la función que estudias.
- Profundidad
- Distancia desde la raíz.
- FE
- Altura del subárbol derecho menos altura del izquierdo.
- Rotación
- Cambio local de enlaces que conserva el orden.
- Recoloreo
- Cambio de colores para reparar reglas rojo-negro.
- Black-height
- Cantidad de nodos negros por camino hasta NIL.
- Heapify
- Proceso de restaurar la propiedad de heap.
Teclado y accesibilidad
En la visualización, usa → para avanzar un paso, ← para retroceder, Inicio para volver al comienzo, Fin para ir al último paso y Espacio para pausar la reproducción. Los estados y explicaciones también se presentan con texto, no solo con color.
Alcance de esta pantalla
- Las funciones C no producen un reporte textual propio de recoloreos y rotaciones. La aplicación muestra sus instrucciones en la traza; no son operaciones independientes del menú.
Referencia de operaciones y datos de entrada
Operaciones de la aplicación y datos
- Altura
altura - Sin datos adicionales.
- Buscar
buscar - Valor (entero)
- Eliminar
eliminar - Valor (entero)
- Inorden
inorden - Sin datos adicionales.
- Insertar
insertar - Valor (entero)
- Limpiar
limpiar - Sin datos adicionales.
- Validar
validar - Sin datos adicionales.
Los métodos C siguientes pueden incluir helpers, utilidades sin botón propio y llamadores equivalentes. Un bloque con varias funciones no cambia el nombre de la operación de la aplicación. Las vistas de memoria usan identidades simbólicas; tras free, sus registros históricos no son punteros utilizables.
Ejecutar operación crea una llamada; Anterior/Siguiente recorren la misma traza. Cerrar y reactivar Paso a paso no vuelven a ejecutar. Las consultas completadas pueden conservarse en el historial o journal aunque no muten la estructura; los rechazos no agregan llamadas exitosas. Restablecer TAD (o Reiniciar, según el módulo) borra la estructura y su historial.
Referencia avanzada por operación
Abre sólo el tema que estás estudiando. Estos detalles distinguen el contrato C, su representación visual y las funciones auxiliares de la aplicación.
Reglas y validación
- Árbol balanceado por reglas de color (rojo/negro) que limitan la altura.
- Durante la simulación observa recoloreos y rotaciones para mantener raíz negra, sin rojos consecutivos y con altura negra consistente entre caminos.
- La consulta Validar pública del TAD comprueba las cinco reglas RN y orden estricto; comprueba altura negra uniforme, colores y enlaces al padre sobre objetos vivos.
Inorden, Altura y main
- Inorden y Altura usan auxiliares documentados de la aplicación/main, no exportados por el C/H.
- Inorden imprime valores ascendentes con espacio final, sin salto; Altura cuenta niveles (nodos del camino más largo): NULL vale 0 y una hoja vale 1.
- El main calcula las consultas exitosas, incluidas repeticiones, sin imprimir resultados web prefabricados.
Hojas conceptuales
- El C usa NULL como hoja negra; el backend presenta vistas NIL.
Eliminar y reparar
- Eliminar busca el entero, trasplanta el hijo o la reserva del sucesor y libera la reserva original z, sin copiar su valor sobre ella ni reservar o imprimir.
- El color original guardado determina la reparación con x y x_parent; tras free los alias a z son indeterminados, aunque su dirección se reutilice.
- Su referencia arbol debe ser escribible y no NULL; un árbol vacío sí se admite.
- Eliminar ausente es no-op nativo pero rechazo de aplicación; Liberar no actualiza la raíz por valor, el caller debe asignar NULL sin leer el alias antiguo.
Limpiar y vida de memoria
- Limpiar libera izq, der y nodo en postorden: O(n) tiempo, O(h) pila; no imprime ni reserva y no crea/libera reservas NIL.
- Los alias a nodos liberados son inutilizables.
- Repetir solo es seguro después de asignar NULL a la raíz; las consultas o comparaciones de punteros antiguos no son una comprobación segura.
Insertar: rango y caller
- Insertar acepta enteros entre -2147483648 y 2147483647, el int de C de 32 bits; fuera de rango se rechaza antes de modificar el árbol o guardar historial, sin truncar.
- El main conserva las operaciones exitosas y libera al final las reservas restantes; después asigna NULL a la raíz.
- Esa limpieza del caller no agrega operaciones ni printf.
Insertar: reserva y reparación
- Insertar busca sin reservar y solo crea un nodo si el entero no existe; escribe nro, hijos NULL, padre y ROJO antes de publicar el enlace.
- Los cinco casos reparan por recoloreos/rotaciones; raíz negra al terminar, sin reservas NIL.
- El C retorna void: referencia raíz NULL, duplicado o fallo de malloc son no-op sin confirmación impresa; la aplicación rechaza duplicados antes de llamar.
- Los auxiliares de reparación requieren su contexto y no son métodos del menú.
- La rotación no cambia enteros ni colores por sí sola y no certifica cada estado parcial como rojo-negro válido.
Código fuente completo del TAD
Descarga el archivo C principal de Árbol rojo-negro y su encabezado para estudiarlo o compilarlo en tu equipo.
Referencia: estructura del TAD en C
typedef struct nodoRBT {
int nro; /**< Dato entero almacenado. */
char rbt_color; /**< Color del nodo: r para rojo, n para negro. */
struct nodoRBT *padre; /**< Enlace al padre del nodo. */
struct nodoRBT *izq; /**< Hijo izquierdo. */
struct nodoRBT *der; /**< Hijo derecho. */
} nodoRBT;
typedef struct nodoRBT *RBT;Código C y llamadores equivalentes
Insertar · código C
Inserta respetando reglas de orden/balance del TAD y actualiza punteros estructurales necesarios.
/**
* @brief Consulta el abuelo sin mutar ni transferir propiedad.
* @param n Nodo prestado vivo o NULL.
* @return Alias prestado al padre del padre, o NULL si n o su padre son NULL.
* @pre Los enlaces de nodos no NULL consultados apuntan a reservas vivas consistentes.
* @note No recorre todo el arbol, imprime, reserva o libera memoria; O(1).
*/
RBT rbt_abuelo(RBT n) {
if ((n != NULL) && (n->padre != NULL))
return n->padre->padre;
else
return NULL;
}
/**
* @brief Consulta el hermano del padre mediante rbt_abuelo.
* @param n Nodo prestado vivo o NULL.
* @return Alias al hijo opuesto del abuelo; NULL si no hay abuelo o tio.
* @pre Los enlaces padre/hijos consultados son vivos y consistentes.
* @note Si el padre es hijo izquierdo devuelve der; en otro caso devuelve izq.
* No muta, imprime, reserva ni libera; el alias no transfiere propiedad; O(1).
*/
RBT rbt_tio(RBT n) {
RBT a = rbt_abuelo(n);
if (a == NULL)
return NULL;
if (n->padre == a->izq)
return a->der;
else
return a->izq;
}
/**
* @brief Rota a la derecha conservando identidad y orden de los nodos.
* @param r Referencia escribible a la raiz; NULL es no-op.
* @param nodoRBT Pivote vivo; NULL o hijo izq NULL es no-op.
* @pre Para una rotacion efectiva, el pivote pertenece al arbol y padre/hijos
* son vivos y consistentes. Su hijo izquierdo y el posible subarbol transferido son vivos.
* @note Actualiza el enlace del padre o *r, transfiere B->der a A->izq,
* coloca A como B->der y corrige padres. No reserva, libera, imprime ni recolorea;
* O(1). La rotacion aislada no garantiza todas las reglas rojo-negro.
* Los enlaces intermedios son pasos de escritura, no un arbol final certificado.
*/
void rbt_rotar_dcha(RBT *r, RBT nodoRBT) {
if (r == NULL || nodoRBT == NULL || nodoRBT->izq == NULL) {
return;
}
RBT padre = nodoRBT->padre;
RBT A = nodoRBT;
RBT B = A->izq;
RBT C = B->der;
if (padre != NULL) {
if (padre->der == A)
padre->der = B;
else
padre->izq = B;
} else
*r = B;
A->izq = C;
B->der = A;
A->padre = B;
if (C)
C->padre = A;
B->padre = padre;
}
/**
* @brief Rota a la izquierda conservando identidad y orden de los nodos.
* @param r Referencia escribible a la raiz; NULL es no-op.
* @param nodoRBT Pivote vivo; NULL o hijo der NULL es no-op.
* @pre Para una rotacion efectiva, el pivote pertenece al arbol y padre/hijos
* son vivos y consistentes. Su hijo derecho y el posible subarbol transferido son vivos.
* @note Actualiza el enlace del padre o *r, transfiere B->izq a A->der,
* coloca A como B->izq y corrige padres. No reserva, libera, imprime ni recolorea;
* O(1). La rotacion aislada no garantiza todas las reglas rojo-negro.
* Los enlaces intermedios son pasos de escritura, no un arbol final certificado.
*/
void rbt_rotar_izda(RBT *r, RBT nodoRBT) {
if (r == NULL || nodoRBT == NULL || nodoRBT->der == NULL) {
return;
}
RBT padre = nodoRBT->padre;
RBT A = nodoRBT;
RBT B = A->der;
RBT C = B->izq;
if (padre != NULL) {
if (padre->der == A)
padre->der = B;
else
padre->izq = B;
} else
*r = B;
A->der = C;
B->izq = A;
A->padre = B;
if (C)
C->padre = A;
B->padre = padre;
}
/**
* @brief Recolorea padre/abuelo y rota el abuelo del caso alineado.
* @param n Nodo vivo en configuracion LL o RR de la reparacion.
* @param arbol Referencia escribible y no NULL a la raiz.
* @pre Contexto valido de insercion; padre y abuelo vivos no NULL, enlaces
* consistentes y tio negro o NULL. No es entrada general para un nodo arbitrario.
* @note Padre NEGRO y abuelo ROJO; LL rota dcha, el caso espejo rota izda.
* Las rotaciones conservan reservas/valores y pueden cambiar *arbol. Void,
* sin reserva/free/printf; no asegura una invariante global en cada escritura parcial.
*/
void rbt_insercion_caso5(RBT n, RBT *arbol) {
RBT a = rbt_abuelo(n);
n->padre->rbt_color = NEGRO;
a->rbt_color = ROJO;
if ((n == n->padre->izq) && (n->padre == a->izq)) {
rbt_rotar_dcha(arbol, a);
} else {
rbt_rotar_izda(arbol, a);
}
}
/**
* @brief Alinea un triangulo y pasa el alias ajustado a caso5.
* @param n Nodo vivo con padre rojo y abuelo no NULL.
* @param arbol Referencia escribible y no NULL a la raiz.
* @pre Contexto valido de insercion, tio negro o NULL y enlaces consistentes.
* @note LR: rota izda en padre y nuevo_n=n->izq; RL: rota dcha en padre
* y nuevo_n=n->der. Si ya esta alineado no rota aqui. n no se reasigna:
* nuevo_n es otro local y puede identificar al padre previo. Luego llama caso5.
* Retorna void; no reserva/libera/imprime ni recolorea directamente.
*/
void rbt_insercion_caso4(RBT n, RBT *arbol) {
RBT a = rbt_abuelo(n);
RBT nuevo_n = n;
if ((n == n->padre->der) && (n->padre == a->izq)) {
rbt_rotar_izda(arbol, n->padre);
nuevo_n = n->izq;
} else if ((n == n->padre->izq) && (n->padre == a->der)) {
rbt_rotar_dcha(arbol, n->padre);
nuevo_n = n->der;
}
rbt_insercion_caso5(nuevo_n, arbol);
}
/**
* @brief Recolorea padre y tio rojos o delega al caso geometrico.
* @param n Nodo vivo con padre rojo y abuelo no NULL.
* @param arbol Referencia escribible y no NULL a la raiz.
* @pre Contexto valido de insercion, enlaces vivos consistentes.
* @note Tio rojo: padre y tio NEGRO, abuelo ROJO y caso1 sobre el abuelo.
* Tio negro o NULL: caso4. La recursion puede propagar la reparacion a ancestros;
* no cambia valores ni reserva/libera/imprime. Retorno void.
*/
void rbt_insercion_caso3(RBT n, RBT *arbol) {
RBT t = rbt_tio(n);
RBT a;
if ((t != NULL) && (t->rbt_color == ROJO)) {
n->padre->rbt_color = NEGRO;
t->rbt_color = NEGRO;
a = rbt_abuelo(n);
a->rbt_color = ROJO;
rbt_insercion_caso1(a, arbol);
} else {
rbt_insercion_caso4(n, arbol);
}
}
/**
* @brief Termina si el padre es negro o delega a caso3.
* @param n Nodo vivo con padre no NULL en la reparacion.
* @param arbol Referencia escribible y no NULL a la raiz.
* @pre Contexto valido de insercion y enlaces vivos consistentes.
* @note Padre negro no cambia campos; padre rojo requiere reparar. Este caso
* no comprueba globalmente orden, altura negra ni padres. Void, sin reserva/free/printf.
*/
void rbt_insercion_caso2(RBT n, RBT *arbol) {
if (n->padre->rbt_color == NEGRO)
return;
else
rbt_insercion_caso3(n, arbol);
}
/**
* @brief Pinta de negro un nodo sin padre o continua la reparacion.
* @param n Nodo vivo de la reparacion, insertado o ancestro al propagar recoloreos.
* @param arbol Referencia escribible y no NULL a la raiz.
* @pre Contexto valido de insercion; n no es NULL y enlaces vivos son consistentes.
* @note Decide por n->padre==NULL, no por una comparacion con *arbol.
* Si no es raiz llama caso2. Retorna void, sin reserva, free ni printf.
*/
void rbt_insercion_caso1(RBT n, RBT *arbol) {
if (n->padre == NULL)
n->rbt_color = NEGRO;
else
rbt_insercion_caso2(n, arbol);
}
/**
* @brief Inserta un entero distinto mediante enlace BST y reparacion rojo-negro.
* @param arbol Referencia escribible a la raiz; NULL es no-op.
* @param dato Entero a insertar, incluidos los extremos del tipo int.
* @pre Si arbol no es NULL, *arbol es NULL o un arbol rojo-negro finito, ordenado,
* con enlaces padre/hijos consistentes y reservas vivas propias del TAD.
* @note Retorna void. Duplicado, arbol NULL o fallo de malloc no mutan el arbol
* ni imprimen confirmacion. No hay codigo de estado para distinguir esos casos.
* Busca sin reservar; luego malloc crea una reserva inicialmente no inicializada.
* Escribe dato, hijos NULL, padre y ROJO antes de publicar el enlace o la raiz.
* Los auxiliares reparan por recoloreos/rotaciones sin reservar ni liberar nodos.
* Solo despues de la reparacion imprime "El numero ha sido insertado" con tabulador y salto.
* La raiz final es negra; las hojas C son NULL, no reservas NIL. Tiempo O(log n)
* y pila O(log n) bajo la precondicion; malloc reserva un nodo y no se llama free.
* @warning El caller no debe interpretar el retorno void como una confirmacion
* ni volver a ejecutar para descubrir si la primera llamada inserto.
*/
void rbt_insertar(RBT *arbol, int dato) {
if (arbol == NULL) {
return;
}
RBT padre = NULL;
RBT actual = *arbol;
while (actual != NULL && dato != actual->nro) {
padre = actual;
if (dato < actual->nro)
actual = actual->izq;
else if (dato > actual->nro)
actual = actual->der;
}
if (actual != NULL)
return;
actual = malloc(sizeof(struct nodoRBT));
if (actual == NULL) {
return;
}
actual->nro = dato;
actual->izq = actual->der = NULL;
actual->padre = padre;
actual->rbt_color = ROJO;
if (padre == NULL) {
*arbol = actual;
} else if (dato < padre->nro) {
padre->izq = actual;
} else if (dato > padre->nro) {
padre->der = actual;
}
rbt_insercion_caso1(actual, arbol);
printf("\tEl numero ha sido insertado\n");
}Eliminar · código C
Localiza el objetivo y aplica el caso de borrado correspondiente (hoja, un hijo, dos hijos o rebalanceo).
/**
* @brief Lee el color prestado o representa una hoja NULL como negra.
* @param n Nodo vivo prestado, o NULL; no consume propiedad.
* @return NEGRO para NULL; en otro caso el char literal almacenado en rbt_color.
* @note No valida el char, no escribe ni reserva/libera/imprime; tiempo y pila O(1).
*/
static char colorOf(RBT n) { return (n == NULL) ? NEGRO : n->rbt_color; }
/**
* @brief Rota a la derecha conservando identidad y orden de los nodos.
* @param r Referencia escribible a la raiz; NULL es no-op.
* @param nodoRBT Pivote vivo; NULL o hijo izq NULL es no-op.
* @pre Para una rotacion efectiva, el pivote pertenece al arbol y padre/hijos
* son vivos y consistentes. Su hijo izquierdo y el posible subarbol transferido son vivos.
* @note Actualiza el enlace del padre o *r, transfiere B->der a A->izq,
* coloca A como B->der y corrige padres. No reserva, libera, imprime ni recolorea;
* O(1). La rotacion aislada no garantiza todas las reglas rojo-negro.
* Los enlaces intermedios son pasos de escritura, no un arbol final certificado.
*/
void rbt_rotar_dcha(RBT *r, RBT nodoRBT) {
if (r == NULL || nodoRBT == NULL || nodoRBT->izq == NULL) {
return;
}
RBT padre = nodoRBT->padre;
RBT A = nodoRBT;
RBT B = A->izq;
RBT C = B->der;
if (padre != NULL) {
if (padre->der == A)
padre->der = B;
else
padre->izq = B;
} else
*r = B;
A->izq = C;
B->der = A;
A->padre = B;
if (C)
C->padre = A;
B->padre = padre;
}
/**
* @brief Rota a la izquierda conservando identidad y orden de los nodos.
* @param r Referencia escribible a la raiz; NULL es no-op.
* @param nodoRBT Pivote vivo; NULL o hijo der NULL es no-op.
* @pre Para una rotacion efectiva, el pivote pertenece al arbol y padre/hijos
* son vivos y consistentes. Su hijo derecho y el posible subarbol transferido son vivos.
* @note Actualiza el enlace del padre o *r, transfiere B->izq a A->der,
* coloca A como B->izq y corrige padres. No reserva, libera, imprime ni recolorea;
* O(1). La rotacion aislada no garantiza todas las reglas rojo-negro.
* Los enlaces intermedios son pasos de escritura, no un arbol final certificado.
*/
void rbt_rotar_izda(RBT *r, RBT nodoRBT) {
if (r == NULL || nodoRBT == NULL || nodoRBT->der == NULL) {
return;
}
RBT padre = nodoRBT->padre;
RBT A = nodoRBT;
RBT B = A->der;
RBT C = B->izq;
if (padre != NULL) {
if (padre->der == A)
padre->der = B;
else
padre->izq = B;
} else
*r = B;
A->der = C;
B->izq = A;
A->padre = B;
if (C)
C->padre = A;
B->padre = padre;
}
/**
* @brief Sustituye el enlace propietario a u y actualiza el padre de v.
* @param root Referencia escribible y no NULL a la raiz.
* @param u Nodo vivo enlazado al arbol, no NULL, con padre consistente.
* @param v Subarbol vivo sustituto, o NULL.
* @pre Las reservas y enlaces son validos en el contexto de eliminacion.
* @note Si u no tiene padre escribe *root; si no, escribe el hijo izq/der del padre.
* Solo cuando v existe le asigna u->padre. Conserva los campos de u, incluido
* su padre antiguo para el caller; no libera u ni cambia valores o colores.
* Void, sin reserva/free/printf; tiempo y pila O(1).
*/
static void transplantar(RBT *root, RBT u, RBT v) {
if (u->padre == NULL) {
*root = v;
} else if (u == u->padre->izq) {
u->padre->izq = v;
} else {
u->padre->der = v;
}
if (v) v->padre = u->padre;
}
/**
* @brief Recorre enlaces izquierdos y retorna su ultimo nodo prestado.
* @param n Raiz viva de una cadena izquierda finita; NULL admitido.
* @return Alias prestado al extremo izquierdo, o NULL para entrada NULL.
* @note Es el minimo bajo orden BST; no valida ese orden. Solo modifica su
* parametro local, sin reconectar, reservar, liberar ni imprimir. Tiempo O(h), pila O(1).
*/
static RBT minimo(RBT n) {
while (n && n->izq) n = n->izq;
return n;
}
/**
* @brief Repara el deficit de negro mediante hermanos, recoloreos y rotaciones.
* @param root Referencia escribible y no NULL a la raiz.
* @param x Reserva viva que reemplaza al eliminado, o NULL.
* @param x_parent Padre vivo del reemplazo; necesario cuando x es NULL salvo arbol vacio.
* @pre Contexto de eliminacion valido: referencias restantes vivas y padres consistentes.
* @note NULL se considera NEGRO, no una reserva NIL. Mientras x no sea raiz y
* sea negro, distingue lado, hermano y colores de sus hijos; las ramas espejo
* reparan con las rotaciones reales o propagan x al padre. Si x_parent es NULL
* sale por break; al finalizar, x no NULL se pinta NEGRO. Retorna void, sin
* reserva, free ni printf; no cambia enteros. Tiempo O(h), pila auxiliar O(1).
*/
static void arreglarEliminacion(RBT *root, RBT x, RBT x_parent) {
/* Mientras x no sea la raiz y sea negro, corregimos desde abajo hacia arriba */
while (x != *root && colorOf(x) == NEGRO) {
if (x_parent == NULL) {
break;
}
int es_izq = (x == (x_parent ? x_parent->izq : NULL));
RBT w = x_parent ? (es_izq ? x_parent->der : x_parent->izq) : NULL; /* hermano de x */
RBT w_izq = w ? w->izq : NULL;
RBT w_der = w ? w->der : NULL;
if (es_izq) {
/* CASO 1 (simétrico): w rojo -> rotar izda en padre */
if (colorOf(w) == ROJO) {
w->rbt_color = NEGRO;
x_parent->rbt_color = ROJO;
rbt_rotar_izda(root, x_parent);
w = x_parent->der; w_izq = w ? w->izq : NULL; w_der = w ? w->der : NULL;
}
/* CASO 2: w negro y sus dos hijos negros -> recolorear w y subir */
if (colorOf(w_izq) == NEGRO && colorOf(w_der) == NEGRO) {
if (w) w->rbt_color = ROJO;
x = x_parent;
x_parent = x ? x->padre : NULL;
} else {
/* CASO 3: w negro, hijo derecho negro, hijo izquierdo rojo -> rotar dcha en w */
if (colorOf(w_der) == NEGRO) {
if (w_izq) w_izq->rbt_color = NEGRO;
if (w) { w->rbt_color = ROJO; rbt_rotar_dcha(root, w); }
w = x_parent ? x_parent->der : NULL; w_izq = w ? w->izq : NULL; w_der = w ? w->der : NULL;
}
/* CASO 4: w negro, hijo derecho rojo -> rotar izda en padre y recolorear */
if (w) w->rbt_color = colorOf(x_parent);
x_parent->rbt_color = NEGRO;
if (w_der) w_der->rbt_color = NEGRO;
rbt_rotar_izda(root, x_parent);
x = *root; /* terminamos */
}
} else {
/* Lado derecho: casos espejo */
if (colorOf(w) == ROJO) {
w->rbt_color = NEGRO;
x_parent->rbt_color = ROJO;
rbt_rotar_dcha(root, x_parent);
w = x_parent ? x_parent->izq : NULL; w_izq = w ? w->izq : NULL; w_der = w ? w->der : NULL;
}
if (colorOf(w_der) == NEGRO && colorOf(w_izq) == NEGRO) {
if (w) w->rbt_color = ROJO;
x = x_parent;
x_parent = x ? x->padre : NULL;
} else {
if (colorOf(w_izq) == NEGRO) {
if (w_der) w_der->rbt_color = NEGRO;
if (w) { w->rbt_color = ROJO; rbt_rotar_izda(root, w); }
w = x_parent ? x_parent->izq : NULL; w_izq = w ? w->izq : NULL; w_der = w ? w->der : NULL;
}
if (w) w->rbt_color = colorOf(x_parent);
x_parent->rbt_color = NEGRO;
if (w_izq) w_izq->rbt_color = NEGRO;
rbt_rotar_dcha(root, x_parent);
x = *root;
}
}
}
if (x) x->rbt_color = NEGRO;
}
/**
* @brief Desconecta un entero y libera su reserva original, reparando el arbol.
* @param arbol Referencia escribible y no NULL a la raiz; *arbol puede ser NULL.
* @param key Entero C a buscar; si no existe, retorna sin modificar ni imprimir.
* @pre Arbol finito, ordenado y rojo-negro con padres consistentes y reservas vivas.
* @note Retorna void. Vacio/ausente son no-op nativos, no un codigo de error.
* Con cero/un hijo trasplanta ese hijo; con dos hijos traslada el sucesor minimo
* del derecho, su reserva y enlaces, no copia el valor sobre z. Siempre free(z),
* nunca free(y) si el sucesor y es otra reserva. No reserva ni ejecuta printf.
* Guarda y_color_original antes de liberar; solo si era NEGRO ejecuta fix-up
* con x y x_parent vivos o NULL. Colorea la raiz final de NEGRO si existe.
* Tiempo O(h), O(log n) bajo la precondicion; pila auxiliar O(1), sin recursion.
* @warning Tras free(z), todos sus alias son indeterminados y no deben leerse.
* Si y era z, ese alias tambien termina; el color guardado es un char independiente.
* El caller conserva propiedad del arbol restante y debe liberarlo al finalizar.
*/
void rbt_eliminar(RBT *arbol, int key) {
RBT z = *arbol;
while (z != NULL && z->nro != key) {
if (key < z->nro) z = z->izq; else z = z->der;
}
if (z == NULL) return; /* no encontrado */
RBT y = z;
char y_color_original = y->rbt_color;
RBT x = NULL;
RBT x_parent = NULL;
if (z->izq == NULL) {
x = z->der;
x_parent = z->padre;
transplantar(arbol, z, z->der);
} else if (z->der == NULL) {
x = z->izq;
x_parent = z->padre;
transplantar(arbol, z, z->izq);
} else {
y = minimo(z->der);
y_color_original = y->rbt_color;
x = y->der;
if (y->padre == z) {
x_parent = y;
} else {
transplantar(arbol, y, y->der);
x_parent = y->padre;
y->der = z->der;
y->der->padre = y;
}
transplantar(arbol, z, y);
y->izq = z->izq;
y->izq->padre = y;
y->rbt_color = z->rbt_color;
}
free(z);
if (y_color_original == NEGRO) {
arreglarEliminacion(arbol, x, x_parent);
}
if (*arbol) (*arbol)->rbt_color = NEGRO;
}Buscar · código C
Navega por comparaciones hasta encontrar el valor o concluir que no existe.
/**
* @brief Busca un entero por comparaciones sin mutar ni reservar memoria.
* @param nodoRBT Raiz prestada de un arbol valido; NULL admitido.
* @param dato Entero buscado.
* @return Alias prestado al nodo encontrado o NULL; no transfiere propiedad.
* @note Imprime un mensaje distinto para arbol vacio, dato presente o dato ausente.
* El alias pierde validez cuando esa reserva es liberada; no debe liberarse por la consulta.
*/
RBT rbt_buscar(RBT nodoRBT, int dato) {
RBT actual = nodoRBT;
if (nodoRBT == NULL) {
printf("\n\tEl arbol esta vacio\n\n");
return NULL;
}
while (actual != NULL) {
if (dato == actual->nro) {
printf("\n\tEl numero %d existe en el arbol\n", dato);
return actual;
} else if (dato < actual->nro)
actual = actual->izq;
else if (dato > actual->nro)
actual = actual->der;
}
printf("\n\tEl numero %d NO existe en el arbol\n", dato);
return NULL;
}Inorden · código C
Recorrido izquierda-raíz-derecha; en ABB produce valores ordenados de menor a mayor.
/**
* @brief Imprime los enteros en orden ascendente, cada uno seguido de un espacio.
* @param nodo Raiz prestada; NULL no imprime nada.
* @note Auxiliar de la aplicacion y del main, no exportado por el C/H. No muta ni libera.
*/
void rbt_inorden(RBT nodo) {
if (nodo == NULL) return;
rbt_inorden(nodo->izq);
printf("%d ", nodo->nro);
rbt_inorden(nodo->der);
}Altura · código C
Calcula la altura estructural a partir de la profundidad máxima de sus ramas.
/**
* @brief Calcula la altura en nodos, sin mutar el arbol.
* @param nodo Raiz prestada; NULL admitido.
* @return 0 para NULL; uno mas que la mayor altura de los hijos en otro caso.
* @note Auxiliar de la aplicacion y del main, no exportado por el C/H.
*/
int rbt_altura(RBT nodo) {
if (nodo == NULL) return 0;
int altIzq = rbt_altura(nodo->izq);
int altDer = rbt_altura(nodo->der);
return (altIzq > altDer ? altIzq : altDer) + 1;
}Validar · código C
Consulta las reglas indicadas en la referencia avanzada de este TAD. No modifica ni repara la estructura; observa las condiciones que hacen devolver verdadero o falso.
#include <limits.h>
/**
* @brief Obtiene altura negra o cero si falla una regla estructural.
* @param nodo Nodo prestado vivo y legible; NULL representa hoja negra.
* @param padreEsperado Identidad del padre exigido, NULL en la raiz.
* @param tieneMin Indica presencia de cota inferior exclusiva.
* @param minimo Cota inferior cuando tieneMin es uno.
* @param tieneMax Indica presencia de cota superior exclusiva.
* @param maximo Cota superior cuando tieneMax es uno.
* @return Altura negra positiva (NULL=1) o cero ante invalidez.
* @note Solo lectura, sin reservas, liberaciones ni salida. Padres y cotas
* estrictas rechazan ciclos y nodos compartidos vivos. No admite referencias
* colgantes. Usa O(h) pila y O(n) tiempo en arboles validos.
*/
static int rbt_validar_altura_negra(RBT nodo, RBT padreEsperado, int tieneMin, int minimo, int tieneMax, int maximo) {
if (nodo == NULL) return 1;
if (nodo->padre != padreEsperado) return 0;
if (nodo->rbt_color != ROJO && nodo->rbt_color != NEGRO) return 0;
if (tieneMin && nodo->nro <= minimo) return 0;
if (tieneMax && nodo->nro >= maximo) return 0;
if (nodo->rbt_color == ROJO &&
((nodo->izq != NULL && nodo->izq->rbt_color == ROJO) ||
(nodo->der != NULL && nodo->der->rbt_color == ROJO))) return 0;
int bhIzq = rbt_validar_altura_negra(nodo->izq, nodo, tieneMin, minimo, 1, nodo->nro);
if (bhIzq == 0) return 0;
int bhDer = rbt_validar_altura_negra(nodo->der, nodo, 1, nodo->nro, tieneMax, maximo);
if (bhDer == 0 || bhIzq != bhDer) return 0;
if (nodo->rbt_color == NEGRO && bhIzq == INT_MAX) return 0;
return bhIzq + (nodo->rbt_color == NEGRO ? 1 : 0);
}
/**
* @brief Comprueba reglas RN, orden estricto y padres/ciclos/nodos compartidos.
* @param raiz Raiz prestada; NULL valido; referencias no NULL vivas y legibles.
* @return 1 valido, 0 invalido, sin reparar ni mutar la representacion.
* @note Solo lectura: no reserva, libera ni imprime. NULL es hoja negra;
* altura negra uniforme y raiz negra con padre NULL. No punteros colgantes.
*/
int rbt_validar(RBT raiz) {
if (raiz == NULL) return 1;
if (raiz->rbt_color != NEGRO) return 0;
return rbt_validar_altura_negra(raiz, NULL, 0, 0, 0, 0) != 0;
}Limpiar · código C
Recorre la estructura liberando todos los nodos y deja el puntero raíz en estado nulo para reinicio seguro.
/**
* @brief Libera cada reserva en postorden sin asignar la raiz del llamador.
* @param arbol Raiz por valor del arbol a consumir; NULL admitido.
* @pre Los enlaces forman un arbol finito sin ciclos ni reservas compartidas;
* cada nodo no NULL es una reserva viva compatible con free.
* @note Recorre primero izq, luego der y libera el nodo. No reserva memoria,
* imprime, retorna un puntero ni rebalancea. Los colores no condicionan la liberacion.
* Las hojas C son NULL, no reservas NIL. Tiempo O(n), pila recursiva O(h).
* @warning Tras liberar, los alias a cada reserva tienen valor indeterminado y no
* deben leerse ni desreferenciarse. El llamador debe asignar NULL a su variable raiz
* sin leer su valor anterior. Repetir con NULL es seguro; repetir con el alias antiguo no.
*/
void rbt_liberar(RBT arbol) {
if (arbol == NULL)
return;
rbt_liberar(arbol->izq);
rbt_liberar(arbol->der);
free(arbol);
}