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
- Identifica primero si la operación pide padre, hijo o ambos.
- Observa que añadir un hijo al padre 10 no modifica la rama del padre 20.
- 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
- 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.
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
- Insertar un hijo sin padre
- Compartir enlaces entre ramas
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
- No existe eliminar_todos_los_hijos de un padre en el TAD suministrado.
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;
}
}