Al cerrar el módulo anterior dejamos TaskFlow con un tablero de tareas sobre listas enlazadas, un reparto de turnos round-robin con listas circulares y un historial de navegación con lista doblemente enlazada. Y quedó una promesa en el aire: una estructura que se implementa "en unas pocas líneas" porque ya sabes insertar y borrar por la cabeza de una lista enlazada en O(1), y que hará posible el deshacer de TaskFlow. Esa estructura es la pila (stack), y esta lección la presenta como tipo de dato abstracto: qué es, qué contrato ofrece, dónde aparece constantemente en tu vida como programador y por qué encaja como un guante en el deshacer de TaskFlow. Todavía no implementaremos nada en detalle: primero hay que entender bien el concepto.

Contenido

  1. De la lista enlazada a la pila
  2. El principio LIFO
  3. El contrato del TDA pila
  4. Cómo funciona una pila: diagrama
  5. Pilas en la vida del programador
  6. La pila en TaskFlow: el deshacer
  7. Costes esperados del contrato

De la lista enlazada a la pila

En el módulo 2 aprendiste que en una ListaEnlazada hay dos operaciones especialmente baratas:

  • insertar_al_inicio(dato): crear un Nodo y engancharlo como nueva cabeza → O(1).
  • Borrar la cabeza: mover el puntero cabeza al siguiente nodo → O(1).

Ahora hazte esta pregunta: ¿qué pasa si te prohíbes todas las demás operaciones? Nada de insertar_en, nada de borrar por predicado en mitad de la lista, nada de recorrer para buscar. Solo puedes tocar un extremo: la cabeza.

Lo que obtienes es una estructura más restringida... y precisamente por eso más potente conceptualmente. Al limitar las operaciones, la estructura adquiere un comportamiento predecible que modela situaciones reales: la pila.

Esta idea —ganar claridad quitando libertad— es recurrente en estructuras de datos. Una pila no es "una lista a la que le faltan cosas": es un contrato distinto, con sus propias garantías, que casualmente se puede construir sobre una lista. Recuerda la distinción TDA vs. implementación del módulo 1: allí vimos PilaConLista y PilaConDiccionario como ejemplo de que un mismo contrato admite varias implementaciones. Ha llegado el momento de entender ese contrato de verdad.

El principio LIFO

Una pila es una colección donde los elementos entran y salen siempre por el mismo extremo, llamado cima (top). La consecuencia es la regla que define a la pila:

LIFO: Last In, First Out — el último en entrar es el primero en salir.

Analogía 1: la pila de platos

Imagina la pila de platos limpios de una cocina:

  • Cuando friegas un plato, lo colocas encima de la pila.
  • Cuando necesitas un plato, coges el de encima.
  • El plato del fondo puede llevar semanas ahí: solo saldrá cuando se hayan retirado todos los que tiene encima.

Nadie saca un plato del medio (acabaría todo en el suelo). Esa restricción física es exactamente la restricción lógica de la pila.

Analogía 2: las pestañas cerradas del navegador

Cuando pulsas Ctrl+Mayús+T en tu navegador para reabrir una pestaña cerrada, ¿cuál reaparece? La última que cerraste. Si lo pulsas otra vez, reaparece la anterior a esa. El navegador guarda las pestañas cerradas en una pila: cada cierre "apila" una pestaña, cada reapertura "desapila" la más reciente.

LIFO frente a FIFO (solo una mención)

Existe la política contraria, FIFO (First In, First Out, como la cola del supermercado): esa es la cola, protagonista del módulo 4. Aquí solo nos quedamos con el contraste:

Política Estructura Sale primero... Ejemplo cotidiano
LIFO Pila El último que entró Pila de platos
FIFO Cola El primero que entró Cola del supermercado

El contrato del TDA pila

Como todo TDA, la pila se define por sus operaciones, no por cómo se implementen. Este es el contrato que usaremos en todo el módulo (y los nombres exactos que implementaremos en la lección 03-03):

Operación Qué hace Qué devuelve
push(elemento) Coloca elemento en la cima Nada
pop() Retira el elemento de la cima El elemento retirado
peek() Consulta la cima sin retirarla El elemento de la cima
esta_vacia() Comprueba si no hay elementos True / False
tamano() Cuenta los elementos Un entero ≥ 0

Fíjate en tres detalles del contrato:

  • pop hace dos cosas a la vez: retira y devuelve. No existe "retirar sin saber qué retiras".
  • peek existe porque pop es destructivo. Muchas veces quieres saber qué hay en la cima (por ejemplo, "¿cuál sería la próxima acción a deshacer?") sin deshacerla todavía.
  • No hay acceso por posición. No puedes preguntar "¿qué hay en la posición 3?". Si necesitas eso, no necesitas una pila; necesitas una lista.

En la lección 03-02 veremos cada operación paso a paso, con trazas del estado de la pila; en la 03-03 las implementaremos de dos formas distintas. Hoy basta con entender qué prometen.

Cómo funciona una pila: diagrama

El siguiente diagrama muestra la idea central: push y pop operan siempre sobre el mismo extremo, la cima.

