Laboratorio de árboles y montículos en C
Rojo-Negro
Acerca de esta estructura
Árbol rojo-negro auto-balanceado.
1 Preparar
Ejemplos guiados
Los ejemplos construyen el estado mediante operaciones públicas reales.
2 Predecir
Prepara una traza para predecir una condición aún no ejecutada.
Practica anticipando una condición C antes de revelar su resultado: elige Insertar y un valor, activa Paso a paso y pulsa Ejecutar operación. Responde Verdadero o Falso; NULL indica ausencia de un nodo referenciado.
Comprobar u omitir avanza varias instrucciones hasta esa decisión; Siguiente paso avanza solo una instrucción. El estado visible es el anterior a la decisión.
Durante un lote, la predicción y los controles individuales quedan bloqueados para no mezclar operaciones. Finaliza el lote o pulsa Detener lote y prepara una operación individual. Detener conserva la operación ya aceptada y descarta las pendientes.
Progreso conceptual de esta sesión: 0 aciertos de 0 intentos.
3 Ejecutar y visualizar
Código C: Insertar
/**
* @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");
}3 Controlar la ejecución
Actual: +0.00x (1.00x real)
Función: — · Profundidad: — · Fase: — · Concepto: —
Paso: 0/0
4 Comprender
Referencia completa de la estructura
Á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 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 nodos: NULL vale 0 y una hoja vale 1. El main calcula las consultas exitosas, incluidas repeticiones, sin imprimir resultados web prefabricados. El C usa NULL como hoja negra; el backend presenta vistas NIL. 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 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 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 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.
Condición, ruta y caso
Pila recursiva y retornos
Invariante y evidencia
Variables C
Memoria y enlaces
Relaciones estructurales
6 Comparar
Elige una pareja o una estructura contra todas. Cada una recibe una copia independiente de la misma entrada inmutable; los pasos corresponden a inserciones completas.
Prepara una comparación para estudiar forma, altura, orden e invariante después de cada inserción.
7 Reflexionar
Consola C (printf)
Programa principal del historial
Estructura del TAD
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;