Sie sind auf Seite 1von 13

Pilas

Los desarrolladores utilizan los arrays y las variantes de listas enlazadas para construir una gran variedad de estructuras de datos complejas. Este pgina explora dos de esas estructuras: las Pilas, las Colas . Cuando presentemos los algoritmos lo haremos ncamente en cdigo Java por motivos de brevedad.

Pilas que "Recuerdan"

La Pila es una estrucutra de datos donde las inserciones y recuperaciones/borrados de datos se hacen en uno de los finales, que es conocido como el top de la pila. Como el ltimo elemento insertado es el primero en recuperarse/borrarse, los desarrolladores se refieren a estas pilas como pilas LIFO (last-in, first-out). Los datos se push (insertan) dentro y se pop (recuperan/borran) de la parte superior de la pila. La siguiente figura ilustra una pila con tres String cada uno insertado en la parte superior de la pila:

Como muestra la figura anterior, las pilas se construyen en memoria. Por cada dato insertado, el itm superior anterior y todos los datos inferiores se mueven hacia abajo. Cuando llega el momento de sacar un tem de la pila, se recpupera y se borra de la pila el tem superior (que en la figura anterior se revela como "third"). Las pilas son muy tiles en varios escenarios de programacin. Dos de los ms comunes son:

Pilas

que contienen direcciones de retorno: Cuando el cdigo llama a un mtodo, la direccin de la primera instruccin que sigue a la llamada se inserta en la parte superior de la pila de llamadas de mtodos del thread actual. Cuando el mtodo llamado ejecuta la instruccin return, se saca la direccin de la parte superior de la pila y la ejecucin contina en sa direccin. Si un mtodo llama a otro mtodo, el comportamiento LIFO de la pila asegura que la instruccin return del segundo mtodo transfiere la ejecucin al primer mtodo, y la del primer mtodo transfiere la ejecucin al cdigo que sigue al cdigo que llam al primer mtodo. Como resultado una pila "recuerda" las direcciones de retorno de los mtodos llamados. Pilas que contienen todos los parmetros del mtodo llamado y las variables locales: Cuando se llama a un mtodo, la JVM reserva memoria cerca de la direccin de retorno y almacena todos los parmetros del mtodo llamado y las variables locales de ese mtodo. Si el mtodo es un mtodo de ejemplar, uno de los parmetros que almacena en la pila es la referencia this del objeto actual.

Es muy comn implementar una pila utilizando un array uni-dimensional o una lista de enlace simple. En el escenario del array uni-dimensional, una variable entera, tpicamente llamada top, contiene el ndice de la parte superior de la pila. De forma similar, una variable de referencia, tambin nombrada normalmente como top, referencia el nodo superior del escenario de la lista de enlace simple.

Una pila es una estructura de datos de acceso restrictivo a sus elementos. Se puede entender como una pila de libros que se amontonan de abajo hacia arriba. En principio no hay libros; despus ponemos uno, y otro encima de ste, y as sucesivamente. Posteriormente los solemos retirar empezando desde la cima de la pila de libros, es decir, desde el ltimo que pusimos, y terminaramos por retirar el primero que pusimos, posiblemente ya cubierto de polvo. En los programas estas estructuras suelen ser fundamentales. La recursividad se simula en un computador con la ayuda de una pila. Asimismo muchos algoritmos emplean las pilas como estructura de datos fundamental, por ejemplo para mantener una lista de tareas pendientes que se van acumulando. Las pilas ofrecen dos operaciones fundamentales, que son apilar y desapilar sobre la cima. El uso que se les de a las pilas es independiente de su implementacin interna. Es decir, se hace un encapsulamiento. Por eso se considera a la pila como un tipo abstracto de datos. Es una estructra de tipo LIFO (Last In First Out), es decir, ltimo en entrar, primero en salir. A continuacin se expone la implementacin de pilas mediante arrays y mediante listas enlazadas. En ambos casos se cubren cuatro operaciones bsicas: Inicializar, Apilar, Desapilar, y Vaca (nos indica si la pila est vaca). Las claves que contendrn sern simplemente nmeros enteros, aunque esto puede cambiarse a voluntad y no supone ningn inconveniente.

