Apuntes DAM
Volver al inicio

Pila (stack)

AlgoritmosEstructuras de datosNivel básicoTambién: stack, LIFO, pila de llamadas

Una colección en la que solo se mete y se saca por arriba: el último que entra es el primero que sale (LIFO). Deshacer, el botón «atrás», la pila de llamadas o comprobar paréntesis.

Visualízalo paso a paso

Cambia los datos, dale a reproducir y sigue cada paso en el dibujo, en la línea de Java que se ejecuta y en sus variables.

Pila: paréntesis equilibrados

Escribe una expresión con paréntesis, corchetes y llaves y mira cómo la pila guarda los que esperan su cierre.

Hasta 24 caracteres; solo cuentan ( ) [ ] { }
  • fuera de juego

Paso 1

Se lee la expresión de izquierda a derecha con una pila vacía. Cada símbolo que abre se apila; cada uno que cierra tiene que casar con el último que se abrió, que está en la cima.

1static boolean equilibrada(String s) {
2    Deque<Character> pila = new ArrayDeque<>();
3    for (char c : s.toCharArray()) {
4        if ("([{".indexOf(c) >= 0) pila.push(c);
5        else if (")]}".indexOf(c) >= 0) {
6            if (pila.isEmpty()) return false;
7            char abre = pila.pop();
8            if ("([{".indexOf(abre) != ")]}".indexOf(c)) return false;
9        }
10    }
11    return pila.isEmpty();
12}

Variables

c
—
pila
vacía

Atajos con el foco dentro del visualizador: ← → paso a paso, Espacio reproducir o pausar, Inicio/Fin ir al principio o al final.

La idea

Una pila es la estructura de datos más sencilla que hay: una colección en la que solo se puede trabajar por un extremo, la cima. Se apila (push) poniendo un elemento encima y se desapila (pop) quitando el de arriba. Por eso el último en entrar es el primero en salir: LIFO, last in, first out.

Esa restricción, que parece una limitación, es justo lo que la hace útil: la pila recuerda las cosas en el orden inverso en que llegaron, que es el orden en que hay que deshacerlas. El último cambio es el primero que se deshace; la última página visitada es a la que se vuelve con «atrás»; el último paréntesis abierto es el primero que hay que cerrar.

Se implementa muy fácil sobre un array con un contador n: apilar es a[n++] = x y desapilar es a[--n], ambas O(1). Si el array se llena, se copia a uno del doble de tamaño; como eso pasa muy pocas veces, apilar sigue siendo O(1) de media (coste amortizado).

La pila más importante de un programa no se ve: es la pila de llamadas. Cada vez que se llama a un método, sus variables locales y el punto de vuelta se apilan; al terminar, se desapilan. Una recursividad sin caso base llena esa pila y termina en StackOverflowError.

Cuándo usarlo

  • Deshacer y rehacer, el historial de un navegador, la navegación entre pantallas de una app (back stack de Android).
  • Comprobar que paréntesis, etiquetas HTML o bloques están bien anidados.
  • Evaluar expresiones (notación polaca inversa, calculadoras) y analizar código (compiladores).
  • Convertir una recursividad en un bucle: el recorrido en profundidad (DFS) o el backtracking con una pila propia.

Cuándo no

  • Si hay que atender en orden de llegada (el primero que entra, el primero que sale): eso es una cola.
  • Si hay que buscar o acceder a elementos del medio: una pila solo deja ver la cima.

Paso a paso

  1. Apilar (push). Se pone el elemento en la cima: con un array, a[n] = x y n++. Si no cabe, se copia a un array el doble de grande.
  2. Desapilar (pop). Se quita y devuelve el de la cima: n-- y return a[n]. Con la pila vacía es un error (excepción o valor especial).
  3. Consultar la cima (peek). Se mira a[n − 1] sin quitarlo.
  4. ¿Vacía?. n == 0. Es lo primero que hay que comprobar antes de desapilar.

El código

Una pila a mano y la notación polaca inversa

