Sei sulla pagina 1di 15

LA PILA (STACK)

DEFINICIÓN Y EJEMPLOS
Una pila es un conjunto ordenado de elementos en el cual se pueden agregar y eliminar
elementos de un extremo, el cual es llamado el tope de la pila. La pila es un objeto dinámico
en constante cambio. La definición de pila especifica que un solo extremo de la pila se
designa como tope.

Pueden colocarse nuevos elementos en el tope de la pila o se pueden quitar elementos de


él. La característica más importante de la pila es que el último elemento insertado en ella
es el primero en suprimirse. Por esta razón, una pila se denomina como una estructura
LIFO (Last In First Out) en la cual, el último elemento insertado, será el primero en ser
eliminado.

Tabla 1. Crecimiento y decrecimiento de una Pila.

FUNCIONES PRIMITIVAS.
Las funciones básicas definidas sobre una pila son push y pop. La función push inserta un
nuevo elemento en el tope de la pila. La función pop elimina el elemento indicado por el
tope de la pila. Existen otras funciones útiles al usar pilas, por ejemplo, antes de aplicar la
operación pop a una pila, se debe verificar que la pila no esté vacía. Así, la función isEmpty
determina si una pila está o no vacía. Estructuras de Datos Ricardo Ruiz Rodríguez 25 Otra
operación que puede ejecutarse sobre una pila es determinar cuál es el elemento que se
encuentra en el tope de la pila pero sin eliminarlo. Esta función puede llamarse stacktop.
Estructuras de C#. Pila (Stack)
Esta estructura es bastante usada para simular métodos recursivos y resolver algunos tipos
de problemas. Esto viene simulando como un montón de objetos que se van apilando (uno
encima de otro). La filosofía de una pila (stack) es “El último que entra es el primero que
sale”. Pueden ver un poco más de que se trata en la figura.

Este será el
primer objeto
en salir

PILA VACÍA

Una pila tiene un constructor con tres sobre cargas:

CONTRUCTOR por defecto que crea una pila vacía


STACK s = new Stack();
- Podemos indicar la cantidad inicial de elementos que tendrá la pila

Stack s = new Stack(int initialCapacity);

- Podemos también pasarle una colección de elementos (List, Quee, ArrayList). Este
constructor copiará todos estos elementos a la pila iniciará

Stack s = new Stack(ICollection col);

- Además de los métodos de la interfaz ICollection (Contains, Remove,


GetEnumerator, etc), encontramos básicamente 3 métodos que explico ahora:

PUBLIC T PEEK ()

//RETORNA EL ÚLTIMO ELEMENTO QUE SE AGREGÓ EN LA PILA.

PUBLIC OBJECT POP ()

// RETORNA EL ÚLTIMO ELEMENTO DE LA PILA AL IGUAL QUE PEEK (), PERO ELIMINA DICHO ELEMENTO
DE LA PILA UNA VEZ DEVUELTO .

PUBLIC VOID PUSH ( OBJECT X )


//AGREGA UN NUEVO ELEMENTO A LA PILA

Ahora veamos la implementación de la clase Pila (Stack) de CSharp:

public class Pila: IEnumerable


