Al representar pilas con listas enlazadas, cada operación LIFO se traduce en manipulaciones de punteros. A continuación se describen las funciones esenciales y el cuidado necesario para evitar fugas de memoria.
#include <stdio.h>
#include <stdlib.h>
typedef struct Nodo {
int valor;
struct Nodo *sig;
} Nodo;
typedef struct {
Nodo *top;
} Pila;
void pila_init(Pila *p) {
p->top = NULL;
}
Se reserva un nuevo nodo y se enlaza por delante del tope actual. La operación es O(1) y no requiere recorrer la lista.
int pila_push(Pila *p, int valor) {
Nodo *nuevo = malloc(sizeof(Nodo));
if (!nuevo) return 0;
nuevo->valor = valor;
nuevo->sig = p->top;
p->top = nuevo;
return 1;
}
Extrae el nodo superior, retorna su valor mediante un parámetro de salida y libera la memoria asociada.
int pila_pop(Pila *p, int *out) {
if (!p->top) return 0;
Nodo *tmp = p->top;
*out = tmp->valor;
p->top = tmp->sig;
free(tmp);
return 1;
}
Permite inspeccionar el valor del tope sin desapilarlo. Es ideal para validaciones intermedias.
int pila_peek(const Pila *p, int *out) {
if (!p->top) return 0;
*out = p->top->valor;
return 1;
}
Verifica si el tope es NULL. Centralizar esta comprobación reduce errores al escribir otras funciones.
int pila_is_empty(const Pila *p) {
return p->top == NULL;
}
Para depurar, podemos recorrer la lista desde el tope hacia abajo e imprimir cada valor. Así confirmamos la secuencia actual.
void pila_print(const Pila *p) {
printf("Pila dinamica:\n");
for (Nodo *n = p->top; n; n = n->sig) {
printf(" %d%s\n", n->valor, n == p->top ? " <- top" : "");
}
}
Antes de finalizar el programa, recorre la lista liberando cada nodo para evitar fugas:
void pila_clear(Pila *p) {
Nodo *curr = p->top;
while (curr) {
Nodo *tmp = curr;
curr = curr->sig;
free(tmp);
}
p->top = NULL;
}
En aplicaciones mayores conviene envolver esta función para integrarla con pruebas de fugas o analizadores como Valgrind.
El siguiente programa combina todas las operaciones y muestra la salida esperada:
#include <stdio.h>
#include <stdlib.h>
typedef struct Nodo {
int valor;
struct Nodo *sig;
} Nodo;
typedef struct {
Nodo *top;
} Pila;
void pila_init(Pila *p) { p->top = NULL; }
int pila_is_empty(const Pila *p) { return p->top == NULL; }
int pila_push(Pila *p, int valor) {
Nodo *nuevo = malloc(sizeof(Nodo));
if (!nuevo) return 0;
nuevo->valor = valor;
nuevo->sig = p->top;
p->top = nuevo;
return 1;
}
int pila_peek(const Pila *p, int *out) {
if (pila_is_empty(p)) return 0;
*out = p->top->valor;
return 1;
}
int pila_pop(Pila *p, int *out) {
if (pila_is_empty(p)) return 0;
Nodo *tmp = p->top;
*out = tmp->valor;
p->top = tmp->sig;
free(tmp);
return 1;
}
void pila_print(const Pila *p) {
printf("Pila actual:\n");
for (Nodo *n = p->top; n; n = n->sig) {
printf(" %d%s\n", n->valor, n == p->top ? " <- top" : "");
}
}
void pila_clear(Pila *p) {
Nodo *curr = p->top;
while (curr) {
Nodo *tmp = curr;
curr = curr->sig;
free(tmp);
}
p->top = NULL;
}
int main(void) {
Pila pila;
pila_init(&pila);
pila_push(&pila, 3);
pila_push(&pila, 6);
pila_push(&pila, 9);
pila_print(&pila);
int peek;
if (pila_peek(&pila, &peek)) {
printf("Peek: %d\n", peek);
}
int valor;
while (pila_pop(&pila, &valor)) {
printf("Pop: %d\n", valor);
}
pila_clear(&pila);
return 0;
}
Compílalo en CLion o tu IDE favorito y observarás el flujo LIFO, además de verificar la liberación completa de memoria.