PILAS

 

PILA.(LIFO)(Last-In,First-Out:Ultimo en entrar, primero en salir) 
   

  Es una lista de elementos a la cual se puede insertar o eliminar elementos sólo por uno de los extremos. En consecuencia, los elementos de una pila serán eliminados en orden inverso al que se insertaron. Es decir, el último elemento que se mete en la pila es el primero que se saca.  Las pilas pertenecen al grupo de datos lineales, ya que los componentes ocupan lugares sucesivos en la estructura.
      Existen numerosos casos prácticos en los que se utiliza el concepto de pila.

                             Pila de latas en un supermercado                               Pila de platos


                                                                 

                                                                        Ejemplos prácticos de Pilas.

**El cocinero cuando necesita un plato limpio, tomará el que está encima de todos, que es el último que se colocó en la pila.

Representación de PILAS.


     Las pilas no son estructuras fundamentales de datos, es decir, no están definidas como tales en los lenguajes de programación (como lo están por ejemplo los arreglos). Las pilas pueden representarse mediante el uso de:
     *Arreglos.
     *Listas enlazadas.


                             PILA
                                            

 

    a)    Pila Llena                                   b) Pila con algunos elementos                                  c) Pila vacía

                                                        

         Donde TOPE = MAX, se quisiera insertar otro elemento, se tendría un error de desbordamiento. la Pila está llena y el espacio reservado de memoria es fijo, no puede expandirse o contraerse.
         Una posible solución a este tipo de errores consiste en definir pilas de gran tamaño, pero esto resultaría ineficiente y costoso si sólo se utilizaran algunos elementos. No siempre es posible saber con exactitud cuál es el número de elementos a tratar, por lo tanto siempre existe la posibilidad de cometer un error de desbordamiento (si se reserva menos espacio del que efectivamente se usará) o bien, de hacer uso ineficiente de la memoria (si se reserva más espacio del que se empleará).

Doble PILA.

                       Existe una solución. Consiste en usar espacios compartidos de memoria para la implementación de pilas. Supóngase que se necesitan dos pilas, cada una de ellas con un tamaño máximo de N elementos. Se definirá un solo arreglo de 2*N elementos, en lugar de dos arreglos de N elementos cada uno.   
                                                                                                      
                                                    Representación de pilas en espacios compartidos.

** La pila1 ocupará de la posición 1 en adelante, mientras que la pila2 ocupará de la posición 2N hacia atrás (2N-1, 2N-2....) Si en algún punto del proceso la PILA1 necesitará más de N espacios y en ese momento la PILA2 no tuviera ocupados sus N lugares, entonces se podrían seguir agregando elementos a la PILA1 sin caer en un error de desbordamiento.
** Otro error para pilas es que se trate de eliminar un elemento y la pila este vacía. Este error se conoce con el nombre de subdesbordamiento.         

OPERACIONES.

Las operaciones elementales que pueden realizarse son:


* Poner un elemento (Push)
* Quitar un elemento (Pop)
    
ALGORITMOS.

INSERTAR....
PONE(PILA,MAX,TOPE,DATO)
{ Este algoritmo pone el elemento DATO en PILA. Actualiza el valor de TOPE.  MAX es el máximo número de elementos que puede almacenar PILA}
Si TOPE < MAX entonces{Verifica que hay espacio libre}
      TOPE = TOPE +1
      PILA[TOPE]=DATO
Si no  Escribir "La pila esta llena-Desbordamiento"
FinSi

ELIMINAR.
QUITA(PILA,MAX,TOPE,DATO)
{Este algoritmo saca el ultimo elemento de la PILA y lo guarda en DATO. Actualiza el valor TOPE}
Si TOPE > 0 entonces {Verifica que la pila no este vacía}
       DATO=PILA[TOPE]
       TOPE = TOPE -1
Si no Escribir "No hay elementos en la Pila-Subdesbordamiento"
Fin Si                                                  

Ejemplo.

Inserción y Eliminación.
                                  a)                                     b)                                                                                                                           c)

      
        

