Pilas (Stacks)



Descargar 21,03 Kb.
Fecha de conversión10.01.2017
Tamaño21,03 Kb.

Pilas (Stacks)

  • Tipos de Datos Abstractos (TDAs)
  • Pilas (Stacks)
  • Aplicación al análisis de una serie de tiempo
  • Implementación Java de una pila
  • Interfaces y excepciones

Tipos de Datos Abstractos (TDAs)

  • Un Tipo de Dato Abstracto es una abstracción de una estructura de dato. (No hay código relacionado)
  • El TDA especifica:
    • qué se puede almacenar en el TDA
    • qué operaciones se pueden realizar sobre/por el TDA
  • Por ejemplo, si se modela una bolsa de caramelos como un TDA, se puede especificar que:
    • este TDA almacena caramelos
    • este TDA permite poner un caramelo y extraer un caramelo.

Tipos de Datos Abstractos (TDAs)

  • Hay una gran cantidad de TDAs formalizados y estándar.
  • En lo sucesivo se mostrarán varios TDAs estándar diferentes (pilas, colas, árboles...)

Pilas (Stacks)

  • Una pila es un contenedor de objetos que se insertan y extraen de acuerdo al principio de último en entrar, primero en salir (last-in-first-out, LIFO).
  • Los objetos se pueden insertar en cualquier momento, pero sólo el último (el insertado más reciente) objecto puede ser extraído.
  • La inserción de un elemento se conoce como “pushing” en la pila. “Popping” de la pila es sinónimo de extraer un elemento.

El Tipo de Dato Abstracto Pila

  • Una pila es un tipo de dato abstracto (TDA) que soporta dos métodos principales:
    • push(o): Inserta un objeto sobre el último o cima de la pila.
    • pop(): Extrae el objeto de la cima de la pila y lo devuelve; si la pila está vacía, ocurre un error.
  • Los siguientes métodos para la gestión de la pila deben ser definidos:
    • size(): Devuelve el número de objetos en la pila.
    • isEmpty(): Devuelve un boolean indicando si la pila está vacía.
    • top(): Devuelve el objeto de la cima de la pila sin extraerlo; si la pila está vacía, ocurre un error.

Aplicación: Series de Tiempo

  • El lapso si del precio de una acción en una día i determinado es el máximo número de días consecutivos (hasta el día en curso) que el precio de la acción ha sido menor o igual a su precio en el día i

Un Algoritmo Ineficiente

  • Hay una forma directa de calcular el lapso de una acción de n días:
  • El tiempo de ejecución de este algoritmo es O(n2).
  • Algoritmo calculaLapso1(P):
  • Input: un array de números P de n-elementos tal que
  • P[i] es el precio de la acción en el día i
  • Output: un array de números S de n-elementos tal que
  • S[i] es el lapso de la acción en el día i
  • for i 0 to n - 1 do k  0
  • done  false
  • repeat
  • if P[i - k]  P[i] then k  k + 1
  • else done  true
  • until (k = i) or done
  • S[i]  k
  • return S

Una pila puede ayudar

  • Vemos que si en el día i puede calcularse fácilmente si se conoce el día previo más próximo a i, tal que el precio es mayor que el precio del día i. Si existe tal día, lo llamanos h(i), en otro caso, por convención se define h(i) = -1
  • El lapso se calcula como si = i - h(i)
  • Usamos una pila para mantener h(i)

Un Algoritmo Eficiente

  • El código para el nuevo algoritmo es:
  • Algoritmo calculaLapso2(P):
  • Input: un array de números P de n-elementos que representan los
  • precios de la acción
  • Output: un array de números S de n-elementos tal que
  • S[i] es el lapso de la acción en el día i
  • Sea D una pila vacía
  • for i 0 to n - 1 do k  0
  • done  false
  • while not(D.isEmpty() or done) do
  • if P[i] ?? P[D.top()] then
  • D.pop()
  • else done  true
  • if D.isEmpty() then h  -1
  • else h  D.top()
  • S[i]  i - h
  • D.push(i)
  • return S

Implementación Java

  • Dado el TDA pila, necesitamos codificar el TDA para usarlo en los programas. Es necesario primero entender dos constructores de programas: interfaces y exceptions.
  • Una interface es una forma de declarar lo que hace una clase. No menciona cómo lo hace.
    • Para una interface, se escriben los nombres de los métodos y los parámetros. Cuando se especifican parámetros, lo que realmente importa son sus tipos.
    • Después, cuando se escribe una class para esa interface, se codifica el contenido de los métodos.
    • La separación de interface e implementation es una técnica de programación muy útil.
  • Ejemplo de Interface:

