Cuando desarrollas aplicaciones, almacenar datos en una lista simple no siempre es suficiente. A menudo necesitas imponer un orden muy estricto sobre cómo se procesan esos datos. Si estás diseñando la función «Deshacer» (Ctrl+Z) de un editor de texto, el último cambio realizado debe ser el primero en eliminarse. Si estás gestionando las descargas de archivos de tu navegador, el primer archivo en la lista de espera debería comenzar a descargarse primero.
Para resolver estas necesidades de flujo de trabajo de forma eficiente, la informática define dos estructuras de datos lineales fundamentales: las pilas (stacks) y las colas (queues). En este tutorial vamos a profundizar en su funcionamiento teórico, analizaremos las ventajas y trampas de rendimiento al implementarlas en Python, y aprenderás a escribir código profesional utilizando las herramientas más óptimas del lenguaje.
1. Pilas (Stacks): El Principio LIFO
Una pila es una estructura de datos que sigue el principio LIFO (Last In, First Out), es decir, el último elemento en entrar es el primero en salir.
La analogía más clara en el mundo físico es una pila de platos: colocas nuevos platos en la parte superior y, cuando necesitas uno, retiras el que está arriba de todo. No puedes sacar un plato del medio o del fondo de la pila sin riesgo de romper todos los demás.
Operaciones Fundamentales de una Pila
- Push (Apilar): Añadir un nuevo elemento en la parte superior de la pila.
- Pop (Desapilar): Eliminar y devolver el elemento de la parte superior de la pila.
- Peek/Top (Ver tope): Consultar qué elemento está en la cima sin llegar a extraerlo.
Implementación de una Pila en Python
En Python, no necesitas librerías externas para implementar una pila: las listas nativas (list) son perfectas para este propósito. Los métodos integrados .append() y .pop() añaden y eliminan elementos al final de la lista de forma ultra eficiente, con un tiempo constante promedio de O(1).
# Crear una pila vacía
pila_navegacion = []
# Push: Apilar páginas visitadas
pila_navegacion.append("todopython.com")
pila_navegacion.append("todopython.com/arrays-listas-python")
pila_navegacion.append("todopython.com/tablas-hash-python")
print("Historial actual:", pila_navegacion)
# Peek: Consultar la página activa sin salir de ella
pagina_activa = pila_navegacion[-1]
print("Página activa (Tope):", pagina_activa)
# Pop: Volver atrás (desapilar)
ultima_pagina = pila_navegacion.pop()
print(f"Retrocediendo de: {ultima_pagina}")
print("Historial restante:", pila_navegacion)
2. Colas (Queues): El Principio FIFO
Una cola es una estructura de datos que sigue el principio FIFO (First In, First Out), es decir, el primer elemento en entrar es el primero en salir.
La analogía obvia es la cola de un supermercado: la primera persona que llega a la caja es la primera en pagar y marcharse. Los nuevos clientes deben sumarse al final de la fila y esperar pacientemente su turno.
Operaciones Fundamentales de una Cola
- Enqueue (Encolar): Añadir un elemento al final de la cola.
- Dequeue (Desencolar): Eliminar y devolver el primer elemento de la cola.
La Trampa de Rendimiento de las Listas Nativas
Aunque es tentador implementar una cola usando una lista nativa aplicando .append() para encolar y .pop(0) para desencolar, esta práctica es un anti-patrón de rendimiento grave en Python.
¿Por qué? Las listas en Python se almacenan en bloques de memoria contiguos como arrays dinámicos. Cuando eliminas el primer elemento usando .pop(0), Python se ve obligado a desplazar todos los elementos restantes una posición a la izquierda en la memoria. Esto convierte una operación simple en una de complejidad lineal O(N). Si tu cola tiene diez mil elementos, desencolar ralentizará drásticamente tu aplicación.
La Solución Profesional: collections.deque
Para solucionar este problema de rendimiento, el módulo estándar de Python incluye la clase deque (double-ended queue o cola de dos extremos). Internamente, deque está implementada como una lista doblemente enlazada, lo que permite insertar y extraer elementos por ambos extremos (tanto izquierdo como derecho) en un tiempo constante garantizado de O(1).
from collections import deque
# Crear una cola eficiente
cola_supermercado = deque()
# Enqueue: Encolar clientes al final de la fila
cola_supermercado.append("Ana")
cola_supermercado.append("Roberto")
cola_supermercado.append("Sofía")
print("Fila de espera:", list(cola_supermercado))
# Dequeue: Atender al cliente que está primero (extracción por la izquierda)
cliente_atendido = cola_supermercado.popleft()
print(f"Atendiendo a: {cliente_atendido}")
print("Fila restante:", list(cola_supermercado))
3. Ejemplos Prácticos Reales
Para asentar estos conceptos, vamos a desarrollar dos sistemas listos para integrarse en cualquier aplicación real.
A. Pila: Implementación de un «Gestor de Historial y Deshacer»
En este ejemplo, creamos un simulador de editor de texto que almacena los cambios en una pila para permitir al usuario deshacer sus acciones (Ctrl+Z):
class EditorTexto:
def __init__(self):
self.contenido = ""
# Pila para almacenar los estados previos
self.pila_deshacer = []
def escribir(self, nuevo_texto):
# Apilamos el estado actual antes de modificarlo
self.pila_deshacer.append(self.contenido)
self.contenido += nuevo_texto
def deshacer(self):
if self.pila_deshacer:
# Desapilamos el último estado guardado
self.contenido = self.pila_deshacer.pop()
else:
print("No hay acciones que deshacer.")
# Demostración del flujo de trabajo
editor = EditorTexto()
editor.escribir("Hola ")
editor.escribir("Mundo ")
editor.escribir("de Python.")
print("Contenido:", editor.contenido) # Salida: Hola Mundo de Python.
editor.deshacer()
print("Tras deshacer (Ctrl+Z):", editor.contenido) # Salida: Hola Mundo
editor.deshacer()
print("Tras deshacer otra vez:", editor.contenido) # Salida: Hola
B. Cola: Implementación de un «Gestor de Impresión»
En este sistema, representamos un servidor de impresión que encola los trabajos entrantes y los procesa rigurosamente en orden de llegada:
from collections import deque
import time
class GestorImpresion:
def __init__(self):
# Usamos deque para asegurar eficiencia de desencolado O(1)
self.cola_trabajos = deque()
def enviar_documento(self, nombre_documento):
self.cola_trabajos.append(nombre_documento)
print(f"Trabajo encolado: '{nombre_documento}'")
def procesar_siguiente(self):
if self.cola_trabajos:
documento = self.cola_trabajos.popleft()
print(f"Imprimiendo: '{documento}'... ¡Listo!")
else:
print("No hay trabajos pendientes de impresión.")
# Demostración del servidor de impresión
impresora = GestorImpresion()
impresora.enviar_documento("Informe_Financiero.pdf")
impresora.enviar_documento("Foto_Vacaciones.jpg")
impresora.enviar_documento("Receta_Pastel.docx")
print(f"\nTrabajos en cola: {len(impresora.cola_trabajos)}")
impresora.procesar_siguiente()
impresora.procesar_siguiente()
4. Tabla de Complejidad Temporal Comparativa
Elegir la estructura de datos correcta tiene implicaciones directas en la escalabilidad de tu software. Aquí tienes una comparativa del rendimiento de las operaciones clave utilizando listas tradicionales frente a collections.deque:
| Estructura | Operación | Complejidad (list) | Complejidad (deque) |
|---|---|---|---|
| Pila (Stack) | Apilar (Push al final) | O(1) – Excelente | O(1) – Excelente |
| Pila (Stack) | Desapilar (Pop al final) | O(1) – Excelente | O(1) – Excelente |
| Cola (Queue) | Encolar (Insertar) | O(1) – Excelente | O(1) – Excelente |
| Cola (Queue) | Desencolar (popleft/pop(0)) | O(N) – Lento (Shifting) | O(1) – Excelente |
Conclusión
Las pilas y colas son estructuras esenciales que sientan las bases de algoritmos complejos de grafos, planificación de tareas y motores de juego. Implementarlas correctamente en Python marca la diferencia entre una aplicación lenta y un sistema optimizado de nivel profesional.
Recuerda la regla de oro: utiliza listas nativas para tus pilas (LIFO) y recurre siempre a collections.deque cuando necesites procesar colas (FIFO). ¡Tu código y el rendimiento de tus servidores te lo agradecerán!

