Las listas doblemente enlazadas incorporan dos punteros en cada nodo: ant y sig. Esto habilita recorridos en ambos sentidos y eliminaciones más eficientes, a cambio de mayor complejidad de mantenimiento. Las siguientes secciones detallan cada operación básica con ejemplos en C.
typedef struct NodoDoble {
int valor;
struct NodoDoble *ant;
struct NodoDoble *sig;
} NodoDoble;
typedef struct {
NodoDoble *cabeza;
NodoDoble *cola;
} ListaDoble;
void lista_doble_inicializar(ListaDoble *lista) {
lista->cabeza = NULL;
lista->cola = NULL;
}
bool lista_doble_insertar_inicio(ListaDoble *lista, int valor) {
NodoDoble *n = malloc(sizeof(NodoDoble));
if (!n) return false;
n->valor = valor;
n->ant = NULL;
n->sig = lista->cabeza;
if (lista->cabeza) {
lista->cabeza->ant = n;
} else {
lista->cola = n;
}
lista->cabeza = n;
return true;
}
malloc y se llenan sus campos.bool lista_doble_insertar_final(ListaDoble *lista, int valor) {
NodoDoble *n = malloc(sizeof(NodoDoble));
if (!n) return false;
n->valor = valor;
n->sig = NULL;
n->ant = lista->cola;
if (lista->cola) {
lista->cola->sig = n;
} else {
lista->cabeza = n;
}
lista->cola = n;
return true;
}
Al conocer el nodo objetivo, una lista doble permite eliminarlo sin recorrer desde la cabeza. Solo se actualizan los punteros de sus vecinos.
void lista_doble_eliminar_nodo(ListaDoble *lista, NodoDoble *nodo) {
if (!nodo) return;
if (nodo->ant) {
nodo->ant->sig = nodo->sig;
} else {
lista->cabeza = nodo->sig;
}
if (nodo->sig) {
nodo->sig->ant = nodo->ant;
} else {
lista->cola = nodo->ant;
}
free(nodo);
}
ant y sig de los vecinos se reasignan para saltar al nodo eliminado.void lista_doble_imprimir(const ListaDoble *lista) {
const NodoDoble *reco = lista->cabeza;
while (reco) {
printf("%d -> ", reco->valor);
reco = reco->sig;
}
puts("NULL");
}
void lista_doble_imprimir_inverso(const ListaDoble *lista) {
const NodoDoble *reco = lista->cola;
while (reco) {
printf("%d <- ", reco->valor);
reco = reco->ant;
}
puts("NULL");
}
En listas dobles es crucial mantener sincronizados cabeza y cola. Siempre que se elimine el único nodo, ambos deben quedar en NULL. Del mismo modo, al insertar el primer elemento se inicializan de forma conjunta.
ant y sig antes de acceder: pueden ser NULL.El siguiente conjunto de archivos permite copiar y pegar en CLion para probar todas las operaciones descritas:
/* lista_doble.h */
#ifndef LISTA_DOBLE_H
#define LISTA_DOBLE_H
#include <stdbool.h>
typedef struct NodoDoble {
int valor;
struct NodoDoble *ant;
struct NodoDoble *sig;
} NodoDoble;
typedef struct {
NodoDoble *cabeza;
NodoDoble *cola;
} ListaDoble;
void lista_doble_inicializar(ListaDoble *lista);
bool lista_doble_insertar_inicio(ListaDoble *lista, int valor);
bool lista_doble_insertar_final(ListaDoble *lista, int valor);
void lista_doble_eliminar_nodo(ListaDoble *lista, NodoDoble *nodo);
void lista_doble_imprimir(const ListaDoble *lista);
void lista_doble_imprimir_inverso(const ListaDoble *lista);
void lista_doble_limpiar(ListaDoble *lista);
#endif
/* lista_doble.c */
#include "lista_doble.h"
#include <stdio.h>
#include <stdlib.h>
void lista_doble_inicializar(ListaDoble *lista) {
lista->cabeza = NULL;
lista->cola = NULL;
}
bool lista_doble_insertar_inicio(ListaDoble *lista, int valor) {
NodoDoble *n = malloc(sizeof(NodoDoble));
if (!n) return false;
n->valor = valor;
n->ant = NULL;
n->sig = lista->cabeza;
if (lista->cabeza) {
lista->cabeza->ant = n;
} else {
lista->cola = n;
}
lista->cabeza = n;
return true;
}
bool lista_doble_insertar_final(ListaDoble *lista, int valor) {
NodoDoble *n = malloc(sizeof(NodoDoble));
if (!n) return false;
n->valor = valor;
n->sig = NULL;
n->ant = lista->cola;
if (lista->cola) {
lista->cola->sig = n;
} else {
lista->cabeza = n;
}
lista->cola = n;
return true;
}
void lista_doble_eliminar_nodo(ListaDoble *lista, NodoDoble *nodo) {
if (!nodo) return;
if (nodo->ant) {
nodo->ant->sig = nodo->sig;
} else {
lista->cabeza = nodo->sig;
}
if (nodo->sig) {
nodo->sig->ant = nodo->ant;
} else {
lista->cola = nodo->ant;
}
free(nodo);
}
void lista_doble_imprimir(const ListaDoble *lista) {
const NodoDoble *reco = lista->cabeza;
while (reco) {
printf("%d -> ", reco->valor);
reco = reco->sig;
}
puts("NULL");
}
void lista_doble_imprimir_inverso(const ListaDoble *lista) {
const NodoDoble *reco = lista->cola;
while (reco) {
printf("%d <- ", reco->valor);
reco = reco->ant;
}
puts("NULL");
}
void lista_doble_limpiar(ListaDoble *lista) {
NodoDoble *reco = lista->cabeza;
while (reco) {
NodoDoble *tmp = reco->sig;
free(reco);
reco = tmp;
}
lista->cabeza = NULL;
lista->cola = NULL;
}
/* main.c */
#include "lista_doble.h"
#include <stdio.h>
int main(void) {
ListaDoble lista;
lista_doble_inicializar(&lista);
lista_doble_insertar_inicio(&lista, 10);
lista_doble_insertar_final(&lista, 20);
lista_doble_insertar_inicio(&lista, 5);
lista_doble_imprimir(&lista);
lista_doble_imprimir_inverso(&lista);
lista_doble_eliminar_nodo(&lista, lista.cabeza->sig);
lista_doble_imprimir(&lista);
lista_doble_limpiar(&lista);
return 0;
}
Copia cada sección en su archivo dentro de CLion, compila y ejecuta para comprobar el comportamiento en ambos sentidos.