Las estructuras de datos son fundamentales en la programación, y una de las más conocidas es la Pila (o Stack en inglés). Si estás aprendiendo C o simplemente quieres repasar tus conocimientos, este artículo te guiará paso a paso para crear tu propia implementación de una pila utilizando arreglos.
¿Qué es una Pila?
Una pila es una estructura de datos lineal que sigue el principio LIFO (Last In, First Out), que en español significa «Último en Entrar, Primero en Salir». Imagina una pila de platos: el último plato que colocas encima es el primero que quitas.

Las operaciones básicas que se pueden realizar en una pila son:
- Apilar (PUSH): Añadir un elemento en el tope de la pila.
- Desapilar (POP): Eliminar y devolver el elemento del tope de la pila.
- Ver Tope (PEEK o TOP): Consultar el elemento del tope sin eliminarlo.
Además, suelen ser útiles otras operaciones como comprobar si la pila está vacía o llena, y obtener el número de elementos.
Preparando el Entorno en C
Para nuestro programa en C, necesitaremos incluir algunas bibliotecas estándar que nos proveerán funciones útiles:
stdio.h: Para funciones de entrada/salida estándar, comoprintf(para imprimir en consola).stdlib.h: Para funciones de utilidad general, incluyendo la gestión de memoria dinámica comomallocyfree.limits.h: Para constantes que definen los límites de los tipos de datos enteros, comoINT_MIN(el mínimo valor para unint), que usaremos para señalar errores en ciertas operaciones.
Asegúrate de tener estas líneas al inicio de tu archivo .c:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h> // Para INT_MIN
Definiendo la Estructura de la Pila
Para representar nuestra pila, definiremos una estructura (struct) en C. Esta estructura contendrá:
tope: Un entero que indica la posición del elemento en el tope de la pila (será un índice del arreglo).capacidad: Un entero que almacena el tamaño máximo de la pila.arreglo: Un puntero a un entero, que será el arreglo donde guardaremos los elementos de la pila.
struct Pila {
int tope;
int capacidad;
int* arreglo; // Usaremos un puntero para asignar memoria dinámicamente
};
Funciones Auxiliares Iniciales
Crear una Pila (crearPila)
Esta función inicializará una nueva pila con una capacidad dada. Es importante manejar la asignación de memoria correctamente:
struct Pila* crearPila(int capacidad) {
// 1. Asignar memoria para la estructura Pila
struct Pila* pila = (struct Pila*)malloc(sizeof(struct Pila));
if (pila == NULL) { // Verificar si malloc falló
perror("Error al asignar memoria para la estructura Pila");
return NULL; // Retornar NULL indica que la creación falló
}
pila->capacidad = capacidad;
pila->tope = -1; // Inicialmente, la pila está vacía. -1 indica que no hay elementos.
// 2. Asignar memoria para el arreglo que contendrá los elementos
pila->arreglo = (int*)malloc(pila->capacidad * sizeof(int));
if (pila->arreglo == NULL) { // Verificar si malloc falló
perror("Error al asignar memoria para el arreglo de la Pila");
free(pila); // Liberar la memoria asignada para la estructura Pila antes de salir
return NULL;
}
printf("Pila creada exitosamente con capacidad %d.\n", capacidad);
return pila;
}
Nota sobre malloc: malloc intenta reservar un bloque de memoria. Si no hay suficiente memoria disponible, devuelve NULL. Es una buena práctica siempre verificar el valor devuelto por malloc.
Comprobar el Estado de la Pila
Estas funciones nos ayudarán a saber si la pila ha alcanzado su capacidad máxima o si no contiene elementos.
// Devuelve 1 (verdadero) si la pila está llena, 0 (falso) en caso contrario
int estaLlena(struct Pila* pila) {
return pila->tope == pila->capacidad - 1;
}
// Devuelve 1 (verdadero) si la pila está vacía, 0 (falso) en caso contrario
int estaVacia(struct Pila* pila) {
return pila->tope == -1;
}
Operaciones Básicas de la Pila
Apilar (PUSH)
La operación PUSH añade un elemento al tope de la pila. Primero, debemos verificar si la pila está llena. Si no lo está, incrementamos tope y guardamos el elemento.
Modificaremos esta función para que devuelva un valor indicando si la operación fue exitosa.
// Añade un elemento a la pila. Devuelve 1 si fue exitoso, 0 si la pila está llena.
int PUSH(struct Pila* pila, int elemento) {
if (estaLlena(pila)) {
printf("Error: Desbordamiento de Pila (Stack Overflow). No se pudo insertar %d.\n", elemento);
return 0; // Indicar fallo
}
pila->arreglo[++pila->tope] = elemento; // Incrementa tope y luego asigna
// printf("%d apilado en la pila.\n", elemento); // Descomentar para depuración
return 1; // Indicar éxito
}
Desapilar (POP)
La operación POP elimina y devuelve el elemento del tope. Si la pila está vacía, no podemos desapilar nada. En este caso, devolveremos INT_MIN (definido en <limits.h>) para indicar un error o una condición especial.
// Elimina y devuelve el elemento del tope de la pila.
// Devuelve INT_MIN si la pila está vacía (subdesbordamiento).
int POP(struct Pila* pila) {
if (estaVacia(pila)) {
printf("Error: Subdesbordamiento de Pila (Stack Underflow).\n");
return INT_MIN; // INT_MIN es el valor entero más pequeño, usado aquí para indicar error.
}
// printf("%d desapilado de la pila.\n", pila->arreglo[pila->tope]); // Descomentar para depuración
return pila->arreglo[pila->tope--]; // Devuelve el elemento y luego decrementa tope
}
Ver Tope (PEEK)
La función PEEK nos permite ver el elemento en el tope sin modificar la pila. Similar a POP, si la pila está vacía, devolveremos INT_MIN.
// Devuelve el elemento del tope de la pila sin eliminarlo.
// Devuelve INT_MIN si la pila está vacía.
int PEEK(struct Pila* pila) {
if (estaVacia(pila)) {
// printf("Error: La pila está vacía. No se puede hacer PEEK.\n"); // Opcional
return INT_MIN;
}
return pila->arreglo[pila->tope];
}
Esta implementación es más directa y segura que una que haga POP y luego PUSH, ya que no modifica el estado de la pila ni corre el riesgo de fallar si la pila estuviera llena después de un POP (un caso muy específico pero posible).
Operaciones Complementarias
Imprimir la Pila (imprimirPila)
Para visualizar el contenido de nuestra pila.
void imprimirPila(struct Pila* pila) {
if (estaVacia(pila)) {
printf("La pila está vacía.\n");
return;
}
printf("Elementos en la pila (desde el fondo hasta el tope):\n");
for (int i = 0; i <= pila->tope; i++) {
printf("%d ", pila->arreglo[i]);
}
printf("\n");
}
Buscar un Dato en la Pila (buscarEnPila)
Esta función buscará un valor específico y devolverá su índice (posición desde el fondo, basado en 0) si lo encuentra, o -1 si no.
// Busca un valor en la pila. Devuelve el índice (0-basado) si se encuentra, -1 en caso contrario.
int buscarEnPila(struct Pila* pila, int valor) {
if (estaVacia(pila)) {
return -1;
}
for (int i = 0; i <= pila->tope; i++) {
if (pila->arreglo[i] == valor) {
return i; // Devuelve el índice donde se encontró el valor
}
}
return -1; // Valor no encontrado
}
Ver Tamaño de la Pila (numElementos)
Nos dice cuántos elementos hay actualmente en la pila.
// Devuelve el número de elementos actualmente en la pila.
int numElementos(struct Pila* pila) {
// 'tope' es el índice del último elemento (0-basado).
// Si tope es -1 (vacía), tope + 1 = 0 elementos.
// Si tope es 0 (1 elemento), tope + 1 = 1 elemento.
return pila->tope + 1;
}
Liberando la Memoria (destruirPila)
Cuando ya no necesitemos la pila, es crucial liberar la memoria que asignamos dinámicamente con malloc. Si no lo hacemos, tendremos una «fuga de memoria» (memory leak).
// Libera la memoria asignada a la pila.
void destruirPila(struct Pila* pila) {
if (pila != NULL) {
if (pila->arreglo != NULL) {
free(pila->arreglo); // Primero liberar el arreglo
pila->arreglo = NULL; // Buena práctica: poner a NULL después de free
}
free(pila); // Luego liberar la estructura de la pila
pila = NULL; // Buena práctica
printf("Pila destruida y memoria liberada.\n");
}
}
Ejemplo Completo y Uso
Ahora, pongamos todo junto en una función main para ver cómo funciona nuestra pila.
#include <stdio.h>
#include <stdlib.h>
#include <limits.h> // Para INT_MIN
// Definición de la estructura Pila
struct Pila {
int tope;
int capacidad;
int* arreglo;
};
// --- Prototipos de las funciones (declaraciones) ---
// Es buena práctica declarar los prototipos antes de main si las definiciones están después
struct Pila* crearPila(int capacidad);
int estaLlena(struct Pila* pila);
int estaVacia(struct Pila* pila);
int PUSH(struct Pila* pila, int elemento);
int POP(struct Pila* pila);
int PEEK(struct Pila* pila);
void imprimirPila(struct Pila* pila);
int buscarEnPila(struct Pila* pila, int valor);
int numElementos(struct Pila* pila);
void destruirPila(struct Pila* pila);
// --- Función Principal (main) ---
int main() {
struct Pila* miPila = crearPila(5); // Creamos una pila con capacidad para 5 elementos
if (miPila == NULL) {
printf("No se pudo crear la pila. Terminando programa.\n");
return 1; // Salir con código de error
}
printf("\n--- Operaciones PUSH ---\n");
PUSH(miPila, 10); // Pila: [10]
PUSH(miPila, 20); // Pila: [10, 20]
PUSH(miPila, 30); // Pila: [10, 20, 30]
imprimirPila(miPila);
printf("Elemento en el tope (PEEK): %d\n", PEEK(miPila));
printf("Número de elementos: %d\n", numElementos(miPila));
printf("\n--- Operaciones POP ---\n");
printf("Elemento desapilado: %d\n", POP(miPila)); // Saca 30. Pila: [10, 20]
imprimirPila(miPila);
printf("Elemento en el tope (PEEK): %d\n", PEEK(miPila));
printf("\n--- Más PUSH e intento de desbordamiento ---\n");
PUSH(miPila, 40); // Pila: [10, 20, 40]
PUSH(miPila, 50); // Pila: [10, 20, 40, 50]
PUSH(miPila, 60); // Pila: [10, 20, 40, 50, 60] ¡Llena!
imprimirPila(miPila);
printf("¿Está llena? %s\n", estaLlena(miPila) ? "Sí" : "No");
PUSH(miPila, 70); // Intento de PUSH en pila llena (mostrará error)
printf("\n--- Búsqueda y más POPs ---\n");
int valorABuscar = 20;
int indice = buscarEnPila(miPila, valorABuscar);
if (indice != -1) {
printf("El valor %d se encuentra en el índice %d.\n", valorABuscar, indice);
} else {
printf("El valor %d no se encuentra en la pila.\n", valorABuscar);
}
printf("Desapilando todos los elementos:\n");
while (!estaVacia(miPila)) {
printf("Desapilado: %d\n", POP(miPila));
}
imprimirPila(miPila);
printf("¿Está vacía? %s\n", estaVacia(miPila) ? "Sí" : "No");
POP(miPila); // Intento de POP en pila vacía (mostrará error)
// Liberar la memoria
destruirPila(miPila);
miPila = NULL; // Buena práctica para evitar usar un puntero que ya no es válido
return 0; // Terminar programa exitosamente
}
// --- Definiciones de las funciones ---
// (Aquí irían las definiciones de crearPila, estaLlena, estaVacia, PUSH, POP, PEEK, etc.,
// que ya hemos visto arriba. Por brevedad, no las repetiré aquí, pero en un archivo .c
// completo, estarían aquí o antes de main si no se usan prototipos.)
// ... (copia aquí las definiciones de las funciones que mostramos antes) ...
// Ejemplo de una definición (las demás seguirían el mismo patrón):
struct Pila* crearPila(int capacidad) {
struct Pila* pila = (struct Pila*)malloc(sizeof(struct Pila));
if (pila == NULL) {
perror("Error al asignar memoria para la estructura Pila");
return NULL;
}
pila->capacidad = capacidad;
pila->tope = -1;
pila->arreglo = (int*)malloc(pila->capacidad * sizeof(int));
if (pila->arreglo == NULL) {
perror("Error al asignar memoria para el arreglo de la Pila");
free(pila);
return NULL;
}
// printf("Pila creada exitosamente con capacidad %d.\n", capacidad); // Movido a la función main para evitar redundancia si se llama múltiples veces
return pila;
}
int estaLlena(struct Pila* pila) {
return pila->tope == pila->capacidad - 1;
}
int estaVacia(struct Pila* pila) {
return pila->tope == -1;
}
int PUSH(struct Pila* pila, int elemento) {
if (pila == NULL) return 0; // Añadida verificación por si la pila no se pudo crear
if (estaLlena(pila)) {
printf("Error: Desbordamiento de Pila (Stack Overflow). No se pudo insertar %d.\n", elemento);
return 0;
}
pila->arreglo[++pila->tope] = elemento;
return 1;
}
int POP(struct Pila* pila) {
if (pila == NULL || estaVacia(pila)) { // Añadida verificación por si la pila no se pudo crear
// printf("Error: Subdesbordamiento de Pila (Stack Underflow) o pila no inicializada.\n"); // El mensaje de error ya está en estaVacia
if (pila != NULL && estaVacia(pila)) printf("Error: Subdesbordamiento de Pila (Stack Underflow).\n");
else if (pila == NULL) printf("Error: Pila no inicializada.\n");
return INT_MIN;
}
return pila->arreglo[pila->tope--];
}
int PEEK(struct Pila* pila) {
if (pila == NULL || estaVacia(pila)) {
if (pila != NULL && estaVacia(pila)) printf("Error: La pila está vacía. No se puede hacer PEEK.\n");
else if (pila == NULL) printf("Error: Pila no inicializada para PEEK.\n");
return INT_MIN;
}
return pila->arreglo[pila->tope];
}
void imprimirPila(struct Pila* pila) {
if (pila == NULL) {
printf("Error: Pila no inicializada para imprimir.\n");
return;
}
if (estaVacia(pila)) {
printf("La pila está vacía.\n");
return;
}
printf("Elementos en la pila (desde el fondo hasta el tope):\n");
for (int i = 0; i <= pila->tope; i++) {
printf("%d ", pila->arreglo[i]);
}
printf("\n");
}
int buscarEnPila(struct Pila* pila, int valor) {
if (pila == NULL || estaVacia(pila)) {
return -1;
}
for (int i = 0; i <= pila->tope; i++) {
if (pila->arreglo[i] == valor) {
return i;
}
}
return -1;
}
int numElementos(struct Pila* pila) {
if (pila == NULL) return 0;
return pila->tope + 1;
}
void destruirPila(struct Pila* pila) {
if (pila != NULL) {
if (pila->arreglo != NULL) {
free(pila->arreglo);
pila->arreglo = NULL;
}
free(pila);
// pila = NULL; // El puntero original en main se pondrá a NULL
printf("Pila destruida y memoria liberada.\n");
}
}
Para compilar y ejecutar:
- Guarda el código anterior como un archivo (por ejemplo,
pila_arreglos.c). - Compila usando un compilador de C como GCC:
gcc pila_arreglos.c -o pila_programa - Ejecuta:
./pila_programa(en Linux/macOS) opila_programa.exe(en Windows).
Conclusión
Crear una pila utilizando arreglos en C es un excelente ejercicio para entender tanto la estructura de datos como conceptos importantes de C como los punteros, la memoria dinámica y las estructuras. Esta implementación es sencilla y eficiente para muchas aplicaciones.
Sin embargo, tiene una limitación principal: el tamaño de la pila es fijo una vez que se crea. Si necesitas una pila que pueda crecer o decrecer dinámicamente sin un límite predefinido (más allá de la memoria disponible), podrías considerar implementar una pila utilizando listas enlazadas.
Espero que esta guía te haya sido útil.
Deja una respuesta