En notación polaca inversa los operadores van detrás de sus operandos: «3 4 + 2 *» es (3 + 4) · 2. Con una pila se evalúa de izquierda a derecha sin paréntesis ni prioridades.

Java
1import java.util.Arrays;
2
3public class Main {
4    /** Una pila de enteros sobre un array que crece cuando se llena. */
5    static class Pila {
6        private int[] a = new int[2];
7        private int n = 0;                                    // cuántos hay; la cima es a[n - 1]
8
9        void apilar(int x) {
10            if (n == a.length) a = Arrays.copyOf(a, 2 * a.length);   // lleno: el doble de sitio
11            a[n++] = x;
12        }
13
14        int desapilar() {
15            if (n == 0) throw new IllegalStateException("pila vacía");
16            return a[--n];
17        }
18
19        @Override public String toString() { return Arrays.toString(Arrays.copyOf(a, n)); }
20    }
21
22    /** Evalúa una expresión en notación polaca inversa: «3 4 +» significa 3 + 4. */
23    static int evaluar(String expr) {
24        Pila p = new Pila();
25        for (String t : expr.split(" ")) {
26            if (t.matches("-?\\d+")) p.apilar(Integer.parseInt(t));        // un número se apila
27            else {
28                int b = p.desapilar(), a = p.desapilar();                     // un operador usa los dos de arriba
29                p.apilar(switch (t) {
30                    case "+" -> a + b;
31                    case "-" -> a - b;
32                    case "*" -> a * b;
33                    default -> a / b;
34                });
35            }
36            System.out.println(String.format("%-3s", t) + "→ " + p);
37        }
38        return p.desapilar();
39    }
40
41    public static void main(String[] args) {
42        System.out.println("Resultado: " + evaluar("3 4 + 2 * 7 -"));
43    }
44}

Salida al ejecutarlo (la misma en los 5 lenguajes)

3  → [3]
4  → [3, 4]
+  → [7]
2  → [7, 2]
*  → [14]
7  → [14, 7]
-  → [7]
Resultado: 7

Las pilas de la biblioteca

En el día a día no se programa la pila: se usa la de la biblioteca.

Java
1// En Java, la pila de la biblioteca es ArrayDeque (la clase Stack es antigua y está sincronizada)
2Deque<String> pila = new ArrayDeque<>();
3pila.push("a");                  // apilar
4pila.push("b");
5String cima = pila.peek();       // "b", sin quitarlo
6String sale = pila.pop();        // "b"
7boolean vacia = pila.isEmpty();  // false: queda "a"

Traza: evaluar «3 4 + 2 * 7 -»

TokenQué se hacePila (la cima a la derecha)
3es un número: se apila[3]
4es un número: se apila[3, 4]
+desapila 4 y 3, calcula 3 + 4 = 7 y lo apila[7]
2es un número: se apila[7, 2]
*desapila 2 y 7, calcula 7 * 2 = 14 y lo apila[14]
7es un número: se apila[14, 7]
-desapila 7 y 14, calcula 14 - 7 = 7 y lo apila[7]

Al final queda un solo número en la pila: el resultado, (3 + 4) · 2 − 7 = 7.

Complejidad

OperaciónCoste
ApilarO(1) amortizado (O(n) las pocas veces que el array crece)
DesapilarO(1)
Consultar la cimaO(1)
Buscar un elementoO(n): hay que desapilar o recorrer

Memoria: O(n). Doblar el tamaño al crecer (y no sumar una cantidad fija) es lo que hace que apilar n elementos cueste O(n) en total.

0102030405015101520tamaño de la entrada (n)operacionesO(n!)O(2ⁿ)O(n²)O(n log n)O(1)O(log n)O(n)
  • Mejor caso: O(1)
  • Caso medio: O(1)
  • Peor caso: O(n)

Apilar y desapilar son O(1); el peor caso (n) es cuando el array interno se llena y hay que copiarlo a uno más grande, algo que pasa muy pocas veces. Las curvas grises son las demás clases, para comparar.

