Sublista

¿Qué representa este TAD?

Una sublista organiza dos niveles: cada nodo padre tiene su propia lista de hijos. Una operación puede cambiar la lista de padres o sólo los hijos de un padre.

Prueba este ejemplo

Inserta los padres 10 y 20. Inserta los hijos 11 y 12 bajo el padre 10. Listar hijos de 10 debe mostrar esos dos valores; el padre 20 no tiene hijos.

Qué observar · tres pasos

  1. Identifica primero si la operación pide padre, hijo o ambos.
  2. Observa que añadir un hijo al padre 10 no modifica la rama del padre 20.
  3. Si eliminas el padre 10, comprueba que se liberan sus hijos antes que el padre. No hay Agregar varios en Sublista.

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.

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: objetivo, reglas y memoria

Objetivo

Modificar una rama sin afectar a las demás.

Estrategia

Localizar primero el padre y luego recorrer sus hijos.

Invariante

Cada hijo pertenece a un único padre.

Memoria dinámica

La liberación de una rama no autoriza liberar ramas vecinas.

Errores frecuentes

Glosario contextual
Nodo
Objeto dinámico con datos y uno o más enlaces.
Enlace
Campo puntero que conecta objetos.
Alias
Dos punteros que designan el mismo objeto.
LIFO
El último en entrar es el primero en salir.
FIFO
El primero en entrar es el primero en salir.
Prioridad
Criterio de selección independiente del orden físico.
Circularidad
El último enlace vuelve al inicio.
malloc
Reserva memoria; puede devolver NULL.
free
Libera una reserva que deja de ser válida.

Alcance de esta pantalla

Referencia de operaciones y datos de entrada

Operaciones de la aplicación y datos

Eliminar hijo eliminar_hijo
Padre (entero); Hijo (entero)
Eliminar padre eliminar_padre
Padre (entero)
Listar hijos hijos_de
Padre (entero)
Insertar hijo insertar_hijo
Padre (entero); Hijo (entero)
Insertar padre insertar_padre
Padre (entero)
Limpiar limpiar
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.

Listar hijos consulta el primer padre coincidente y copia como máximo 1024 hijos en la aplicación; el C respeta la capacidad del destino. El programa principal mostrado está completo e incluye stdio.h y tad_sublista.h. Cópialo en main.c y compílalo junto a tad_sublista.c, con el encabezado en el mismo directorio.

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.

Representación, enlaces y consultas
  • Estructura jerárquica secuencial: cada nodo padre mantiene su propia sublista de hijos.
  • En la animación observa dos niveles de punteros (padres e hijos) y valida que cada operación afecte solo la rama correspondiente sin corromper otras sublistas.
  • Listar hijos conserva la consulta exitosa en el historial tras recargar, sin volver a ejecutarla.
  • El programa principal mostrado está completo: cópialo en main.c; ya incluye stdio.h y tad_sublista.h, y compila junto con el archivo C descargado.
  • La aplicación copia como máximo 1024 hijos por consulta.
  • El arreglo de consulta tiene capacidad suficiente según las inserciones previas; su contenido se obtiene ejecutando el C.

Código fuente completo del TAD

Descarga el archivo C principal de Sublista y su encabezado para estudiarlo o compilarlo en tu equipo.

Referencia: estructura del TAD en C
typedef struct Sublista {
    int nro; /**< Dato entero almacenado. */
    struct Sublista *sgte; /**< Enlace al siguiente nodo. */
} Sublista;

typedef struct Nodo {
    int nro; /**< Dato entero almacenado. */
    struct Nodo *sgte; /**< Enlace al siguiente nodo. */
    Sublista *sub; /**< Cabeza de la sublista asociada. */
} Nodo;

Código C y llamadores equivalentes

Insertar padre · código C

Crea nodo padre en la lista principal de sublistas manteniendo su cadena independiente.

/**
 * @brief Inserta un nuevo nodo padre al final de la lista principal.
 * @param[in,out] lista       Doble puntero a la lista principal.
 * @param[in]     valor_padre Valor del nuevo padre.
 * @return Puntero al nuevo nodo insertado, o NULL si falla.
 * @note Admite valores repetidos como reservas distintas. Con lista NULL o malloc fallido retorna NULL sin modificar la cadena.
 */
