Pila (stack)
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.
- 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
- Apilar (push). Se pone el elemento en la cima: con un array,
a[n] = xyn++. Si no cabe, se copia a un array el doble de grande. - Desapilar (pop). Se quita y devuelve el de la cima:
n--yreturn a[n]. Con la pila vacía es un error (excepción o valor especial). - Consultar la cima (peek). Se mira
a[n − 1]sin quitarlo. - ¿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.
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}class Pila:
"""Una pila de enteros. Una lista de Python ya crece sola: append y pop trabajan por el final."""
def __init__(self):
self._a = []
def apilar(self, x):
self._a.append(x)
def desapilar(self):
if not self._a:
raise IndexError("pila vacía")
return self._a.pop()
def __str__(self):
return str(self._a)
def evaluar(expr):
"""Evalúa una expresión en notación polaca inversa: «3 4 +» significa 3 + 4."""
p = Pila()
for t in expr.split():
if t.lstrip("-").isdigit():
p.apilar(int(t)) # un número se apila
else:
b, a = p.desapilar(), p.desapilar() # un operador usa los dos de arriba
p.apilar({"+": a + b, "-": a - b, "*": a * b}.get(t, a // b if b else 0))
print(f"{t:<3}→ {p}")
return p.desapilar()
print("Resultado:", evaluar("3 4 + 2 * 7 -"))/** Una pila de enteros. Un array de JavaScript ya crece solo: push y pop trabajan por el final. */
class Pila {
#a = [];
apilar(x) { this.#a.push(x); }
desapilar() {
if (this.#a.length === 0) throw new Error("pila vacía");
return this.#a.pop();
}
toString() { return "[" + this.#a.join(", ") + "]"; }
}
/** Evalúa una expresión en notación polaca inversa: «3 4 +» significa 3 + 4. */
function evaluar(expr) {
const p = new Pila();
for (const t of expr.split(" ")) {
if (/^-?\d+$/.test(t)) p.apilar(Number(t)); // un número se apila
else {
const b = p.desapilar(), a = p.desapilar(); // un operador usa los dos de arriba
p.apilar({ "+": a + b, "-": a - b, "*": a * b }[t] ?? Math.trunc(a / b));
}
console.log(t.padEnd(3) + "→ " + p);
}
return p.desapilar();
}
console.log("Resultado: " + evaluar("3 4 + 2 * 7 -"));using System;
class Pila {
private int[] a = new int[2];
private int n = 0; // cuántos hay; la cima es a[n - 1]
public void Apilar(int x) {
if (n == a.Length) Array.Resize(ref a, 2 * a.Length); // lleno: el doble de sitio
a[n++] = x;
}
public int Desapilar() {
if (n == 0) throw new InvalidOperationException("pila vacía");
return a[--n];
}
public override string ToString() => "[" + string.Join(", ", a[..n]) + "]";
}
class Program {
// Evalúa una expresión en notación polaca inversa: «3 4 +» significa 3 + 4.
static int Evaluar(string expr) {
var p = new Pila();
foreach (string t in expr.Split(' ')) {
if (int.TryParse(t, out int x)) p.Apilar(x); // un número se apila
else {
int b = p.Desapilar(), a = p.Desapilar(); // un operador usa los dos de arriba
p.Apilar(t switch { "+" => a + b, "-" => a - b, "*" => a * b, _ => a / b });
}
Console.WriteLine(t.PadRight(3) + "→ " + p);
}
return p.Desapilar();
}
static void Main() => Console.WriteLine("Resultado: " + Evaluar("3 4 + 2 * 7 -"));
}<?php
/** Una pila de enteros sobre un array de PHP (que ya crece solo). */
class Pila {
private array $a = [];
public function apilar(int $x): void { $this->a[] = $x; }
public function desapilar(): int {
if (!$this->a) throw new UnderflowException("pila vacía");
return array_pop($this->a);
}
public function __toString(): string { return "[" . implode(", ", $this->a) . "]"; }
}
/** Evalúa una expresión en notación polaca inversa: «3 4 +» significa 3 + 4. */
function evaluar(string $expr): int {
$p = new Pila();
foreach (explode(" ", $expr) as $t) {
if (preg_match('/^-?\d+$/', $t)) $p->apilar((int) $t); // un número se apila
else {
$b = $p->desapilar(); // un operador usa los dos de arriba
$a = $p->desapilar();
$p->apilar(match ($t) { "+" => $a + $b, "-" => $a - $b, "*" => $a * $b, default => intdiv($a, $b) });
}
echo str_pad($t, 3) . "→ " . $p . "\n";
}
return $p->desapilar();
}
echo "Resultado: " . evaluar("3 4 + 2 * 7 -") . "\n";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.
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"# En Python, una lista ya es una pila: append apila y pop desapila por el final
pila = []
pila.append("a") # apilar
pila.append("b")
cima = pila[-1] # "b", sin quitarlo
sale = pila.pop() # "b"
vacia = not pila # False: queda "a"// En JavaScript, un array ya es una pila: push apila y pop desapila por el final
const pila = [];
pila.push("a"); // apilar
pila.push("b");
const cima = pila[pila.length - 1]; // "b", sin quitarlo (o pila.at(-1))
const sale = pila.pop(); // "b"
const vacia = pila.length === 0; // false: queda "a"// En C#, Stack<T>
var pila = new Stack<string>();
pila.Push("a"); // apilar
pila.Push("b");
string cima = pila.Peek(); // "b", sin quitarlo
string sale = pila.Pop(); // "b"
bool vacia = pila.Count == 0; // false: queda "a"// En PHP, un array con array_push / array_pop, o la clase SplStack $pila = []; $pila[] = "a"; // apilar $pila[] = "b"; $cima = end($pila); // "b", sin quitarlo $sale = array_pop($pila); // "b" $vacia = empty($pila); // false: queda "a"
Traza: evaluar «3 4 + 2 * 7 -»
| Token | Qué se hace | Pila (la cima a la derecha) |
|---|---|---|
| 3 | es un número: se apila | [3] |
| 4 | es un número: se apila | [3, 4] |
| + | desapila 4 y 3, calcula 3 + 4 = 7 y lo apila | [7] |
| 2 | es un número: se apila | [7, 2] |
| * | desapila 2 y 7, calcula 7 * 2 = 14 y lo apila | [14] |
| 7 | es 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ón | Coste |
|---|---|
| Apilar | O(1) amortizado (O(n) las pocas veces que el array crece) |
| Desapilar | O(1) |
| Consultar la cima | O(1) |
| Buscar un elemento | O(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.
- 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 lanzaNoSuchElementException(oEmptyStackException). - Usar
java.util.Stack: funciona, pero está sincronizada (lenta) y hereda deVector, así que permite acceder a cualquier posición. MejorArrayDeque. - Con
ArrayDeque, mezclarpush/pop(trabajan por el principio) conadd/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 - bno esb - 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 QoPosición Q: «(» se abre y no se cierra(el último que quedó abierto). - Los demás caracteres se ignoran.
Ejemplo
{[a+b]*(c)}
([)]
((x)
Equilibrada Posición 3: «)» no casa con «[» de la posición 2 Posición 1: «(» se abre y no se cierra
Ver la solución explicada
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,rehacerymostrar. mostrarescribe 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: «…».
Ejemplo
escribir Hola escribir mundo mostrar deshacer mostrar rehacer mostrar
«Hola mundo» «Hola» «Hola mundo»
Ver la solución explicada
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 aciertosElige 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.Se apilan 1, 2 y 3 y se desapila dos veces. ¿Qué queda en la pila?
2.¿Qué significa LIFO?
3.¿Qué error produce una recursividad sin caso base en Java?
4.¿Qué clase se recomienda como pila en Java?
5.¿Cuánto vale «5 1 2 + 4 * + 3 -» en notación polaca inversa?