Una Interface Pila en Java

  • Mientras, la estructura de datos pila viene en una clase “predefinida” en el java.util package, definimos nuestra propia interface Stack:
  • public interface Stack {
  • // metodos de acceso
  • public int size();
  • public boolean isEmpty();
  • public Object top() throws StackEmptyException;
  • // metodos de actualizacion
  • public void push (Object element);
  • public Object pop() throws StackEmptyException;
  • }

Excepciones

  • Excepciones son otra construcción de programación, útiles para el manejo de errores. Cuando se encuentra un error (o un caso excepcional), se lanza (throw) una excepción.
  • Ejemplo
  • Tan pronto como una excepción se lanza, el control del flujo sale del método en curso.
  • Así, cuando se lanza DolorEstomagoException, se sale del método comerPizza() y se va donde se invoca el método.
  • public void comerPizza() throws DolorEstomagoException
  • {...
  • if (comeDemasiado)
  • throw new DolorEstomagoException(“Duele”);
  • ...}

Más Excepciones

  • En el siguiente código se llama al método comerPizza() en primer lugar.
  • private void simulaReunion()
  • {...
  • try
  • {
  • asistenteConHambre.comerPizza();
  • }
  • catch(DolorEstomagoException e)
  • {
  • System.out.println(“alguien tiene dolor de estomago”);
  • }
  • ...}

Más sobre Excepciones

  • Se retorna a asistenteConHambre.comerPizza(); porque comerPizza() lanza una excepción.
  • El bloque try - catch significa que atiende las excepciones que se especifican en el parámetro catch.
  • Debido a que catch atiende DolorEstomagoException, el flujo de control irá al bloque catch. Por tanto System.out.println se ejecutará.
  • Notar que el bloque catch puede contener cualquier cosa además de System.out.println. Se puede manejar el error atendido en la forma que se desee; incluso se puede lanzar nuevamente con throw.
  • Notar que si en algún sitio de un método se lanza una excepción, se necesita añadir la cláusula throws a continuación del nombre del método.

Más sobre Excepciones

  • Qué es importante en el uso de excepciones? Se puede delegar hacia arriba la responsabilidad del manejo de errores. La delegación hacia arriba significa dejar al código que llamó al código en curso trate el problema.
  • Si nunca se atiende una excepción con catch, se propagará hacia arriba a lo largo de la cadena de métodos de llamada hasta que el usuario lo vea.

Más sobre Excepciones

  • Podemos lanzar y atender excepciones. En Java son Clases.
  • Verificar Dolor EstomagoException.
  • public class DolorEstomagoException extends RuntimeException {
  • public DolorEstomagoException(String err)
  • {
  • super(err);
  • }
  • }

Una Pila basada en Array

  • Crea una pila usando un array especificando un tamaño máximo N para la pila, p.e., N = 1024.
  • La pila consiste de un array S de N-elementos y una variable entera t, el índice al elemento de la cima en el array S.
  • Algoritmo size():
  • return t +1
  • Algoritmo isEmpty():
  • return (t < 0)
  • Algoritmo top():
  • if isEmpty() then
  • throw a StackEmptyException
  • return S[t] ...
  • NOTE: Los índices delArray empiezan en 0, por lo que se inicializa t a -1
  • Pseudocódigo en la derecha.

Pseudocódigo

  • Algoritmo pop():
  • if isEmpty() then
  • throw a StackEmptyException
  • e  S[t]
  • S[t]  null
  • t  t-1
  • return e
  • Algoritmo push(o):
  • if size() = N then
  • throw a StackFullException
  • t  t + 1
  • S[t]  o

Una Pila basada en Array

  • Ambos métodos push y pop corren en tiempo O(1).
  • La implementación basada en array es simple y eficiente.
  • Hay un límite superior predefinido, N, del tamaño de la pila, que puede ser muy pequeño para una aplicación dada, o causar un desperdicio de memoria.
  • StackEmptyException se requiere por la interface.
  • StackFullException es particular para esta implementación.
  • Algorithm pop():
  • if isEmpty() then
  • throw a StackEmptyException
  • e  S[t]
  • S[t]  null
  • t  t-1
  • return e
  • Algorithm push(o):
  • if size() = N then
  • throw a StackFullException
  • t  t + 1
  • S[t]  o