Nodo *sublista_insertar_padre_final(Nodo **lista, int valor_padre) {
    Nodo *nuevo;
    Nodo *actual;

    if (lista == NULL) {
        return NULL;
    }

    nuevo = crear_padre(valor_padre);
    if (nuevo == NULL) {
        return NULL;
    }

    if (*lista == NULL) {
        *lista = nuevo;
        return nuevo;
    }

    actual = *lista;
    while (actual->sgte != NULL) {
        actual = actual->sgte;
    }
    actual->sgte = nuevo;
    return nuevo;
}

/**
 * @brief Crea un nuevo nodo padre.
 * @param[in] valor Valor entero a almacenar.
 * @return Puntero al nuevo nodo, o NULL si falla la memoria.
 * @note Inicializa nro con valor, sgte y sub en NULL; si malloc falla no hay reserva que liberar.
 */
static Nodo *crear_padre(int valor) {
    Nodo *nuevo = (Nodo *)malloc(sizeof(Nodo));
    if (nuevo == NULL) {
        return NULL;
    }
    nuevo->nro = valor;
    nuevo->sgte = NULL;
    nuevo->sub = NULL;
    return nuevo;
}
Insertar hijo · código C

Localiza el padre objetivo y agrega el hijo en su sublista, sin afectar otros padres.

/**
 * @brief Busca el padre por valor e inserta al final de su sublista.
 * @param[in,out] lista Lista principal de padres.
 * @param[in] valor_padre Valor del padre destino.
 * @param[in] valor_hijo Valor del nuevo hijo.
 * @return true si el padre existe y el hijo se inserta; false en otro caso.
 * @note Busca el primer padre coincidente. Si no existe o falla la reserva retorna false, sin crear hijos huérfanos ni modificar otras ramas.
 */
bool sublista_insertar_hijo(Nodo *lista, int valor_padre, int valor_hijo) {
    Nodo *padre = sublista_buscar_padre(lista, valor_padre);
    if (padre == NULL) {
        return false;
    }
    return sublista_insertar_hijo_final(padre, valor_hijo);
}

/**
 * @brief Busca un nodo padre por su valor.
 * @param[in] lista       Puntero al primer nodo de la lista.
 * @param[in] valor_padre Valor a buscar.
 * @return Puntero al nodo padre encontrado, o NULL si no existe.
 * @note Devuelve la primera coincidencia desde la cabeza. Con lista NULL retorna NULL; no modifica nodos ni enlaces.
 */
Nodo *sublista_buscar_padre(Nodo *lista, int valor_padre) {
    Nodo *actual = lista;
    while (actual != NULL) {
        if (actual->nro == valor_padre) {
            return actual;
        }
        actual = actual->sgte;
    }
    return NULL;
}

/**
 * @brief Inserta un nuevo nodo hijo al final de la sublista de un padre.
 * @param[in,out] padre      Puntero al nodo padre.
 * @param[in]     valor_hijo Valor del nuevo hijo.
 * @return true si se insertó exitosamente, false en caso de error.
 * @note Con padre NULL o malloc fallido retorna false sin cambiar enlaces. Los hijos repetidos ocupan reservas distintas.
 */
bool sublista_insertar_hijo_final(Nodo *padre, int valor_hijo) {
    Sublista *nuevo;
    Sublista *actual;

    if (padre == NULL) {
        return false;
    }

    nuevo = crear_hijo(valor_hijo);
    if (nuevo == NULL) {
        return false;
    }

    if (padre->sub == NULL) {
        padre->sub = nuevo;
        return true;
    }

    actual = padre->sub;
    while (actual->sgte != NULL) {
        actual = actual->sgte;
    }
    actual->sgte = nuevo;
    return true;
}

/**
 * @brief Crea un nuevo nodo hijo para una sublista.
 * @param[in] valor Valor entero a almacenar.
 * @return Puntero a la nueva sublista, o NULL si falla la memoria.
 * @note Inicializa nro con valor y sgte en NULL; si malloc falla retorna NULL sin publicar un enlace.
 */
static Sublista *crear_hijo(int valor) {
    Sublista *nuevo = (Sublista *)malloc(sizeof(Sublista));
    if (nuevo == NULL) {
        return NULL;
    }
    nuevo->nro = valor;
    nuevo->sgte = NULL;
    return nuevo;
}
Eliminar padre · código C