a) Si se pusieran los elementos lunes, martes, miércoles, jueves y viernes (en este orden) en PILA.
b) Si se quitara viernes el tope quedaría en jueves.
c) Si se pretendiera eliminar martes antes se tendría que quitar Jueves y Miércoles para que quede martes en la cima de la pila y pueda extraerse.

APLICACIONES.

    Las pilas son una estructura de datos muy usada en la solución de diversos tipos de problemas. Algunos casos representativos son:

Llamada a Subprogramas.


       
Cuando se tiene un programa que llama a subprograma, internamente se usan pilas para guardar el estado de las variables del programa en el momento que se hace la llamada. Así, cuando termina la ejecución del subprograma, los valores almacenados en la pila pueden recuperarse para continuar con la ejecución del programa en el punto en cual fue interrumpido. Además de las variables debe guardarse la dirección del programa en la que se hizo la llamada, porque es a esa posición a la que regresa el control del proceso.
        Por ejemplo, se tiene un programa principal (PP) que llama a los subprogramas UNO y DOS.   A su vez, el subprograma DOS llama al subprograma TRES.  Cada vez que la ejecución de uno de los subprogramas concluye, se regresa el control al nivel inmediato superior.
                                                                          

                            a)                                  b)                                c)                                  d)                                 e)                              f)

    

a) Cuando el programa PP llama a UNO, se guarda en una PILA la posición en la que se hizo la llamada.
b) Al terminar UNO, el control se regresa a PP recuperando previamente la dirección de la PILA.
c) Al llamar a DOS, nuevamente se guarda la dirección de PP en la pila. 
d) Cuando DOS llama a TRES,  se pone en la PILA la dirección de DOS. 
e) Después de procesar TRES, se recupera la posición de DOS para continuar con su ejecución. 
f) Al terminar DOS se regresa el control a PP obteniendo previamente la dirección guardada en la pila.

     
Finalmente podemos concluir que las pilas son necesarias en esta área de aplicaciones por lo siguiente:


* Permiten guardar la dirección del programa (subprograma) desde donde se hizo la llamada a otros subprogramas, para poder regresar y seguir ejecutándolo a partir de la instrucción inmediata a la llamada.
* Permiten guardar el estado de las variables en el momento que se hace la llamada, para poder seguir ocupándolas al regresar del subprograma.

Recursión.


       
Es un concepto amplio difícil de precisar. Aparece en numerosas actividades de la vida diaria, por ejemplo, en una fotografía de una fotografía. Otro caso de recursión muy ilustrativo, es el que se presenta en los programas de televisión en los cuales un periodista transfiere el control a otro periodista que se encuentra en otra ciudad, y éste a su vez pudiera trasferirlo a un tercero.  En este capítulo nos limitaremos a tratar la recursión como herramienta de programación.
        La recursión permite definir un objeto (problemas, estructuras de datos) en términos de sí mismo. Casos típicos de estructuras de datos definidas de manera recursiva son los árboles y las listas ligadas. Algunos ejemplos de problemas que se definen recursivamente se estudiarán posteriormente.
        Un subprograma  que se llama a si mismo se dice que es recursivo.   La recursión en los subprogramas puede darse de dos maneras diferentes:
                            a)                                                                       b)
                     
                                 

a) DIRECTA. El subprograma se llama directamente a sí mismo. 
Por ejemplo P es un subprograma y en alguna parte de él aparece una llamada a sí mismo
b)
INDIRECTA. El subprograma llama a otro subprograma, y éste a su vez llama al primero. 
Por ejemplo: 
*El subprograma P llama al subprograma Q y éste a su vez invoca al primero; es decir el control regresa a P.
*El subprograma llama a otro y éste  a un tercero. Una vez ejecutando  el control regresa al subprograma que lo llamó, lo mismo sucede en todos los niveles. Por ejemplo el subprograma P llama al subprograma Q, y éste llama al subprograma V. Cuando V concluye, el control regresa a Q y al terminar éste, el control se transfiere a P.       

    Funcionamiento interno de la Recursión.
       
