22
Álvarez, María Fajardo, Daniel Sendín, Alba 1

Álvarez, María Fajardo, Daniel Sendín, Albabelar/Ampliacion/Cursos Anteriores/Practicas_C/Curso... · vuelve a la dirección siguiente a la instrucción en la que se llamo, y de

  • Upload
    others

  • View
    4

  • Download
    0

Embed Size (px)

Citation preview

Álvarez, María Fajardo, Daniel Sendín, Alba

1

INCIDE: Definición de Pilas …………………………………………………………

Concepto de Pila………………………………………………………….3 Operaciones fundamentales………………………………………………3 Otras operaciones………………………………………………………...4

Utilidades de las Pilas………………………………………………………

Llamadas a subprogramas………………………………………………..5 Paso de programas recursivos a iterativos………………………………..6 Equilibrado de símbolos………………………………………………….7 Tratamiento de expresiones aritméticas………………………………….9

Transformación de notación infija a postfija…………………….10 Evaluación de la notación postfija……………………………….12

Implementación de las Pilas………………………………………………. Implementación mediante vectores………………………………………14

Implementación mediante listas enlazadas……………………………….16 Programa Demo……………………………………………………………. Referencias………………………………………………………………….

2

DESCRIPCIÓN DE PILA

DEFINICION: Una pila (stack) es un tipo fundamental de lista (Tipo de dato abstracto fundamental) en la que todas las inserciones y supresiones de elementos solo se pueden realizar desde una única posición, el extremo, la cima o tope de la pila (TOP).

El modelo intuitivo de una pila es precisamente una pila de monedas

puestas sobre una mesa, o de cajas sobre el piso, o de bandejas en una estantería, situaciones todas en las que solo es conveniente quitar o agregar un objeto del extremo superior de la pila, al que se denominara en lo sucesivo tope.

Dado que las operaciones de añadir solo se realizan por el extremo superior de la pila, los elementos solo se pueden eliminar en orden inverso a como han sido insertados en la pila. Por esta razón a las pilas se las conoce como listas LIFO (last input, first output), ya que el último elemento en entrar será el primero en salir.

Las tres operaciones más importantes un una pila son:

PUSH: Meter, poner, apilar. Operación que inserta un elemento en la sima de la pila.

POP: Sacar, quitar, desapilar. Operación que elimina el elemento que

ocupaba la posición cima. El elemento que había sido insertado inmediatamente antes que el eliminado pasara a ha ser ahora la nueva posición cima.

TOPE: Operación que devuelve el elemento que ocupa en ese instante la

posición cima pero sin extraerlo.

A aparte de estas operaciones, que son las fundamentales, cuando queremos trabajar con pilas tendremos que utilizar más operaciones, sobre todo necesarias cuando se trata de pilas usando listas enlazadas. Dichas operaciones son las siguientes:

3

CREAR: Lo que hace esta operación es inicializar una pila como pila vacía.

BORRAR: Esta operación lo que hace es convertir una pila P en una pila

vacía.

VACIA?: Devuelve verdadero si la pila no tiene elementos, y falso en caso contrario. No podremos hacer POP ya que se produciría un desbordamiento negativo (underflow) al no tienen ningún elemento que eliminar.

LLENA?: Ocurre lo mismo que con el caso VACIA? Solo que en este

caso te devuelve verdadero si la pila esta llena y no podemos insertar ningún elemento más a no ser que eliminemos alguno antes. Si tratásemos de insertar un elemento más se producirá un error a causa de un desbordamiento (overflow).

Las pilas pueden representarse gráficamente de varias maneras, entre las

cuales destacan las siguientes.

A partir de ahora, para seguir dando propiedades de las pilas y sus operaciones, tendremos que distinguir entre pilas definidas mediante vectores o mediante listas enlazadas, pero eso lo veremos más adelante.

UTILIDADES DE LAS PILAS

Las pilas, además de simplificar algoritmos complejos y permitir acceso rápido a otros programas, son utilizadas ampliamente para solucionar una gran variedad de problemas. Se utilizan en compiladores, sistemas operativos y en programas de implementación. Veamos alguna aplicación interesante de las pilas.

Llamadas a subprogramas:

4

Cuando dentro del programa principal tienes subrutinas o funciones a las