Elimina el primer padre coincidente: libera sus hijos uno a uno y luego ese padre, conservando las demás ramas.

/**
 * @brief Elimina la primera ocurrencia de un nodo padre y todos sus hijos.
 * @param[in,out] lista       Doble puntero a la lista.
 * @param[in]     valor_padre Valor del padre a eliminar.
 * @return true si fue eliminado con éxito, false si no se encontró.
 * @note Libera los hijos uno a uno y después el primer padre coincidente; conserva los demás padres. Con lista NULL o cabeza NULL retorna false sin cambios. Alias externos a las reservas liberadas dejan de ser válidos.
 */
bool sublista_eliminar_padre_primero(Nodo **lista, int valor_padre) {
    Nodo *actual;
    Nodo *anterior = NULL;

    if (lista == NULL || *lista == NULL) {
        return false;
    }

    actual = *lista;
    while (actual != NULL) {
        if (actual->nro == valor_padre) {
            if (anterior == NULL) {
                *lista = actual->sgte;
            } else {
                anterior->sgte = actual->sgte;
            }
            destruir_hijos(&actual->sub);
            free(actual);
            return true;
        }
        anterior = actual;
        actual = actual->sgte;
    }

    return false;
}

/**
 * @brief Libera la memoria de todos los nodos en una sublista de hijos.
 * @param[in,out] lista_hijos Doble puntero al primer nodo hijo.
 * @note Con lista_hijos NULL no accede a memoria; con una cadena válida libera cada hijo y deja su cabeza NULL. No libera el padre.
 */
static void destruir_hijos(Sublista **lista_hijos) {
    Sublista *actual;
    Sublista *next;

    if (lista_hijos == NULL) {
        return;
    }

    actual = *lista_hijos;
    while (actual != NULL) {
        next = actual->sgte;
        *lista_hijos = next;
        free(actual);
        actual = next;
    }
}
Eliminar hijo · código C

Dentro del padre indicado, busca el hijo por valor y lo elimina ajustando enlaces locales.

/**
 * @brief Busca el padre por valor y elimina la primera coincidencia de hijo.
 * @param[in,out] lista Lista principal de padres.
 * @param[in] valor_padre Valor del padre destino.
 * @param[in] valor_hijo Valor del hijo a eliminar.
 * @return true si se elimina un hijo; false si no existe padre o hijo.
 * @note Opera sobre el primer padre coincidente; no elimina otras coincidencias.
 * @note La lista se pasa por valor, pero los enlaces de sus hijos pueden cambiar.
 */
bool sublista_eliminar_hijo(Nodo *lista, int valor_padre, int valor_hijo) {
    Nodo *padre = sublista_buscar_padre(lista, valor_padre);
    if (padre == NULL) {
        return false;
    }
    return sublista_eliminar_hijo_primero(padre, valor_hijo);
}

/**
 * @brief Busca un nodo padre por su valor.
 * @param[in] lista       Puntero al primer nodo de la lista.
 * @param[in] valor_padre Valor a buscar.
 * @return Puntero al nodo padre encontrado, o NULL si no existe.
 * @note Devuelve la primera coincidencia desde la cabeza. Con lista NULL retorna NULL; no modifica nodos ni enlaces.
 */
Nodo *sublista_buscar_padre(Nodo *lista, int valor_padre) {
    Nodo *actual = lista;
    while (actual != NULL) {
        if (actual->nro == valor_padre) {
            return actual;
        }
        actual = actual->sgte;
    }
    return NULL;
}

/**
 * @brief Elimina la primera ocurrencia de un hijo en la sublista de un padre.
 * @param[in,out] padre      Puntero al nodo padre.
 * @param[in]     valor_hijo Valor del hijo a eliminar.
 * @return true si se eliminó correctamente, false si no se encontró.
 * @note Libera solo la primera coincidencia; conserva el padre y los demás hijos.
 * @note Con padre NULL o sin hijos retorna false sin modificar memoria.
 */
bool sublista_eliminar_hijo_primero(Nodo *padre, int valor_hijo) {
    Sublista *actual;
    Sublista *anterior = NULL;

    if (padre == NULL || padre->sub == NULL) {
        return false;
    }

    actual = padre->sub;
    while (actual != NULL) {
        if (actual->nro == valor_hijo) {
            if (anterior == NULL) {
                padre->sub = actual->sgte;
            } else {
                anterior->sgte = actual->sgte;
            }
            free(actual);
            return true;
        }
        anterior = actual;
        actual = actual->sgte;
    }

    return false;
}
Listar hijos · código C

