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

  1. Separa el valor almacenado del color del nodo.
  2. Observa si el paso recolorea o rota: no son la misma acción.
  3. 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

  1. Prepara una operación y predice qué cambiará.
  2. Activa Paso a paso antes de ejecutar si quieres ver la traza; sin ese modo obtienes el resultado final.
  3. Relaciona el dibujo con la línea C resaltada: acaba de ejecutarse. Una condición puede no cambiar ningún nodo.
  4. 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.

  1. 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.
  2. 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.
  3. Elige Cargar valores para añadirlos en orden con resultado final, o Agregar paso a paso para estudiar cada inserción.
  4. 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

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

Ir a la visualización