{

class Node
{
public T value;
public Node next;
public Node(T value, Node next)
{
this.value=value;
this.next=next;
}
}

Node primero;
int cambios;
int contador

//El constructor
public Pila()
{
contador=0;
cambios=0;
}

public T Peek()
{
if(count<=0)
throw new InvalidOperationException("La pila esta vacia");
return primero.value;
}

public T Pop()
{
T temp=primero.value;
primero=primero.next;
contador--;
cambios++;
return temp;
}

public void Push(T x)


{
if(primero==null)
primero= new Node(x);
else
{
primero=new Node(x,primero);
cambios++;
contador++;
}
}

public int Count


{
get{
return contador;
}
}

public bool Contiene(T x)


{
Node temp= primero;
for(int i=0; i
BREVE HISTORIA
El método de pila para la evaluación de expresiones fue propuesto en 1955 y dos años
después patentado por Friedrich L. Bauer, quién recibió en 1988 el premio "IEEE Computer
Society Pioneer Award" por su trabajo en el desarrollo de dicha estructura de datos.

PILA COMO TIPO ABSTRACTO DE DATOS


A modo de resumen tipo de datos, la pila es un contenedor de nodos y tiene dos
operaciones básicas: push (o apilar) y pop (o desapilar). 'Push' añade un nodo a la parte
superior de la pila, dejando por debajo el resto de los nodos. 'Pop' elimina y devuelve el
actual nodo superior de la pila. Una metáfora que se utiliza con frecuencia es la idea de una
pila de platos en una cafetería con muelle de pila. En esa serie, sólo la primera placa es
visible y accesible para el usuario, todas las demás placas permanecen ocultas. Como se
añaden las nuevas placas, cada nueva placa se convierte en la parte superior de la pila,
escondidos debajo de cada plato, empujando a la pila de placas. A medida que la placa
superior se elimina de la pila, la segunda placa se convierte en la parte superior de la pila.
Dos principios importantes son ilustrados por esta metáfora: En primer lugar la última salida
es un principio, la segunda es que el contenido de la pila está oculto. Sólo la placa de la
parte superior es visible, por lo que para ver lo que hay en la tercera placa, el primer y
segundo platos tendrán que ser retirados.

OPERACIONES
Una pila cuenta con 2 operaciones imprescindibles: apilar y Desapilar, a las que en las
implementaciones modernas de las pilas se suelen añadir más de uso habitual.

 Crear : se crea la pila vacía. (constructor)

 Tamaño : regresa el número de elementos de la pila. (size)

 Apilar : se añade un elemento a la pila.(push)

 Desapilar : se elimina el elemento frontal de la pila.(pop)

 Cima : devuelve el elemento que está en la cima de la pila. (top o peek)

 Vacía : devuelve cierto si la pila está sin elementos o falso en caso de que
contenga uno. (empty).
IMPLEMENTACIÓN
Un requisito típico de almacenamiento de una pila de n elementos es O(n). El requisito típico
de tiempo de O (1) las operaciones también son fáciles de satisfacer con un Array o con
listas enlazadas simples.

ESTRUCTURAS DE DATOS RELACIONADAS


El tipo base de la estructura FIFO (el primero en entrar es el primero en salir) es la cola, y
la combinación de las operaciones de la pila y la cola es proporcionado por el deque. Por
ejemplo, el cambio de una pila en una cola en un algoritmo de búsqueda puede cambiar el
algoritmo de búsqueda en primera profundidad (en inglés, DFS) por una búsqueda en
amplitud (en inglés, BFS). Una pila acotada es una pila limitada a un tamaño máximo
impuesto en su especificación.

ARQUITECTURA BÁSICA DE UNA PILA


Una pila típica es un área de la memoria de los computadores con un origen fijo y un tamaño
variable. Al principio, el tamaño de la pila es cero. Un puntero de pila, por lo general en
forma de un registro de hardware, apunta a la más reciente localización en la pila; cuando
la pila tiene un tamaño de cero, el puntero de pila de puntos en el origen de la pila.

Las dos operaciones aplicables a todas las pilas son:

Una operación apilar, en el que un elemento de datos se coloca en el lugar apuntado por el
puntero de pila, y la dirección en el puntero de pila se ajusta por el tamaño de los datos de
partida.

Una operación Desapilar: un elemento de datos en la ubicación actual apuntada por el


puntero de pila es eliminado, y el puntero de pila se ajusta por el tamaño de los datos de
partida.

Hay muchas variaciones en el principio básico de las operaciones de pila. Cada pila tiene
un lugar fijo en la memoria en la que comienza. Como los datos se añadirán a la pila, el
puntero de pila es desplazado para indicar el estado actual de la pila, que se expande lejos
del origen (ya sea hacia arriba o hacia abajo, dependiendo de la aplicación concreta).

Por ejemplo, una pila puede comenzar en una posición de la memoria de mil, y ampliar por
debajo de las direcciones, en cuyo caso, los nuevos datos se almacenan en lugares que
van por debajo de 1000, y el puntero de pila se decrementa cada vez que un nuevo
elemento se agrega. Cuando un tema es eliminado de la pila, el puntero de pila se
incrementa.

derecha, por lo que el máximo elemento se convierte en el máximo a "la derecha". Esta
visualización puede ser independiente de la estructura real de la pila en la memoria. Esto
significa que rotar a la derecha es mover el primer elemento a la tercera posición, la
segunda a la primera y la tercera a la segunda. Aquí hay dos equivalentes visualizaciones
de este proceso:

Manzana Plátano

Plátano ==rotar a la derecha==> Fresa

Fresa Manzana

Fresa Manzana

Plátano ==rotar a la izquierda==> Fresa

Manzana Plátano

Una pila es normalmente representada en los ordenadores por un bloque de celdas de


memoria, con los "de abajo" en una ubicación fija, y el puntero de pila de la dirección actual
de la "cima" de células de la pila.

En la parte superior e inferior se utiliza la terminología con independencia de que la pila


crece realmente a la baja de direcciones de memoria o direcciones de memoria hacia
mayores.

Apilando un elemento en la pila, se ajusta el puntero de pila por el tamaño de elementos


(ya sea decrementar o incrementar, en función de la dirección en que crece la pila en la
memoria), que apunta a la próxima celda, y copia el nuevo elemento de la cima en área de
la pila. Dependiendo de nuevo sobre la aplicación exacta, al final de una operación de apilar,
el puntero de pila puede señalar a la siguiente ubicación no utilizada en la pila, o tal vez
apunte al máximo elemento de la pila.

Si la pila apunta al máximo elemento de la pila, el puntero de pila se actualizará antes de


que un nuevo elemento se apile, si el puntero que apunta a la próxima ubicación disponible
en la pila, que se actualizará después de que el máximo elemento se apile en la pila.

Desapilando es simplemente la inversa de apilar. El primer elemento de la pila es eliminado


y el puntero de pila se actualiza, en el orden opuesto de la utilizada en la operación de
apilar.

Expresión de evaluación y análisis sintáctico

Se calcula empleando la notación polaca inversa utilizando una estructura de pila para los
posibles valores. Las expresiones pueden ser representadas en prefijo, infijo, postfijo.

La conversión de una forma de la expresión a otra forma necesita de una pila. Muchos
compiladores utilizan una pila para analizar la sintaxis de las expresiones, bloques de
programa, etc. Antes de traducir el código de bajo nivel.

La mayoría de los lenguajes de programación son de contexto libre de los idiomas que les
permite ser analizados con máquinas basadas en la pila.

Por ejemplo, el cálculo: ((1 + 2) * 4) + 3, puede ser anotado como en notación postfija con
la ventaja de no prevalecer las normas y los paréntesis necesarios:

12+4*3+

La expresión es evaluada de izquierda a derecha utilizando una pila:

Apilar cuando se enfrentan a un operando y Desafilar dos operandos y evaluar el valor


cuando se enfrentan a una operación.

Apilar el resultado. De la siguiente manera (la Pila se muestra después de que la operación
se haya llevado a cabo):

ENTRADA OPERACIÓN PILA

1 Apilar operando 1

2 Apilar operando 1, 2

+ Añadir 3

4 Apilar operando 3, 4

* Multiplicar 12
3 Apilar operando 12, 3

+ Añadir 15

El resultado final, 15, se encuentra en la parte superior de la pila al final del cálculo

Problema 1:
Confeccionar una clase que administre una lista tipo pila (se debe poder insertar, extraer e
imprimir los datos de la pila)

USING SYSTEM ;
USING SYSTEM.COLLECTIONS.GENERIC;
USING SYSTEM.LINQ;
USING SYSTEM.TEXT;

NAMESPACE LISTASTIPOPILA1
{
CLASS PILA
{
CLASS NODO
{
PUBLIC INT INFO;
PUBLIC NODO SIG;
}
PRIVATE NODO RAIZ;
PUBLIC PILA()
{
RAIZ = NULL;
}
PUBLIC VOID INSERTAR(INT X)
{
NODO NUEVO;
NUEVO = NEW NODO();
NUEVO.INFO = X;
IF (RAIZ == NULL)
{
NUEVO.SIG = NULL;
RAIZ = NUEVO;
}
ELSE
{
NUEVO.SIG = RAIZ;
RAIZ = NUEVO;
}
}
PUBLIC INT EXTRAER()
{
IF (RAIZ != NULL)
{
INT INFORMACION = RAIZ.INFO;
RAIZ = RAIZ.SIG;
RETURN INFORMACION;
}
ELSE
{
RETURN INT.MAXVALUE;
}
}
PUBLIC VOID IMPRIMIR()
{
NODO RECO=RAIZ;
CONSOLE.WRITELINE ("LISTADO DE TODOS LOS ELEMENTOS DE LA PILA.");
WHILE (RECO!=NULL)
{
CONSOLE.WRITE(RECO.INFO+"-");
RECO=RECO.SIG;
}
CONSOLE.WRITELINE();
}
STATIC VOID MAIN(STRING[] ARGS)
{
PILA PILA1=NEW PILA();
PILA1.INSERTAR(10);
PILA1.INSERTAR(40);
PILA1.INSERTAR(3);
PILA1.IMPRIMIR();
CONSOLE.WRITELINE("EXTRAEMOS DE LA PILA:"+PILA1.EXTRAER());
PILA1.IMPRIMIR();
CONSOLE.READKEY();
}
}
}

Analicemos las distintas partes de este programa:

class Nodo
{
public int info;
public Nodo sig;
}

private Nodo raiz;

Para declarar un nodo debemos utilizar una clase. En este caso la información del nodo
(info) es un entero y siempre el nodo tendrá una referencia de tipo Nodo, que le llamamos
sig.

El puntero sig apunta al siguiente nodo o a null en caso que no exista otro nodo. Este
puntero es interno a la lista. Para poder acceder a los atributos los definimos de tipo public.

También definimos un puntero de tipo Nodo llamado raiz. Este puntero tiene la dirección
del primer nodo de la lista. En caso de estar vacía la lista, raiz apunta a null (es decir no
tiene dirección)

El puntero raiz es fundamental porque al tener la dirección del primer nodo de la lista nos
permite acceder a los demás nodos.

public Pila()
{
raiz = null;
}
En el constructor de la clase hacemos que raiz guarde el valor null. Tengamos en cuenta
que si raiz tiene almacenado null la lista está vacía, en caso contrario tiene la dirección del
primer nodo de la lista.

public void Insertar(int x)


{
Nodo nuevo;
nuevo = new Nodo();
nuevo.info = x;
if (raiz == null)
{
nuevo.sig = null;
raiz = nuevo;
}
else
{
nuevo.sig = raiz;
raiz = nuevo;
}
}

Uno de los métodos más importantes que debemos entender en una pila es el de Insertar
un elemento en la pila.

Al método llega la información a insertar, en este caso en particular es un valor entero.

LA CREACIÓN DE UN NODO REQUIERE DOS PASOS:


- Definición de un puntero o referencia a un tipo de dato Nodo:
Nodo nuevo;
- Creación del nodo (creación de un objeto):

nuevo = new Nodo();

Cuando se ejecuta el operador new se reserva espacio para el nodo. Realmente se crea el
nodo cuando se ejecuta el new.

Paso seguido debemos guardar la información del nodo:

nuevo.info = x;
En el campo info almacenamos lo que llega en el parámetro x. Por ejemplo si llega un 5 el
nodo queda:

Por último queda enlazar el nodo que acabamos de crear al principio de la lista.
Si la lista está vacía debemos guardar en el campo sig del nodo el valor null para indicar
que no hay otro nodo después de este, y hacer que raíz apunte al nodo creado (sabemos
si una lista está vacía si raíz almacena un null)

if (raiz == null)
{
nuevo.sig = null;
raiz = nuevo;
}

Gráficamente podemos observar que cuando indicamos raíz=nuevo, el puntero raíz guarda
la dirección del nodo apuntado por nuevo.

Tener en cuenta que cuando finaliza la ejecución del método el puntero nuevo desaparece,
pero no el nodo creado con el operador new.

En caso que la lista no esté vacía, el puntero sig del nodo que acabamos de crear debe
apuntar al que es hasta este momento el primer nodo, es decir al nodo que apunta raíz
actualmente.

else
{
nuevo.sig = raiz;
raiz = nuevo;
}

Como primera actividad cargamos en el puntero sig del nodo apuntado por nuevo la
dirección de raiz, y posteriormente raiz apunta al nodo que acabamos de crear, que será
ahora el primero de la lista.
Antes de los enlaces tenemos:
Luego de ejecutar la línea:
nuevo.sig = raiz;
Ahora tenemos:

Por último asignamos a raíz la dirección que almacena el puntero nuevo.


Raíz = nuevo;
La lista queda:

EL MÉTODO EXTRAER:

public int Extraer()


{
if (raiz != null)
{
int informacion = raiz.info;
raiz = raiz.sig;
return informacion;
}
else
{
return int.MaxValue;
}
}

El objetivo del método extraer es retornar la información del primer nodo y además borrarlo
de la lista.
Si la lista no está vacía guardamos en una variable local la información del primer nodo:
int información = raiz.info;

Avanzamos raiz al segundo nodo de la lista, ya que borraremos el primero:

raiz = raiz.sig;

El nodo que previamente estaba apuntado por raiz es eliminado automáticamente, al no


tener ninguna referencia.
Retornamos la información:

return informacion;

En caso de estar vacía la pila retornamos el número entero máximo y lo tomamos como
código de error (es decir nunca debemos guardar el entero mayor en la pila)

return int.MaxValue;

Es muy importante entender gráficamente el manejo de las listas. La interpretación gráfica


nos permitirá plantear inicialmente las soluciones para el manejo de listas.

Por último expliquemos el método para recorrer una lista en forma completa e imprimir la
información de cada nodo:

public void Imprimir()


{
Nodo reco=raiz;
Console.WriteLine("Listado de todos los elementos de la pila.");
while (reco!=null)
{
Console.Write(reco.info+"-");
reco=reco.sig;
}
Console.WriteLine();
}

Definimos un puntero auxiliar reco y hacemos que apunte al primer nodo de la lista:
Nodo reco=raiz;

Disponemos una estructura repetitiva que se repetirá mientras reco sea distinto a null.
Dentro de la estructura repetitiva hacemos que reco avance al siguiente nodo:

while (reco!=null)
{
Console.Write(reco.info+"-");
reco=reco.sig;
}

Es muy importante entender la línea:

reco = reco.sig;

Estamos diciendo que reco almacena la dirección que tiene el puntero sig del nodo
apuntado actualmente por reco.
Gráficamente:

Al analizarse la condición:
while (reco!=null)

Se valúa en verdadero ya que reco apunta a un nodo y se vuelve a ejecutar la línea:


reco=reco.sig;

Ahora reco apunta al siguiente nodo:

La condición del while nuevamente se valúa en verdadera y avanza el puntero reco al


siguiente nodo:

reco=reco.sig;

Ahora sí reco apunta a null y ha llegado el final de la lista (Recordar que el último nodo de
la lista tiene almacenado en el puntero sig el valor null, con el objetivo de saber que es el
último nodo)

Para poder probar esta clase recordemos que debemos definir un objeto de la misma y
llamar a sus métodos:

static void Main(string[] args)


{
Pila pila1=new Pila();
pila1.Insertar(10);
pila1.Insertar(40);
pila1.Insertar(3);
pila1.Imprimir();
Console.WriteLine("Extraemos de la pila:"+pila1.Extraer());
pila1.Imprimir();
Console.ReadKey();
}

Insertamos 3 enteros, luego imprimimos la pila, extraemos uno de la pila y finalmente


imprimimos nuevamente la pila.

Potrebbero piacerti anche