Las pilas son una estructura de datos fundamental que se basa en el principio LIFO, que significa Last In First Out, o lo que es lo mismo, el último elemento que introducimos es el primero que sacamos. Para entenderlo mejor, podemos imaginar una torre de platos limpios en un restaurante. Cada plato que lavamos lo colocamos encima de la torre, y cuando un cliente pide un plato, le damos el que está en la cima, el último que hemos colocado, porque es el más accesible. Esto evita tener que mover todos los platos para alcanzar uno que esté más abajo.
En programación, una pila funciona exactamente igual. Los elementos se apilan siempre por un extremo, y cuando queremos obtener un elemento, accedemos al que está en la cima. Para manejar esta estructura, necesitamos implementar tres operaciones básicas. La primera es apilar, conocida como push, que consiste en introducir un elemento en la pila. Lo habitual es insertar siempre por el mismo extremo, y en el caso de usar listas enlazadas, resulta más práctico hacerlo por la cabeza de la lista.
Por ejemplo, si empezamos con una pila vacía representada por un nodo vacío, al apilar un elemento, este se inserta en la cabeza. Si apilamos un número 25, la pila tendrá un solo elemento, y si apilamos otro número, como 41, este nuevo elemento será la cima, es decir, el primero en la lista. Podemos seguir apilando más elementos de la misma forma.
Para acceder al elemento que está en la cima, usamos la operación peek, que nos devuelve el último elemento introducido sin eliminarlo. En la implementación con listas enlazadas, esto equivale a obtener la cabeza de la lista. Por ejemplo, si la pila tiene los elementos 25, 41, 79 y 24, el peek nos devolverá 24, que es el elemento en la cabeza.
La tercera operación fundamental es desapilar, o pop, que elimina el elemento que está en la cima. Si hemos insertado los elementos por la cabeza, desapilar será equivalente a eliminar la cabeza de la lista. Siguiendo el ejemplo anterior, si desapilamos, primero quitaremos el 24, luego el 79, después el 41 y finalmente el 25, quedando la pila vacía.
Además de estas operaciones, es útil implementar funciones adicionales como comprobar si la pila está vacía, para evitar errores al intentar desapilar cuando no hay elementos, y obtener el tamaño actual de la pila, para saber cuántos elementos contiene.
Aunque hemos visto cómo implementar pilas con listas enlazadas, también es posible hacerlo con arrays. En este caso, imaginemos un array de tamaño fijo n que puede almacenar hasta n elementos. Para controlar la pila, necesitamos un puntero que indique la posición libre donde se insertará el siguiente elemento.
Cuando apilamos un elemento, lo colocamos en la posición apuntada por el puntero y luego incrementamos el puntero para señalar la siguiente posición libre. Por ejemplo, si el puntero está en la posición 0, insertamos el elemento ahí y lo movemos a la posición 1. Si desapilamos, primero decrementamos el puntero para que apunte al último elemento insertado y luego lo eliminamos o lo devolvemos.
Un caso especial a tener en cuenta es cuando la pila está llena. Si intentamos apilar un elemento cuando el puntero ya apunta fuera del array, se produce un desbordamiento de pila, conocido como stackoverflow. Esto es un error que debemos detectar y manejar adecuadamente para evitar problemas en el programa.
En resumen, una pila debe permitir al menos las operaciones de apilar (push), desapilar (pop) y obtener la cima (peek). La implementación puede variar, usando listas enlazadas o arrays, pero el comportamiento siempre sigue el principio LIFO. Con estas bases, podemos construir pilas eficientes y seguras para nuestros programas.