Internamente se utiliza una estructura tipo PILA para guardar los valores de las variables y constantes locales del programa o subprograma que efectúa la llamada. De esta manera, una vez concluida la ejecución, se puede seguir con los pasos que quedaron pendientes recuperando los valores necesarios de la pila. Con cada llamada recursiva se crea una copia de todas las variables y constantes que estén vigentes, y se guarda esa copia en la pila.   Además se guarda una referencia a la siguiente instrucción a ejecutar.  Al regresar se toma  la imagen guardada en el tope de la pila y se continúa operando. Esta acción se repite hasta que la pila quede vacía. 
Ejemplo:
    Calcular Factorial de un número N.       N=4



        En el primer paso N = 4; por lo tanto , siguiendo el algoritmo se debe hacerse 4 * 3!.  Ante la imposibilidad de realizar esta operación (porque primero debe calcularse 3!)  se guarda el valor de n en la pila y se continua trabajando con N = 3. 
        Con el nuevo valor de N procedemos de manera similar que con N = 4. La diferencia radica en que la entrada, N, esta mas cercana al estado básico. En consecuencia, en el paso 2 se agrega el valor 3 a la PILA para multiplicarlo posteriormente 2!.  En el paso tres se agrega el valor 2 a la PILA y en el paso cuatro se guarda el valor 1 en la PILA. Una vez alcanzado el estado básico, paso cinco, se comienza a extraer los valores almacenados en la pila y a operar con ellos. En el paso seis se extrae de la PILA el valor 1 y se multiplica por el factorial de 0, obteniendo 1!. Luego se extrae el 2 y se multiplica por el factorial de 1, para obtener 2!.    Se continúa así hasta que la pila quede vacía, lo cual ocurre en el paso nueve donde se calcula el factorial de 4. 

   El problema de las Torres de Hanoi es un problema clásico de recursión, ya que pertenece a la clase de problemas cuya solución se simplifica notablemente al utilizar recursión

Tratamiento de Expresiones Aritméticas.

    Un problema interesante en computación es poder convertir expresiones en notación INFIJA a su equivalente en notación POSTFIJA(ó PREFIJA). 
* Dada la expresión A+B se dice que está en notación INFIJA, y su nombre se debe a que el operador (+) esta entre los operadores (A y B).
* Dada la expresión AB+ se dice que está en notación POSTFIJA, y su nombre se debe a que el operador (+) esta después entre los operadores (A y B)
* Dada la expresión +AB se dice que está en notación PREFIJA, y su nombre se debe a que el operador (+) esta antes de los operadores 
(A y B).
     La ventaja de usar expresiones en notación polaca POSTFIJA ó PREFIJA radica en que no son necesarios los paréntesis para indicar orden de operación, ya que este queda establecido por ubicación de los operadores con respecto a los operandos.
     Para convertir una expresión dada en notación INFIJA a una notación POSTFIJA (ó PREFIJA), deberán establecerse previamente ciertas condiciones:
-- Solamente se manejarán los siguientes operadores (Están dados ordenadamente de mayor a menor según su prioridad de ejecución):
          ^   (potencia)
         * /  (multiplicación y división)
         + - (suma y resta)
-- Los operadores de mas alta prioridad se ejecutan primero
-- Si hubiera en una expresión  dos o mas operadores de igual prioridad, entonces se procesarán de izquierda a derecha.
-- Las subexpresiones parentizadas tendrán más prioridad que cualquier operador.

Ejemplo:

    Se presenta 2 casos de traducción de notación INFIJA a POSTFIJA.  El primero de ellos es una expresión simple, mientras que el segundo presenta un mayor grado de complejidad. 

a) expresión infija :X+Z*W                                             b)Expresión INFIJA:( X+Z )*W / T ^ Y - V
                           
a)Expresión POSTFIJA: XZW*+                                  b) Expresión POSTFIJA: XZ + W * T Y ^ / V -    

