PROBLEMA 1 (ANÁLISIS DE ALGORITMOS) Se desea calcular el tiempo de ejecución de la función f, cuyo código aparece abajo. func f (ent/sal a: array[1..N] de Entero) dev (s: Entero) alg s := g (a, 0, 1) fin func g (ent/sal a: array[1..N] de Entero, r: Entero, i: Entero) dev (s: Entero) alg si i > N: s := r | i ≤ N: r := r + a[i] s := g (a, r, i+1) fsi fin (a) ¿Cómo escogería el tamaño del problema n para la función recursiva g? (b) Determine el tiempo de ejecución Tg(n) de una llamada recursiva a g. (c) Determine el tiempo de ejecución de ejecución Tf(N) de la función f. Nota: Usar constantes que engloben el tiempo de ejecución de las operaciones elementales. Solución: (a) El tamaño del problema n para la función recursiva g: n=N–i+1 (b) El tiempo de ejecución Tg(n) de una llamada recursiva a g vendrá dado por: k1 ⎧ Tg (n) = ⎨ ⎩Tg (n − 1) + k2 ,n = 0 ,n > 0 Ecuación característica: ( x − 1) 2 = 0 ⇒ Tg ( n) = c1n + c2 ; Condiciones iniciales: Tg (0) = k1 = c2 , Tg (1) = Tg (0) + k2 = k1 + k2 = c1 + c2 ⇒ c1 = k2 , c2 = k1 ⇒ Tg ( n) = k2 n + k1 (c) El tiempo de ejecución de ejecución Tf(N) de la función f será: T f ( N ) = Tg ( N − 1 + 1) + k3 = Tg ( N ) + k3 = k2 N + k1 + k3 PROBLEMA 2 (ANÁLISIS DE ALGORITMOS) Dado el siguiente algoritmo: proc procesa(ent/sal a: array[1..k] de Entero, prim: Entero, ult: Entero) var i, j, aux: Entero alg si prim < ult (1) i := prim (2) j := ult (3) mientras i < j (4) si a[i] != a[j] (5) aux := a[i] (6) a[i] := a[j] * 4 + 3 (7) a[j] := aux * 4 – 3 (8) fsi i := i + 1 (9) j := j – 1 (10) fmientras procesa(a, prim + 1, ult - 1) (11) fsi fin (a) ¿Cómo escogería el tamaño de problema n para el proceso recursivo procesa? (b) Determine el caso mejor y el tiempo de ejecución T(n) para el proceso procesa en ese caso. (c) Determine el caso peor y el tiempo de ejecución T(n) para el proceso procesa en ese caso. Notas: • Utilizar las constantes que se crean necesarias para englobar los costes de las operaciones (indicando qué operaciones engloban). • Suponer que la llamada inicial es procesa(a, 1, k) Solución: a) n = ult – prim + 1 b) y c) En cada llamada recursiva el tamaño del problema disminuye en dos unidades por el decremento de ult y el incremento de prim. El bucle (líneas (4) a (10)) se ejecuta n/2 (división entera) veces. Si nunca se cumple la condición de la línea (5) estaremos en el caso mejor, que corresponde a un array donde los elementos están dispuestos de forma simétrica. La diferencia entre el caso mejor y peor corresponde a las instrucciones (6), (7) y (8). Por tanto, la diferencia de coste entre un caso y otro, será una constante que llamaremos k5. T (n) = { k1 T (n − 2) + ⎣⎢ n / 2 ⎦⎥ k2 + k3 n ≤ 1 ( prim ≥ ult ) n > 1 ( prim < ult ) Para el caso mejor, k2 = k4, mientras que para el caso peor, k2 = k4 + k5, donde: k1 : Engloba las operaciones del caso base (evaluación de (1)) k2 : Coste de cada iteración del bucle mientras, instrucciones de la (4) a la (10) k3 : Engloba las operaciones (1), (2), (3), evaluación de la condición del bucle (4) al salir, y cálculos de parámetros de la llamada recursiva (11) k4 : Engloba la evaluación de la condición (4) y (5), y las instrucciones (9) y (10) k5 : Engloba las operaciones (6), (7) y (8) Lo resolveremos suponiendo n par. Para n impar se resolvería de forma análoga, simplemente cambiando la constante k3. Del caso recursivo podemos deducir: T(n) – T(n – 2) = n k2 + k3 2 La ecuación característica resultante es ( x 2 − 1)( x − 1) 2 = 0 (ec. no homogénea con b = 1, d = 1), con soluciones r1 = 1 repetida 3 veces y r2 = -1 repetida 1 vez. Por tanto: T ( n) = c1 ( −1) n + c2 (1) n + c3 n(1) n + c4 n 2 (1) n = c1 (−1) n + c2 + c3 n + c4 n 2 Será de orden cuadrático (Θ(n )) si la constante c4 es diferente a 0. Usando T(0), T(2), T(4) y T(6), se deduce que: c1 = k1, c2 = 0, c3 = (k2 + k3) / 2, c4 = (1/4) k2. Si n fuese impar usaríamos T(1), T(3), T(5) y T(7). 2 PROBLEMA 3 (ALGORITMOS VORACES) Se dispone de N vasijas para transportar aceite. La tabla capac de N enteros indica sus diferentes capacidades (∀i:1..N | capac[i] > 0) y se encuentra ordenada ascendentemente. En otro array aceite de N enteros se indican los litros de aceite que hay en cada una de ellas (∀i:1..N | 0 ≤ aceite[i] ≤ capac[i]). Diseñar un algoritmo voraz que obtenga el máximo número de vasijas llenas mediante el trasvase de unas a otras. La solución sol al problema debe ser un array NxN de enteros tal que cada elemento sol[i, j] sea el número de litros que se trasvasan de la vasija i a la vasija j. Problema capac aceite 5 2 10 5 15 10 25 15 50 4 1 2 3 4 5 1 0 0 0 0 3 Solución 2 3 0 0 0 0 0 0 4 5 1 0 4 0 0 0 0 0 5 0 0 0 0 0 Para este ejemplo, una posible solución sería la presentada, donde se trasvasan 3 litros de la vasija 5 a la 1, 1 litro de la vasija 5 a la 2, 4 litros de la vasija 4 a la 2 y 5 litros de la vasija 4 a la 3. Solución: proc voraz() alg inicializa() mientras No fin() seleccionaYElimina () si prometedor() anotaEnSolucion() fsi fmientras fin Clase VasijasAceite hereda EsquemaVZ capac: Array [1..N] de Entero aceite: Array [1..N] de Entero pri, ult: Entero solucion: Array [1..N,1..N] de Entero proc inicializa() alg pri := 1 ult := N capac := <inicializa con la capacidad de las vasijas> aceite := <inicializa con las cantidades almacenadas por las vasijas> solucion := <inicializa con ceros> fin func fin() dev (b:Logico) b := (pri>=ult) fin proc seleccionaYElimina() alg si aceite[ult]=0: ult := ult – 1 fsi si capac[pri] = aceite[pri]: pri := pri + 1 fsi fin func prometedor() dev (b:Logico) alg b := (capac[pri] > aceite[pri] Y aceite[ult] > 0 Y pri < ult) fin proc anotaEnSolución() alg si aceite[ult] > capac[pri] – aceite[pri]: solucion[pri, ult] := capac[pri] – aceite[pri] otras: solucion[pri, ult] := aceite[ult] fsi aceite[pri] := aceite[pri] + solucion[pri, ult] aceite[ult] := aceite[ult] – solucion[pri, ult] fin fClase PROBLEMA 4 (ALGORITMOS VORACES) Diseñar un algoritmo voraz que maximice el valor de una mochila de peso máximo pMax, en la que se pueden introducir N tipos de objetos que vienen representados por objetos: array [1..N] de Registro (no ordenado), cuyos campos son peso, valor y cantidad, que indican respectivamente el peso del objeto, su valor económico y el número de ejemplares que hay de cada uno. Solución: proc voraz() alg inicializa() mientras No fin() seleccionaYElimina () si prometedor() anotaEnSolucion() fsi fmientras fin Atributos de la Clase Mochila: k, val, pRestante: Entero objetos: Array [1..N] de Registro sol: Array[1..N] de Entero proc inicializa() alg quicksort(objetos) // ordena decrecientemente por la relación valor/peso sol := <array de ceros> < val, k > := < 0, 0 > pRestante := pMax fin func fin() dev (b: Logico) alg b:=(k=N O pRestante=0) fin proc seleccionaYElimina () alg k:=k+1 fin func prometedor() dev (b: Logico) alg b := cierto fin proc anotaEnSolucion() alg sol[x] := minimo(objetos[x].cantidad, ⎣pRestante/objetos[x].peso⎦ ) si sol[x] > 0: pRestante := pRestante – sol[x]*objetos[x].peso val := val + sol[x]*objetos[x].valor fsi fin PROBLEMA 5 (DIVIDE Y VENCERÁS) Sea a: array[1..N] de Elemento, no ordenado, se desea obtener los elementos mínimo y máximo de a. Para ello se dispone de la función compara(x, y: Elemento), que devuelve un valor negativo si x < y, positivo si x > y o cero si x = y. Diseñar un algoritmo de Divide y Vencerás para obtener los resultados deseados. El algoritmo debe minimizar el número de llamadas a la función compara. Justificar la solución adoptada. Solución: func dyV(x: Problema) dev (s: Solucion) var subproblemas: array[] de Problema subsoluciones: array[] de Solucion a,i: entero alg si esCasoBase(x): s:= resuelveCasoBase(x) |otras: subproblemas := divide(x) a := tamaño(subproblemas) subsoluciones := <crea array de a Solucion> desde i:=1 hasta a subsoluciones[i] := dYV(subproblemas[i]) fdesde s := combina(subsoluciones) fsi fin Clase MinMaxDyV hereda de EsquemaDyV a: array [1..N] de Elemento xini: Problema sol: Solucion Clase Problema i,j: Entero proc Problema(pi,pj:Entero) alg <i,j> := <pi,pj> fin fClase Clase Solucion minimo, maximo: Elemento proc Solucion(pmin,pmax:Elemento) alg <minimo,maximo> := <pmin,pmax> fin fClase proc resuelve() alg xini := <crea Problema(1,N)> sol := dyV(xini) <tratar sol> fin func esCasoBase(x:Problema) dev (b:Logico) alg b := ((x.j-x.i)<2) // 1 ó 2 elementos fin func resuelveCasoBase(x:Problema) dev (s:Solucion) alg si x.i=x.j: s := <crea Solucion(a[x.i],a[x.i])> |otras: // 2 elementos si compara(a[x.i],a[x.j])<0: s := <crea Solucion(a[x.i],a[x.j])> |otras: s := <crea Solucion(a[x.j],a[x.i])> fsi fsi fin func divide(x:Problema) dev (probs: array[] de Problema) var k: Entero alg k := (i+j) div 2 probs := <crea array[1..2] de Problema> probs[1] := <crea Problema(i,k)> probs[1] := <crea Problema(k+1,j)> fin func combina(sols:array[] de Solucion) dev (s:Solucion) alg s := <crea Solucion> si compara(sols[1].minimo,sols[2].minimo)<0: s.minimo := sols[1].minimo |otras: s.minimo := sols[2].minimo fsi si compara(sols[1].maximo,sols[2].maximo)<0: s.minimo := sols[2].maximo |otras: s.minimo := sols[1].maximo fsi fin fClase Justificación: Un método sencillo consiste en recorrer el array en busca del máximo y el mínimo, lo que podría suponer un total de 2N – 2 llamadas a la función compara. Para minimizar en lo posible el número de comparaciones de elementos la cuestión clave es que sólo se necesita una llamada a compara para obtener el mínimo y el máximo de dos elementos. Mediante el esquema de divide y vencerás se puede conseguir sacar provecho de esta propiedad, de manera que se subdivida el array de forma sucesiva hasta llegar a ese tamaño. La forma más sencilla es hacerlo en dos mitades. De esta manera, el número de comparaciones que se realizaría usando divide y vencerás para n elementos, NC(n), vendría dado por: 0 ,n = 1 ⎧ ⎪ NC ( n ) = ⎨ 1 ,n = 2 ⎪2 NC ( n / 2) + 2 , n > 2 ⎩ si n es potencia de 2. Resolviendo la ecuación, quedaría NC(n)=c1n+c2. Para determinar c1 y c2 consideremos dos supuestos para el caso base: subproblema con 1 ó 2 elementos: • Si 1 elemento: NC(1) = 0 = c1+c2; NC(2) = 2NC(1)+2 = 2 = 2c1+c2 Î c1 = 2 ; c2 = –2 Î NC(N) = 2N–2 (igual número de comparaciones que en recorrido lineal) (Nótese que se necesitarían 2 comparaciones para obtener el mínimo y el máximo de 2 elementos si el caso base sólo contempla tener un elemento) • Si 2 elementos : NC(2) = 1 = 2c1+c2; NC(4) = 2NC(2)+2 = 4 = 4c1+c2 Î c1 = 3/2 ; c2 = –2 Î NC(N) = (3/2)N–2 Dado que N no tiene por qué ser potencia de 2, el caso base debe considerar también que hay que resolver subproblemas de tamaño 1. PROBLEMA 6 (DIVIDE Y VENCERÁS) Un sistema de localización de fuegos en un área geográfica dispone de un array de NxN lógicos, tal que N es potencia de 2, donde V indica que hay fuego en una determinada cuadrícula y F que no (se omite en el dibujo por claridad). Se dispone de una función hayFuego(a: Area) dev (b: Lógico) que para un Area geográfica dada devuelve Verdadero si hay fuego en el área que se le pasa o falso en caso contrario. En el ejemplo la función hayFuego para todo el área del dibujo (NxN) devuelve Verdadero, pero para el área que recoge el cuadrado inferior izquierdo de tamaño 4x4 devuelve Falso. Para la cuadro superior derecha de tamaño 1x1 devuelve Verdadero. V V V Diseñar un algoritmo de divide y vencerás que devuelva una lista con los puntos donde hay fuego en esa área. Solución func dyV(x: Problema) dev (s: Solucion) var subproblemas: array[] de Problema subsoluciones: array[] de Solucion a,i: entero alg si esCasoBase(x): s:= resuelveCasoBase(x) |otras: subproblemas := divide(x) a := tamaño(subproblemas) subsoluciones := <crea array de a Solucion> desde i:=1 hasta a subsoluciones[i] := dYV(subproblemas[i]) fdesde s := combina(subsoluciones) fsi fin Clase Punto i,j: Entero fclase // coordenadas del punto Clase Area p: Punto n: Entero fClase // punto del área con menor i,j // longitud del lado Clase Problema a: Area fClase Clase Solucion listp: Lista de Punto fClase func esCasoBase (x: Problema) dev (b: Logico) alg b := (hayFuego(x.a) = falso O x.a.n = 1) fin func resuelveCasoBase(x: Problema) dev (s: Solucion) alg s := <nueva Solucion> s.listp := <nueva lista Vacía> si hayFuego(x.a): s.lisp.añadir(<nuevo Punto(x.a.i,x.a.j)>) fsi fin func divide(x: Problema) dev (subpr: Array [] Problema) alg subpr := <nuevo array [4] de Problema> mitad := x.a.n/2 subpr[1] := <nuevo Problema(x.a.i, x.a.j, mitad)> subpr[2] := <nuevo Problema(x.a.i+mitad, x.a.j, mitad)> subpr[3] := <nuevo Problema(x.a.i, x.a.j+mitad, mitad)> subpr[4] := <nuevo Problema(x.a.i+mitad, x.a.j+mitad, mitad)> fin func combina(sub[]: Solucion) dev (s: Solucion) alg s := <nueva Solucion> s.listp := <nueva lista Vacía> desde i :=1 hasta sub.tamaño s.listp := s.listp + sub[i].listp //concatena listas fdesde fin