C++ Implementacin mediante array Esta implementacin es esttica, es decir, da un tamao mximo fijo a la pila, y si se sobrepasa dicho lmite se produce un error. La comprobacin de apilado en una pila llena o desapilado en una pila vaca no se han hecho, pero s las funciones de comprobacin, que el lector puede modificar segn las necesidades de su programa. - Declaracin:
struct tpila { int cima; int elementos[MAX_PILA]; };

Nota: MAX_PILA debe ser mayor o igual que 1. - Procedimiento de Creacin:


void crear(struct tpila *pila) { pila->cima = -1; }

- Funcin que devuelve verdadero si la pila est vaca:


int vacia(struct tpila *pila) { return (pila->cima == -1); }

- Funcin que devuelve verdadero si la pila est llena:


int llena(struct tpila *pila) { return (pila->cima == MAX_PILA); }

- Procedimiento de apilado:
void apilar(struct tpila *pila, int elem) { pila->elementos[++pila->cima] = elem; }

- Procedimiento de desapilado:
void desapilar(struct tpila *pila, int *elem) { *elem = pila->elementos[pila->cima--]; }

Programa de prueba:
#include <stdio.h> int main(void) { struct tpila pila; int elem; crear(&pila); if (vacia(&pila)) printf("\nPila vacia."); if (llena(&pila)) printf("\nPila llena."); apilar(&pila, 1); desapilar(&pila, &elem); return 0; }

Puesto que son muy sencillos, el usuario puede decidir implementar una pila 'inline', es decir, sin usar procedimientos ni funciones, lo cual aumentar el rendimiento a costa de una cierta legibilidad. Es ms, los problemas que aparecen resueltos en esta web en general utilizan las pilas con arrays de forma 'inline'. Adems, esta implementacin es algo ms rpida que con listas enlazadas, pero tiene un tamao esttico. En C y en algn otro lenguaje de programacin puede modificarse el tamao de un array si ste se define como un puntero al que se le reserva una direccin de memoria de forma explcita (mediante malloc en C). Sin embargo, a la hora de alterar dinmicamente esa regin de memoria, puede ocurrir que no haya una regin en la que reubicar el nuevo array (mediante realloc en C) impidiendo su crecimiento.

Implementacin mediante lista enlazada Para hacer la implementacin se utiliza una lista con cabecera ficticia (ver apartado de listas). Dado el carcter dinmico de esta implementacin no existe una funcin que determine si la pila est llena. Si el usuario lo desea puede implementar un anlisis del cdigo devuelto por la funcin de asignacin de memoria. - Declaracin:
struct tpila { int clave;

struct tpila *sig; };

- Procedimiento de creacin:
void crear(struct tpila **pila) { *pila = (struct tpila *) malloc(sizeof(struct tpila)); (*pila)->sig = NULL; }

- Funcin que devuelve verdadero si la pila est vaca:


int vacia(struct tpila *pila) { return (pila->sig == NULL); }

- Procedimiento de apilado (apila al comienzo de la lista):


void apilar(struct tpila *pila, int elem) { struct tpila *nuevo; nuevo = (struct tpila *) malloc(sizeof(struct tpila)); nuevo->clave = elem; nuevo->sig = pila->sig; pila->sig = nuevo; }

- Procedimiento de desapilado (desapila del comienzo de la lista):