que tienes que llamar, el programa principal tiene que recordar el lugar donde hizo la llamada y guardar el estado en el que se encontraba el programa en ese momento, con sus variables y valores previos a la llamada para poder retomar cuando el subprograma haya finalizado, ya que de otra forma el procedimiento sobrescribirá en ellas. Todo ellos se guarda en una pila.

Cuando, además, se trata de programas con llamadas recursivas es de

gran utilidad, ya que al hacer tantas paradas unas dentro de otras, si no se controla el tamaño de la pila, se puede producir un desbordamiento.

Veamos con más claridad como funciona una pila para las llamadas a los

subprogramas mediante un ejemplo.

Tenemos el programa principal con los subprogramas A, B, C, donde el programa principal llama al subprograma A, y este, una vez ejecutándose, llama al subprograma B. El programa B tiene una llamada al subprograma C, entonces el PP A B C. B no continua la ejecución hasta que termina el subprograma C, y A no termina la suya hasta que no termina el B. Lo mismo ocurre con el programa principal. Luego:

Esta operación se consigue mediante una pila que en la que se guardan las direcciones de retorno.

5

Cuando un programa o subprograma llama a otro, la dirección se

introduce en la pila, por supuesto en la cima, y cuando un subprograma termina, vuelve a la dirección siguiente a la instrucción en la que se llamo, y de este modo, la pila se va vaciando y el puntero de la pila queda libre siempre apuntando a la siguiente dirección de retorno, de la siguiente forma:

Paso de programas recursivos a iterativos.

La recursión es un método muy utilizado y una herramienta muy valiosa

y poderosa a la hora de crear algoritmos claros y concisos que se utiliza para problemas que se resuelven resolviendo ese mismo problema pero en una dimensión más pequeña una y otra vez hasta llegar al problema base y luego dar marcha atrás, es decir, problemas que se definen en términos de sí mismos, que se repiten una y otra vez a ellos mismos para calcular una solución. Pero el uso de la recursión es costoso en términos de consumo de memoria. Si además tenemos en cuenta que muchos lenguajes de programación no cuentan con la facilidad de implementar programas recursivos, seria bueno encontrar un procedimiento alternativo que permita simularlo.

Pero para simular un programa recursivo es necesario la utilización de

pilas, ya que se esta llamando continuamente a subprogramas que a la vez vuelven a llamarse a si mismo. Cada vez que un subprograma hace una llamada, hay que guardar la dirección a la cual debe regresar cuando acabe de ejecutar el subprograma llamado y las variables y valores actuales antes de la llamada, pues de lo contrario estos valores podrían ser sobrescritos.

Cada colección de variables que se guarda antes de hacer una llamada se

llama registro de activación.

Veamos como se utiliza esta eliminación de la recursión mediante un ejemplo sencillo hecho en clase. El cálculo del número factorial.

El programa principal es de la forma:

main ( ) {

6

int n, factorial (); scanf (“%d”, &n); printf (“Este es el factorial de %d: %d”, n, factorial(n)); }

int factorial (int n) { /*función recursiva que calcula el factorial*/ if (n == 1 or n == 0) return 1; /*caso base*/ else return (n*factorial (n-1)); }

Cuando le damos un valor a n, al llamar al subprograma por primera vez tendremos que guardar esa n en alguna parte. Este valor lo guardo en la pila, que para este tipo de información es una estructura muy adecuada, ya que ésta no va a poder ser utilizada hasta que termine la llamada al subprograma. La forma de guardar los datos es la siguiente:

A medida que se van realizando llamadas, se van guardando los valores

de n (guardados en los registros de activación) en la pila; así, los valores correspondientes a llamadas más externas estarán más abajo en la pila, y los correspondientes a llamadas más internas, más arriba.

Una vez que haya concluido este proceso, lo que hace el programa es

sacar el tope de la pila, sacar el nuevo tope y multiplicarlos, y luego ir sacando iterativamente los topes y multiplicándoles por el producto anterior.

Equilibrado de símbolos.

También pueden utilizarse las pilas para revisar programas en busca de errores, como por ejemplo en el caso de los símbolos.

En cada compilador se utilizan las pilas para comprobar si se han

producido errores, para que cada paréntesis, llave o corchete de apertura tenga su correspondiente cierre. Es decir, que si un programa presenta más símbolos de apertura que de clausura, el programa tendría errores y no seria válido.

También tenemos que tener en cuenta el orden en el que estén dichos

