Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los...

42
Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de Dato Abstracto) Stack (Pila) Stack: contenedor que opera con el principio LIFO (Last In First Out), es decir, el último elemento que ingresa es el primero que sale. TDA: tipo de dato con implementación oculta (representación privada y operaciones públicas)

Transcript of Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los...

Page 1: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados

Use el TDA (Tipo de Dato Abstracto) Stack (Pila) Stack: contenedor que opera con el principio LIFO (Last In First Out), es decir, el último elemento que ingresa es el primero que sale. TDA: tipo de dato con implementación oculta (representación privada y operaciones públicas)

Page 2: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

TDA Stack Operación significado explicación new Stack()

crear stack vacío

void push(Object x)

x

Poner x en stack.

Object pop( )

Sacar un valor de stack.

void reset( )

vaciar stack

boolean empty( ) ¿ ?

true si stack está vacío

boolean full( ) ¿ ?

true si stack está lleno

Page 3: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

static public void main(String[]args) { Stack s=new Stack(); BR A=new BR(new FR(args[0])); String linea; boolean ok = true; while((linea=A.readLine())!=null){ for(int i=0; i< linea.size(); i++) { char c = linea.charAt(i); if (c == ´{´ || c== ´(´ || == ´[´) { if(s.full()) U.abort(“overflow”); if (c == ´{´) s.push(“}”); else if (c == ´[´) s.push(“]”); else s.push(“)”); } else if (c == ´}´ || c== ´)´ || == ´]´) { if(s.empty() || !c.equals((String)s.pop()) { ok = false; break; } } }

//stack deberia estar vacío al final if ( !s.empty() || !ok) System.out.println(“error en los paréntesis”);}

Page 4: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

TDA Stack: implementación con un arreglo

Ejemplo:

a[0] n=3 N=100

class Stack{protected Object[]a;protected int n;protected static final int N=100;//static: dato global//(para todos los objetos de la clase)

ABC

C

B

A

Page 5: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

public Stack(){ a = new Object[N]; n = 0; } public boolean empty(){ return n == 0; } public boolean full(){ return n >= N; } public void reset(){ a = new Object[N]; n = 0; }

Page 6: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

public void push(Object x) { a[n++] = x; //a[n]=x; ++n; } public Object pop() { return a[--n];//--n; return a[n]; } }//fin class Stack Problema: Esta implementación no permite reaccionar cuando algo sale mal (se quiere poner en un stack lleno, sacar de un stack vacío)

Page 7: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

TDA Stack con Operación significado explicación new Stack()

crear stack vacío

void push(Object x) throws StackFull

x

Poner x en stack. Si está lleno produce la excepción StackFull

Object pop( ) throws StackEmpty

Sacar un valor de stack. Si está vacío produce la excepción StackEmpty

void reset( )

vaciar stack

boolean empty( ) ¿ ?

true si stack está vacío

boolean full( ) ¿ ?

true si stack está lleno

Page 8: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

static public void main(String[]args) throws Exception{ Stack s=new Stack(); BR A=new BR(new FR(args[0])); String linea; boolean ok = true; try {

while((linea=A.readLine())!=null){ for(int i=0; i< linea.size(); i++) { char c = linea.charAt(i); if (c == ´{´ || c== ´(´ || == ´[´) {

s.push(c+””); //ponemos un String (objeto) } else if (c == ´}´ || c== ´)´ || == ´]´) { if(!c.equals((String)s.pop()) { ok = false; break; } } } } catch (StackEmpty e) { ok = false; } } catch (StackFull e) { U.abort(“overflow”); }

//stack deberia estar vacío al final if (!ok) System.out.println(“error en los paréntesis”);}

Solución 2: atrapando las excepciones (“el golpe avisa”)

Page 9: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

public void push(Object x)throws StackFull { if( n >= N ) throw new StackFull(); a[n++] = x; //a[n]=x; ++n; } public Object pop()throws StackEmpty { if( n == 0 ) throw new StackEmpty(); return a[--n];//--n; return a[n]; } }//fin class Stack throws Nombre: indica que si en el método se produce la excepción indicada, entonces no se atrapará y se trasmitirá/propagará al método que lo invocó (que a su vez la podrá atrapar o propagar) throw new Nombre(): lanza/produce/genera la excepción indicada

Page 10: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Definición de excepciones class StackFull extends Exception{} class StackEmpty extends Exception{} Exception clase predefinida clase madre de todas las excepciones excepciones de Input/Output:

class IOException extends Exception{…} class FileNoutFoundException extends IOException{…} etc

excepciones al ejecutar programas: class RunTimeException extends Exception{…} class NullPointerException extens RunTimeException{…} class ArrayIndexOutOfBoundsException extends RunTimeException{…} etc

Page 11: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Alternativa2: atrapando excepciones predefinidas public void push(Object x)throws StackFull{ try{ a[n++] = x; }catch(ArrayIndexOutOfBoundsException x){ throw new StackFull(); } } public Object pop()throws StackEmpty{ try{ return a[--n]; }catch(ArrayIndexOutOfBoundsException x){

n=0; //estaba con -1 throw new StackEmpty(); } } }//fin class Stack

Page 12: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Stack: representación con lista enlazada C B A null primero valor sgte valor sgte valor sgte class Nodo { public Object valor; //visible public Nodo sgte; //visible public Nodo(Object x,Nodo y){ valor=x; sgte=y; } }

C B A

Page 13: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

class Stack { //representación: ref a primer nodo protected Nodo primero; public Stack(){ primero=null; } public boolean empty(){ return primero == null; } public boolean full(){ return false; //nunca está lleno } public void reset(){ primero=null; }

Page 14: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

public void push(Object x) throws StackFull { //crear un nodo con valor x y sgte nulo _________ Nodo r=new Nodo(x,null);//r| x |null| //enlazarle lista anterior r.sgte=primero;//r| x | || y | | … //dejarlo como primero de la lista primero=r;//1º| x | || y | | … }

Page 15: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Alternativamente:

public void push(Object x) throws StackFull{ //agregar al comienzo primero=new Nodo(x, primero);

//1º| x | || y | | …}

Page 16: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

public Object pop() throws StackEmpty{ //excepción si lista está vacía if(primero==null) throw new StackEmpty();

//recuperar primer valor Object aux=primero.valor;//1º|x|||y||

//eliminar primer nodo primero=primero.sgte; ;//1º|y||…

//devolver primer valorreturn aux;

}}//fin class Stack

Page 17: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Alternativamente public Object pop()throws StackEmpty{ try{

Object aux=primero.valor;//excepcion si null primero=primero.sgte;

return aux; } catch(NullPointerException x) { throw new StackEmpty(); } } }//fin class Stack

Page 18: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Ejemplo de uso: eliminación de recursividad

• Suponga que una función F realiza un llamado recursivo dentro de su código, lo que se ilustra en la siguiente figura:

Page 19: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Tail Recursion • Si la llamada recursiva es lo último que hace la función F, se

puede substituir por un ciclo while. • Este caso es conocido como tail recursion y es muy simple

eliminarla. Ejemplo:

void imprimir(int[] a, int j) // versión recursiva{ if (j<a.length) { System.out.println(a[j]); imprimir(a, j+1); // tail recursion }}void imprimir(int[] a, int j) // versión iterativa{ while (j<a.length) { System.out.println(a[j]); j=j+1; }}

Page 20: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Caso General: uso de pilas• Por ejemplo: recorrido en preorden de un arbol binario.

// "raiz" es la referencia a la raiz del arbol// llamado inicial: preorden(raiz) // version recursiva void preorden(Nodo nodo){ if (nodo!=null) { System.out.print(nodo.elemento); preorden(nodo.izq); preorden(nodo.der); }}

Page 21: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Preorden no recursivo: primera versión void preorden(Nodo nodo){ Nodo aux; Pila pila=new Pila(); // pila de nodos

pila.apilar(nodo); while(!pila.estaVacia()) // mientras la pila no este vacia

{ aux=pila.desapilar(); if (aux!=null) { System.out.print(aux.elemento); // primero se apila el nodo derecho y luego el izquierdo // para mantener el orden correcto del recorrido// al desapilar los nodos pila.apilar(aux.der); pila.apilar(aux.izq); } }}

Page 22: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Preorden no recursivo: segunda versión • dado que siempre el ultimo nodo apilado dentro del bloque if es

aux.izq podemos asignarlo directamente a aux hasta que éste sea null, es decir, el bloque if se convierte en un bloque while y se cambia el segundo apilar por una asignación de la referencia

void preorden(Nodo nodo){ Nodo aux; Pila pila=new Pila(); // pila de nodos pila.apilar(nodo); while(!pila.estaVacia()) // mientras la pila no este vacia { aux=pila.desapilar(); while (aux!=null) { System.out.print(aux.elemento); pila.apilar(aux.der); aux=aux.izq; } }}

Page 23: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Corolario• Si bien los programas no recursivos son más eficientes

que los recursivos, la eliminación de recursividad (excepto en el caso de tail recursion) le quita claridad al código del programa.

• Por lo tanto: – A menudo es conveniente eliminar el tail recursion. – Un método recursivo es menos eficiente que uno no recursivo,

pero sólo en pocas oportunidades vale la pena eliminar la recursión.

Page 24: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Problema. Simular una cola de atención de clientes de acuerdo al siguiente diálogo: Eventos: 1=llegada de cliente 2=atención de cliente 0=fin de eventos evento? 1 cliente? A A evento? 1 cliente? B A B evento? 2 se atiende A A B evento? 1 cliente? C B C … evento? 0

Page 25: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Indicación. Use una Queue (Cola) que es un contenedor que operacon el principio FIFO (First In First Out), es decir, el primerelemento que ingresa es el primero que sale.Operación significadonew Queue()

void enque(Object x)throws QueueFull

x

Object deque( )throws QueueEmpty

void reset( )

boolean empty( ) ¿ ?

boolean full( ) ¿ ?

Page 26: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Queue q=new Queue(); char evento; while((evento=U.readChar(“evento?”))!=’0’){ if(evento==’1’) try{ q.enque( U.readLine(“cliente?”) ); }catch(QueueFull x){ U.println(“rechazado”); } else if(evento==’2’) try{ U.println(“se atiende “+(String)q.deque()); }catch(QueueEmpty x){ U.println(“no hay clientes”); } else U.println(“evento incorrecto”); }

Page 27: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

TDA Queue: implementación con un arreglo Ejemplo:

A B C a a[0] ... a[n-1] ... a[N-1] class Queue{ protected Object[]a; protected int n; protected static final int N=100; public Queue(){reset();} public void reset(){a=new Object[N];n=0;} public boolean empty(){return n==0;} public boolean full(){return n>=N;}

A B C

Page 28: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

public void enque(Object x)throws QueueFull{ if(n>=N) throw new QueueFull(); a[n++] = x; } public Object deque()throws QueueEmpty{ if(n==0) throw new QueueEmpty();

Object aux=a[0]; for(int i=0;i<n-1;++i)

a[i]=a[i+1]; --n; return aux; } }//fin class Queue deque: O(n), es decir, depende del tamaño de la cola

Page 29: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Implementación TDA Queue: cola “flotante” en un arreglo Ejemplo: enque y deque O(1) A B C a a[0] … a[ip] ... a[iu] ... a[N-1] class Queue { protected Object[]a; protected int n; //largo de la cola protected int ip, iu;//indices de 1º y último protected static final int N=100;

A B C

Page 30: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

public Queue(){ reset(); } public boolean empty(){ return n == 0; } public boolean full(){ return n >= N; } public void reset(){ a=new Object[N]; n=0;

ip=0; //índice de primer objeto que llegue iu=-1;//para que primer enque le sume 1

}

Page 31: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

public void enque(Object x)throws QueueFull{ if( n >= N ) throw new QueueFull(); iu=(iu+1) % N;//++iu; if(iu==N) iu=0; a[iu] = x; ++n; } public Object deque()throws QueueEmpty{ if( n == 0 ) throw new QueueEmpty();

Object aux = a[ip]; ip = (ip + 1) % N; --n; return aux; } }//fin class Queue class QueueFull extends Exception{} class QueueEmpty extends Exception{} Propuesto: implementar usando el arreglo y sólo dos enteros

Page 32: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Representación de una Queue con una lista enlazada Alternativa1 : (enque toma tiempo proporcional a Nº de nodos) A B C null primero valor sgte valor sgte valor sgte

class Queue { protected Nodo primero; public Queue(){primero=null;} public void reset(){primero=null;} public boolean empty(){return primero==null;} public boolean full(){return false;}

Page 33: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

public Object deque()throws QueueEmpty{ if(primero==null)throw new QueueEmpty();

Object aux=primero.valor; primero=primero.sgte; return aux; } public void enque(Object x)throws QueueFull{ //crear nuevo nodo Nodo r=new Nodo(x,null); //si lista vacía, agregar al comienzo if(primero==null){primero=r; return;} //agregar al final Nodo ultimo=primero; while(ultimo.sgte!=null)ultimo=ultimo.sgte; ultimo.sgte = r; } } Nota. operación O(n)

Page 34: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Alternativa 2: con referencias al primer y último nodo A B C null primero valor sgte valor sgte sgte ultimo class Queue{ protected Nodo primero, ultimo; public Queue(){ reset(); } public void reset(){ primero=ultimo=null; } public boolean empty(){ return primero==null; } public boolean full(){ return false; }

Page 35: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

public void enque(Object x) throws QueueFull { Nodo r=new Nodo(x, null);

if(ultimo==null) //si lista vacía ultimo=primero=r;// dejar con un nodo

else //si no ultimo=ultimo.sgte=r;//enlazar al final } public Object deque()throws QueueEmpty {

if(primero==null)throw new QueueEmpty(); if(primero==ultimo) ultimo=null;//un nodo Object aux=primero.valor; primero=primero.sgte; return aux; } } Nota. Operaciones enque y deque O(1)

Page 36: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

TDA Cola de prioridad• Una cola de prioridad es un tipo de datos abstracto que almacena un

conjunto de datos que poseen una llave perteneciente a algún conjunto ordenado, y permite insertar nuevos elementos y extraer el máximo (o el mínimo, en caso de que la estructura se organice con un criterio de orden inverso).

• Es frecuente interpretar los valores de las llaves como prioridades, con lo cual la estructura permite insertar elementos de prioridad cualquiera, y extraer el de mejor prioridad.

• Dos formas simples de implementar colas de prioridad son: • Una lista ordenada:

– Inserción: O(n) – Extracción de máximo: O(1)

• Una lista desordenada: – Inserción: O(1) – Extracción de máximo: O(n)

Page 37: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Heaps• Un heap es un árbol binario de una forma especial, que permite su

almacenamiento en un arreglo sin usar punteros. • Un heap tiene todos sus niveles llenos, excepto posiblemente el de más abajo,

y en este último los nodos están lo más a la izquierda posible. • Ejemplo:

• La numeración por niveles (indicada bajo cada nodo) son los subíndices en donde cada elemento sería almacenado en el arreglo. En el caso del ejemplo, el arreglo sería:

Page 38: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Implementación de Heaps• La característica que permite que un heap se pueda almacenar sin punteros es

que, si se utiliza la numeración por niveles indicada, entonces la relación entre padres e hijos es:

Hijos del nodo j = {2*j, 2*j+1} Padre del nodo k = floor(k/2)

• Un heap puede utilizarse para implementar una cola de prioridad almacenando los datos de modo que las llaves estén siempre ordenadas de arriba a abajo (a diferencia de un árbol de búsqueda binaria, que ordena sus llaves de izquierda a derecha). En otras palabras, el padre debe tener siempre mayor prioridad que sus hijos .

Page 39: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Inserción en Heaps• Agregar el nuevo elemento en la primera posición libre del heap, esto es, el

próximo nodo que debería aparecer en el recorrido por niveles -> al final del arreglo. Con esto la forma del heap se preserva, pero la restricción de orden no tiene por qué cumplirse.

• Solución: hacerlo “subir” hasta un punto donde se vuelva a cumplir la condición

a[++n]=x; for(j=n; j>1 && a[j]>a[j/2]; j/=2){

# intercambiamos con el padre t=a[j]; a[j]=a[j/2]; a[j/2]=t; }

• El proceso de inserción, en el peor caso, toma un tiempo proporcional a la altura del árbol, esto es, O(log n).

Page 40: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Extracción del máximo (eliminación)• El máximo está en la raíz del árbol (casillero 1 del arreglo). Al sacarlo de ahí,

ese lugar queda vacante. Para llenarlo, tomamos al último elemento del heap y lo trasladamos al lugar vacante. En caso de que no esté bien ahí de acuerdo a su prioridad (¡que es lo más probable!), lo hacemos descender intercambiándolo siempre con el mayor de sus hijos. Es decir "se hunde" hasta su nivel de prioridad.

m=a[1]; # La variable m lleva el máximo a[1]=a[n--]; # Movemos el último a la raíz y achicamos el heap j=1; while(2*j<n) # mientras tenga algún hijo { k=2*j; # el hijo izquierdo if(k+1<=n && a[k+1]>a[k]) k=k+1; # el hijo derecho es el mayor if(a[j]>a[k]) break; # es mayor que ambos hijos t=a[j]; a[j]=a[k]; a[k]=t; j=k; # lo intercambiamos con el mayor hijo }

Este algoritmo también demora un tiempo proporcional a la altura del árbol en el peor caso, esto es, O(log n).

Page 41: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Transformar un arreglo en un heap• Sacando lso elemento del arreglo y poniéndolos en un heap: O(n log n) y

ocupa el doble de memoria• Modificarlo para hacerlo más eficiente y en el mismo arreglo.• La idea es invertir el orden de las "mitades" del arreglo, haciendo que el

"input" esté a la izquierda y el "heap" a la derecha.

Page 42: Problema. Leer las líneas de un archivo que contiene un programa en Java y verificar que todos los paréntesis estén bien balanceados Use el TDA (Tipo de.

Transformar un arreglo en un heap• En realidad, si el "heap" está a la derecha, entonces no es realmente un heap,

porque no es un árbol completo (le falta la parte superior), pero sólo nos interesa que en ese sector del arreglo se cumplan las relaciones de orden entre a[k] y {a[2*k],a[2*k+1]}.

• En cada iteración, se toma el último elemento del "input" y se le "hunde" dentro del heap de acuerdo a su nivel de prioridad.

• La razón es que, al ser "hundido", un elemento paga un costo proporcional a su distancia al fondo del árbol.

• Dada las características de un árbol, la gran mayoría de los elementos están al fondo o muy cerca de él, por lo cual pagan un costo muy bajo. En un análisis aproximado, la mitad de los elementos pagan 0 (ya están al fondo), la cuarta parte paga 1, la octava parte paga 2, etc.

• Sumando todo esto, tenemos que el costo total está acotado por