7 - Operaciones fundamentales de una pila con listas enlazadas

API dinámica centrada en nodos

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;
}

7.1 Push (insertar al inicio de la lista)

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;
}

7.2 Pop (eliminar el primer nodo)

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;
}

7.3 Peek

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;
}

7.4 isEmpty

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;
}

7.5 Impresión de nodos

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" : "");
  }
}

7.6 Liberación correcta de memoria

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.

Código completo para CLion

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.