símbolos, porque puede que aunque tenga los mismos símbolos de apertura que de clausura, tenga errores, ya que dichos símbolos no estén colocados correctamente. Por ejemplo, si nos encontramos en este caso: ([{])} el programa daría un error, ya que la segunda llave no esta colocado en su lugar y trastocaría toda la estructura interna del programa.

7

Para ello puede crearse un programa que dado un fichero en el lenguaje del compilador correspondiente nos diga si hay algún tipo de error en la anidacion de los símbolos mediante la utilización de pilas. El programa funcionaria de la siguiente forma:

Creamos un pila vacía de caracteres y se abre el fichero en el que se

encuentra el programa. Se leen uno a uno los caracteres, pero solo nos quedamos con aquellos que sean paréntesis, corchetes o llaves. Cuando nos encontremos con un carácter de apertura lo metemos en la pila y seguimos leyendo caracteres hasta que lleguemos al siguiente. Mientras sea un carácter de apertura, se mete en la pila.

Cuando nos encontremos con un carácter de clausura, lo que tenemos que hacer es mirar a la cima de la pila. Si lo que nos encontramos es el símbolo de apertura correspondiente al símbolo que estamos mirando, lo sacamos de la pila, en caso contrario, se mete en la pila. Y así sucesivamente hasta que hayamos leído todo el fichero.

Una vez finalizada la lectura del fichero sabremos si están bien anidados, y por lo tanto si el programa es aparentemente correcto si y solo si la pila esta vacía, ya que cada símbolo que hemos abierto, lo habremos cerrado correctamente.

Pero este programa tiene una pega, ya que si el fichero que estamos

leyendo con el contenido del programa tiene comentarios en los que escribe símbolos no anidados o erróneamente colocados, no afectaría al programa que queremos revisar ya que es un comentario, pero si que afectaría al nuestro, ya que la pila no quedaría vacía. Es decir, que un trozo de código bien hecho puede interpretarse como un trozo con errores. Algún ejemplo:

/*Con un comentario así ( [ ) ] el programa nos daría un error*/

Printf (“con una expresión así también nos daría un error %c:”, ‘}’)

Por lo tanto si queremos utilizar este programa para depurar errores un nuestro programa, tendremos que asegurarnos previamente de que los comentarios que hemos puesto son correctos y que no pueden inducir a error al

8

igual que los símbolos que utilizamos como escritura tampoco nos van a dar ningún problema.

Para un programa como este emplearemos listas enlazadas, pues no

sabemos cual es la longitud del fichero y tampoco cual es la cantidad de símbolos que este contiene, por lo que un vector podría quedarse corto o pasarse de largo.

Tratamiento de expresiones aritméticas:

Una expresión aritmética esta formada por operandos y operadores, como por ejemplo (a+b) - c*d. En este caso +, -, * son los operadores y a, b, c, d los operandos. Los operandos pueden ser valores, variables o incluso otras expresiones, mientras que los operadores son los conocidos de las expresiones matemáticas, por lo que el resultado puede ser de tipo numérico o variables.

La forma habitual que tenemos de escribir las operaciones matemáticas

es poniendo el operador entre dos operando, y que puede llevar paréntesis para encerrar subexpresiones con mayor prioridad en cada operación, al igual que los operadores tienen cada uno una prioridad diferente, ya que, por ejemplo, la multiplicación * tienen prioridad sobre la suma +.Veamos el grado de prioridad de estos operadores.

1. Paréntesis ( ) 2. Potencial ^ 3. Producto/cociente * / 4. Suma/resta + -

También hay que tener en cuenta que cuando se evalúa una operación se

hace de izquierda a derecha, excepto en el caso de las potencias, que se hace de derecha a izquierda.

Esta forma de escribir una expresión, se denomina notación usual o

infija.

Existen otras formas de escribir expresiones aritméticas, en el que se diferencian por la situación de los operadores respecto de los operandos. La forma en la que los operadores se colocan delante de los operandos es conocida como notación polaca o prefija.

Vemos un ejemplo:

Notación infija: a*b/(a+c) Notación polaca: */ab+ac

Además de estas existe otro tipo de notación, el que a nosotros más nos

interesa, que es la notación postfija, en la que se colocan los operadores a

9

continuación de los operandos, y en la cuál no existe jerarquía en las operaciones.

Las diferentes formas de escribir una misma expresión algebraica

dependen de la ubicación de los operadores respecto a los operandos. Es importante tener en cuenta que tanto en la notación prefija como en la postfija no son necesarios los paréntesis para cambiar el orden de evaluación. Veamos algunos ejemplos de notación infija respecto de la notación postfija