void desapilar(struct tpila *pila, int *elem) { struct tpila *aux; aux = pila->sig; *elem = aux->clave; pila->sig = aux->sig; free(aux);

Programa de prueba:
int main(void) { struct tpila *pila; int elem; crear(&pila); if (vacia(pila)) printf("\nPila vacia!"); apilar(pila, 1); desapilar(pila, &elem); return 0;

Ver pilasle.c para tener una implementacin completa con lista enlazada. En este caso, hacerlo 'inline' puede afectar seriamente la legibilidad del programa. Si el usuario desea hacer un programa a prueba de balas puede probar el siguiente procedimiento de apilado, que simplemente comprueba si hay memoria para una asignacin de memoria:
void apilar(struct tpila *pila, int elem) { struct tpila *nuevo; if ((nuevo = (struct tpila *) malloc(sizeof(struct tpila))) == NULL) generar_error(); else { nuevo->clave = elem; nuevo->sig = pila->sig; pila->sig = nuevo; } }

Es obvio que si se llama al procedimiento generar_error es que el sistema se ha quedado sin memoria, o al menos se ha agotado la regin de memoria que el sistema operativo y/o el compilador dedican para almacenar los datos que la ejecucin del programa crea.

Otras consideraciones - Cuantos elementos hay apilados? En algunos casos puede ser interesante implementar una funcin para contar el nmero de elementos que hay sobre la pila. En la implementacin con arrays esto es directo. Si se hace sobre listas enlazadas entonces hay que hacer alguna pequea modificacin sobre la declaracin e implementacin:
struct nodo { int clave; struct nodo *sig; }; struct tpila { int numero_elems; /* mantiene el numero de elementos */ struct nodo *cima; };

Los detalles de la implementacin no se incluyen, pues es sencilla. - Cmo vaciar la pila?

En el caso de la implementacin con array es directo, basta con inicializar la cima al valor de vaco. Si es una lista enlazada hay que ir borrando elemento a elemento (o desapilarlos todos). Los detalles se dejan para el lector.

Elegir entre implementacin con listas o con arrays. El uso del array es idneo cuando se conoce de antemano el nmero mximo de elementos que van a ser apilados y el compilador admite una regin contigua de memoria para el array. En otro caso sera ms recomendable usar la implementacin por listas enlazadas, tambin si el nmero de elementos llegase a ser excesivamente grande. La implementacin por array es ligeramente ms rpida. En especial, es mucho ms rpido a la hora de eliminar los elementos que hayan quedado en la pila. Por lista enlazada esto no es tan rpido. Por ejemplo, pinsese en un algoritmo que emplea una pila y que en algunos casos al terminar ste su ejecucin deja algunos elementos sobre la pila. Si se implementa la pila mediante una lista enlazada entonces quedaran en memoria una serie de elementos que es necesario borrar. La nica manera de borrarlos es liberar todas las posiciones de memoria que le han sido asignadas a cada elemento, esto es, desapilar todos los elementos. En el caso de una implementacin con array esto no es necesario, salvo que quiera liberarse la regin de memoria ocupada por ste.

JAVA He modelado mis implementaciones de pilas despus de encontrar la arquitectura del API Collections de Java. Mis implementaciones constan de un interface Stack para una mxima flexibilidad, las clases de implementacin ArrayStack y LinkedListStack, y una clase de soporte FullStackException. Para facilitar su distribucin, he empaquetado estas clases en un paquete llamado com.javajeff.cds, donde cds viene de estructura de datos complejas. El siguiente listado presenta el interface Stack:

// Stack.java package com.javajeff.cds; public interface Stack { boolean isEmpty (); Object peek (); void push (Object o); Object pop (); }
Sus cuatro mtodos determinan si la pila est vaca, recuperan el elemento superior sin borrarlo de la pia, situan un elemento en la parte superior de la pila y el ltimo recuera/borra el elemento superior. Aparte de un constructor especfico de la implementacin, su programa nicamente necesita llamar a estos mtodos. El siguiente listado presenta una implementacin de un Stack basado en un array unidimensional:

// ArrayStack.java

package com.javajeff.cds; public class ArrayStack implements Stack private int top = -1; private Object [] stack; {

public ArrayStack (int maxElements) { stack = new Object [maxElements]; } public boolean isEmpty () { return top == -1; } public Object peek () { if (top < 0) throw new java.util.EmptyStackException (); return stack [top]; } public void push (Object o) { if (top == stack.length - 1) throw new FullStackException (); stack [++top] = o; } public Object pop () { if (top < 0) throw new java.util.EmptyStackException (); return stack [top--]; } } ArrayStack revela una pila como una combinacin de un ndice entero privado top y variables de referencia de un array uni-dimensional stack. top identifica el elemento superior de la pila y lo inicializa a -1 para indica que la pila est vaca. Cuando se crea un objeto ArrayStack llama a public ArrayStack(int maxElements) con un valor entero que representa el nmero mximo de elementos. Cualquier intento de sacar un elemento de una pila vaca mediante pop() resulta en el lanzamiento de una java.util.EmptyStackException. De forma similar, cualquier intento de poner ms elementos de maxElements dentro de la pila utilizando push(Object o) lanzar una FullStackException, cuyo cdigo aparece en el siguiente
listado:

// FullStackException.java package com.javajeff.cds; public class FullStackException extends RuntimeException { }


Por simetra con EmptyStackException, FullStackException extiende RuntimeException. Como resultado no se necesita aadir FullStackException a la clausula throws del mtodo. El siguiente listado presenta una implementacin de Stack utilizando una lista de enlace simple:

// LinkedListStack.java package com.javajeff.cds;

public class LinkedListStack implements Stack private static class Node { Object o; Node next; } private Node top = null; public boolean isEmpty () { return top == null; }

public Object peek () { if (top == null) throw new java.util.EmptyStackException (); return top.o; } public void push (Object o) { Node temp = new Node (); temp.o = o; temp.next = top; top = temp; } public Object pop () { if (top == null) throw new java.util.EmptyStackException (); Object o = top.o; top = top.next; return o; }

LinkedListStack revela una pila como una combinacin de una clase anidada privada de alto nivel llamada Node y una variable de referencia privada top que se inicialia a null para indicar una pila vaca. Al contrario que su contrapartida del array uni-dimensional, LinkedListStack
no necesita un constructor ya que se expande dinmicamente cuando se ponen los tems en la pila. As, void push(Object o) no necesita lanzar una FullStackException. Sin embargo, Object pop() si debe chequear si la pila est vaca, lo que podra resultar en el lanzamiento de una EmptyStackException. Ahora que ya hemos visto el interface y las tres clases que generan mis implementaciones de las pilas, juguemos un poco. El siguiente listado muestra casi todo el soporte de pilas de mi paquete com.javajeff.cds:

// StackDemo.java import com.javajeff.cds.*; class StackDemo { public static void main (String [] args) { System.out.println ("ArrayStack Demo"); System.out.println ("---------------"); stackDemo (new ArrayStack (5)); System.out.println ("LinkedListStack Demo"); System.out.println ("--------------------"); stackDemo (new LinkedListStack ()); } static void stackDemo (Stack s) {

System.out.println ("Pushing \"Hello\""); s.push ("Hello"); System.out.println ("Pushing \"World\""); s.push ("World"); System.out.println ("Pushing StackDemo object"); s.push (new StackDemo ()); System.out.println ("Pushing Character object"); s.push (new Character ('C')); System.out.println ("Pushing Thread object"); s.push (new Thread ("A")); try { System.out.println ("Pushing \"One last item\""); s.push ("One last item");

} catch (FullStackException e) { System.out.println ("One push too many"); } System.out.println (); while (!s.isEmpty ()) System.out.println (s.pop ()); try { s.pop (); } catch (java.util.EmptyStackException e) { System.out.println ("One pop too many"); } System.out.println ();

} }

