Para implementar una lista enlazada en C, comenzamos definiendo las estructuras necesarias. En nuestro caso, trabajaremos con una estructura llamada libro para almacenar datos, y una estructura nodo que contendrá un libro y un puntero al siguiente nodo. Esto nos permite enlazar los nodos formando la lista. Además, definiremos una estructura lista que mantendrá un puntero a la cabeza de la lista y un campo para la longitud, facilitando el manejo y conteo de elementos.
Para crear un nuevo nodo, utilizamos memoria dinámica con malloc. Copiamos los datos del libro al nodo y establecemos su puntero siguiente a NULL para evitar punteros colgantes. También es importante implementar una función para destruir nodos, liberando la memoria con free cuando ya no los necesitemos.
Insertar un elemento al principio de la lista es sencillo: creamos un nuevo nodo, hacemos que su puntero siguiente apunte a la cabeza actual de la lista y luego actualizamos la cabeza para que apunte a este nuevo nodo. Así, el nuevo nodo queda al inicio.
Para insertar al final, debemos recorrer la lista hasta encontrar el último nodo, cuyo puntero siguiente es NULL. Si la lista está vacía, simplemente asignamos el nuevo nodo como cabeza. En caso contrario, avanzamos con un puntero hasta el último nodo y enlazamos el nuevo nodo al final.
También podemos insertar un nodo después de una posición específica n. Primero, creamos el nodo, luego verificamos si la lista está vacía; si es así, simplemente agregamos el nodo. Si no, recorremos la lista hasta la posición n, teniendo cuidado de no superar el final. Para insertar, enlazamos el nuevo nodo con el siguiente del nodo en la posición n y luego actualizamos el puntero siguiente del nodo en n para que apunte al nuevo nodo.
Para acceder a un libro en la posición n, recorremos la lista incrementando un contador hasta llegar a esa posición. Si la lista está vacía o n es mayor que la longitud, devolvemos NULL para indicar que no existe ese elemento. En caso contrario, devolvemos un puntero al libro en esa posición.
Contar los elementos puede hacerse recorriendo la lista y sumando uno por cada nodo, pero es más eficiente mantener un campo longitud en la estructura lista. Cada vez que insertamos o eliminamos un nodo, actualizamos este contador, evitando recorridos innecesarios.
Eliminar el primer elemento implica guardar un puntero a la cabeza, actualizar la cabeza para que apunte al segundo nodo y luego liberar la memoria del nodo eliminado. También decrementamos la longitud. Si la lista está vacía, no hacemos nada.
Para eliminar el último elemento, primero verificamos que la lista no esté vacía. Luego, recorremos la lista hasta el penúltimo nodo, que es aquel cuyo siguiente apunta al último nodo (que tiene su siguiente en NULL). Guardamos un puntero al último nodo, actualizamos el siguiente del penúltimo a NULL, liberamos la memoria del último nodo y decrementamos la longitud. Si la lista tiene un solo elemento, simplemente eliminamos la cabeza y actualizamos la lista a vacía.
Eliminar un nodo en una posición n requiere verificar que n esté dentro del rango válido. Si n es cero, eliminamos el primer nodo. Para otros valores, recorremos la lista hasta el nodo anterior a n, enlazamos su siguiente con el nodo que sigue al que queremos eliminar, liberamos la memoria del nodo eliminado y actualizamos la longitud.
Finalmente, podemos implementar una función para verificar si la lista está vacía comprobando si la cabeza es NULL.
Con estas operaciones básicas, podemos manejar listas enlazadas en C para almacenar y manipular colecciones de datos complejos, como listas de libros, con control eficiente de memoria y acceso dinámico a los elementos.
A continuación, mostramos un ejemplo básico de las estructuras y algunas funciones clave para ilustrar lo explicado:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char titulo[100];
char autor[100];
int anio;
} libro;
typedef struct nodo {
libro dato;
struct nodo *siguiente;
} nodo;
typedef struct {
nodo *cabeza;
int longitud;
} lista;
// Crear un nuevo nodo con los datos de un libro
nodo* crearNodo(libro l) {
nodo *nuevo = (nodo*) malloc(sizeof(nodo));
if (nuevo == NULL) {
printf("Error al asignar memoria\n");
exit(1);
}
strcpy(nuevo->dato.titulo, l.titulo);
strcpy(nuevo->dato.autor, l.autor);
nuevo->dato.anio = l.anio;
nuevo->siguiente = NULL;
return nuevo;
}
// Destruir un nodo liberando memoria
void destruirNodo(nodo *n) {
free(n);
}
// Insertar al principio de la lista
void insertarPrincipio(lista *l, libro lib) {
nodo *nuevo = crearNodo(lib);
nuevo->siguiente = l->cabeza;
l->cabeza = nuevo;
l->longitud++;
}
// Insertar al final de la lista
void insertarFinal(lista *l, libro lib) {
nodo *nuevo = crearNodo(lib);
if (l->cabeza == NULL) {
l->cabeza = nuevo;
} else {
nodo *puntero = l->cabeza;
while (puntero->siguiente != NULL) {
puntero = puntero->siguiente;
}
puntero->siguiente = nuevo;
}
l->longitud++;
}
// Obtener libro en posición n (0-based)
libro* obtenerLibro(lista *l, int n) {
if (l->cabeza == NULL || n < 0) return NULL;
nodo *puntero = l->cabeza;
int posicion = 0;
while (puntero != NULL && posicion < n) {
puntero = puntero->siguiente;
posicion++;
}
if (puntero == NULL) return NULL;
return &(puntero->dato);
}
// Eliminar el primer elemento
void eliminarPrincipio(lista *l) {
if (l->cabeza == NULL) return;
nodo *aEliminar = l->cabeza;
l->cabeza = l->cabeza->siguiente;
destruirNodo(aEliminar);
l->longitud--;
}
// Eliminar el último elemento
void eliminarFinal(lista *l) {
if (l->cabeza == NULL) return;
if (l->cabeza->siguiente == NULL) {
destruirNodo(l->cabeza);
l->cabeza = NULL;
} else {
nodo *puntero = l->cabeza;
while (puntero->siguiente->siguiente != NULL) {
puntero = puntero->siguiente;
}
destruirNodo(puntero->siguiente);
puntero->siguiente = NULL;
}
l->longitud--;
}
// Verificar si la lista está vacía
int estaVacia(lista *l) {
return l->cabeza == NULL;
}
Con estas bases, podemos extender la funcionalidad para insertar en posiciones intermedias, eliminar nodos específicos y manejar la lista de forma segura y eficiente.