Nombre Password [ Regístrate ]

Pilas

 

* Definición
* Implementación mediante array
* Implementación mediante lista enlazada
* Otras consideraciones

 

Definición

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; después 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 terminaríamos 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 implementación 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 continuación se expone la implementación de pilas mediante arrays y mediante listas enlazadas. En ambos casos se cubren cuatro operaciones básicas: Inicializar, Apilar, Desapilar, y Vacía (nos indica si la pila está vacía). Las claves que contendrán serán simplemente números enteros, aunque esto puede cambiarse a voluntad y no supone ningún inconveniente.

 

Implementación mediante array

Esta implementación es estática, es decir, da un tamaño máximo fijo a la pila, y si se sobrepasa dicho límite se produce un error. La comprobación de apilado en una pila llena o desapilado en una pila vacía no se han hecho, pero sí las funciones de comprobación, que el lector puede modificar según las necesidades de su programa.

- Declaración:

struct tpila
{
  int cima;
  int elementos[MAX_PILA];
};

Nota: MAX_PILA debe ser mayor o igual que 1.


- Procedimiento de Creación:

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


- Función que devuelve verdadero si la pila está vacía:

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


- Función 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 más, los problemas que aparecen resueltos en esta web en general utilizan las pilas con arrays de forma 'inline'. Además, esta implementación es algo más rápida que con listas enlazadas, pero tiene un tamaño estático.

En C y en algún otro lenguaje de programación puede modificarse el tamaño de un array si éste se define como un puntero al que se le reserva una dirección de memoria de forma explícita (mediante malloc en C). Sin embargo, a la hora de alterar dinámicamente esa región de memoria, puede ocurrir que no haya una región en la que reubicar el nuevo array (mediante realloc en C) impidiendo su crecimiento.

 

Implementación mediante lista enlazada

Para hacer la implementación se utiliza una lista con cabecera ficticia (ver apartado de listas). Dado el carácter dinámico de esta implementación no existe una función que determine si la pila está llena. Si el usuario lo desea puede implementar un análisis del código devuelto por la función de asignación de memoria.

- Declaración:

struct tpila
{
  int clave;
  struct tpila *sig;
};

- Procedimiento de creación:

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

- Función que devuelve verdadero si la pila está vacía:

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 implementación 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 asignación 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 región de memoria que el sistema operativo y/o el compilador dedican para almacenar los datos que la ejecución del programa crea.

 

Otras consideraciones

- ¿Cuantos elementos hay apilados?

En algunos casos puede ser interesante implementar una función para contar el número de elementos que hay sobre la pila. En la implementación con arrays esto es directo. Si se hace sobre listas enlazadas entonces hay que hacer alguna pequeña modificación sobre la declaración e implementación:

struct nodo
{
  int clave;
  struct nodo *sig;
};
struct tpila
{
  int numero_elems; /* mantiene el numero de elementos */
  struct nodo *cima;
};

Los detalles de la implementación no se incluyen, pues es sencilla.


- ¿Cómo vaciar la pila?

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

 

Elegir entre implementación con listas o con arrays.

El uso del array es idóneo cuando se conoce de antemano el número máximo de elementos que van a ser apilados y el compilador admite una región contigua de memoria para el array. En otro caso sería más recomendable usar la implementación por listas enlazadas, también si el número de elementos llegase a ser excesivamente grande.

La implementación por array es ligeramente más rápida. En especial, es mucho más rápido a la hora de eliminar los elementos que hayan quedado en la pila. Por lista enlazada esto no es tan rápido. Por ejemplo, piénsese en un algoritmo que emplea una pila y que en algunos casos al terminar éste su ejecución deja algunos elementos sobre la pila. Si se implementa la pila mediante una lista enlazada entonces quedarían 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 implementación con array esto no es necesario, salvo que quiera liberarse la región de memoria ocupada por éste.

 


© (2001-2008) ALGORITMIA.NET - Política de privacidad