1 - Introducción a las pilas (Stacks)

Visión general

Las pilas son estructuras de datos lineales que modelan el principio de apilar y desapilar como en la vida cotidiana: colocar elementos uno sobre otro y retirarlos en orden inverso. En Python las usamos como un Tipo de Dato Abstracto (TDA) definido por su comportamiento antes que por su implementación interna.

En este primer tema aclaramos de forma conceptual qué resuelven, por qué se asocian con el acrónimo LIFO (Last In, First Out) y cuándo conviene preferirlas frente a colas o listas. También revisamos ejemplos prácticos para conectar la teoría con situaciones cotidianas y con el código que construiremos en los temas siguientes.

1.1 ¿Qué es una pila?

Una pila es un contenedor donde las inserciones (push) y las extracciones (pop) ocurren en un único extremo denominado tope. No se recorren posiciones intermedias ni se permite acceder directamente a elementos alejados del tope, lo que simplifica su implementación y asegura complejidad constante en las operaciones principales.

En Python podemos representarla con una lista y un marcador implícito en su longitud, o envolverla en una clase para dejar claro el contrato:

class Pila:
  def __init__(self, capacidad=None):
    self._datos = []
    self._capacidad = capacidad

  def push(self, valor):
    if self._capacidad is not None and len(self._datos) >= self._capacidad:
      raise OverflowError("Pila llena")
    self._datos.append(valor)

  def pop(self):
    if not self._datos:
      raise IndexError("Pila vacia")
    return self._datos.pop()

  def peek(self):
    if not self._datos:
      raise IndexError("Pila vacia")
    return self._datos[-1]

  def esta_vacia(self):
    return len(self._datos) == 0

La clase anterior muestra los componentes esenciales: almacenamiento (lista interna), tope (el último elemento), y métodos que controlan el flujo de datos. También se incluyen consultas al elemento superior (peek) y verificación de pila vacía.

Para probarla solo necesitamos un pequeño script de ejemplo ejecutable con Python 3:

if __name__ == "__main__":
  pila = Pila()

  pila.push(10)
  pila.push(20)
  pila.push(30)

  print("Desapilado:", pila.pop())
  print("Desapilado:", pila.pop())
  print("Desapilado:", pila.pop())

Al ejecutarlo con python tema1_demo.py o desde un cuaderno interactivo, las impresiones permiten verificar cómo se cumple el orden LIFO.

Puede ejecutarlo en forma inmediata utilizando un editor online con un intérprete de Python:

Abrir editor online
Diagrama conceptual de operaciones push y pop
Vista conceptual de las operaciones push y pop en una pila.

1.2 LIFO (Last In, First Out)

LIFO describe el orden de servicio de la pila: el último elemento insertado es el primero en salir. Ese comportamiento aparece en dominios como la ejecución de funciones (stack de llamadas), el manejo de expresiones algebraicas y la navegación por historiales.

  1. Suponiendo una pila vacía, apilamos los valores 3, 7 y 5 en ese orden.
  2. El tope queda apuntando a 5. Si invocamos pop, se obtiene 5 y el nuevo tope pasa a ser 7.
  3. Si insertamos otro valor (por ejemplo 9), quedará sobre el 7 y será el próximo en salir.

Como no hay desplazamiento de elementos internos, cada operación requiere un paso O(1), lo que vuelve a las pilas ideales para resolver problemas que necesitan revertir el orden de datos o mantener historiales acotados.

1.3 Comparación con colas y listas

Las pilas coexisten con otras estructuras lineales. La tabla siguiente resume las diferencias más relevantes:

Característica Pila Cola Lista enlazada
Extremo de inserción Un solo extremo (tope) Final de la cola Posiciones arbitrarias mediante nodos
Extremo de extracción El mismo tope Comienzo de la cola Depende de los enlaces definidos
Acceso aleatorio No permitido No permitido Se puede recorrer nodo por nodo
Complejidad básica O(1) push/pop O(1) enqueue/dequeue Inserción/eliminación O(1) si se conoce el nodo previo

Elegir una u otra estructura depende del orden deseado: pilas para revertir, colas para mantener orden cronológico y listas cuando se requiere insertar o eliminar en posiciones intermedias.

1.4 Ventajas y limitaciones

  • Ventajas: operaciones determinísticas y O(1); facilidad para verificar invariantes; implementaciones compactas usando listas o collections.deque sin librerías externas.
  • Limitaciones: no permite buscar elementos internos ni reordenar sin desapilar; con listas muy grandes conviene medir el costo de crecer en memoria o definir una capacidad.
  • Implicancias en Python: conviene validar explícitamente pila vacía para evitar excepciones y documentar si se usará una lista o un deque según las necesidades de rendimiento.

1.5 Casos cotidianos

Las pilas son intuitivas porque emulan procesos familiares:

  • Pila de platos: los platos recién lavados se apilan sobre los anteriores y se retiran desde arriba para evitar que los inferiores soporten más peso.
  • Funcionalidad deshacer (undo): cada acción se registra en una pila, permitiendo revertirla extrayendo el último estado.
  • Navegación web: los botones Atrás/Adelante gestionan dos pilas: historial y futuras visitas. Cada visita mueve elementos entre ambas.

Comprender estos paralelismos facilita visualizar la ejecución de algoritmos en Python: cualquier flujo que requiera deshacer o volver sobre pasos previos puede traducirse al modelo de pilas con costos previsibles.