Capítulo 4 Pilas Las pilas son estructuras muy sencillas y poderosas, también conocidas como LIFO (last in, first out) por la forma en que se trabaja con ellas. Ejemplo de ellas son las pilas de charolas en una panadería, donde sólo se puede tomar la charola que está arriba de pila y si se quiere depositar una charola se debe hacer arriba de las demás. 4.1. Motivación Se tienen tres palos o postes, en el primero de ellos se tienen n discos de diferente tamaño cada uno. El disco de mayor diámetro se encuentra en el fondo del palo y el de diámetro menor hasta arriba, con la restricción que ningún disco de tamaño mayor puede colocarse sobre uno menor. Los otros dos palos están vacíos. El problema consiste en pasar los discos del primer palo al segundo, usando el tercer palo como auxiliar. Este es un ejemplo de uso de una pila. Si el número de discos es tres, la solución al problema consta de los siguientes pasos: Mover el disco 1 del palo A al 2 Mover el disco 2 del palo A al 3 Mover el disco 1 del palo B al 3 Mover el disco 3 del palo A al 2 Mover el disco 1 del palo C al 1 Mover el disco 2 del palo C al 2 Mover el disco 1 del palo A al 2 Como son los movimientos de los discos? Siempre se toma el disco de más arriba y se coloca hasta arriba del palo al que se desea pasar. Esto da pie a una estructura de datos denominada pila. Una pila es una estructura con la restricción que las inserciones y supresiones son siempre por el mismo lado, denominado tope. 1
CAPÍTULO 4. PILAS 2 Se puede observar que para 3 discos se requieren 7 movimientos. En general para n discos se requieren 2 n 1 movimientos. Existe una leyenda que dice que en la ciudad de Hanoi hay unos monjes dedicados a resolver el problema con 64 discos. (De ahí que este problema se conoce como el problema de las torres de Hanoi.) Si mover cada disco les toma un día, entonces resolver el problema tomará 2 64 1 días. La parte fea de la historia es que el día que resuelvan el problema se terminará el mundo :) Aunque el problema, no usa pilas en su solución programada se incluye aquí porque es un ejemplo ilustrativo del concepto de pila y se presenta como un ejemplo de recursión más elaborada. La solución al problema consiste en pasar primero el disco1 del palo1 al palo2. Entonces el problema se reduce a pasar los n 1 discos restantes del palo1 al palo3 y finalmente regresar esos n 1 discos del palo3 al palo2. A continuación la implementación recursiva de la solución. Programa que juega a las torres de Hanoi. * @version Septiembre 2004. public class Hanoi { private final int tamanio; public Hanoi () { System.out.println("Torres de Hanoi"); tamanio = 3; solucion(tamanio, a, b, c ); public Hanoi (int tam) { System.out.println("Torres de Hanoi"); tamanio = tam; solucion(tamanio, a, b, c ); private void solucion (int n, char a, char b, char c) { if (n == 1) System.out.println("Mover el disco "+n+" del palo "+a+" al palo "+b); else{ solucion(n-1, a, c, b); System.out.println("Mover el disco "+n+" del palo "+a+" al palo "+b); solucion(n-1, c, b, a);
CAPÍTULO 4. PILAS 3 static public void main (String [ ] args) { Hanoi han = new Hanoi(); 4.2. Implementación Una pila es una lista con la restricción que las inserciones y supresiones son siempre por el mismo lado, denominado tope. La interfaz para el manejo de este tipo de datos es la siguiente: interfaz Apilable { public void push(object); public void pop(); public Object top(); Las operaciones básicas con las pilas son: push. Inserta el elemento al frente de la pila. pop. Elimina el elemento que se insertó al último. Si la pila está vacía causa un error. top. Regresa el valor del elemento que se insertó al último. Si la pila está vacía causa un error (NoSuchElementException). Con este método no se altera el estado de la pila. estávacía. Para saber si la pila está vacía. limpiar. Para vaciar el contenido de una pila. A continuación la implementación de las operaciones para pilas usando nodos ligados. public class Pila extends Apilable { private Nodo tope; * Construye la pila. public Pila() { tope = null;
CAPÍTULO 4. PILAS 4 * Verifica que la pila esté llena. En esta implementación * @return false siempre. public boolean estállena() { return false; * Verifica que la pila esté vacía. * @return true si lo está y falso en otro caso. public boolean estávacía (){ return tope == null; * Vacía una pila. public void vaciapila() { tope = null; * Devuelve el elemento del tope de la pila (sin alterar ésta) * o bien null si se encuentra vacía. public Object top() { return (estávacía())? null : tope.elemento; * Extrae el elemento del tope de la pila. * Devuelve null si la pila está vacía. public Object pop() { if (estávacía()) return null; Object dato = tope.elemento; tope = tope.sgte; return dato;
CAPÍTULO 4. PILAS 5 * Inserta un nuevo elemento en la pila. * @param x el elemento a insertar. public void push(object x) { tope = new Nodo(x, tope); * Iterador para conseguir todos los elementos de la pila sin alterarla. public java.util.iterator elementos() { return new MiIterador(); private class MiIterador implements java.util.iterator { private Nodo posicion = tope; public boolean hasnext() { return tope!= null; public Object next() { //throws NoSuchElementException { if (hasnext()) { Object o = posicion.elemento; posicion = posicion.sgte; return o; // throw new NoSuchElementException(); return null; public void remove() { throw new IllegalStateException(); El tiempo que toma cada una de las operaciones sobre pilas con la implementación anterior es constante y no depende del tamaño de la pila. 4.3. Balanceo de elementos A pesar de ser muy pocas las instrucciones sobre una pila, éstas son además de eficientes muy poderosas. En esta sección se presenta un problema que requiere en su solución el uso de una pila.
CAPÍTULO 4. PILAS 6 El problema consiste en determinar si una expresión aritmética tiene bien anidados sus paréntesis. Considerando tres tipos de éstos. Es decir, que no ocurra que se tengan más paréntesis de un tipo que de otro y que cierren adecuadamente. Por ejemplo, (a+[b*c]/[d-h])*25 {a+{b+c+{d+e+e {a+{b+c+{d+e+e ((a+b es correcta es correcta es incorrecta es incorrecta El algoritmo es el siguiente: 1. Crear una pila vacía. 2. Leer cada elemento de la cadena, para cada uno hacer: a) Si el caracter es un símbolo que abre ([{, meterlo a la pila. b) Si es uno que cierra y la pila está vacía, reportar un error. c) Si es uno que cierra y la pila no está vacía, sacar de la pila un caracter y verificar que corresponda con el símbolo que abre. Si no es así reportar un error. d) Cualquier otro caracter ignorarlo. 3. Si la pila no está vacía reportar un error. La programación del algoritmo anterior se presenta a continuación: public class Parentesis { Pila pila = new Pila(); public static void main (String [ ] args) { Parentesis linea = new Parentesis(args[0]); public Parentesis (String linea) { for (int i = 0; i < linea.length(); i++) if (linea.charat(i) == ( ) pila.push(new Character( ) )); else if (linea.charat(i) == { ) pila.push(new Character( )); else if (linea.charat(i) == [ ) pila.push(new Character( ] )); else if (linea.charat(i) == ) ) verifica( ) ); else if (linea.charat(i) == ) verifica( ); else if (linea.charat(i) == ] ) verifica( ] ); if (pila.estávacía())
CAPÍTULO 4. PILAS 7 System.out.println("Parentesis balanceados"); else reportaerror(); private void verifica (char c) { if (pila.estávacía()) reportaerror(); Character s = pila.pop(); if (c!= s.charvalue()) reportaerror(); private void reportaerror() { System.out.println("Parentesis NO balanceados"); System.exit(1); 4.4. Conversión de infija a postfija Si se tiene la expresión A+B se piensa sumar A con B, es decir primero se piensa la operación y luego los operandos. La notación usual se conoce como infija (A+B) y la que pensamos, prefija (+AB). Existe otra notación que se denomina postfija en la cual el operador va después de los operandos. Por ejemplo, AB+. Ejemplos de expresiones en notación postfija son: A+B*C --> A+(BC*) --> A(BC*)+ --> ABC*+ (A+B)*C --> (AB+)*C --> (AB+)C* --> AB+C* A+B-C --> AB+C- (A+B)*(C-D) --> AB+CD-* ((A+B)*C-(D-E))^(F+G) --> AB+C*DE--FG+^ Tener la expresión de esta forma, facilita su evaluación como se verá en la siguiente sección. Para simplificar este algoritmo sólo se utilizan los operadores de suma, multiplicación y paréntesis, con las reglas de precedencia de siempre y se asume que la expresión es correcta. Ejemplos: a + b * c + (d * e + f) * g ----> abc*+de*f+g*+ El algoritmo de conversión empieza teniendo una pila vacía y una expresión, en notación infija, correcta.
CAPÍTULO 4. PILAS 8 1. Leer un elemento. 2. Mientras haya elementos, hacer los pasos a al e: a) Si se tiene un operando se coloca directamente en la salida. (El orden de estos no se altera). b) Si es el primer operador se debe colocar en la pila incluyendo un paréntesis izquierdo. c) Si es un paréntesis derecho se sacan de la pila los operadores que ahí se encuentren y se van colocando en la salida cada uno, hasta encontrar el paréntesis izquierdo correspondiente, el cual se saca de la pila pero no se coloca en la salida. d) Si es un operador diferente de paréntesis derecho, se sacan de la pila todos los operadores de mayor o igual prioridad que el que se tienen en la mano, excepto el paréntesis izquierdo. Al finalizar se coloca el operador en la pila. e) Leer el siguiente elemento 3. Se sacan de la pila todos los operadores. En este algoritmo la pila representa los operadores pendientes. El algoritmo es de orden O(n). Una pequeña observación a-b-c se convierte en ab-c- lo cual es correcto debido a que la asociación es de izquierda a derecha. Como no es el caso de la exponenciación, no funcionaría este algoritmo para ese operador. 4.5. Evaluación de expresiones Una vez que se tiene una expresión aritmética en notación postfija su evaluación es muy sencilla y también requiere de una pila. El algoritmo es el siguiente: 1. Inicia con una pila vacía 2. Se lee cada símbolo de la expresión. Para cada uno hacer: a) Si el símbolo es un operando se mete a la pila. b) En caso contrario, se trata de un operador. En este caso se sacan los dos elementos del tope de la pila; se realiza la operación y el resultado de la misma se coloca en la pila. op2 = p.pop(); op1 = p.pop(), valor = op1 simbolo op2; p.push(valor);
CAPÍTULO 4. PILAS 9 3. Finalmente se saca de la pila el resultado. Ejemplo, seguir el algoritmo con la siguiente expresión: 6 2 3 + - 3 8 2 / + * 2 ^3 + 4.6. Juego de cartas