Devuelve los hijos del primer padre coincidente sin modificar la estructura. El C copia como máximo la capacidad del arreglo destino y retorna la cantidad copiada.

/**
 * @brief Copia los hijos del primer padre con el valor indicado.
 * @param[in] lista Lista principal de padres.
 * @param[in] valor_padre Valor del padre buscado.
 * @param[out] destino Arreglo que recibe los valores de los hijos.
 * @param[in] capacidad Capacidad del arreglo destino.
 * @return Cantidad copiada, o -1 si no existe el padre.
 * @note Consulta solo el primer padre coincidente. Si no existe retorna -1; si existe pero destino es NULL o capacidad no positiva retorna 0 sin escribir. No modifica la estructura.
 */
int sublista_obtener_hijos(Nodo *lista, int valor_padre, int *destino, int capacidad) {
    Nodo *padre = sublista_buscar_padre(lista, valor_padre);
    if (padre == NULL) {
        return -1;
    }
    return sublista_copiar_hijos(padre, destino, capacidad);
}

/**
 * @brief Busca un nodo padre por su valor.
 * @param[in] lista       Puntero al primer nodo de la lista.
 * @param[in] valor_padre Valor a buscar.
 * @return Puntero al nodo padre encontrado, o NULL si no existe.
 * @note Devuelve la primera coincidencia desde la cabeza. Con lista NULL retorna NULL; no modifica nodos ni enlaces.
 */
Nodo *sublista_buscar_padre(Nodo *lista, int valor_padre) {
    Nodo *actual = lista;
    while (actual != NULL) {
        if (actual->nro == valor_padre) {
            return actual;
        }
        actual = actual->sgte;
    }
    return NULL;
}

/**
 * @brief Copia los valores de los hijos de un padre a un arreglo.
 * @param[in]  padre     Puntero constante al padre.
 * @param[out] destino   Arreglo donde se copiarán los valores.
 * @param[in]  capacidad Tamaño máximo del arreglo destino.
 * @return El número de elementos copiados.
 * @note Copia como máximo capacidad valores y retorna los copiados, no el total de hijos si el arreglo es menor. Con padre NULL, destino NULL o capacidad no positiva retorna 0 sin escribir. Las posiciones restantes del arreglo no se inicializan.
 */
int sublista_copiar_hijos(const Nodo *padre, int *destino, int capacidad) {
    int usados = 0;
    const Sublista *actual;

    if (padre == NULL || destino == NULL || capacidad <= 0) {
        return 0;
    }

    actual = padre->sub;
    while (actual != NULL && usados < capacidad) {
        destino[usados] = actual->nro;
        usados++;
        actual = actual->sgte;
    }
    return usados;
}
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 completamente la memoria de todos los padres y sus respectivos hijos.
 * @param[in,out] lista Doble puntero a la lista principal.
 * @note Con lista NULL no accede a memoria. Libera los hijos de cada padre antes de ese padre, deja la cabeza NULL y no altera listas ajenas. Alias externos a nodos liberados dejan de ser válidos.
 */
void sublista_destruir(Nodo **lista) {
    Nodo *actual;
    Nodo *next;

    if (lista == NULL) {
        return;
    }

    actual = *lista;
    while (actual != NULL) {
        next = actual->sgte;
        destruir_hijos(&actual->sub);
        *lista = next;
        free(actual);
        actual = next;
    }
}

/**
 * @brief Libera la memoria de todos los nodos en una sublista de hijos.
 * @param[in,out] lista_hijos Doble puntero al primer nodo hijo.
 * @note Con lista_hijos NULL no accede a memoria; con una cadena válida libera cada hijo y deja su cabeza NULL. No libera el padre.
 */
static void destruir_hijos(Sublista **lista_hijos) {
    Sublista *actual;
    Sublista *next;

    if (lista_hijos == NULL) {
        return;
    }

    actual = *lista_hijos;
    while (actual != NULL) {
        next = actual->sgte;
        *lista_hijos = next;
        free(actual);
        actual = next;
    }
}

Ir a la visualización