Cuando se ejecuta StackDemo, produce la siguiente salida:

ArrayStack Demo --------------Pushing "Hello" Pushing "World" Pushing StackDemo object Pushing Character object Pushing Thread object Pushing "One last item" One push too many Thread[A,5,main] C StackDemo@7182c1 World Hello One pop too many LinkedListStack Demo -------------------Pushing "Hello" Pushing "World" Pushing StackDemo object

Pushing Character object Pushing Thread object Pushing "One last item" One last item Thread[A,5,main] C StackDemo@cac268 World Hello One pop too many

EJEMPLO Apilar N elementos y desapilarlos crea el efecto de invertir su orden: INICIO Lea N CrearPila(Pila1) Para i=1, N Lea dato Apilar(Pila1, dato) Fp MQ no PilaVacia(Pila1) Escriba Desapilar(Pila1) FMQ FIN

CrearPila( pila) cima = -1 Fin Apilar( pila, dato) cima = cima + 1 Elem(cima) = dato Fi n Funcion Desapilar( pila) Dato = elem(cima) Cima = cima - 1 Regrese( dato) Funcion PilaVacia( pila) Regrese ( cima == -1)

TAREA Validar parentizacin de expresiones aritmticas. Ej. ((5+3) / (8-4)) Leer una cadena y evaluar carcter por carcter, cada vez que se encuentre un parntesis abierto lo adiciona a la pila, cuando sea un parntesis cerrado saca de la pila. Si la expresin est bien al final de la cadena la fila debe quedar vaca. Se debe tener en cuenta que al desapilar se debe validar que la pila no est vaca. Ignorar los caracteres diferentes a parntesis.