A) El primer operador que se procesa durante la traducción de la expresión es la multiplicación, Se coloca el operador de tal manera de que los operandos afectados por él lo precedan. Para el operador de suma se sigue el mismo criterio, los dos operandos los preceden. En este caso el primer operando es X y el segundo es ZW* 
B) En el paso 1 se convierte la subexpresión parentizada por ser la de mas alta prioridad.  Luego se sigue con el operador de potencia, así con los demás según su jerarquía. Como la multiplicación y la división tienen igual prioridad se procesa primero la multiplicación por encontrarse más a la izquierda  en la expresión. El operador de la resta es el último que se mueve. 
    

Algoritmo Conversión POSTFIJA.

CONV-POSTFIJA(EL,EPOS)
//Este algoritmo traduce una expresión infija EL a postfija EPOS, haciendo uso de una pila PILA
//TOPE es una variable de tipo entero
TOPE
= 0
Repetir mientras EL sea diferente de la cadena vacía.
  Tomar el símbolo mas a la izquierda de EL, recortando luego la expresión.
   
Si  símbolo es paréntesis izquierdo entonces
        {poner símbolo en PILA}
    TOPE = TOPE +1 
    PILA [TOPE] = símbolo
   
Si no 
        
Si símbolo es paréntesis Derecho entonces
            
Repetir mientras PILA [TOPE] <> paréntesis Izquierdo
                 EPOS = PILA[TOPE]  
                TOPE = TOPE -1
           
fin -mientras
         //Sacamos el paréntesis izquierdo de PILA y no lo agregamos a EPOS.
         TOPE = TOPE -1
        
Si no
             
Si símbolo es un operando entonces
                   agregar símbolo a EPOS
             
Si no  {Es un operador}
                  
Repetir mientras TOPE >0 y  prioridad del operador <= prioridad del operador de la 
                                                                                                       cima de la PILA
                       EPOS=EPOS + PILA[TOPE]
                       TOPE = TOPE - 1
                  
Fin-mientras
                   TOPE =TOPE +1 
                   PILA[TOPE]=Símbolo
             
fin si
       
finsi
   finsi
fin-mientras
Repetir mientras TOPE > 0
      EPOS=EPOS + PILA[TOPE]
      TOPE = TOPE -1
Fin mientras
Escribir EPOS.

Algoritmo Conversión PREFIJA.

CONV-POSTFIJA(EL,EPOS)
//Este algoritmo traduce una expresión infija EL a prefija EPRE, haciendo uso de una pila PILA
//TOPE es una variable de tipo entero
TOPE
= 0
Repetir mientras EL sea diferente de la cadena vacía.
  Tomar el símbolo mas a la derecha de EL, recortando luego la expresión.
   
Si  símbolo es paréntesis derecho entonces
        {  poner símbolo en PILA}
    TOPE = TOPE +1 
    PILA [TOPE] = símbolo
   
Si no 
        
Si símbolo es paréntesis izquierdo entonces
            
Repetir mientras PILA [TOPE] <> paréntesis derecho
                 EPRE= EPRE+PILA[TOPE]  
                TOPE = TOPE -1
           
fin -mientras
         //Sacamos el paréntesis izquierdo de PILA y no lo agregamos a EPRE.
         TOPE = TOPE -1
        
Si no
             
Si símbolo es un operando entonces
                   agregar símbolo a EPRE
             
Si no  {Es un operador}
                  
Repetir mientras TOPE >0 y  prioridad del operador < prioridad del operador de la 
                                                                                                       cima de la PILA
                       EPRE=EPRE + PILA[TOPE]
                       TOPE = TOPE - 1
                  
Fin-mientras
                   TOPE =TOPE +1 
                   PILA[TOPE]=Símbolo
             
fin si
       
finsi
   finsi
fin-mientras
Repetir mientras TOPE > 0
      EPRE=EPRE + PILA[TOPE]
      TOPE = TOPE -1
Fin mientras
Escribir EPRE  en forma invertida.

Ordenación

         Generalmente el método de ordenación rápida (QuickSort) por facilidad de implementación se hace de manera recursiva. Sin embargo, para implementarlo de manera iterativa (con ciclos) es necesario la utilización de 2 pilas auxiliares para ir almacenando los limites inferiores y  superiores de cada división o parte del arreglo.