Pila basada en Array en Java

  • public class ArrayStack implements Stack {
  • // Implementacion de la interface Stack usando un array.
  • public static final int CAPACITY = 1024; // capacidad de la pila por defecto
  • private int capacity; // maxima capacidad de la pila
  • private Object S[ ]; // S mantiene los elementos de la pila
  • private int top = -1; // elemento cima de la pila
  • public ArrayStack( ) { // Inicializa la pila
  • this(CAPACITY);// con la capacidad por defecto}
  • public ArrayStack(int cap) { // Initializa la pila con la capacidad dada
  • capacity = cap;
  • S = new Object[capacity];}

Pila basada en Array en Java (1)

  • public int size( ) { //Devuelve el tamaño en curso de la pila
  • return (top + 1);}
  • public boolean isEmpty( ) { // Devuelve true si la pila esta vacia
  • return (top < 0);}
  • public void push(Object obj) throws StackFullException{
  • // Push un nuevo elemento en la pila
  • if (size() == capacity)
  • throw new StackFullException(“pila llena.”);
  • S[++top] = obj;}

Pila basada en Array en Java (2)

  • public Object top( )// Devuelve el elemento de la cima
  • throws StackEmptyException {
  • if (isEmpty( )) throw new StackEmptyException(“Pila vacia.”);
  • return S[top];}
  • public Object pop() // Pop extrae el elmento de la cima
  • throws StackEmptyException {
  • Object elem;
  • if (isEmpty( )) throw new StackEmptyException(“Pila vacia.”);
  • elem = S[top];
  • S[top--] = null; // Dereferencia S[top] y decrementa top
  • return elem;
  • }

Más sobre Pilas

  • Pilas que crecen
  • Análisis Amortizado
  • Pilas en la JVM (Java virtual machine)

A Pila creciente basada en array

  • En lugar de generar un StackFullException, se puede reemplazar el array S con uno más grande para continuar procesando las operaciones push.
      • Algoritm push(o):
      • if size() = N then
      • A  new array of length f(N)
      • for i  0 to N - 1
      • A[i]  S[i]
      • S  A
      • t  t + 1
      • S[t]  o
  • Qué capacidad debe tener el nuevo array?
    • Estrategia ajustada (añadir una constante): f(N) = N + c
    • Estrategia creciente (duplicar): f(N) = 2N

Estrategias ajustada vs. creciente comparación

  • Para comparar las dos estrategias se usa el siguiente modelo de costo:
  • OPERACIÓN
  • operación push regular: añadir un elemento
  • operación push especial : crear un array de tamaño f(N), copiar N elementos, y añadir un elemento
  • TIEMPO DE EJECUCIÓN
  • 1
  • f(N)+N+1

Estrategia Ajustada (c=4)

  • empieza con array de tamaño 0
  • el costo de un especial push es 2N + 5
  • push fase n N cost
  • 1 1 0 0 5
  • 2 1 1 4 1
  • 3 1 2 4 1
  • 4 1 3 4 1
  • 5 2 4 4 13
  • 6 2 5 8 1
  • 7 2 6 8 1
  • 8 2 7 8 1
  • 9 3 8 8 21
  • 10 3 9 12 1
  • 11 3 10 12 1
  • 12 3 11 12 1
  • 13 4 12 12 29

Eficiencia de la Estrategia Ajustada

  • Se consideran k fases, donde k = n/c
  • Cada fase corresponde a un nuevo tamaño de array
  • El costo de la fase i es 2ci
  • El costo total de n operaciones push es el costo total de k fases, con k = n/c:
  • 2c (1 + 2 + 3 + ... + k),
  • que es O(k2) y O(n2).

Estrategia Creciente

  • Empieza con una array de tamaño 0, entonces crece 1, 2, 4, 8, ...
  • El costo de un push especial es 3N + 1 para N>0
  • push fase n N costo
  • 1 0 0 0 2
  • 2 1 1 1 4
  • 3 2 2 2 7
  • 4 2 3 4 1
  • 5 3 4 4 13
  • 6 3 5 8 1
  • 7 3 6 8 1
  • 8 3 7 8 1
  • 9 4 8 8 25
  • 10 4 9 16 1
  • 11 4 10 16 1
  • 12 4 11 16 1
  • ... ... ... ... ...
  • 16 4 15 16 1
  • 17 5 16 16 49

Eficiencia de la Estrategia Creciente

  • Se consideran k fases, donde k = log n
  • Cada fase corresponde a un nuevo tamaño de array
  • El costo de la fase i es 2 i + 1
  • El costo total de n operaciones push es el costo total de k fases, con k = log n
  • 2 + 4 + 8 + ... + 2 log n + 1 =
  • 2n + n + n/2 + n/4 + ... + 8 + 4 + 2 = 4n - 1
  • La estrategia creciente es mejor!

Análisis Amortizado

  • El tiempo de ejecución amortizado de una operación dentro de una serie de operaciones es el tiempo de ejecución en el peor de los casos de la serie de operaciones completa, dividido por el número de operaciones.
  • El método contable determina el tiempo de ejecución amortizado con un sistema de créditos y débitos
  • Para ello se modela el computador como una máquina operada con monedas que requiere un cyber-dólar para una cantidad constante de tiempo de ejecución.
    • Se configura un esquema para cargar operaciones. Esto se conoce como esquema amortizado.
    • Se puede sobrecargar y subcargar otras operaciones. Por ejemplo, se puede cargar cada operación con la misma cantidad.
    • El esquema siempre provee del suficiente dinero para pagar el costo actual de la operación.
    • El costo total de las series de operaciones no es más que la cantidad total cargada.
  • (tiempo amortizado)  (total $ cargados) / (# operaciones)

Esquema Amortizado para la Estrategia Creciente

  • Al final de una fase debemos haber ahorrado suficiente dinero para pagar el push especial de la siguiente fase.
  • Al final de la fase 3 deseamos tener ahorrado $24.
  • La cantidad ahorrada paga el crecimiento del array.
  • Se carga $7 para un push. Los $6 ahorrados para un push regular se “guardan” en la segunda mitad del array.

Análisis Amortizado de la Estrategia Creciente

  • push n N balance cargo costo
  • 1 0 0 $0 $5 $2
  • 2 1 1 $3 $7 $4
  • 3 2 2 $6 $7 $7
  • 4 3 4 $6 $7 $1
  • 5 4 4 $12 $7 $13
  • 6 5 8 $6 $7 $1
  • 7 6 8 $12 $7 $1
  • 8 7 8 $18 $7 $1
  • 9 8 8 $24 $7 $25
  • 10 9 16 $6 $7 $1
  • 11 10 16 $12 $7 $1
  • 12 11 16 $18 $7 $1
  • ... ... ... ... ... ...
  • 16 15 16 $42 $7 $1
  • 17 16 16 $48 $7 $49

Casting con una Pila Genérica

  • Tener un ArrayStack que puede almacenar solo objetos Integer o Estudiante.
  • Para conseguirlo con una pila genérica, el objeto devuelto debe tener un cast con el tipo de dato correcto.
  • Ejemplo:
        • public static Integer[] reverse(Integer[] a) {
        • ArrayStack S = new ArrayStack(a.length);
        • Integer[] b = new Integer[a.length];
        • for (int i = 0; i < a.length; i++)
        • S.push(a[i]);
        • for (int i = 0; i < a.length; i++)
        • b[i] = (Integer)(S.pop()); // la operación pop devuelve un Object
        • // y se fuerza a un Integer antes de
        • // asignarlo a b[i].
        • return b;
        • }

Pilas en la Java Virtual Machine

  • Cada proceso ejecutado en un programa Java tiene su propia Java Method Stack.
  • Cada vez que se invoca un método, se inserta enla pila (stack).
  • El uso de una pila para esta operación permite que Java realice varias cosas útiles:
    • Realizar llamadas a métodos recursivos
    • Imprimir trazas de la pila para localizar un error
  • Java también incluye una pila de operandos que se usa para evaluar instrucciones aritméticas, p.e.
  • Integer add(a, b):
  • OperandStack Op
  • Op.push(a)
  • Op.push(b)
  • temp1 Op.pop()
  • temp2 Op.pop()
  • Op.push(temp1 + temp2)
  • return Op.pop()

Pila de métodos en Java



La base de datos está protegida por derechos de autor ©absta.info 2016
enviar mensaje

    Página principal