En la práctica

  • La pila de llamadas de la JVM, de Python o del navegador: cada método llamado es un marco en la pila; las trazas de excepciones la muestran de arriba abajo.
  • Deshacer en editores, IDEs y Photoshop: una pila de cambios para deshacer y otra para rehacer.
  • El historial del navegador y el back stack de Android: «atrás» desapila la pantalla actual.
  • Los compiladores y los validadores de HTML o XML comprueban con una pila que cada etiqueta que se abre se cierra en orden.

Errores típicos

  • Desapilar sin comprobar si está vacía: pop() sobre una pila vacía lanza NoSuchElementException (o EmptyStackException).
  • Usar java.util.Stack: funciona, pero está sincronizada (lenta) y hereda de Vector, así que permite acceder a cualquier posición. Mejor ArrayDeque.
  • Con ArrayDeque, mezclar push/pop (trabajan por el principio) con add/removeLast (por el final): se rompe el orden.
  • En la notación polaca, desapilar los operandos al revés: el primero que sale es el de la derecha (b), no el de la izquierda. a - b no es b - a.
  • Recursividad sin caso base: la pila de llamadas se llena y salta StackOverflowError.

Ejercicios

Cada ejercicio se corrige solo con sus pruebas (algunas ocultas). Escribe tu solución en el editor y pulsa Ejecutar o Comprobar; la solución explicada está debajo, por si te atascas.

1. Paréntesis con el error señalado