flowchart TD
    IN(["push(accion_4)"]) -->|entra por arriba| C
    C -->|"pop() la retira"| OUT(["devuelve accion_3"])
    subgraph PILA["Pila (crece hacia arriba)"]
        direction TB
        C["cima → accion_3"]
        B["accion_2"]
        A["accion_1 (fondo)"]
        C --- B --- A
    end

Lectura del diagrama:

  • accion_1 fue la primera en entrar y quedó en el fondo: será la última en salir.
  • accion_3 es la cima actual: peek() la mostraría, pop() la retiraría.
  • Si ahora hiciéramos push(accion_4), esta pasaría a ser la nueva cima, tapando a accion_3.

Observa el paralelismo con la lista enlazada: la cima de la pila jugará el papel de la cabeza de la lista. Por eso la promesa de 02-05 no era exagerada: ya tienes toda la mecánica aprendida.

Pilas en la vida del programador

Las pilas no son una curiosidad académica: las estás usando ahora mismo, aunque no las veas.

La pila de llamadas (call stack)

Cada vez que Python ejecuta una función, apila un registro con sus variables locales y el punto de retorno. Cuando la función termina, se desapila y la ejecución vuelve a donde estaba. Por eso, cuando una función a() llama a b() y esta a c(), al terminar c() volvemos a b(), y al terminar b() volvemos a a(): el último en entrar es el primero en salir. Cuando veas un traceback de error en Python, estás viendo una foto de esa pila. Lo exploraremos con código en la lección 03-04.

Ctrl+Z: deshacer en cualquier editor

Todo editor de texto guarda cada cambio en una pila. Ctrl+Z desapila el cambio más reciente y lo revierte. Es la aplicación estrella de las pilas y la que construiremos para TaskFlow.

El historial de navegación

El botón "atrás" del navegador se comporta como una pila de páginas visitadas. En el módulo 2 ya montamos HistorialTareas con una lista doblemente enlazada para movernos atrás y adelante; en la lección 03-04 veremos que "atrás y adelante" también puede modelarse con dos pilas cooperando, y compararemos ambos enfoques.

Otras apariciones (las verás más adelante)

  • Comprobar que paréntesis y corchetes están balanceados (03-04).
  • Evaluar expresiones matemáticas (03-04).
  • Recorrer árboles y grafos en profundidad (DFS): lo anticiparemos en 03-04 y lo desarrollarás en los módulos 6 y 7.

La pila en TaskFlow: el deshacer

Situémonos en TaskFlow. Un usuario trabaja con sus tareas, representadas como diccionarios:

tarea = {"id": 7, "titulo": "Revisar presupuesto", "prioridad": 2, "estado": "pendiente"}

A lo largo de la mañana, el usuario:

  1. Crea la tarea 7.
  2. Le cambia la prioridad de 2 a 1.
  3. La marca como "en_curso".
  4. La asigna a Ana ("asignada_a": "ana").

Ahora pulsa Deshacer. ¿Qué espera que ocurra? Que se revierta la asignación a Ana, no que se borre la tarea. Pulsa Deshacer otra vez: la tarea vuelve a "pendiente". Otra vez: la prioridad vuelve a 2.

Es decir: el deshacer revierte las acciones en orden inverso al que se hicieron. La última acción realizada es la primera en deshacerse. Eso es LIFO al pie de la letra, y por eso la estructura correcta para el deshacer es una pila:

  • Cada vez que el usuario hace algo → push(accion) en la pila de historial.
  • Cada vez que pulsa Deshacer → pop() recupera la acción más reciente y se revierte.
  • ¿Está el botón Deshacer activo? → esta_vacia() lo decide.
  • "Deshacer: cambiar prioridad" como texto del botón → peek() sin tocar nada.

Fíjate en que las cuatro necesidades reales de la interfaz se corresponden una a una con el contrato del TDA. Cuando eso ocurre, has elegido bien la estructura.

Costes esperados del contrato

Parte del contrato de la pila es su rendimiento. Una pila bien implementada garantiza que todas sus operaciones son de tiempo constante:

Operación Coste esperado ¿Por qué es razonable esperarlo?
push(e) O(1) Solo se toca la cima (como insertar por la cabeza)
pop() O(1) Solo se toca la cima (como borrar la cabeza)
peek() O(1) Es una consulta de la cima, sin modificar nada
esta_vacia() O(1) Basta comprobar si hay cima
tamano() O(1) Mantendremos un contador, como el tamano de ListaEnlazada

Ninguna operación depende de cuántos elementos haya: da igual que el historial de TaskFlow tenga 10 acciones o 10 millones, deshacer la última costará lo mismo. Compara esto con la lista, donde buscar o insertar en medio era O(n): al restringir el contrato, hemos podido garantizar que todo lo que la pila ofrece es O(1). En la lección 03-03 verificaremos esta tabla con timeit, y matizaremos la diferencia entre O(1) amortizado y O(1) garantizado según la implementación.