Notación infija Notación postfija 2*(3+8/(6-4)) 23864-/+*

3+2*7-1 327*+1-

1*2*3*4+5 12*3*4*5+

Vemos que hacemos lo mismo que haríamos en notación infija y no hemos tenido que acudir a paréntesis ni a jerarquías de operaciones. Por eso la evaluación de expresiones postfijas es muy fácil de implementar.

A la hora de evaluar una expresión aritmética de notación infija,

tendremos que realizar dos pasos:

• Transformación de notación infija a notación postfija. • Evaluación de la notación postfija.

Para ello tendremos que emplear un tipo TDA pila. Veamos que ocurre

en cada uno de los casos.

Transformación de notación infija a notación postfija

Partimos de una expresión con notación infija que tiene operadores y operandos, que queremos transformar en una notación postfija.

La transformación se realiza mediante la utilización de una pila en la que

se van metiendo los operadores y los paréntesis izquierdos. La expresión aritmética se va leyendo carácter a carácter y va guardándose en una pila o en un array, según de lo que se trate, de la siguiente forma:

Si es un operando, se guardara directamente en el array o en un

fichero.

Si es un operador y la pila está vacía, lo meteremos en la pila. Si la pila no esta vacía, tendremos que distinguir el grado de prioridad de dicho operador. Por lo tanto:

Si la cima de la pila es un operador de menor prioridad, entonces lo meteremos en la pila.

10

Sin embargo si la cima de la pila es un operador de mayor o igual prioridad, entonces tendremos que sacar los operadores de la pila uno a uno y los escribiremos en el array o fichero hasta que en la cima haya en operador de menor prioridad, un paréntesis de apertura o hasta que la pila quede vacía. Será entonces cuando metamos el operador. De esta forma nos aseguramos de que las operaciones con mayor prioridad se realizan anteriormente, pues sus operadores son los que antes salen de la pila

Si es un paréntesis tendremos que distinguir los dos casos existentes:

Si es un paréntesis de apertura se mete en la pila, se considerara de menor prioridad para todo.

Cuando se lee el otro paréntesis de clausura hay que sacar todos los operadores de la pila que pasaran a formar parte de la notación postfija hasta llegar al paréntesis de apertura, que se eliminara, ya que los paréntesis no forman parte de la notación postfija.

De esta forma aseguramos que las operaciones contenidas en un paréntesis van a ser agrupadas.

El proceso se termina cuando ya no hay mas ítems que leer, y es entonces

cuando sacamos uno a uno los operadores que pudieran quedar en la pila y los escribimos al final de nuestro array o fichero. Todo habrá concluido cuando no haya nada más que leer y la pila este vacía.

En estos casos también es conveniente utilizar pilas implementadas

mediante listas enlazadas, ya que no sabemos cual va a ser la longitud de la pila y si utilizamos un vector podemos quedarnos cortos o pasarnos de largo.

Veamos algún ejemplo que nos aclare las cosas:

“a*(b+c-(d/e^f)-g)”