Cada línea es una expresión. Comprueba si sus paréntesis, corchetes y llaves están bien anidados y, si no, di exactamente dónde está el error. Para poder decir la posición, la pila no guarda los caracteres sino las posiciones de los que están abiertos. Completa comprobar.

  • Entrada: una expresión por línea, como {[a+b]*(c)} (las posiciones se cuentan desde 1).
  • Salida, una línea por expresión: Equilibrada, Posición P: «)» cierra sin que haya nada abierto, Posición P: «]» no casa con «(» de la posición Q o Posición Q: «(» se abre y no se cierra (el último que quedó abierto).
  • Los demás caracteres se ignoran.
☕JavaParéntesis con el error señaladoMedio

Ejemplo

Entrada (lo que se escribe por teclado)
{[a+b]*(c)}
([)]
((x)
Salida esperada
Equilibrada
Posición 3: «)» no casa con «[» de la posición 2
Posición 1: «(» se abre y no se cierra
⏳
Test oculto #3
⏳
Test oculto #4
⏳
Test oculto #5
0/5 tests pasados · pulsa un test para ver su entrada y su salida esperada
Ver la solución explicada
java
1import java.util.*;
2
3public class Main {
4    /** Comprueba los ( ) [ ] { } de s (las posiciones empiezan en 1) y devuelve el mensaje. */
5    static String comprobar(String s) {
6        Deque<Integer> pila = new ArrayDeque<>();          // posiciones de los que están abiertos
7        for (int i = 0; i < s.length(); i++) {
8            char c = s.charAt(i);
9            if ("([{".indexOf(c) >= 0) pila.push(i);
10            else if (")]}".indexOf(c) >= 0) {
11                if (pila.isEmpty()) return "Posición " + (i + 1) + ": «" + c + "» cierra sin que haya nada abierto";
12                int j = pila.pop();
13                char a = s.charAt(j);
14                if ("([{".indexOf(a) != ")]}".indexOf(c))
15                    return "Posición " + (i + 1) + ": «" + c + "» no casa con «" + a + "» de la posición " + (j + 1);
16            }
17        }
18        if (!pila.isEmpty()) {
19            int j = pila.peek();
20            return "Posición " + (j + 1) + ": «" + s.charAt(j) + "» se abre y no se cierra";
21        }
22        return "Equilibrada";
23    }
24
25    static List<String> lineas(Scanner sc) {
26        List<String> l = new ArrayList<>();
27        while (sc.hasNextLine()) {
28            String s = sc.nextLine().trim();
29            if (!s.isEmpty()) l.add(s);
30        }
31        return l;
32    }
33
34    public static void main(String[] args) {
35        for (String s : lineas(new Scanner(System.in))) System.out.println(comprobar(s));
36    }
37}

El anidamiento correcto significa que cada cierre corresponde al último abierto que aún no se ha cerrado: justo lo que hay en la cima de la pila.

Guardar posiciones en vez de caracteres no cuesta nada (el carácter se recupera con charAt) y permite dar mensajes de error útiles, como hace un compilador.

2. Deshacer y rehacer

Un editor de texto mínimo con deshacer y rehacer. Cada cambio guarda el texto anterior en la pila de deshacer y vacía la de rehacer; deshacer recupera el anterior (y guarda el actual en la de rehacer); rehacer hace lo contrario. El main ya lee las órdenes: completa editar, deshacer y rehacer.

  • Órdenes, una por línea: escribir texto (añade el texto al final, tal cual), borrar n (quita los n últimos caracteres), deshacer, rehacer y mostrar.
  • mostrar escribe el texto entre comillas: «Hola mundo».
  • Si no hay nada que deshacer o rehacer: Nada que deshacer / Nada que rehacer. Otra orden: Orden no válida: «…».
☕JavaDeshacer y rehacerMedio

Ejemplo

Entrada (lo que se escribe por teclado)
escribir Hola
escribir  mundo
mostrar
deshacer
mostrar
rehacer
mostrar
Salida esperada
«Hola mundo»
«Hola»
«Hola mundo»
⏳
Test oculto #3
⏳
Test oculto #4
⏳
Test oculto #5
0/5 tests pasados · pulsa un test para ver su entrada y su salida esperada
Ver la solución explicada
java
1import java.util.*;
2
3public class Main {
4    static String texto = "";
5    static final Deque<String> deshacer = new ArrayDeque<>();   // estados anteriores
6    static final Deque<String> rehacer = new ArrayDeque<>();    // estados deshechos
7
8    /** Un cambio nuevo: el texto actual se guarda para poder deshacer y lo deshecho ya no se puede rehacer. */
9    static void editar(String nuevo) {
10        deshacer.push(texto);
11        rehacer.clear();
12        texto = nuevo;
13    }
14
15    static boolean deshacer() {
16        if (deshacer.isEmpty()) return false;
17        rehacer.push(texto);
18        texto = deshacer.pop();
19        return true;
20    }
21
22    static boolean rehacer() {
23        if (rehacer.isEmpty()) return false;
24        deshacer.push(texto);
25        texto = rehacer.pop();
26        return true;
27    }
28
29    public static void main(String[] args) {
30        Scanner sc = new Scanner(System.in);
31        while (sc.hasNextLine()) {
32            String linea = sc.nextLine();
33            if (linea.isBlank()) continue;
34            if (linea.startsWith("escribir ")) editar(texto + linea.substring(9));
35            else if (linea.matches("borrar \\d{1,3}")) editar(texto.substring(0, Math.max(0, texto.length() - Integer.parseInt(linea.substring(7)))));
36            else if (linea.equals("deshacer")) { if (!deshacer()) System.out.println("Nada que deshacer"); }
37            else if (linea.equals("rehacer")) { if (!rehacer()) System.out.println("Nada que rehacer"); }
38            else if (linea.equals("mostrar")) System.out.println("«" + texto + "»");
39            else System.out.println("Orden no válida: «" + linea + "»");
40        }
41    }
42}

Dos pilas bastan: lo deshecho va a la pila de rehacer en el mismo orden en que se podría volver a aplicar.

Un cambio nuevo vacía la pila de rehacer porque esos estados ya no son continuación del texto actual: es lo que hacen todos los editores.

Test

Test: Pila (stack)

0/5 respondidas · 0 aciertos

Elige una respuesta en cada pregunta: verás al momento si es correcta y por qué. Con un 80 % de aciertos se da por superada.

  1. 1.Se apilan 1, 2 y 3 y se desapila dos veces. ¿Qué queda en la pila?

  2. 2.¿Qué significa LIFO?

  3. 3.¿Qué error produce una recursividad sin caso base en Java?

  4. 4.¿Qué clase se recomienda como pila en Java?

  5. 5.¿Cuánto vale «5 1 2 + 4 * + 3 -» en notación polaca inversa?

Relacionado