Errores Comunes y Consejos

  • Confundir pila con lista: si te sorprendes queriendo acceder "al tercer elemento" de una pila, detente. O estás usando la estructura equivocada, o estás rompiendo el contrato. El contrato restringido es la gracia de la pila, no una limitación a esquivar.
  • Confundir LIFO con FIFO: un truco mnemotécnico: pila de platos (el último arriba sale primero), cola del cine (el primero en llegar entra primero). Si dudas, dibuja tres elementos entrando y pregúntate cuál sale.
  • Pensar que peek modifica la pila: peek es solo lectura. Si tras un peek la pila cambió, la implementación está mal (lo vigilaremos en 03-03).
  • Creer que la pila "recuerda posiciones": en una pila un elemento no tiene índice estable; su única propiedad posicional es "cuántos elementos tiene encima", y cambia con cada operación.
  • Consejo: cuando analices un problema, pregúntate: "¿lo último que llega es lo primero que necesito procesar?". Si la respuesta es sí, casi seguro que hay una pila esperando.

Ejercicios

Ejercicio 1: predice la salida

Sin implementar nada (puedes hacerlo con papel y lápiz), parte de una pila vacía y aplica esta secuencia. ¿Qué devuelve cada pop() y peek(), y qué queda en la pila al final?

push("crear tarea 1")
push("cambiar prioridad tarea 1")
pop()
push("crear tarea 2")
push("asignar tarea 2 a Ana")
peek()
pop()
pop()

Ejercicio 2: ¿pila o no pila?

Para cada situación, indica si el comportamiento natural es LIFO (pila) o no, y justifica en una frase:

  1. Los mensajes de un chat, mostrados en el orden en que llegaron.
  2. La tecla "retroceso" (backspace) borrando caracteres de lo que escribes.
  3. La impresora de la oficina procesando documentos enviados por varias personas.
  4. Salir de varios menús anidados de una aplicación pulsando "volver" repetidamente.

Ejercicio 3: diseña el contrato en TaskFlow

Escribe (solo en pseudocódigo o frases, sin implementar) qué operación del contrato de la pila usarías para cada necesidad de la interfaz de TaskFlow:

  1. Mostrar el botón "Deshacer" en gris cuando no haya nada que deshacer.
  2. Mostrar como tooltip del botón el texto de la próxima acción a deshacer.
  3. Registrar que el usuario acaba de completar la tarea 12.
  4. Ejecutar el deshacer cuando el usuario pulsa el botón.
  5. Mostrar "Historial: 8 acciones" en la barra de estado.

Soluciones

Solución 1:

Paso Operación Devuelve Pila tras la operación (cima a la izquierda)
1 push("crear tarea 1") crear tarea 1
2 push("cambiar prioridad tarea 1") cambiar prioridad tarea 1, crear tarea 1
3 pop() "cambiar prioridad tarea 1" crear tarea 1
4 push("crear tarea 2") crear tarea 2, crear tarea 1
5 push("asignar tarea 2 a Ana") asignar tarea 2 a Ana, crear tarea 2, crear tarea 1
6 peek() "asignar tarea 2 a Ana" (sin cambios)
7 pop() "asignar tarea 2 a Ana" crear tarea 2, crear tarea 1
8 pop() "crear tarea 2" crear tarea 1

Al final queda solo "crear tarea 1". Observa que peek no alteró nada: el pop siguiente devolvió el mismo elemento.

Solución 2:

  1. No es pila (es FIFO): los mensajes se muestran en orden de llegada. Es una cola (módulo 4).
  2. Pila: el retroceso borra el último carácter escrito; el texto se comporta como una pila de caracteres.
  3. No es pila (es FIFO): sería injusto que el último documento enviado se imprimiera primero. Cola de impresión, literalmente.
  4. Pila: cada menú abierto se apila; "volver" desapila el más reciente. Es el mismo patrón que la pila de llamadas.

Solución 3:

  1. esta_vacia() → si devuelve True, botón en gris.
  2. peek() → consulta la cima sin retirarla; perfecta para un tooltip.
  3. push(accion) → registra la acción de completar la tarea 12.
  4. pop() → recupera la acción más reciente para revertirla.
  5. tamano() → devuelve el número de acciones almacenadas.

Conclusión

En esta lección has conocido la pila como tipo de dato abstracto: una colección LIFO donde todo ocurre por la cima, con un contrato de cinco operaciones (push, pop, peek, esta_vacia, tamano) que promete coste O(1) en todas ellas. Has visto que esa restricción no es una debilidad sino la clave de su utilidad: modela con exactitud el deshacer de un editor, las pestañas cerradas del navegador, la pila de llamadas de Python y —lo que nos ocupa— el deshacer de TaskFlow, donde cada necesidad de la interfaz encaja con una operación del contrato. También has comprobado que la pila hereda directamente lo que aprendiste en el módulo 2: la cima es la cabeza de una lista enlazada con las operaciones caras prohibidas. En la siguiente lección abriremos el capó de cada operación: veremos push, pop y peek paso a paso, con trazas del estado de la pila, decidiremos qué hacer cuando alguien hace pop sobre una pila vacía y empezaremos a practicar con el deshacer de TaskFlow usando la list de Python como pila provisional.

© Copyright 2026. Todos los derechos reservados