a el operando a pasa a un fichero o array, y * a la pila ab el ( pasa a la pila y el operando b pasa al fichero abc el + pasa a la pila y el operando c se escribe en el fichero

La pila tiene la siguiente forma:

+ ( *

- ( *

Se nos presenta el problema del -, que como tiene igual prioridad que el + se saca +, se escribe en el fichero y se mete – en la pila.

abc+ el ( se escribe en la pila abc+de el operando d pasa al array, el / a la pila, y e pasa al array abc+def ^ lo metemos en la pila, y f lo escribimos en el fichero.

11

Ahora se cierra un paréntesis y tenemos la siguiente pila:

^ / ( - ( *

Luego nuestra expresión quedará, tras haber sacado los operadores hasta paréntesis y eliminándolo después de haberlo sacado así: abc+def^/

abc+def^/-g el –se compara con la cima de la pila, que al ser de igual prioridad se escribe en el array o fichero y en – se mete en la pila.

abc+def^/-g- al terminar el otro paréntesis, escribimos todo lo que hay en la

pila hasta llegar al paréntesis de apertura. La pila será ahora de la forma:

*

Como ya no hay mas ítems escribimos el contenido de la pila al final de la notación postfija, y la pila quedará vacía, y el resultado será: abc+def^/-g-*

Evaluación de la notación postfija:

Cuando tenemos una expresión aritmética en notación postfija, con valores reales, y queremos evaluarla, lo que tendremos que hacer será utilizar una pila de operandos, es decir, de números reales.

Tendremos un algoritmo que evaluará la expresión, leyendo los datos de

la expresión uno a uno y tratándolos según su tipo.

Si es un número real, lo meteremos en la pila. Si es un operador, lo que haremos será sacar dos números reales de la

pila y les aplicaremos el operador sobre ellos. El resultado lo meteremos en la pila. Hay que tener en cuenta a los operadores resta y cociente (- y /) ya que en ocasiones no nos es indiferente el orden de los números. Para ellos, el elemento que antes hubiese entrado en la pila, es decir, el que sacamos en segundo lugar, será el que aparezca a la izquierda del operador.

Resumidamente, se trata de recorrer la expresión y cada vez que

encontremos un operador, se lo aplicamos a los dos últimos números que

12

hayamos introducido en la pila. Entonces el número real que quede en la pila será el resultado.

Tanto en este caso, como en el anterior, como no sabemos el tamaño de

la expresión, será mejor utilizar pilas implementadas mediante listas enlazadas.

Veamos un ejemplo de la evaluación de la notación postfija:

Evaluemos la expresión postfija: “327*+1-“

Tendremos una pila de la forma:

Y nos encontramos con un operador *, y lo que hacemos es: 7 2 3

TOPE (Pila) = 7 POP (Pila) TOPE (Pila):=2 POP (Pila) 7*2=14 PUSH (pila, 14)

14 3

Ahora nos encontramos con el operador +, y procedemos de igual

manera:

TOPE (Pila)=14 POP (Pila) TOPE (Pila)=3 POP (Pila) 14+3=17 PUSH (Pila, 17) PUSH (Pila, 1)

1 17

Nos encontramos con el operador - . Lo que hacemos es sacar los dos valores de la pila con y resolver, y ese

será el resultado: 17 – 1= 16

Por lo tanto 327*+1- = 16

13

IMPLEMENTACIÓN DE LAS PILAS:

Mediante vectores:

Una pila puede implementarse mediante vectores, en cuyo caso la dimensión

es fija. En este caso se dirá que es una implementación estática. Cuando lo implementamos mediante un vector hay que tener en cuenta que el tamaño de la pila no puede exceder el número de elementos del array.

Definiremos, antes de implementar una pila mediante vectores, el número

máximo de elementos que podremos meter en el apila. Será n

La declaración de pila es:

Elemento = E; Pila = registro de

cima: numérico; arreglo: vector [1…n] de elemento:

fin registro;

Luego la pila será:

Donde cima corresponde a la posición en el vector del último elemento insertado en la lista, o sea que a de apuntar en todo momento al elemento tope. Así si pila.cima=01 entenderemos que la pila esta vacía, mientras que si pila.cima = n la pila estará llena.

Esta implementación no es apropiada cuando no se conoce el número

máximo de elementos que puede tener una pila ya que podría quedarse corta por faltarle posiciones, y se produciría un error, o por pasarse de larga, es decir, utilizar mucha más memoria de la estrictamente necesaria y desaprovecharla. De todas formas, esta implementación tiene una gran ventaja, que es la sencillez y rapidez con la que se programa y se realizan las operaciones.

Veamos cuales son las operaciones básicas:

#defina tam_max 20 /*este será el tamaño máximo (20), se puede cambiar*/ Typedef int elemento; /*será una pila de enteros*/ Typedef struct {

int cima; elemento v[tam_max];

14

}pila; /*El tipo elemento variará, al igual que el tam_máx, dependiendo del

programa*/

Crear_Pila (PILA *P){ /*Creamos una pila vacía*/ Pila.cima = tam_max; }

Borrar_Pila (PILA *P){ /*Borramos una pila, la dejamos vacía*/ while(!VACIA(P)) POP(P); }

Vacia? (PILA *P){ /*Preguntamos si la pila está vacía*/ if (pila.cima == tam_max){

return 1; }

else { return 0; }

}

Llena? (PILA *P){ /*Comprobamos si la pila esta llena*/ if (pila.cima == 0){

return 1; }

else { return 0; }

}

PUSH (PILA *P, elemento x){ /*Inserta un elemento en la pila(si puede)*/ int copia_cima, valor_llena; valor_llena = llena?(P); if (valor_llena == 1) {

return 0; }

else { copia_cima = *P.tope; *P.v[copia_cima-1] = x; *P.cima = copia_cima-1; return 1; }

}

15

POP (PILA *P){ /*Elimina el primer elemento del vector, la cima*/ int valor_vacia; valor_vacia = Vacia?(P); if (valor_vacia == 1){

return 0; }

else{ *P.cima = *P.cima+1; return 1; }

}

TOPE (PILA *P){ /*Te devuelve el tope de la pila*/ int copia_cima; if ( vacia?(P) == 0){

copia_cima = P.cima; return P.v[copia_cima]; }

else{ printf(“\nError. Has aplicado la función tope a una pila vacia\n”);ç }

}

Mediante listas enlazadas:

La realización dinámica de una pila se hace almacenando los elementos como nodos de una lista enlazada, con la particularidad de que siempre que se desee hacer una operación sobre ella se hace por el mismo extremo que se extrae.

Esta realización tiene la ventaja de que el tamaño se ajusta exactamente a

los elementos de la pila, no hay un máximo de elementos, por lo tanto tampoco puede haber una lista llena. Sin embargo, para cada elemento es necesaria más memoria ya que hay que guardar el campo de enlace.

Una pila realizada con listas enlazadas crece y decrece dinámicamente.

En tiempo de ejecución se reserva memoria según se ponen los elementos en la pila y se libera memoria según se extraen elementos de la pila.

A diferencia de la cabecera de una lista usual, la cabecera de una pila no

va a ser un registro que incluya el número de nodos y punteros al primer y último elemento, sino que va a ser simplemente un puntero que apunta al primer nodo.

La declaración de la pila será:

16

Elemento = E; Nodo = registro de

info: elemento; sgte: puntero a Nodo;

fin registro;

Pila = puntero a NODO.

Las operaciones principales serán las siguientes:

Crear_pila(PILA *P){ /*Creaciones de la pila*/ P->longitud=0; P->prim=NULL; }

Borrar_Pila (PILA *P){ /*libera la pila*/ while(!VACIA(P)) POP(P); }

PUSH (PILA *P,elemento *x){ /*Apila un elemento más*/ POSICION temp; temp = crea_nodo(x); temp->info=*x; if(VACIA(P)){

P->prim=temp; temp->sgte=NULL; }

else{ temp->sgte=P->prim; P->prim=temp; }

P->longitud++; }

POP (PILA *P){ /*Elimina el tope de la pila*/ NODE *q; if (VACIA(P)) printf("No existe tope,ya que la pila está vacía.\n"); else {

q = P->prim; P->prim = q->sgte; free(q); P->longitud--; }

}

17

TOPE (PILA *P){ /*Devuelve el elemento tope de la pila*/ if (VACIA(P)) printf("No existe tope.\n"); else return(P->prim->info); }

PROGRAMA DEMO: #include <stdio.h> #include <stdlib.h> #include <string.h> /*Declaraciones*/ typedef int elemento; struct NODE{ elemento info; struct NODE *sgte; }; typedef struct NODE nodo; typedef NODE *POSICION; typedef struct { int longitud; POSICION prim; } PILA; /*Creaciones de la pila*/ void crear_pila(PILA *P) { P longitud=0; P prim=NULL; } /* Creación de un nodo a partir de un elemento dado */ NODE * crea_nodo (elemento *i) { static NODE *p; p = (NODE *) malloc (sizeof(NODE)); if (p == NULL) printf ("\nError. No puedo crear puntero a estructura\n"); else{ p info= *i; /* Copio datos originales, en campo info de nodo creado */ p sgte = NULL; }

18

Return (p); } /*la pila está vacía?*/ int VACIA (PILA *P) { int resp,tam; tam = P longitud; resp = (tam == 0)?1:0; return resp; } /*Devuelve el elemento tope de la pila*/ elemento TOPE (PILA *P) { if (VACIA(P)) printf ("No existe tope.\n"); else return (P prim info); } /*Elimina el tope de la pila*/ void POP (PILA *P) { NODE *q; if (VACIA (P) ) printf ("No existe tope,ya que la pila está vacía.\n" ); else { q = P prim; P prim = q sgte; free(q); P longitud--; } } /*libera la pila*/ void DESTRUIR (PILA *P) { While (!VACIA(P)) POP (P); } /*Apila un elemento más*/ void PUSH (PILA *P,elemento *x) { POSICION temp; temp = crea_nodo(x); temp info =*x;

19

if(VACIA(P)){ P prim = temp; Temp sgte = NULL; } else{ temp sgte = P prim; P prim = temp; } P longitud++; } /*Da la vuelta a la pila*/ PILA GIRA (PILA *P) { PILA S; crear_pila (&S); elemento r; do{ r = TOPE (P); PUSH (&S, &r); POP (P); }while (VACIA(P) == 0); return S; } /*Mostrar el contenido de la pila*/ void contenido_pila(PILA *P) { PILA S; elemento r; crear_pila (&S); /*Con dos pilas, muestra la pila inicial correctamente*/ S = GIRA (P); printf("El contenido de la pila es:\n"); do{ r = TOPE (&S); printf ("%d\n",r); PUSH (P,&r); POP (&S); }while (VACIA(&S) == 0); } /* Empezamos nuestro programa principal en el utilizaremos todos los subprogramas anteriores, cada uno para una cosa*/ main (){ PILA P,S;

20

FILE *in; elemento x; int opcion; int resp; char nombre[20]; char linea[10]; do{ /*Bucle do para que puedas realizar varias operaciones al mismo tiempo*/

printf("Elije una de las siguientes opciones:\n1. Crear una pila con los datos de un fichero.\n2. Crear una pila con los datos por teclado.\n3. Guardar una pila en un fichero.\n4. Borrar una pila.\n5. Insertar un elemento en una pila.\n6. Eliminar un elemento en una pila.\n7. Mostrar el contenido de una pila.\n8. Terminar.\n"); scanf("%d",&opcion); if (opcion == 1){ /*Abre un archivo, lee los datos y los mete en una pila*/ crear_pila (&P); printf("dame el nombre del fichero del que sacar los datos(con extension .txt)"); scanf("%s",nombre); if ((in = fopen(nombre, "r")) != NULL){ do{ fscanf (in,"%d",&x); PUSH (&P,&x); }while (fgets(linea,10,in) != NULL); fclose(in); } else printf ("No se pudo abrir el fichero "); } if (opcion == 2){ /*Crea una pila mediante los datos que le das por teclado*/ crear_pila(&P); do{ /*El usuario elige cuantos datos quiere darle a la pila*/ printf ("\n¿Quieres introducir un elemento?(s=1/n=2)"); scanf ("%d",&resp); if (resp == 1){ printf ("\nDame el elemento que quieres introducir"); scanf ("%d", &x); PUSH (&P, &x); } }while(resp==1); } if (opcion == 3){ /*Dada una pila la gira, para que al meterla este en orden*/ crear_pila(&S); S = GIRA(&P); printf("Dame el nombre del fichero en que quieres guardar la pila(con extension.txt)"); scanf("%s",nombre); if((in = fopen(nombre, "w")) != NULL){ /*Abre un fichero y guardara la pila*/

21

do{ fprintf(in,"%d\n",TOPE(&S)); POP (&S); Resp = VACIA(&S); }while (resp==0); } else printf("No se pudo abrir el fichero"); } if (opcion == 4){ /*Elimina la pila*/ DESTRUIR (&P); } if (opcion == 5){ /*En esta función mete un elemento a la pila*/ printf("\nDame el elemento que quieres introducir"); scanf("%d", &x); PUSH (&P, &x); } if (opcion == 6){ /*En este otro caso saca un elemento de la pila*/ POP (&P); } if (opcion == 7){ /*Muestra los datos de la pila por pantalla*/ contenido_pila (&P); } }while(opcion!=8); } REFERENCIAS: Libros:

Estructuras de datos y algoritmos. Mark Allen Weiss Estructuras de datos. Aho, Hopcroft, Ullman Estructuras de datos. Cairo, Guardati Fundamentos de programación. Algoritmos, estructurasde datos. Una

perspectiva en C. Joyanes Aguilar Fundamentos de programación. Algoritmos, estructurasde datos y objetos.

Joyanes Aguilar Paginas web:

www.fcad.uner.edu.ar/C_LicSistemas/ Materias/est_datos/MartinezQuiroga_06PilasyFilas.pdf -

www.politeca.ing.ula.ve/Contenido/Digiteca/ProyectosTesisGrado/Pregrado/UniversidaddelosAndes/IngenieriaSistemas/DepartamentoComputacion/2005/RodriguezJose.pdf

22