SEGUNDO
Tamao del problema: Entero 0 que nos da una idea de la complejidad del problema.
Caso base: Problema de tamao pequeo cuya solucin es directa, no recursiva.
Caso recursivo: Resto de problemas.
Un algoritmo recursivo es aqul que expresa la solucin de un problema en trminos de llamada o llamadas a s
mismo. Cada llamada a s mismo se denomina llamada recursiva.
La recursividad se presenta como una alternativa a la iteracin.
Los algoritmos recursivos son apropiados si la definicin del problema est planteada de forma recursiva.
Para que una definicin recursiva sea vlida debe tener al menos un caso base, y cada caso recursivo definirse
en base a otro de menor tamao.
Veamos un ejemplo: queremos definir el problema factorial de n (n!) y plantearlo de forma recursiva. La
definicin ser de la siguiente forma:
! = {
1
( 1)!
=0
>0
Con la tcnica conocida como Divide y Vencers, el problema original es dividido en sub-problemas ms
pequeos (en este caso en 1 sub-problema).
Lo primero que debemos tener en cuenta es que cuando vamos a hacer una definicin recursiva de un problema
siempre debemos apreciar un conjunto de problemas, llamado tambin dominio. En este caso, el conjunto de
problemas viene dado por n! para todo n >= 0. A cada uno de esos problemas le asociaremos un tamao. El
tamao del problema deber ser un entero mayor o igual que cero que nos d una idea de la complejidad del
problema. Problemas de tamao mayor sern ms complejos que otros de tamao menor. Puede haber distintas
formas para escoger el tamao de un problema. En este caso el tamao del problema factorial de n (n!) ser n.
En lo que sigue representaremos los problemas por p, p1, p2, , pr. Un conjunto de problemas lo representamos
por P. Cada problema tendr unas propiedades x. Cada propiedad especfica la representaremos mediante un
superndice: x = (x1, , xn). Dentro de un conjunto de problemas P los valores de sus propiedades identifican al
problema de manera nica.
Un problema podemos pensarlo como un objeto. El diseo de los problemas lleva a cabo con la metodologa
orientada a objetos. Por la misma razn las propiedades de un problema pueden clasificarse en bsicas y
derivadas, individuales y compartidas, consultables y modificables, etc.
1
Para cada problema del conjunto de problemas podemos indicar una invariante del problema, I(x), que es una
expresin lgica que debe ser vlida para las propiedades de cada problema en particular. Tambin podemos
indicar el dominio, D(x), que es una expresin que es vlida para las propiedades de todos los problemas que
estn incluidos en el conjunto de problemas de inters. En general llamaremos aserto, A(x), a cualquier expresin
lgica construida con las propiedades del problema. Al escribir los asertos usamos los operadores lgicos NOT,
AND, OR representados como: , , .
A cada problema podemos asociar el concepto de tamao que es una nueva propiedad derivada del mismo. El
tamao de un problema es una medida de la cantidad de informacin necesaria para representarlo.
Normalmente representaremos el tamao de un problema mediante n y lo calcularemos mediante una funcin
sobre las propiedades del mismo. Lo representamos por n = t(x) o n = t(p). El tamao del problema deber ser
un entero mayor o igual que cero que nos d una idea de la complejidad del mismo. Problemas de tamao mayor
sern ms complejos que otros de tamao menor. Puede haber distintas formas para escoger el tamao de un
problema.
Dentro de un conjunto de problemas aquellos que tienen una solucin directa los llamamos casos base. Estos
suelen tener un tamao pequeo. En la definicin recursiva del factorial aparece un problema cuya solucin es
directa, es el problema 0! (es decir, el problema de tamao n = 0) por lo que en el conjunto de problemas el
problema 0! es un caso base, donde su solucin es 1. Existen problemas en los que puede haber ms de un caso
base. El resto de problemas del conjunto considerado (en este caso todos los que tienen n>0) los denominaremos
casos recursivos. Un caso recursivo se define en funcin de otros problemas de tamao menor. Estos los
denominaremos sub-problemas.
Un mismo problema puede tener diferentes definiciones recursivas. Otra definicin recursiva para el factorial en
el que se presentan dos casos base sera:
1
! = { 1
( 1)!
1
2
1
=0
=1
>1
Otra definicin recursiva con menor evidencia pero importante es que ahora el conjunto de problemas es (n,m),
n 0, m 1 y la definicin recursiva de la solucin sera:
! = (, 1),
(, ) = {
( 1, )
=0
>0
En este caso hemos definido el problema n! en base a otro que depende de dos parmetros: el problema
fac(n,m). Damos una definicin recursiva de este problema teniendo en cuenta que el tamao de fac(n,m) es n
y el de (n,m) tambin es n. El problema original (n!) lo hacemos igual a fac(n,1). Podemos comprobar con un
ejemplo que la definicin es adecuada. En efecto:
3! = (3, 1) = (2, 3 1) = (1, 2 3 1) = (0, 1 2 3 1) = 1 2 3 1
En este tema aprenderemos a transformar unas definiciones recursivas en otras y a escoger la mejor de ellas
para diferentes propsitos.
Pero para que una definicin recursiva sea vlida debe tener al menos un caso base, y cada caso recursivo
definirse en base a otro de menor tamao. Por lo tanto las siguientes son propiedades del factorial:
0!
( + 1)!
+1
=1
= !
=0
>0
La definicin anterior no constituye un algoritmo correcto porque el caso recursivo se ha definido en base a otros
problemas de tamao mayor. Es necesario que los sub-problemas usados para definir el caso recursivo tengan
un tamao menor para conseguir que en cada paso recursivo (paso de un problema a los sub-problemas que lo
2
int factorial(int n) {
int r;
assert(n >= 0);
if (n == 0) {
r = 1;
} else {
r = n * factorial(n - 1);
}
return r;
}
Este algoritmo recursivo es la transcripcin mimtica de la primera definicin recursiva que dimos para el
factorial. Las otras definiciones tienen algoritmos similares. Hemos escrito la asercin assert(n>=0) para insistir
en que el conjunto de problemas que hemos considerado est formado por aquellos que cumple que n 0.
Otro ejemplo similar lo tenemos para el clculo de la edad de una persona conocido su ao de nacimiento y el
ao actual. Podramos resolverlo recursivamente de la siguiente forma:
(, ) = {
0
1 + ( + 1, )
edad(2010, 2013) = 3
1+edad(2011, 2013) = 1+2 = 3
nac act
nac act
nac act
nac = act
edad(2013, 2013)
edad(2012, 2013)
edad(2011, 2013)
edad(2010, 2013)
int act) {
<= act);
act) {
0;
1 + edad(nac + 1, act);
Otro ejemplo de problema definido recursivamente tenemos el mximo comn divisor ( m.c.d. (a, b) ). Son
conocidas varias propiedades del mximo comn divisor:
(, )
{ (, 0)
(, )
= (, )
=
= (, %)
Para hacer una definicin recursiva (disear un algoritmo recursivo) debemos escoger un dominio (conjunto de
problemas) y un tamao. Suponemos que el dominio est dado para todos los valores enteros a, b que son
mayores o iguales a cero pero no ambos iguales a cero. El tamao de un problema dado los escogemos como el
valor de b. Con estas ideas, y las propiedades anteriores, una definicin recursiva es:
(, ) = {
(, %)
=0
>0
Vemos que el problema est bien definido: el sub-problema tiene un tamao menor que el del problema (dado
que el resto de una divisin entera es menor que el dividendo y el divisor), es decir, el menor elemento; y todos
los problemas del dominio tienen un tamao mayor o igual a cero.
Podemos comprobar la idoneidad de la definicin con algunos ejemplos:
(9,6) = (6,9%6) (6,3) = (3,0) = 3
(8,12) = (12,8%12) (12,8) = (12,12%8) (8,4) = (4,4%8) (4,0) = 4
int mcd(int a, int b) {
int r;
assert(a >= 0 && b >= 0 && !((a == 0) && (b == 0)));
if (b == 0) {
r = a;
} else {
r = mcd(b, a % b);
}
return r;
}
Otro ejemplo, la suma de una lista de enteros, definida como: suma(lista, tamao, pos):
= 0
{ }
= ( 1)
(, , ) = { []
[] + (, , + 1) < ( 1)
import java.util.Arrays;
import java.util.List;
public class SumaListaEnteros {
public static Integer suma(List<Integer> lista, Integer tamao, Integer pos) {
Integer r;
if (pos == tamao - 1) {
r = lista.get(pos);
} else {
r = lista.get(pos) + suma(lista, tamao, pos + 1);
}
return r;
}
public static void main(String[] args) {
List<Integer> lista = Arrays.asList(12, 3, 5, 8);
Integer tamao = lista.size();
Integer pos = 0;
System.out.println(suma(lista, tamao, pos));
}
}
= 0
= 0
> 0
import java.util.Arrays;
import java.util.List;
public class MayorEnLista {
public static Integer mayor(List<Integer> lista, Integer tamao, Integer pos) {
Integer r;
if (pos == 0) {
r = lista.get(pos);
} else {
r = Math.max(lista.get(pos), mayor(lista, tamao, pos - 1));
}
return r;
}
public static void main(String[] args) {
List<Integer> lista = Arrays.asList(2, 30, 5, 8);
Integer tamao = lista.size();
Integer pos = tamao - 1;
System.out.println(mayor(lista, tamao, pos));
}
}
Es decir, se intercambian los valores de x e y. Sin embargo, indicar { y = x no es una implementacin correcta.
La idea general es usar variables nuevas, asignar a estas los valores de las expresiones, y posteriormente asignar
las nuevas variables a las antiguas, es decir, asigna los elementos de una tupla a los de otra del mismo tipo.
Igualmente se puede usar entre listas pero en este caso todos los elementos tienen el mismo tipo.
Es conveniente conocer con detalle la forma de implementar este operador, su relacin con asertos sobre las
variables antes y despus de su ejecucin y, tambin, su relacin con los bloque bsicos de cdigo. Veamos cada
uno de estos conceptos y sus relaciones.
Si designamos por el valor de la variable antes de la asignacin paralela:
(x1, x2, , xm) ( e1(x1, x2, , xm), e2(x1, x2, , xm), , em(x1, x2, , xm) )
1, 2, , )
entonces los valores posteriores de esas variables son = (
En una asignacin paralela se toman los valores previos de cada una de las variables y, de forma independiente
(paralela), se calculan los valores finales de cada una de las variables asignadas. Por otra parte, un bloque bsico
(en los lenguajes imperativos) es una secuencia de asignaciones que van produciendo efectos laterales:
x1 = g1(x); x2 = g2(x); ; xn = gn(x);
Una asignacin paralela y un bloque bsico son conjuntos de asignaciones bastante diferentes. Los lenguajes de
programacin usuales disponen de bloques bsicos pero no de asignacin paralela. Debemos aprender a
convertir una asignacin paralela en un bloque bsico y viceversa.
Veamos un ejemplo: la asignacin paralela: (x, y) (y, x) asigna el antiguo valor de x a y, yviceversa. Es decir,
intercambia el valor de la x por el de la y, y viceversa. Sin embargo x=y; y=x, es un bloque bsico que hace que
tanto x como y tomen el antiguo valor de la y. Aunque el bloque bsico se parece a la asignacin paralela el
resultado es diferente. Sin embargo a=x; x=y; y=a, es un bloque bsico que s intercambia, como la asignacin
paralela, los valores de x y de y, aunque usa una variable adicional. Como ahora veremos es posible obtener un
bloque bsico a partir de una asignacin paralela y viceversa. Los bloques bsicos son los elementos necesarios
a la hora de implementar el algoritmo en un lenguaje de programacin como Java o C. Las asignaciones paralelas
son ms cmodas cuando hablamos de esquemas algortmicos de las transformaciones de unos en otros.
Otro elemento importante es la transformacin que sufren los asertos sobre variables cuando hacemos una
asignacin paralela. Si A(x) es un aserto A(x)[(x1|e1), (x2|e2), , (xm|em)] entonces podemos designar la
sustitucin simblica de cada una de las variables por sus respectivas expresiones. En la expresin anterior
usaremos indistintamente A(x)[] o A(x)(). Con esa notacin ya podemos mostrar una relacin muy importante
entre la asignacin paralela, los asertos en un punto dado del cdigo y la sustitucin simblica. El aserto A(x)
cuando se ejecuta la asignacin paralela:
(x1, x2, , xm) ( e1(x1, x2, , xm), e2(x1, x2, , xm), , em(x1, x2, , xm) )
se transforma en
A(x) A(x) [(x1|e1), (x2|e2), , (xm|em)] A(x)[ x e(x) ]
Donde hemos expresado la identidad entre la sustitucin simblica y la asignacin paralela. Por ejemplo el aserto
A(x, y) x = M y = N se transforma, cuando se ejecuta la asignacin paralela (x, y ) (y, x) (o el bloque bsico
equivalente) en A(x, y ) y = M x = N. Es decir, se intercambian los valores de las variables.
6
m
a
=
em ;
(x1 ,x1 ,,xm ) (e1 ,e2 ,,em )
1
x = a1 ;
x2 = a2 ;
{ xm = a m ;
Por ejemplo,
= ;
= ;
= ;
(, ) (y, x) { = ; { = ;
= ;
= ;
En el tercer paso hemos eliminado la variable a, la ecuacin donde se defina y hemos sustituido su uso por su
valor en la ecuacin que define la variable x.
Veamos ahora cmo conseguir una asignacin paralela equivalente a un bloque bsico (y consecuentemente
una sustitucin simblica equivalente). La idea es partir de los valores iniciales de las variables (que
representaremos como: , , etc.) e ir acumulando los valores de las variables a partir de los iniciales. Cuando
tengamos los valores de las variables al final del bloque bsico a partir de sus valores iniciales tendremos la
asignacin paralela equivalente:
= ; (|)
=
{ = { = ; (|, |)
(x, y)(y, x)
=
= ; (|, |, |)
Como hemos visto el operador de asignacin paralela se reduce a un bloque bsico formado por una secuencia
de asignaciones cuando las variables del lado izquierdo y del lado derecho son distintas. Si la asignacin paralela
es entre listas, con variables distintas, la implementacin puede hacer con una estructura for de la forma:
[ 1 , 2 , , ] [1 , 2 , , ]
( = 0; < ; + +) {
=
}
Pero cuando hay variables comunes en el lado derecho y en el izquierdo debemos usar variables temporales en
el bloque bsico para implementar la asignacin paralela.
Veamos otro ejemplo: (a, b) (b, a%b).
Como en el lado derecho y en el izquierdo comparten variables, la implementacin directa como secuencia de
asignaciones, es incorrecta. En efecto la secuencia de asignaciones siguiente no es equivalente a la asignacin
paralela anterior porque la primera asignacin cambia el valor de la a y este valor se usa en la segunda en lugar
del valor original, esto es, a = b; b = (a%b);, luego la implementacin correcta es: c = b; d = (a%b); a = c; b = d;.
Que puede ser simplificada eliminando la variable d, c = b; b = (a%b); a=c;.
Hemos visto la relacin existente ente asignaciones paralela, bloques bsicos, asertos y sustituciones simblicas.
Estas relaciones podemos generalizarlas al caso de las asignaciones paralelas generalizadas y los bloques
bsicos generalizados.
Una asignacin paralela generalizada es de la forma:
1 ()
2 ()
{
0 ()
1 ()
2 ()
1 ()2 ()
O en forma de cdigo:
if (g1(x)) {
x e1( x);
} else if (g2(x)) {
x e2( x);
}
else {
x e0( x);
}
Llamamos bloque bsico generalizado a una secuencia de boques if, else if, else con bloque bsicos en
las ramas del if. El bloque bsico generalizado equivalente a la asignacin paralela generalizada es de la forma:
if (g1) {
b1;
} else if (g2) {
b2;
}
else {
b0;
}
Donde b1, b2, , b0 son bloques bsicos equivalentes a las asignaciones paralelas x e1(x), x e2(x), , x e0.
Asumiendo que s1, s2, , s0 sean las sustituciones equivalentes respectivas entonces el bloque bsico
generalizado transforma un aserto en la forma A(x) a otro de la forma A(x).
A(x) (A(x) g1(x))[s1] (A(x) g2(x))[s2] (A(x) g0(x))[s0]
donde g0(x) = g1(x) g2(x)
Esta distincin es importante porque nos permite identificar a un problema dentro del conjunto slo por sus
parmetros individuales.
Veamos un ejemplo, sea una definicin recursiva para el clculo del mximo valor de una lista de enteros
proporcionada la lista (dt) y el nmero total de elementos que contiene (n): maxVal(dt, n)
Problema: E = (dt, n)
Con estos datos es casi imposible plantear una definicin recursiva. Por lo tanto, generalizamos:
Ahora es ms fcil dar una definicin para mv(i, dt, n), basndose en la idea que el mximo de los valores
contenidos en las posiciones de un sub-lista (i, n) es el mximo entre la primera posicin y el mximo del valor
contenido en las posiciones restantes excluyendo la primera.
Por ltimo, hay que escoger qu problema generalizado (i, dt, n), equivale al problema original, (dt, n).
La funcin maxVal(dt, n) se encargar de instanciar el problema generalizado escogido mv(i, dt, n) y controlar
que los parmetros de entrada (globales) sean correctos.
No obstante, podemos plantear otras soluciones a la descrita anteriormente. Veamos en primer lugar una posible
definicin recursiva y luego el algoritmo correspondiente.
(, ) = (0, , , )
[]
(, , , ) = {
max([], ( + 1, , , ))
max(, ) = ? :
=1
>1
La definicin recursiva anterior se basa en la siguiente idea: el mximo de los valores contenidos en las posiciones
de una lista es el mximo entre la primera posicin y el mximo del valor contenido en las posiciones restantes
excluyendo la primera.
9
Veamos los elementos de esta definicin recursiva. En primer lugar hemos generalizado el problema. El problema
original era encontrar el mximo valor de las posiciones de una lista dt con n elementos. El problema
generalizado se define como encontrar el mximo valor de las posiciones i (incluida) hasta la j (no incluida) de
una lista dt con n elementos. El problema original se obtiene dando los valores 0 y n a los parmetros i y j,
respectivamente, del problema generalizado.
Los parmetros del problema generalizado podemos clasificarlos en: compartidos (dt, n) e individuales (i, j).
Por tanto, un problema concreto dentro del conjunto considerado lo identificamos con el par de parmetros i, j.
Los otros dos parmetros son compartidos por todos los problemas.
El resto de las funciones del esquema son:
d(i, j, dt, n)
b1(i, j, dt, n)
sb1(i, j, dt, n)
sp1(i, j)
c(i, j, dt, n, rp1)
=
=
=
=
=
Como vemos, el papel de los parmetros compartidos y el de los individuales es diferente. Como son suficientes
los parmetros individuales para identificar el problema, entonces el tamao, si es caso base o no, slo dependen
de los parmetros individuales.
El dominio es una expresin sobre los parmetros individuales que tambin puede depender de algunos
parmetros compartidos. El sub-problema recibe los parmetros compartidos del problema de partida. En el
resto de expresiones se usan tambin los parmetros compartidos.
10
Llamaremos a este esquema recursivo sin memoria. Este esquema es denominado usualmente como divide y
vencers sin memoria.
Una versin ms compacta sera:
() = {
()
(, (()))
()
! ()
O en forma de esquema:
R f(E x) {
R r;
assert(D(x));
if(b(x)){
r = sb(x);
} else {
r = c(x,f(sp(x)));
}
return r;
}
11
Como hemos comentado antes, al hacer una definicin recursiva debemos considerar un conjunto de problemas.
Representamos los problemas por p, p1, p2, , pr. Un conjunto de problemas lo representaremos por P. Cada
problema tendr m propiedades. Cada propiedad especfica la representaremos mediante un superndice: x =
(x1, , xn). Puede haber k sub-problemas. Entonces tenemos:
y = [x1, x2, , xk]
Por ltimo un problema puede devolver u resultados y por tanto: r = (r1, r2, , ru).
En el prrafo anterior hemos escogido la notacin () para representar tuplas de valores, es decir, agregados de
valores de distintos tipos. Hemos representado por [] listas de valores de un tipo dado.
Por tanto: LR Lista<R>, LE Lista<E> y los tipos R y E son tuplas de valores.
Segn el nmero de llamadas que realice el algoritmo recursivo hay dos tipos de recursividad:
Recursividad simple o lineal:
En cada llamada recursiva se ejecuta a lo sumo,
una llamada a la propia funcin.
Recursividad mltiple:
En cada llamada recursiva puede ejecutarse
ms de una llamada a la propia funcin.
1
! = { 1
( 1)!
=0
=1
>1
0
() = { 1
( 1) + ( 2)
=0
=1
>1
12
else {
<q1,,qn> = sp1(p1,,pn);
assert(t(q1,,qn) < t(p1,,pn));
rp1 = f(q1,,qn);
r = c(p1,,pn,rp1,);
}
return r;
}
Como hemos comentado antes, al hacer una definicin recursiva debemos considerar un conjunto de problemas.
Representaremos los problemas por letras maysculas P, Q, a su vez cada problema, del conjunto de
problemas, viene representado por un conjunto de parmetros: P = <p1, ,pn>, Q = <q1, ,qn>,
El algoritmo recursivo busca la solucin para un problema dado tomando los parmetros que lo representan, es
decir, p1 de tipo T1, , pn de tipo Tn.
En el esquema recursivo aparecen las siguientes funciones y variables:
d(p1,,pn): Es una funcin lgica especfica del dominio de conjunto de problemas. Es decir, verdadero si el
problema pertenece al conjunto de problemas. Se recoge en un aserto el hecho de que el problema sea uno de
los considerados.
t(p1,,pn): Es una funcin lgica especfica del dominio de conjunto de problemas. Es decir, verdadero si el
problema pertenece al conjunto de problemas. Cada sub-problema debe ser de un tamao inferior al problema
de partida. Esto se recoge en un aserto del esquema.
b1(p1,,pn): Es una funcin lgica que devuelve verdadero si el problema es un caso base. En general podramos
tener varios casos base: b1(p1,,pn), b2(p1,,pn),
sb1(p1,,pn): Es una funcin que devuelve un valor de tipo T que es la solucin del caso base 1. Igualmente
podramos tener sb2(p1,,pn),
sp1(p1,,pn): Es una funcin que calcula los parmetros del primer sub-problema al que se reduce el problema
original. En el esquema hemos escrito <q1,,qn> = sp1(p1,,pn). Donde <q1,,qn> son los parmetros del primer
sub-problema que, en general, pueden ser varios. La forma de implementar esto es con una secuencia de
expresiones para calcular cada uno de los parmetros: q1 = e11(p1,,pn), , qn = e1n(p1,,pn). En general, en una
definicin recursiva, puede haber varios sub-problemas.
rp1, rps,: Son variables de tipo T que guardan la solucin del sub-problema 1, 2, . r es una variable de tipo T
que guarda la solucin del problema. En aquellos casos en que el tipo T sea void no habr return en el
esquema. La solucin ser devuelta en un parmetro de entrada-salida.
c(p1,,pn,rp1,): Es una funcin, que llamaremos funcin de combinacin, que obtiene el resultado combinando
los parmetros del problema con el resultado de los sub-problemas (resultados de las llamadas recursivas).
Debe tenerse en cuenta las siguientes interpretaciones, donde es el operador de asignacin paralela:
t([x1, x2, , xk]) < t(x) t(x1) < t(x2) < t(x) t(xk) < t(x)
f([x1, x2, , xk]) [f(x1), f(x2), , f(xk)]
s = f(x) [s1, s2, , sk] f([x1, x2, , xk]) s1 = f(x1); s2 = f(x2); ; sk = f(xk)
y = sp(x) [x1, x2, , xk] [sp1(x), sp2(x), , spk(x)]) x1 = sp(x); x2 = sp(x); ; xk = sp(x)
13
Veamos cmo en el ejemplo del factorial podemos identificar cada una de estas funciones:
int factorial(int n) {
int r;
assert(n >= 0);
if (n == 0) {
r = 1;
} else {
r = n * factorial(n - 1);
}
return r;
}
=
=
=
=
=
=
n>=0
n
n==0
1
n-1
n*r1
=
=
=
=
=
=
Para comprender algunas de las funciones anteriores podemos escribir la llamada recursiva (r = mcd(b, a%b);)
en la forma:
a1 = b;
b1 = a%b;
rp1 = mcd(a1, b1);
r = rp1;
14
=0
=1
>1
Si nos fijamos en el conjunto de operaciones que componen el proceso iterativo, observamos que debe obtener
resultados que ya han sido calculados previamente. Por lo que se puede optimizar el proceso almacenando en
una estructura de datos (como un mapa o diccionario) aquellos resultados parciales que va obteniendo y
recuperarlos en caso de tener que volverlos a calcular.
Para evitar estas repeticiones diseamos un nuevo algoritmo recursivo que llamaremos Divide y Vencers con
Memoria.
De forma general es necesario una variable memo, de tipo HashMap, que en Java tiene los mtodos:
Boolean memo.containsKey(x):
R r = memo.get(x):
R f1(E x, Map<E,R> m) {
R r;
R s;
E y;
if(contains(m,x)){
r = get(m,x);
} else if(b(x)){
r = sb(x);
put(m,x,r);
}else{
y = sp(x);
s = f1(z,m);
r = c(x,s);
put(m,x,r);
}
return r;
}
15
import java.util.HashMap;
public class SerieDeFibonacci {
private static HashMap<Integer, Long> memo = new HashMap<Integer, Long>();
public static Long fibonacci(Integer n) {
Long r;
memo.put(0, 0L);
memo.put(1, 1L);
if (memo.containsKey(n)) {
r = memo.get(n);
} else {
r = fibonacci(n - 1) + fibonacci(n - 2);
}
memo.put(n, r);
return r;
}
public static void main(String[] args) {
Integer n = new Integer(args[0]);
System.out.println(fibonacci(n));
}
}
Estado: Conjunto de valores concretos para los parmetros que definen los problemas considerados.
Estado inicial: Estado que representa el problema inicial.
Estado final: Estado que representa al problema final.
Transicin: Funcin que calcula un nuevo estado a partir de otro.
16
Es importante en la recursividad lineal final que la solucin del problema sea la misma que la del caso base
puesto que cada problema tiene la misma solucin que el sub-problema al que se reduce.
Dada una definicin recursiva final:
() = (())
() = {
()
(())
()
! ()
Pero, un algoritmo no final, se podra transformar en otro final para as poder transformarlo a iterativo?
Una solucin puede ser mediante la generalizacin del problema, aadiendo parmetros acumuladores.
()
(, (()))
()
! ()
(, )
((), (, ))
()
! ()
La transformacin de recursividad mltiple a iterativo consiste en generalizar el problema incorporando los subproblemas necesarios y sus soluciones calculadas. La iteracin empieza por los casos base y acaba en el problema
original.
17
Con esto se consigue la correccin parcial, para la correccin total, los parmetros de las llamadas recursivas
se deben acercar al caso base.
Para la correccin parcial del caso del factorial se debe demostrar que factorial(n) realiza n!
-
Por hiptesis inductiva, factorial(n-1) devuelve (n-1)! , por lo que factorial(n) devuelve n*(n-1)!,
que en realidad es n!
Truco: Pasos para transformar una funcin recursiva no final en otra s final.
1
=0
( 1) 0
=0
( 1, ) 0
18
SOLUCIN:
Algoritmo No Final:
(, ) = { 0
1 + ( 1, )
<
=
>
Algoritmo Final:
(, , ) = {
( 1, , + 1)
<
=
>
Iterativo:
funcin entero edad (entero anoAct, entero anoNac)
entero acu = 0
mientras (anoAct != anoNac) hacer
acu = acu + 1
anoAct = anoAct - 1
fin-mientras
retornar acu
fin-funcin
19
#include <stdio.h>
#include <assert.h>
int edad_N(int anoAct, int anoNac) {
int res = 0;
if (anoAct == anoNac) {
res = 0;
} else {
res = 1 + edad_N(anoAct, anoNac + 1);
}
return res;
}
int edad_F(int anoAct, int anoNac, int acu) {
int res = 0;
if (anoAct == anoNac) {
res = acu;
} else {
res = edad_F(anoAct, anoNac + 1, acu + 1);
}
return res;
}
int edad_I(int anoAct, int anoNac) {
int res = 0;
while (anoAct != anoNac) {
res = res + 1;
anoNac = anoNac + 1;
}
return res;
}
int metodo() {
int opc;
printf("\nTIPO DE ALGORITMO");
printf("\n
1 - Recursividad NO FINAL");
printf("\n
2 - Recursividad FINAL");
printf("\n
3 - Iterativo");
printf("\nOpc: ");
scanf("%d", &opc);
printf("\n");
return opc;
}
int main() {
int anoAct;
int anoNac;
int r;
printf("\nEDAD DE UNA PERSONA\n\n");
printf("Ano Actual
: ");
scanf("%d", &anoAct);
printf("Ano Nacimiento: ");
scanf("%d", &anoNac);
switch (metodo()) {
case 1: r = edad_N(anoAct, anoNac);
case 2: r = edad_F(anoAct, anoNac, 0);
case 3: r = edad_I(anoAct, anoNac);
}
printf("Esa persona tiene %d anios.\n", r);
return 0;
}
20
break;
break;
break;
=
<
(, ) = { (, )
(, %) >
SOLUCIN:
Algoritmo No Final:
La definicin recursiva indicada en el enunciado es Final, por tanto no existe No Final.
Algoritmo Final:
(, ) = { (, )
(, %)
=
<
>
Algoritmo Iterativo:
funcin entero mcd (entero a, entero b)
entero res
entero tmp
mientras (b != 0) hacer
tmp = a
a=b
si (a < b) entonces
b = tmp
sino
b = tmp % b
fin-si
fin-mientras
res = a
retornar res
fin-funcin
21
#include <stdio.h>
int mcd_F(int a, int b) {
int res;
if (b == 0) {
res = a;
} else {
if (a < b) {
res = mcd_F(b, a);
} else {
res = mcd_F(b, a % b);
}
}
return res;
}
int mcd_I(int a, int b) {
int res;
int tmp;
while (b != 0) {
tmp = a;
a = b;
if (a < b) {
b = tmp;
} else {
b = tmp % b;
}
}
res = a;
return res;
}
int metodo() {
int opc;
printf("\nTIPO DE ALGORITMO");
printf("\n
1 - Recursividad NO FINAL (NO SOPORTADO)");
printf("\n
2 - Recursividad FINAL");
printf("\n
3 - Iterativo");
printf("\nOpc: ");
scanf("%d", &opc);
printf("\n");
return opc;
}
int main() {
int a, b;
int r;
printf("\nALGORITMO DE EUCLIDES: MAXIMO COMUN DIVISOR (mcd)\n\n");
printf("\nEjemplo: mcd(273,2366) = 91\n\n");
printf("Introduzca el primer numero: ");
scanf("%d", &a);
printf("Introduzca el segundo numero: ");
scanf("%d", &b);
switch (metodo()) {
case 1:
break;
case 2: r = mcd_F(a, b); break;
case 3: r = mcd_I(a, b); break;
}
printf("El maximo comun divisor de %d y %d es %d \n", a, b, r);
return 0;
}
22
SOLUCIN:
Algoritmo No Final:
() = { 1
( 1)
<0
=0
>0
Algoritmo Final:
(, ) = {
( 1, )
<0
=0
>0
Algoritmo Iterativo:
funcin entero factorial (entero n)
entero acu = 1
mientras (n != 0) hacer
acu = acu * n
n=n-1
fin-mientras
retornar acu
fin-funcin
23
#include <stdio.h>
int factorial_N(int n) {
int res = 1;
if (n == 0) {
res = 1;
} else {
res = n * factorial_N(n - 1);
}
return res;
}
int factorial_F(int n, int acu) {
int res = 1;
if (n == 0) {
res = acu;
} else {
res = res * factorial_F(n - 1, n * acu);
}
return res;
}
int factorial_I(int n) {
int res = 1;
while (n != 0) {
res = res * n;
n--;
}
return res;
}
int metodo() {
int opc;
printf("\nTIPO DE ALGORITMO");
printf("\n
1 - Recursividad NO FINAL");
printf("\n
2 - Recursividad FINAL");
printf("\n
3 - Iterativo");
printf("\nOpc: ");
scanf("%d", &opc);
printf("\n");
return opc;
}
int main() {
int n;
int r;
printf("\nFACTORIAL DE N\n\n");
printf("Introduzca un valor para n: ");
scanf("%d", &n);
switch (metodo()) {
case 1: r = factorial_N(n);
break;
case 2: r = factorial_F(n, 1); break;
case 3: r = factorial_I(n);
break;
}
printf("El factorial de %d, \(%d!) es %d \n", n, n, r);
return 0;
}
24
1.10.4 Potencia.
Dada una base (a) y un exponente (n), calcular la potencia de an.
SOLUCIN:
Algoritmo No Final:
(, ) = {
1
(, 1)
=0
>0
Algoritmo Final:
(, , ) = {
(, 1, )
=0
>0
Algoritmo Iterativo:
funcin entero potencia (entero a, entero n)
entero acu = 1
mientras (n > 0) hacer
acu = acu * a
n=n-1
fin-mientras
retornar acu
fin-funcin
25
#include <stdio.h>
int potencia_N (int a, int n) {
int res;
if (n == 0) {
res = 1;
} else {
res = a * potencia_N(a, n-1);
}
return res;
}
int potencia_F(int a, int n, int acu) {
int res = 1;
if (n==0) {
res = acu;
} else {
res = potencia_F(a, n-1, a*acu);
}
return res;
}
int potencia_I(int a, int n) {
int res = 1;
while (n>0){
res = res * a;
n--;
}
return res;
}
int metodo() {
int opc;
printf("\nTIPO DE ALGORITMO");
printf("\n
1 - Recursividad NO FINAL");
printf("\n
2 - Recursividad FINAL");
printf("\n
3 - Iterativo");
printf("\nOpc: ");
scanf("%d", &opc);
printf("\n");
return opc;
}
int main() {
int a, n;
int r;
printf("\nPOTENCIA DE A^N\n\n");
printf("Introduzca la base: ");
scanf("%d", &a);
printf("Introduzca el exponente: ");
scanf("%d", &n);
switch (metodo()) {
case 1: r = potencia_N(a, n);
break;
case 2: r = potencia_F(a, n, 1); break;
case 3: r = potencia_I(a, n);
break;
}
printf("El resultado para la entrada %d^%d es %d", a, n, r);
return 0;
}
26
8
9
SOLUCIN:
Algoritmo No Final:
(, ) =
( (, ))
2
=1
1 (%2) = 0
2
1
( (,
))
2
{
1 (%2) = 1
Algoritmo Final:
(, , 2 , )
2
(, , , ) =
1 2
(,
, , )
{
2
=0
0 (%2) = 0
0 (%2) = 1
: (, , , 1)
Algoritmo Iterativo:
funcin entero potencia (entero a, entero n)
entero r = a
entero acu = 1
mientras (n > 0) hacer
si (n % 2 = 1) entonces
acu = acu * r
fin-si
r=r*r
n=n/2
fin-mientras
retornar acu
fin-funcin
27
#include <stdio.h>
#include <math.h>
int potencia_N(int a, int n) {
int res;
if (n == 1) {
res = a;
} else {
if (n % 2 == 0) {
res = pow(potencia_N(a, n / 2), 2);
} else {
res = pow(potencia_N(a, (n - 1) / 2), 2) * a;
}
}
return res;
}
int potencia_F(int a, int n, int r, int acu) {
int res;
if (n == 0) {
res = acu;
} else {
if (n % 2 == 0) {
res = potencia_F(a, n / 2, r * r, acu);
} else {
res = potencia_F(a, (n 1) / 2, r * r, acu * r);
}
}
return res;
}
int potencia_I(int a, int n) {
int r = a;
int acu = 1;
while (n > 0) {
if (n % 2 == 1) {
acu = acu * r;
}
r = r * r;
n = n / 2;
}
return acu;
}
int metodo() {
int opc;
printf("\nTIPO DE ALGORITMO");
printf("\n
1 - Recursividad NO FINAL");
printf("\n
2 - Recursividad FINAL");
printf("\n
3 - Iterativo");
printf("\nOpc: ");
scanf("%d", &opc);
printf("\n");
return opc;
}
int main() {
int a, n;
int r;
printf("\nPOTENCIA DE A^N SEGN PARIDAD DEL EXPONENTE\n\n");
printf("Introduzca la base: ");
scanf("%d", &a);
printf("Introduzca el exponente: ");
scanf("%d", &n);
switch (metodo()) {
case 1: r = potencia_N(a, n);
break;
case 2: r = potencia_F(a, n, a, 1); break;
case 3: r = potencia_I(a, n);
break;
}
printf("El resultado para la entrada %d^%d es %d", a, n, r);
return 0;
}
28
sumaDig(n) = 2 + 3 + 4 + 7 = 16
SOLUCIN:
Algoritmo No Final:
() = {
0
(\10) + %10
=0
>0
Algoritmo Final:
() = {
(\10, + (%10))
=0
>0
Algoritmo Iterativo:
funcin entero sumaDig (entero n)
entero acu = 0
mientras (n != 0) hacer
acu = acu + (n % 10)
n = n \ 10
fin-mientras
retornar acu
fin-funcin
29
#include <stdio.h>
int sumaDig_N(int n) {
int res;
if (n == 0) {
res = 0;
} else {
res = sumaDig_N(n / 10) + (n % 10);
}
return res;
}
int sumaDig_F(int n, int acu) {
int res;
if (n == 0) {
res = acu;
} else {
res = sumaDig_F(n / 10, (n % 10) + acu);
}
return res;
}
int sumaDig_I(int n) {
int res = 0;
while (n != 0) {
res = res + (n % 10);
n = n / 10;
}
return res;
}
int metodo() {
int opc;
printf("\nTIPO DE ALGORITMO");
printf("\n
1 - Recursividad NO FINAL");
printf("\n
2 - Recursividad FINAL");
printf("\n
3 - Iterativo");
printf("\nOpc: ");
scanf("%d", &opc);
printf("\n");
return opc;
}
int main() {
int n;
int r;
printf("\nSUMAR LOS DIGITOS DE UN NUMERO\n\n");
printf("Introduzca el numero: ");
scanf("%d", &n);
switch (metodo()) {
case 1: r = sumaDig_N(n);
break;
case 2: r = sumaDig_F(n, 0);
break;
case 3: r = sumaDig_I(n);
break;
}
printf("La suma de los digitos del numero %d es %d\n", n, r);
return 0;
}
Nota: En lenguaje C, si la variable n es entera, n/10 devuelve la divisin entera, lo que anotamos como n\10.
30
( 1) + ( 2)
01
>1
SOLUCIN:
Algoritmo No Final:
() = {
( 1) + ( 2)
1
>1
Algoritmo Final:
La definicin recursiva indicada no existe como Final.
Algoritmo Iterativo:
funcin entero fib (entero n)
entero res
entero n1, n0
entero i
si (n <= 1) entonces
res = n
sino
n1 = 0
n0 = 1
i =2
mientras (i < n) hacer
res = n1 + n0
n1 = n0
n0 = res
i=i+1
fin-mientras
fin-si
retornar res
fin-funcin
31
#include <stdio.h>
int fib_F(int n) {
int res;
if (n <= 1) {
res = n;
} else {
res = fib_F(n - 1) + fib_F(n - 2);
}
return res;
}
int fib_I(int n) {
int res;
int n1, n0;
int i;
if (n <= 1) {
res = n;
} else {
n1 = 0;
n0 = 1;
i = 2;
while (i < n) {
res = n1 + n0;
n1 = n0;
n0 = res;
i = i + 1;
}
}
return res;
}
int metodo() {
int opc;
printf("\nTIPO DE ALGORITMO");
printf("\n
1 - Recursividad NO FINAL");
printf("\n
2 - Recursividad FINAL (NO SOPORTADO)");
printf("\n
3 - Iterativo");
printf("\nOpc: ");
scanf("%d", &opc);
printf("\n");
return opc;
}
int main() {
int n;
int r;
printf("\nCALCULO DE LA SERIE DE FIBONACCI\n\n");
printf("\nSerie : 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377...");
printf("\nNumero: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ...\n\n");
printf("Introduzca el numero de Fibonacci que quiere obtener: ");
scanf("%d", &n);
switch (metodo()) {
case 1: r = fib_F(n-1); break;
case 2:
break;
case 3: r = fib_I(n);
break;
}
printf("El numero de la serie de Fibonacci %d es el %d \n", n, r);
return 0;
}
32
Calcule el valor de las combinaciones sin repeticin de n objetos tomados de k en k, esto es: ( )
SOLUCIN:
Algoritmo No Final:
0
(, ) = { 1
( 1, ) + ( 1, 1)
>
= =0
. . .
Algoritmo Final:
La definicin recursiva indicada no existe como Final.
Algoritmo Iterativo:
funcin entero CombSR (entero n, entero k)
entero acu = 1
mientras (k < n) hacer
acu = acu + (k) + (k-1)
n=n-1
fin-mientras
retornar acu
fin-funcin
33
#include <stdio.h>
int combSR_F(int n, int k) {
int res;
if (k > n) {
res = 0;
} else {
if ((k == 0) || (k == n)) {
res = 1;
} else {
res = combSR_F(n - 1, k) + combSR_F(n - 1, k - 1);
}
}
return res;
}
int combSR_I(int n, int k) {
int acu = 1;
while (k < n) {
acu = acu + (k) + (k - 1);
n--;
}
return acu;
}
int metodo() {
int opc;
printf("\nTIPO DE ALGORITMO");
printf("\n
1 - Recursividad NO FINAL");
printf("\n
2 - Recursividad FINAL (NO SOPORTADO)");
printf("\n
3 - Iterativo");
printf("\nOpc: ");
scanf("%d", &opc);
printf("\n");
return opc;
}
int main() {
int n, k;
int r;
printf("\nCOMBINACIONES SIN REPETICION DE N ELEMENTOS TOMADOS DE K EN k.\n\n");
printf("\nEjemplo: Elementos=16, Grupos=3, resultado = 560\n\n");
printf("Introduzca los elementos: ");
scanf("%d", &n);
printf("Introduzca los grupos: ");
scanf("%d", &k);
switch (metodo()) {
case 1: r = combSR_F(n, k); break;
case 2:
break;
case 3: r = combSR_I(n, k); break;
}
printf("El resultado para la entrada Csr(%d ^ %d) es %d \n",n, k, r);
return 0;
}
34
SOLUCIN:
Algoritmo No Final:
(, , ) = {
= ; 0
1 + (( ), , )
>
. . .
Algoritmo Final:
(, , , ) = {
= ;
(( ), , , ( + 1))
>
. . .
Algoritmo Iterativo:
funcin entero division (entero a, entero b, entero s)
entero res
entero acu = 0
mientras (a > b) hacer
a = (a b)
acu = acu + 1
fin-mientras
s=a
res = acu
retornar res
fin-funcin
35
#include <stdio.h>
int division_N(int a, int b, int * s) {
int res;
if (b > a) {
*s = a;
res = 0;
} else {
res = 1 + division_N((a - b), b, s);
}
return res;
}
int division_F(int a, int b, int * s, int acu) {
int res = 0;
if (b > a) {
*s = a;
res = acu;
} else {
res = division_F((a - b), b, s, (acu + 1));
}
return res;
}
int division_I(int a, int b, int * s) {
int res;
int acu = 0;
while (a > b) {
a = (a - b);
acu++;
}
*s = a;
res = acu;
return res;
}
int metodo() {
int opc;
printf("\nTIPO DE ALGORITMO");
printf("\n
1 - Recursividad NO FINAL");
printf("\n
2 - Recursividad FINAL");
printf("\n
3 - Iterativo");
printf("\nOpc: ");
scanf("%d", &opc);
printf("\n");
return opc;
}
int main() {
int a, b;
int s = 0;
int r;
printf("\nDIVISION ENTERA POR RESTAS SUCESIVAS.\n\n");
printf("Introduzca el numerador: ");
scanf("%d", &a);
printf("Introduzca el denominador: ");
scanf("%d", &b);
switch (metodo()) {
case 1: r = division_N(a, b, &s);
break;
case 2: r = division_F(a, b, &s, 0);
break;
case 3: r = division_I(a, b, &s);
break;
}
printf("%d / %d = %d (resto = %d) \n", a, b, r, s);
return 0;
}
36
1.10.10
Calcular la suma de todos los elementos de un vector conocido, as como tambin su tamao.
SOLUCIN:
Algoritmo No Final:
() = {
0
[] + ( 1)
<0
0
Algoritmo Final:
(, ) = {
( 1, [] + )
<0
0
Algoritmo Iterativo:
funcin entero suma (entero n)
entero acu = 0
mientras (n >= 0) hacer
acu = acu + vector[n]
n=n-1
fin-mientras
retornar acu
fin-funcin
37
#include <stdio.h>
int vector[10] = { 2, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
const int TAM = 10;
int suma_N(int n) {
int res;
if (n < 0) {
res = 0;
} else {
res = vector[n] + suma_N(n - 1);
}
return res;
}
int suma_F(int n, int acu) {
int res = 0;
if (n < 0) {
res = acu;
} else {
res = res + suma_F(n - 1, vector[n] + acu);
}
return res;
}
int suma_I(int n) {
int res = 0;
while (n >= 0) {
res = res + vector[n];
n--;
}
return res;
}
int metodo() {
int opc;
printf("\nTIPO DE ALGORITMO");
printf("\n
1 - Recursividad NO FINAL");
printf("\n
2 - Recursividad FINAL");
printf("\n
3 - Iterativo");
printf("\nOpc: ");
scanf("%d", &opc);
printf("\n");
return opc;
}
int main() {
int i;
int r;
printf("\nSUMA DE UN VECTOR DE 10 ELEMENTOS\n\n");
switch (metodo()) {
case 1: r = suma_N(TAM - 1);
break;
case 2: r = suma_F(TAM - 1, 0); break;
case 3: r = suma_I(TAM - 1);
break;
}
printf("La suma del vector ");
for (i=0; i<TAM ; i++) printf("%d ", vector[i]);
printf("es %d \n", r);
return 0;
}
38
1.10.11
Determinar si todos los elementos de un vector, de tamao conocido, son nmeros pares.
SOLUCIN:
Algoritmo No Final:
La definicin recursiva indicada no existe como No Final.
Algoritmo Final:
[0]%2
(, ) = { 1
(, 1)
= 0
> 0 []%2 = 1
> 0 []%2 1
Algoritmo Iterativo:
funcin entero paridad (entero[] vector, entero tam)
entero res = 0
// 0 = cierto, 1 = falso
mientras (tam >= 0 y res = 0) hacer
res = vector[tam-1] % 2
tam = tam - 1
fin-mientras
retornar res
fin-funcin
39
#include <stdio.h>
int vector[5] = { 2, 4, 6, 8, 1 };
const int TAM = 5;
int paridad_N(int * dt, int
int res = 0;
if (n == 0) {
res = dt[0]
} else {
if (dt[n] %
res
} else {
res
}
n) {
// true
% 2;
2 == 1) {
= 1;// false
= paridad_N(dt, n - 1);
}
return res;
}
int paridad_I(int * dt, int n) {
int res = 0;
// true
while (n >= 0 && res == 0) {
res = dt[n - 1] % 2;
n--;
}
return res;
}
int metodo() {
int opc;
printf("\nTIPO DE ALGORITMO");
printf("\n
1 - Recursividad NO FINAL (NO SOPORTADO)");
printf("\n
2 - Recursividad FINAL");
printf("\n
3 - Iterativo");
printf("\nOpc: ");
scanf("%d", &opc);
printf("\n");
return opc;
}
int main() {
int i;
int r;
printf("\nPARIDAD DE UN VECTOR DE 5 ELEMENTOS\n\n");
switch (metodo()) {
case 1:
break;
case 2: r = paridad_N(vector, TAM);
break;
case 3: r = paridad_I(vector, TAM);
break;
}
printf("Son pares todos estos elementos: ");
for (i=0; i<TAM ; i++) printf("%d ", vector[i]);
if (r) printf("%s\n", "pues No."); else printf("%s\n", "pues Si.");
return 0;
}
40
1.10.12
SOLUCIN:
Algoritmo No Final:
[]
max([ 1], (, 1))
= 0
> 0
(, 1, max(, [ 1]) )
= 0
> 0
(, ) = {
max(, ) = {
>
. . .
Algoritmo Final:
(, , ) = {
Algoritmo Iterativo:
funcin entero mayorValor (entero[] vector, entero tam)
entero res = vector[tam-1]
mientras (tam >= 0) hacer
res = max (res, vector[tam-1])
tam = tam - 1
fin-mientras
retornar res
fin-funcin
41
#include <stdio.h>
int vector[5] = { 2, 4, 36, 1, -8 };
const int TAM = 5;
int max(int a, int b) {
if (a < b)
return b;
else
return a;
}
int mayorValor_N(int * dt, int n) {
int res = 0;
if (n == 0) {
res = dt[n];
} else {
res = max(dt[n], mayorValor_N(dt, n - 1));
}
return res;
}
int mayorValor_F(int * dt, int n, int elto) {
int res = 0;
if (n == 0) {
res = elto;
} else {
res = mayorValor_F(dt, n - 1, max(dt[n - 1], elto));
}
return res;
}
int mayorValor_I(int * dt, int n) {
int res = dt[n - 1];
while (n >= 0) {
res = max(res, dt[n]);
n--;
}
return res;
}
int metodo() {
int opc;
printf("\nTIPO DE ALGORITMO");
printf("\n
1 - Recursividad NO FINAL");
printf("\n
2 - Recursividad FINAL");
printf("\n
3 - Iterativo");
printf("\nOpc: ");
scanf("%d", &opc);
printf("\n");
return opc;
}
int main() {
int i;
int r;
printf("\nOBTENER EL VALOR MAS GRANDE DE UN VECTOR\n\n");
switch (metodo()) {
case 1: r = mayorValor_N(vector, TAM);
break;
case 2: r = mayorValor_F(vector, TAM, vector[0]);
break;
case 3: r = mayorValor_I(vector, TAM);
break;
}
printf("Vector: "); for (i=0; i<TAM ; i++) printf("%d ", vector[i]);
printf("El valor mas grande es: %d\n", r);
return 0;
}
42
1.10.13
SOLUCIN:
Algoritmo No Final:
(, ) = {
min(, , ) = {
0
min(, , (, 1))
= 0
> 0
[] < []
. . .
Algoritmo Final:
(, , ) = {
(, 1, min(, , 1))
= 0
> 0
Algoritmo Iterativo:
funcin entero posMenor (entero[] vector, entero tam)
entero res = 0
mientras (tam > 0) hacer
res = min (vector, res, tam-1)
tam = tam - 1
fin-mientras
retornar res
fin-funcin
funcin entero min (entero[] vector, entero a, entero b)
entero res
si (vector[a] < vector[b]) entonces
res = a
sino
res = b
fin-si
retornar res
fin-funcin
43
#include <stdio.h>
int vector[5] = { 3, 8, 9, 1, 2 };
const int TAM = 5;
int min(int * dt, int a, int b) {
if (dt[a] < dt[b])
return a;
else
return b;
}
int posMenor_N(int * dt, int n) {
int res = 0;
if (n == 0) {
res = 0;
} else {
res = min(dt, n, posMenor_N(dt, n - 1));
}
return res;
}
int posMenor_F(int * dt, int n, int elto) {
int res = 0;
if (n == 0) {
res = elto;
} else {
res = posMenor_F(dt, n - 1, min(dt, n, elto));
}
return res;
}
int posMenor_I(int * dt, int n) {
int res = 0;
while (n > 0) {
res = min(dt, res, n - 1);
n--;
}
return res;
}
int metodo() {
int opc;
printf("\nTIPO DE ALGORITMO");
printf("\n
1 - Recursividad NO FINAL");
printf("\n
2 - Recursividad FINAL");
printf("\n
3 - Iterativo");
printf("\nOpc: ");
scanf("%d", &opc);
printf("\n");
return opc;
}
int main() {
int i;
int r;
printf("\nOBTENER LA POSICION DEL MENOR VALOR DE UN VECTOR\n\n");
switch (metodo()) {
case 1: r = posMenor_N(vector, TAM);
break;
case 2: r = posMenor_F(vector, TAM, vector[0]); break;
case 3: r = posMenor_I(vector, TAM);
break;
}
printf("\nVector: "); for (i=0; i<TAM ; i++) printf("%d ", vector[i]);
printf("\nEl menor valor esta en la posicion: %d (empezando desde 0).\n", r);
return 0;
}
44
1.10.14
Calcular el producto escalar de dos vectores, de iguales tamaos conocidos, declarados globlamente.
SOLUCIN:
Algoritmo No Final:
() = {
0
< 0
(1 [] 2 []) + [ 1] 0
Algoritmo Final:
(, ) = {
( 1, (1 [] 2 []) + )
< 0
0
Algoritmo Iterativo:
funcin entero prodEsc (entero pos)
entero acu = 0
mientras (pos >= 0) hacer
acu = acu + (v1[n] * v2[n])
pos = pos - 1
fin-mientras
retornar acu
fin-funcin
45
#include <stdio.h>
int v1[3] = { 2, 5, 3 };
int v2[3] = { 3, 3, 4 };
const int TAM = 3;
int prodEsc_N(int n) {
int res = 0;
if (n < 0) {
res = 0;
} else {
res = (v1[n] * v2[n]) + prodEsc_N(n - 1);
}
return res;
}
int prodEsc_F(int n, int acu) {
int res = 0;
if (n < 0) {
res = acu;
} else {
res = prodEsc_F(n - 1, (v1[n] * v2[n]) + acu);
}
return res;
}
int prodEsc_I(int n) {
int res = 0;
while (n >= 0) {
res = res + v1[n] * v2[n];
n--;
}
return res;
}
int metodo() {
int opc;
printf("\nTIPO DE ALGORITMO");
printf("\n
1 - Recursividad NO FINAL");
printf("\n
2 - Recursividad FINAL");
printf("\n
3 - Iterativo");
printf("\nOpc: ");
scanf("%d", &opc);
printf("\n");
return opc;
}
int main() {
int i;
int r;
printf("\nCALCULAR EL PRODUCTO ESCALAR DE 2 VECTORES\n\n");
switch (metodo()) {
case 1: r = prodEsc_N(TAM-1);
break;
case 2: r = prodEsc_F(TAM-1, 0);
break;
case 3: r = prodEsc_I(TAM-1);
break;
}
printf("\nVector v1: "); for (i=0; i<TAM ; i++) printf("%d ", v1[i]);
printf("\nVector v2: "); for (i=0; i<TAM ; i++) printf("%d ", v2[i]);
printf("\nEl producto escalar de v1 x v2 es: %d\n", r);
return 0;
}
46
1.10.15
2 ( 1) + ( 2) 2 ( 4)
<4
. . .
SOLUCIN:
Algoritmo No Final:
La definicin recursiva indicada ya es Final.
Algoritmo Final:
La definicin recursiva indicada no existe como Final.
Algoritmo Iterativo:
funcin entero func (entero n)
entero res
entero n0, n1, n2, n3
entero i
si (n < 4) entonces
res = n
sino
n3 = 3
n2 = 2
n1 = 1
n0 = 0
i =4
mientras (i <= n) hacer
res = (2 * n3) + (n2) (2 * n0)
n3 = n2
n2 = n1
n1 = n0
n0 = res
i=i+1
fin-mientras
fin-si
retornar res
fin-funcin
47
#include <stdio.h>
int func_N(int n) {
int res;
if (n < 4) {
res = n;
} else {
res = (2 * func_N(n - 1)) + func_N(n - 2) - (2 * func_N(n - 4));
}
return res;
}
int func_I(int n) {
int res = 0;
int n0, n1, n2, n3;
int i;
if (n < 4) {
res = n;
} else {
n3 = 3;
n2 = 2;
n1 = 1;
n0 = 0;
i = 4;
while (i <= n) {
res = (2 * n3) + n2 (2 * n0);
n3 = n2;
n2 = n1;
n1 = n0;
n0 = res;
i = i + 1;
}
}
return res;
}
int metodo() {
int opc;
printf("\nTIPO DE ALGORITMO");
printf("\n
1 - Recursividad NO FINAL");
printf("\n
2 - Recursividad FINAL (NO SOPORTADO)");
printf("\n
3 - Iterativo");
printf("\nOpc: ");
scanf("%d", &opc);
printf("\n");
return opc;
}
int main() {
int n;
int r;
printf("\nUNA FUNCION DADA\n\n");
printf("Introduzca valor de n: ");
scanf("%d", &n);
switch (metodo()) {
case 1: r = func_N(n); break;
case 2:
break;
case 3: r = func_I(n); break;
}
printf("El resultado para el valor n=%d es %d", n, r);
return 0;
}
48
1.10.16
=0
=1
=2
3
SOLUCIN:
Algoritmo No Final:
La definicin recursiva indicada ya es Final.
Algoritmo Final:
La definicin recursiva indicada no existe como Final.
Algoritmo Iterativo:
funcin entero func_I (entero n)
entero res
entero n0, n1, n2
entero i
n2 = 1
n1 = 1
n0 = 2
si (n < 3) entonces
segn (n) {
caso 0 entonces res = n0
caso 1 entonces res = n1
caso 2 entonces res = n2
fin-segn
sino
i=3
mientras (i <= n) hacer
res = (2 * n2) + (3 *n1) (n0)
n2 = n1
n1 = n0
n0 = res
i =i+1
fin-mientras
fin-si
retornar res
fin-funcin
49
#include <stdio.h>
int func_N(int n) {
int res;
if (n == 0)
if (n == 1)
if (n == 2)
if (n >= 3)
res
}
return res;
}
{
{
{
{
= 2
res = 2;}
res = 1;}
res = 1;}
* func_N(n - 1) + 3 * func_N(n - 2) - func_N(n - 3);
int func_I(int n) {
int res;
int n0, n1, n2;
int i;
n0 = 2;
n1 = 1;
n2 = 1;
if (n < 3) {
if (n ==
if (n ==
if (n ==
} else {
i = 3;
while (i
0) { res = n0; }
1) { res = n1; }
2) { res = n2; }
<= n) {
res = (2 * n2) + (3 * n1) (n0);
n0 = n1;
n1 = n2;
n2 = res;
i = i + 1;
}
}
return res;
}
int metodo() {
int opc;
printf("\nTIPO DE ALGORITMO");
printf("\n
1 - Recursividad NO FINAL");
printf("\n
2 - Recursividad FINAL (NO SOPORTADO)");
printf("\n
3 - Iterativo");
printf("\nOpc: ");
scanf("%d", &opc);
printf("\n");
return opc;
}
int main() {
int n;
int r;
printf("\nUNA FUNCION DADA\n\n");
printf("Introduzca valor de n: ");
scanf("%d", &n);
switch (metodo()) {
case 1: r = func_N(n); break;
case 2:
break;
case 3: r = func_I(n); break;
}
printf("El resultado para el valor n=%d es %d", n, r);
return 0;
}
50
1.10.17
Dado un nmero n, de tipo entero positivo, se desea conviertir dicho nmero en una cadena de caracteres.
SOLUCIN:
Algoritmo No Final:
La definicin recursiva indicada no existe como No Final.
2() = {
""
2(\10) + (%10)
=0
0
() = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}[]
Algoritmo Final:
2(, ) = {
2(\10, + (%10))
=0
0
Algoritmo Iterativo:
funcin cadena int2txt (entero n)
cadena txt =
mientras (n != 0) hacer
txt = (n%10) & txt
n = n \ 10
fin-mientras
retornar txt
fin-funcin
51
Implementcin en Java:
Implementcin en C:
#include <stdio.h>
char txt[10];
char * int2txt_F(int n, int pos, char * txt) {
static char digitos[] = "0123456789";
txt[pos] = digitos[n % 10];
if ((n / 10) == 0) {
return txt;
} else {
return int2txt_F(n / 10, pos + 1, txt);
}
}
char * int2txt_I(int n) {
static char digitos[] = "0123456789";
int pos = 0;
while (n != 0) {
txt[pos++] = digitos[n % 10];
n = n / 10;
}
return txt;
}
int main() {
int n;
printf("\nCONVERTIR UN NUMERO EN CADENA\n\n");
printf("Introduzca el numero: ");
scanf("%d", &n);
printf("\nAlgoritmo Recursivo. >%s<", int2txt_F(n, 0, txt));
printf("\nAlgoritmo Iterativo. >%s<", int2txt_I(n));
return 0;
}
52
1.10.18
Dada una cadena de caracteres cad, de tamao tam, obtener su cadena inversa.
SOLUCIN:
Algoritmo No Final:
(, , ) = {
[]
[] & (, , 1)
= 0
> 0
Variante:
(, , ) = {
[], [] [], []
<
Algoritmo Final:
(, , , ) = { [ 1] = []
}
(, , 1, )
= 1
. . .
#include <stdio.h>
char texto[] = "Hola, estamos probando la inversion de una cadena.";
char acu[] = "";
const int TAM = 50;
char * inverso(char * cad, int tam, int pos, char * acu) {
if (pos == -1) {
return acu;
} else {
acu[tam - pos - 1] = cad[pos];
return inverso(cad, tam, pos - 1, acu);
}
}
int main() {
printf("\nINVERTIR UNA CADENA\n\n");
printf("Cadena normal
: %s\n", texto);
printf("Cadena invertida: %s\n", inverso(texto, TAM, TAM-1, acu));
return 0;
}
53
1.10.19
Siguiendo el esquema de la figura, determine cuntos tringulos hay hasta alcanzar el nivel n.
Nivel
1
2
3
Tri
1
3
5
1
4
9
SOLUCIN:
() = {
() = {
1
2 + ( 1)
=1
>1
1
() + ( 1)
=1
>1
luego tenemos:
() = {
1
(2 1) + ( 1)
=1
>1
a tener solamente:
public int sumador(int n) {
return n == 1 ? 1 : ((2 * n - 1) + sumador(n - 1));
}
Algoritmo Final:
(, ) = {
( 1, (2 1) + )
=1
>1
Algoritmo Iterativo:
public int sumador(int n) {
int i = 1;
int acu = 0;
while (i <= n) {
acu += (2 * n - 1);
i--;
}
return acu;
}
54
()
()
De la misma manera definimos el concepto de mayor, equivalente, mayor igual y menor igual:
() > ( )
()
=
()
lim
()
<
()
() = () 0 < lim
()
<
()
() () 0 lim
() () 0 < lim
()
()
Es decir, dos funciones son equivalentes en el infinito cuando el lmite de su cociente es un nmero mayor que
cero y menor que infinito.
La relacin de equivalencia anterior define un conjunto de clases de equivalencia. Cada clase de equivalencia es
un Orden de Complejidad Exacto. Ms exactamente, el orden de complejidad exacto de una funcin g(n), que
representaremos por (g(n)), es el conjunto de funciones que son equivalentes a ella en el infinito, luego:
(()) = {() |
lim
()
()
= , 0 < < }
De entre todas las funciones que forman parte de un mismo orden de complejidad exacto escogemos una de
ellas como representante de la clase. As por ejemplo (g(n)) representa el conjunto de funciones equivalentes
a n en el infinito. Entre estas funciones se encuentran los polinomios de primer grado en n (funciones lineales).
55
El orden anterior < definido entre funciones se puede extender a los rdenes de complejidad. La relacin de
equivalencia entre funciones se convierte en relacin de igualdad entre rdenes de complejidad. Cuando quede
claro por el contexto usaremos < en lugar de < y usaremos = en vez de = . Igualmente definimos los operadores
binarios y entre rdenes de complejidad x e y de la siguiente forma:
(, ) = {
<
(, ) = {
>
As tambien, definimos los operadores binarios max y min que los usaremos indistintamente entre rdenes
de complejidad o entre funciones y, si debido al contexto no hay duda, los sustituiremos por max y min.
()
=1
()
En la definicin anterior f(n) y g(n) son del mismo orden de complejidad y k es denominado constante
multiplicativa. Es decir, el smbolo nos da el primer trmino del desarrollo en serie en el infinito.
orden constante
orden logartmico
orden lineal
orden cuasi-lineal
orden cuadrtico
orden polinmico (k = 2 (orden cuadrtico))
orden exponencial (k > 2)
orden factorial
Para hacernos una idea intuitiva de la jerarqua de rdenes de complejidad y las relaciones entre ellos veamos
la siguiente tabla. En ella se muestra, en primer lugar, el efecto, en el tiempo T(n), de una duplicacin de n, el
tamao del problema. Para ello escogiendo las constantes multiplicativas adecuadas se muestran los valores de
las T(n) para un valor de n = 200 siempre que para n = 100 el valor sea de 1s. En segundo lugar se muestra el
tamao del problema que puede ser resuelto en un tiempo t = 2s suponiendo que el tiempo necesitado para un
problema de tamao n = 100 es de 1s. La observacin ms importante es la gran diferencia entre los algoritmos
de orden exponencial o mayor y los de orden polinmico o menor. Al comparar dos algoritmos de distinto orden
hay que tener muy en cuenta las constantes multiplicativas.
K1
K2
K3
K4
K5
K6
56
T(n)
log(n)
n
n log(n)
n2
n3
2n
Efecto de duplicar el
tamao del problema
n = 100
n = 200
1s
115 s
1s
200 s
1s
230 s
1s
400 s
1s
800 s
1s
127 1030 s
Efecto de duplicar el
tiempo disponible
n = 100
t=2s
n = 100 n = 10000
n = 100 n = 200
n = 100 n = 178
n = 100 n = 141
n = 100 n = 126
n = 100 n = 101
lim
()
()
lim
()
()
<
>0
Para indicar que una funcin f(n) es de un orden de complejidad dado g(n) se indica indistintamente como
O(f(n))=g(n) o bien f(n) O(g(n)). Igualmente con las otras notaciones , o bien .
Algunas propiedades:
( () + ) = (())
( () + ) = (())
( () + ) = (())
(()) = (()) (())
(()) (())
(()) (())
() = (() + () (() + ())) = ()
(() ()) = (()) (())
(()) > 1 (() ()) > ()
(()) = 1 (() ()) = ()
()
(())
(()) 1 (
)>
()
(())
()
) < ()
()
()
(()) = 1 (
) = ()
()
(()) < 1 (
Donde ai y bi los son nmeros reales, con los bi todos distintos, y pi(n) polinomios de orden di en n. La ecuacin
se denomina homognea si los bi , di son todos iguales a cero.
La solucin de la ecuacin es:
() =
=1 =0
Donde los cij se calcularan a partir de las condiciones iniciales, ri son las l races distintas, cada una de
multiplicidad mi, de la denominada ecuacin caracterstica generalizada:
(0 + 1 1 + + ) ( 1 )1 +1 ( 2 )2+1 ( ) +1 = 0
57
(()) = ( ) = ( 1 )
=1 =0
1+5
2
= 1618 y 2 =
15
2
= 0618
1+5
2
) )
Casos particulares:
1er caso:
Un caso particular de este tipo de ecuaciones que tiene mucha utilidad es:
() = ( ) + ()
Cuyas races son c, a1/b . El resto de las races de (xb - a) = 0 son complejas.
() ( ) { > 1}
( )
() = { (+1 )
( )
>
=
<
() ( ) { = 1}
( )
() = { (+1 )
( )
>
=
<
2 caso:
Una variante del caso anterior es la recurrencia:
() = ( ) + ()
Siendo como antes g(n) un polinomio de grado d. Observando que (log n)p (log(n-b))p podemos ver
que la solucin tendr la forma h(n)(log n)p . Dnde h(n) verifica la ecuacin previa.
() ( ) { > 0}
( )
() { ( +1 )
( )
58
>
=
<
() ( ) { = 0}
( )
() { ( ())
( )
>
=
<
() = () + ( + 1) + + ()
=
De una forma ms general consideraremos los sumatorios donde el ndice toma valores ms generales que en el
caso anterior. Esto lo indicamos de la forma:
() = ((0)) + ((1)) + + (( ))
=,
=()
En esta notacin el ndice, ahora x, toma los valores proporcionados por la funcin h(i) cuando i toma los valores
0, 1, 2, . Asumimos que h(i) es una funcin montona creciente que cumple adems h(0) = a, h(in) = n. Siendo
in el valor que debe tomar i para que se cumpla la condicin anterior. Ejemplos concretos son cuando ha(i) = a +
ri, hg(i) = a + ri . En el primer caso x toma los valores de una progresin aritmtica con diferencia r. En el segundo,
los valores de una progresin geomtrica de razn r.
Alternativamente podemos considerar la funcin g(i) montona decreciente que cumple g(0) = n, g(in) = a.
Ejemplos concretos son cuando ga(i) = n - ri, gg(i) = nr - i . En el primer caso x toma los valores de una progresin
aritmtica con diferencia -r. En el segundo, los valores de una progresin geomtrica de razn 1/r.
=
=()
() = ((0)) + ((1)) + + (( ))
=
Por tanto, los dos sumatorios siguientes son iguales por ser equivalentes las correspondientes funciones que
generan los ndices:
=
=
= ()
() = ()
=,
= ()
=
= ()
() = ()
=,
= ()
Todo lo anterior podemos resumirlo al considerar que un sumatorio cuyos ndices toman el valor de una
secuencia aritmtica creciente tiene el mismo valor que otro cuyos valores siguen una secuencia aritmtica
decreciente con diferencia cambiada de signo. Igualmente ocurre en el caso de la secuencia geomtrica. Esta
idea puede generalizarse a otros patrones de generacin de ndices por lo que, de ahora en adelante,
consideraremos principalmente patrones montonos crecientes en los sumatorios.
Como estamos interesados en el orden de complejidad, y con los elementos anteriores podemos ver las
relaciones que existen entre los sumatorios y las ecuaciones de recurrencia, podemos realizar por tanto diversas
simplificaciones, obteniendo:
() = ( 1) + () () =
=
=+
() = ( ) + () () =
=
=
() ()
=
() ()
=
59
1 ()
1 ()
() (())
(()) =
=,
= ()
=0
(()) ()
()
1
()
( ())
()
(1 ())
=+
()
1
()
Para progresiones aritmticas donde h(x) = arx, h(x) = aln rrx, h(h-1(x)) = xln r tenemos:
()
()
(1 ())
1 ()
ln
0, 0
( (ln ) ) +1 (ln )
=+
0, 0, > 1 ( (ln ) ) {
=
(ln )+1
(ln )+1
=0
>0
Ejemplo 1:
=+2
1 2
1
1
1
2 (3 3 ) (3 ) (3 )
2
2
6
6
Podemos conseguir el resultado anterior teniendo en cuenta que la solucin de la recurrencia siguiente
tiene el mismo orden de complejidad:
() = ( 2) + 2
Ejemplo 2:
1
=3
1 1
1 1
1
1
[ln ]
[ln ln ] (log )
ln 3
ln 3
ln 3
ln 3
Podemos conseguir el resultado anterior teniendo en cuenta que la solucin de la recurrencia siguiente
tiene el mismo orden de complejidad:
() = ( ) + 1
3
60
()()
| ()=
En cada uno de los casos anteriores hablaremos del clculo del caso peor, mejor o medio para estimar el tiempo
de ejecucin de un algoritmo dado.
61
Secuencia de
bloques bsicos
S1;
S2;
Sk;
Estructura if
if(g)
{
S1;
}
else
{
S2;
}
Estructura while
62
while(g)
{
S;
}
() = 1 () + 2 () + + ()
= + (1 , 2 )
)
=
+ (1
, 2
=
+ 1 1
+ 2 2
() = + ( + ())
Siendo fi (i = 1, 2) la frecuencia de
ejecucin del bloque i.
Donde g es la expresin lgica de
la guarda.
Donde s1 y s2 son dos secuencias
de bloques bsicos.
Siendo I el conjunto de valores
que va tomando el tamao en las
sucesivas iteraciones.
Donde g es la expresin lgica de
la guarda.
El razonamiento se puede generalizar al caso de los bucles anidados y de los bucles consecutivos. Este nmero
es fcil de obtener en los siguientes casos:
Algoritmos iterativos sin bucles anidados aunque puedan tener varios estructuras while consecutivos:
la instruccin crtica es una del cuerpo de uno de los bucles. Justamente el que tenga ms iteraciones. El
nmero de iteraciones de un bucle se obtiene a partir del cardinal del conjunto de valores que toma el
ndice.
Varios bucles anidados: la instruccin crtica es una del cuerpo del bucle interior y entonces tenemos:
() =
1 1
2 2
Algoritmos iterativos con varios bucles while anidados pero donde las variables de ndice de cada bucle
no dependen en sus lmites unas de otras: slo depende del tamao del problema n, entonces:
() = 1 ()2 () ()
63
0
(, (1 ), (2 ), , ( ))
Donde x representa el problema de tamao n, xi los subproblemas de tamao ni con ni < n, donde c representa
la funcin que combina las soluciones de los problemas y la solucin del caso base de los que puede haber ms
de uno. El tiempo de ejecucin, T(n), para este tipo de problemas verifica la recurrencia:
() = () + () + (1 ) + (2 ) + + ( )
Donde Tp, Tc son, respectivamente, los tiempos para calcular los sub-problemas y para combinar las soluciones.
Suponiendo que un problema de tamao n se divide en a sub-problemas, todos del mismo tamao, n/b, y que
el coste de dividir el problema en sub-problemas ms el coste de combinar las soluciones es g(n), entonces la
ecuacin de recurrencia ser para los casos recursivos (n > n0):
() = ( ) + (),
> 0
() = () + ( ) ,
> 0
=1
Cuando el tamao de los sub-problemas se escoge aleatoriamente y queremos calcular casos medios, aparecen
otras recurrencias ms complejas, como:
1
1
() = 1 + (() + ( 1))
=0
64
Esto es debido a que, supuesto calculados los sub-problemas, el tiempo para calcular un problema de ndice x es
g(T(x)), es decir, el tiempo necesario para calcular los sub-problemas y componer la solucin. Luego el tiempo
necesario para calcular un problema es la suma de lo que se requiere para cada uno de los sub-problemas, que
es necesario resolver, que forman el problema.
Un caso particular, muy usado, se presenta cuando g(n) = k . Entonces la frmula anterior queda:
() = (()) = 1 = |1| = (|1|)
Es decir, en este caso muy general, el tiempo de ejecucin es proporcional al nmero de problemas que es
necesario resolver.
En general un problema puede ser representado por x = (x1, x2, , xk) por lo que para calcular |Ix| tenemos que
calcular |(x1, x2, , xk)| donde las propiedades se mueven en el conjunto de valores que identifican los subproblemas que hay que resolver para calcular la solucin de x.
Un caso un poco ms general es cuando podemos calcular la cantidad de sub-problemas de un tamao dado n
en funcin de n solamente. Sea esta funcin sp(n), entonces
() =
(() ())
y si g(n) = k, entonces
() =
(() ()) = ()
Donde ahora In es el conjunto de enteros que incluye los diferentes tamaos de los sub-problemas necesarios
para resolver un problema de tamao n.
Ejemplos:
Problema de Fibonacci: cada problema se representa por su propiedad n, g(n) = k. Comprobamos que en
este caso I = {0,1,2,n}. Luego la complejidad del problema de Fibonacci resuelto con Memoria es: (n).
Problema de la Potencia Entera: cada problema se representa por su propiedad Exponente, n=Exponente, y
g(n) = k. Luego la complejidad del problema de la Potencia Entera, de tamao n, resuelto sin Memoria,
verifica que = {, 2 , 4 , ,1} , | | = log 2 ( + 1) , es: (log n).
Estas ideas nos permiten decidir cundo usar Divide y Vencers con o sin Memoria. Slo usaremos la Memoria
cuando la complejidad del problema se reduzca (caso del problema de Fibonacci). Si la complejidad es la misma
(caso del problema de la Potencia Entera) no es conveniente usar Memoria. Las razones para que la complejidad
sea distinta (usando memoria y sin usarla) es la aparicin de sub-problemas repetidos al resolver un problema.
Esto ocurre en el problema de Fibonacci y no ocurre en el problema de la Potencia Entera. Por lo tanto, un criterio
directo para decidir si usar memoria o no, es verificar si aparecen sub-problemas repetidos o no.
65
()
( ) + ()
1 0
> 0
0
) + (0 ) = (0 )
() = {
3 ( ) + 16
2
1 0
> 0
Para n0 debe ser indiferente usar el caso base que realizar una llamada recursiva, por tanto el umbral
ptimo deber verificar que:
(0 ) = 3 (
(0 ) = 02
0 2
3 02
) + 160
]
+ 160 = 02 ,
2
4
160 =
1 2
,
4 0
0 = 64
Sean los siguientes ejemplos donde consideramos las variables previamente declaradas de tipo entero,
determine la complejidad de los siguientes algoritmos:
Problema 1:
() 1 + (3 + 3) + 1
=1
Luego tenemos,
() 6 = 0 = 0 1 = 0
=1
=1
() ()
=1
67
Problema 2:
i = 1;
while (i <= n) {
s = s + i;
i = i + r;
}
Caso 1:
Cdigo equivalente:
i = 1;
while (i <= n) {
s = s + i;
i = i + r;
}
for (i = 1; i <= n; i += r)
{
s = s + i;
}
Caso 2:
Cdigo equivalente:
i = n;
while (i >= 1) {
s = s + i;
i = i - r;
}
for (i = n; i >= 1; i -= r)
{
s = s + i;
}
luego,
()
0 0 0+1 (log )0 = 0
=1+
()
0 0
=1+
() ()
Tambin podemos plantearlo mediante una ecuacin recursiva considerando que el tamao es m = n - i.
() = ( ) + 0
68
i = 1;
while (i <= n) {
s = s + i;
i = i / r;
}
Caso 1:
Cdigo equivalente:
i = 1;
while (i <= n) {
s = s + i;
i = i * r;
}
Caso 2:
for (i = 1; i <= n; i *= r)
{
s = s + i;
}
Cdigo equivalente:
i = n;
while (i >= 1) {
s = s + i;
i = i / r;
}
for (i = n; i >= 1; i /= r)
{
s = s + i;
}
luego,
() 0 0 log()+1 = 0 log()
() ( )
O bien,
=
()
0 0
=1
1
ln
ln
() ( )
Tambin podemos plantearlo mediante una ecuacin recursiva considerando que el tamao asociado al
bucle directamente la variable i.
() = ( ) + 0
69
Problema 4:
for (int i = 1; i <= n - 1; i++) {
for (int j = i + 1; j <= n; j++) {
for (int k = 1; k <= j; k++) {
r = r + 2;
}
}
}
=1 =1
=1
=1
1
1
11
( )3 () ( )
() 0 0 0 ( )2 0 ( 2 2 ) 0
2
2
23
=1 =1 =1
Problema 5:
for (i = 1; i < n; i++) {
for (j = 1; j < i * i; j++) {
z++;
}
}
1 2 1
1 2 1
1
() 0 0 1 0 2 0 3
3
=1 =1
=1 =1
() ( )
=1
Problema 6:
Suponiendo que: A(i) (i).
i = 1;
x = 0;
while ((i * i) <= n) {
x = x + A(i);
i = i + 1;
}
()
=1
1
1
2
() () ()
2
2
Problema 7:
i = n;
while (i >= 1) {
j = 1;
while (j <= i) {
s = s + j;
j = j + 2;
}
i = i / 3;
}
=3 =
() 0
=1
70
=3
1
1 1
1 0
0
2
2 ln 3
=2
=1
() ()
Las probabilidades de que el algoritmo entre o no a ejecutar el bucle depender del resultado de
comparar a[i] con k, luego,
ProbabilidadS (a[i]=k) =
1
1
k+1
ProbabilidadNO (a[i]k) =
1
k
k+1
Por tanto,
1
0
+1
(,
+1 1
=1
=0
0 1
(
+
)
+1 +1
=0
(0 + 1 ) 1
( + 1 ) ( )
+1
+1 0
=0
(, ) 0 0 ( )
=0
1
(,
) 0 0 1 2 ( ) = 3
=0
=0
=0
(, )
2
(, 1)
( 1)(0 + 1 )
(n)
2
2
( ) (0 + 1 ) ( ) (0 + 1 )
2
2
= 2
(n)
+1
+1
2
2
( 1)(1)(0 + 1 )
(1)
Problema 9:
Suponiendo que B(n, m) (n) para todo valor de j.
i = n;
while (i >
x =
i =
for
1) {
x + x;
i / 4;
(int j = 1; j <= n; j++) {
x = x * B(j, n);
}
}
=4
() 0
=4
1
1
1
0 2 1 0 2
ln
2
2 ln 4
=1
=1
() ( )
=1
71
Problema 10:
int busca(int[] a, int c, int n) {
int inf = 0;
int sup = n - 1;
int i, r = 0;
boolean fin = false;
while (sup >= inf && !fin) {
i = (inf + sup) / 2;
if (a[i] == c) {
r = i;
fin = true;
} else if (a[i] > c) {
sup = i - 1;
} else {
inf = i + 1;
}
}
if (!fin) {
r = -1;
}
return r;
}
Sea la variable m = sup inf + 1 que representa el tamao de un sub-array. Observando que para la
siguiente iteracin, el tamao resultante puede ser m/2 (m-1)/2 , en el caso ms desfavorable, la
secuencia de valores por los que pasa la variable m (para valores posibles del guarda igual a true) es
{,
21
22
, , 1}
=2
() 0 1 0
=1
1
ln
ln 2
Problema 11:
Suponiendo que 0 b < a.length
void Func (int a[], int b) {
int i, j;
i = b;
while (i >= 0) {
for (j = 0; j <= i; j++) {
a[i] = j + a[j];
}
i = i / 3;
}
}
Problema 12:
int Func (int a[], int i, int j) {
int s, s1, s2, r, s11, s22;
int k = (i + j) / 2;
s1 = a[k];
s11 = a[k];
s2 = a[k];
s22 = a[k];
for (r = k - 1; r >= i; r--) {
s11 = s11 + a[r];
if (s11 > s1) {
s1 = s11;
}
}
for (r = k + 1; r <= j; r++) {
s22 = s22 + a[r];
if (s22 > s2) {
s2 = s22;
}
}
s = s1 + s2 - a[k];
return s;
}
72
() ( )
En los ejemplos siguientes usaremos los resultamos obtenidos para los algoritmos iterativos resueltos
anteriormente.
Hemos de tener en cuenta las soluciones explicadas a las ecuaciones de recurrencia siguientes:
Ecuacin de Recurrencia
() = ( ) + ()
() = ( ) + ()
() = ( ) + ( )
Orden de Complejidad
( )
() {(+1 )
( )
( )
() {( log )
(log )
( )
() {( +1 )
(log )
<1
=1
>1
<
=
>
<
=
>
Problema 13:
Suponiendo que B(n, m) (n) para todo valor de j.
int Func (int[] a, int prim, int ult) {
int mitad, terc, s;
if (prim >= ult) {
s = a[prim];
} else {
mitad = (prim + ult) / 2;
terc = (ult - prim) / 3;
s = a[mitad] + Func(a, prim, prim + terc) + Func(a, ult - terc, ult);
}
return s;
}
Determinamos el valor de n:
El caso base prim ult se corresponde con n = 1.
En el caso recursivo suponemos que n es el tamao del sub-intervalo del array: n = ult prim + 1. Es fcil
ver que si n es potencia de 3, el tamao del problema se divide por 3 en ambas llamadas recursivas:
1
=
= 1
3
3
3
1 = ( + ) + 1 = + 1 = 1 + 1 =
3
3
2 = ( ) + 1 = + 1 = 1 + 1 =
3
3
Sabiendo que: =
por tanto:
() = ( ) + ( ) + 0
3
3
() = 2 ( ) + 0
3
Luego: () ( ) ( )
73
Problema 14:
Luego: () ( )
Problema 15:
a + 1) - Func(n / 2, a - 1);
i <= n; i++) {
i;
() = (log ) () ( )
=
Luego: () ( ) = ( )
() =
=1
2 1
2 1
0 = 0
1 0 0 2
=1
=1
=1
=1
=1
1 3
+ (3 )
3
() = ( 2) + = ( 2) + 3
Problema 18:
int g (int[] a, int i) {
int s, r = 0;
if (i > a.length) {
s = r;
} else {
r += a[i];
s = g(a, i + 1);
}
return s;
}
Para la funcin g, sea tamao n = a.length - i n1 = a.length - (i+1) = n-1 n1 = k0 - (i+1) n1 = n-1
Tenemos, () = ( 1) + 0 Donde: a = 1, b = 1, d = 0, Luego: () ()
Para la funcin g, sea tamao n = a.length n2 = k0 n2 = n-0
Tenemos, () = ( 0) = () ()
75
Problema 19:
void problema(int[] a, int p) {
if (p >= 0) {
pasada(a, p);
problema(a, p - 1);
}
}
void pasada(int[] a, int i) {
int temp;
if (i < a.length - 1 && a[i] > a[i + 1]) {
temp = a[i];
a[i] = a[i + 1];
a[i + 1] = temp;
pasada(a, i + 1);
}
}
Sea tamao de la pasada n = a.length - i nps = a.length - (i+1) = n-1 nps = k0 - (i+1) nps = n-1
( ) = ( 1) + 0
Cuya solucin es ( ) ( )
Por tanto, asumiendo que el tamao de problema es npr = p , tenemos:
( ) = ( 1) + ( ) = ( 1) + ( )
Luego: ( ) ( 2 ).
Problema 20:
void procesa(int a[], int prim, int ult) {
int i, j, aux;
if (prim < ult) {
i = prim;
j = ult;
while (i < j) {
if (a[i] != a[j]) {
aux = a[i];
a[i] = a[j] * 4 + 3;
a[j] = aux * 4 - 3;
}
i = i + 1;
j = j - 1;
}
procesa(a, prim + 1, ult - 1);
}
}
76
Dadas las siguientes recurrencias, determine el orden de complejidad de cada una de ellas. Recuerde:
() = ( ) + ()
() ( ) { > 0}
( )
() { ( +1 )
( )
>
=
<
() ( ) { = 0}
( )
() { ( ())
( )
>
=
<
() = 2 ( ) + log()
2
Sol.: a=2, b=2, d=1, p=1. a ? bd 2 = 21 a = bd ( +1 ) = (1 1+1 ) = ( 2 )
() = 4 ( ) + log()
2
Sol.: a=4, b=2, d=0, p=1. a ? bd 4 > 20 a > bd ( ) = (24 ) = (2 )
() = 2 ( ) + log()
2
1
Sol.: a=2, b=2, d=0, p=1. a ? bd 2 > 20 a > bd ( ) = (2 2 ) = ( 2 ) = ()
() = 6 ( ) + 2 log()
3
Sol.: a=6, b=3, d=2, p=1. a ? bd 6 < 32 a < bd ( ) = (2 1 ) = (2 log )
() = 2 ( ) + 10n
2
Sol.: a=2, b=2, d=1, p=0. a ? bd 2 = 21 a = bd ( log()) = (1 log()) = ( log )
() = 8 ( ) + 1000n2
2
Sol.: a=8, b=2, d=2, p=0. a ? bd 8 > 22 a > bd ( ) = (28 ) = (3 )
() = 2 ( ) + n2
2
Sol.: a=2, b=2, d=2, p=0. a ? bd 2 < 22 a < bd ( ) = (2 )
() = 2 ( ) + n051
4
Sol.: a=2, b=4, d=051, p=0. a ? bd 2 < 4051 a < bd ( ) = (051 )
() = 3 ( ) +
3
Sol.: a=3, b=3, d=05, p=0. a ? bd 3 > 305 a > bd ( ) = (3 3 ) = ()
Nota:
a = coeficiente, b = denominador.
d = exponente de n (si no est vale 0), p = grado del logaritmo (si no est vale 0).
77
Para obtener el Orden de Complejidad de una definicin recursiva debemos seguir los siguientes pasos:
Paso 2: Obtenemos la Ecuacin de Recurrencia, T(n), nicamente de los componentes recursivos de la Definicin
Recursiva anterior, es decir, no se tiene en cuenta los casos base.
Ejemplo: Tenemos la funcin de la Serie de Fibonacci:
0
() = { 1
( 1) + ( 2)
=0
=1
>1
Descartamos los casos base y nos quedamos con la Ecuacin Recursiva, esto es:
( 1) + ( 2)
() = ( 1) + ( 2)
Debemos tener en cuenta que, en el caso de aparecer una expresin negativa, se tomar como positiva:
( 3) ( 4)
() = ( 3) + ( 4)
Paso 3: Obtenemos la Funcin de Recurrencia a partir de la Ecuacin de Recurrencia de forma genrica anterior.
Pasamos a la izquierda todos los elementos excepto constantes y otros polinomios, si lo hubiera:
() = ( 1) + ( 2)
() ( 1) ( 2) = 0
distinguimos dos polinomios diferentes, que trataramos de distinta manera, estos son:
(22 + 2) + (3 )
grado (d1) = 2
Segundo polinomio (3 ) :
grado (d2) = 0
coeficiente (b2) = 3
sustituyendo en:
1 1 () + 2 2 () + + ()
obtendramos:
1 1 () + 3 2 ()
78
Nos falta determinar el valor de la variable k, que toma el valor de cuntos pasos antes, como mximo,
requera la Ecuacin Recursiva realizar para su clculo, en este caso sera, de fib(n - 2), entonces k = 2.
Por tanto tendremos:
((1) 20 + (1) 21 + (1) 22 ) ( 1)2+1 ( 3)0+1 = 0
( 2 1) ( 1)3 ( 3) = 0
Paso 5: Calculamos las races del Polinomio Caracterstico (valores que lo hacen cero) y sus multiplicidades.
( 3)
( 1)3
( 2 1)
1 = +3
2 = +1
1 + 5
+1 62 +1
2
1 5
4 =
0 62 1
2
{
3 =
= 1
= 3
= 1
= 1
Paso 6: Obtenemos el Orden de Complejidad (), sustituyendo los valores definidos, en la siguiente expresin:
(()) = (1 )
Concluimos por tanto que el Orden de Complejidad de la funcin recursiva propuesta es:
() ( ).
79
2.7.4.1
Ejercicio propuesto n 1
=0
=1
>1
4
1
( 1) + ( 2) + 2
3
3
1 = 1
( 2 1)
=1
1 + 5
16
2
1 5
3 =
06
2
{
2 =
1+5
2
=1
=1
, y con su multiplicidad, m = 1.
Paso 6: Obtenemos el Orden de Complejidad (), sustituyendo los valores definidos, en la siguiente
expresin:
(()) = (1 )
1 + 5
1 + 5
(()) = (11 (
) ) = ((
) ) = ( )
2
2
.
Concluimos por tanto que el Orden de Complejidad de la definicin recursiva propuesta es: () ( ).
80
Ejercicio propuesto n 2
Paso 1:y Paso2: Ya tenemos la Ecuacin de Recurrencia, nos la ha proporcionado el propio enunciado.
1 = 1
=4
Paso 6: Obtenemos el Orden de Complejidad (), sustituyendo los valores definidos, en la siguiente
expresin:
(()) = (1 )
Concluimos por tanto que el Orden de Complejidad de la definicin recursiva propuesta es: () ( ).
81
2.7.4.3
Ejercicio propuesto n 3
Paso 1:y Paso2: Ya tenemos la Ecuacin de Recurrencia, nos la ha proporcionado el propio enunciado.
1 = +1
2 = +2
{
3 = 2
=2
=1
=1
Paso 6: Obtenemos el Orden de Complejidad (), sustituyendo los valores definidos, en la siguiente
expresin:
(()) = (1 )
82
Ejercicio propuesto n 4
Dada una lista ordenada que ha sido rotada k veces (k desconocido). Se pide:
Disear un algoritmo que encuentre el elemento mayor y cuya complejidad sea (log ).
2. Cul es el tamao del problema?
3. Calcular el T(n) para el caso mejor y peor del algoritmo diseado.
1.
Solucin:
-
Caso 1: 1 2 3 4
Si v[j-1] > v[i] entonces el elemento mayor es el que est en la ltima posicin del vector, esto es, en j-1.
El caso 1 es un caso base.
Caso 2: 3 4 1 2
Si v[mitad-1] > v[mitad] entonces el elemento mayor es el que est en la posicin mitad-1 del vector.
El caso 2 es un caso base.
Caso 3: 4 1 2 3
Si v[0] > v[mitad-1] entonces el elemento mayor estar en la primera mitad del vector, luego haremos
recursin desde la posicin i hasta la posicin mitad del vector.
Caso 4: 2 3 4 1
Si v[mitad] > v[j-1] entonces la segunda mitad estar desordenada, luego haremos recursin desde la
posicin mitad hasta la posicin j-1 del vector.
Definicin Recursiva:
[ 1]
[ 1] > []
[ 1]
[ 1] > []
(, , ) =
(, , 1)
[] > [ 1]
[] > [ 1]
{ (, , )
1 ( )
2 ( )
3 ( )
4 ( )
83
Dado el siguiente algoritmo que realiza la ordenacin de un array de enteros a, donde n representa la dimensin
del array.
void InsertionSort(int a[], int n) {
int j, p;
int temp;
for (p = 1; p < n; p++) {
temp = a[p];
for (j = p; j > 0 && a[j - 1] > temp; j--) {
a[j] = a[j - 1];
a[j - 1] = temp;
}
}
}
Se pide:
1. Describir qu es el tamao del problema. Justificar la respuesta.
2. Describir qu caso es el mejor y peor para ese algoritmo y calcular el T(n) de cada uno de dichos casos.
3. Al ser un algoritmo iterativo que proviene de un algoritmo recursivo final. Indicar el cdigo en lenguaje
C de dicho algoritmo recursivo final.
4. Calcular el T(n) de este nuevo algoritmo para el caso mejor y peor.
SOLUCIN:
Aparatado 1)
El tamao del problema viene determinado por el parmetro n que representa la dimensin del array,
dado que la condicin del primer bucle depende del valor de dicho parmetro. Esto conduce que a
medida que se haga este parmetro mayor la complejidad temporal ser mayor tambin.
Aparatado 2)
El caso mejor es cuando el vector este ordenado ascendentemente o todos los elementos del vector son
iguales pues entonces la condicin a[j - 1] >temp es siempre falsa y por tanto no se ejecuta el cuerpo del
segundo bucle. Por tanto,
1
() = = ( 1) ()
=1
El caso peor es cuando el array viene ordenado descendentemente donde siempre la condicin a[j - 1]>
temp es verdadera y por tanto se ejecuta el cuerpo del bucle interno.
1
() = = ( + 1) = ( 1)
=1 =0
84
=1
+ ( 1) (2 )
2
n) {
Esto ayuda a determinar el caso base del algoritmo recursivo final como la negacin de (p < n), la solucin
al caso base como vaca y el siguiente elemento en la recursin p++, quedando entonces la funcin
recursiva final como:
void InsertSortRec(int a[], int p, int n) {
int j, temp;
if (p < n) {
temp = a[p];
for (j = p; j > 0 && a[j - 1] > temp; j--) {
a[j] = a[j - 1];
a[j - 1] = temp;
}
InsertSortRec(a, p + 1, n);
}
}
Para que sea equivalente al algoritmo iterativo del que procede la llamada a este algoritmo recursivo
debera ser InserSortRec(a, 0, n), siendo n el tamao del array, como en el algoritmo iterativo.
85
Aparatado 4)
Hay que tener ahora en cuenta que para el algoritmo recursivo el tamao es n - p, pues ambos
parmetros determinan la condicin de finalizacin, de acuerdo a la condicin que aparece en la
sentencia if. Para la primera llamada al ser p = 0 entonces tamao es n - 0 = n. Y el siguiente problema
al incrementar p en 1, el tamao ser ya n - 1.
El caso mejor es cuando el vector este ordenado ascendentemente o todos los elementos del vector son
iguales pues entonces la condicin a[j - 1] >temp es siempre falsa y por tanto no se ejecuta el bucle que
existe en este algoritmo recursivo. Entonces
() = ( 1) +
Que es un caso particular de este tipo de ecuaciones:
() = ( ) + ()
Siendo en general p(n) = (nd), la solucin es:
(/ )
() = { (
+1
( )
Por tanto la solucin es:
>1
) =1
<1
() ()
El caso peor es cuando el array viene ordenado descendentemente donde siempre la condicin a[j - 1]>
temp es verdadera y por tanto se ejecuta el cuerpo del bucle del algoritmo recursivo hasta n veces
cuando p = n. Entonces
() = ( 1) +
Por tanto la solucin es:
86
() (2 )
DESCRIPCIN
Devuelve la longitud de la cadena.
Devuelve la posicin de la primera aparicin de ch.
Devuelve la posicin de la ltima aparicin de ch.
Devuelve el carcter que est en la posicin index
Devuelve la subcadena comprendida entre las
posiciones n1 y n2 ambas incluidas.
Devuelve la cadena convertida a maysculas.
Devuelve la cadena convertida a minsculas.
Devuelve una nueva cadena resultante de reemplazar
todas las ocurrencias de oldChar en esta cadena con
newChar.
Igual que equals pero sin considerar maysculas y
minsculas.
Devuelve 0 si las dos cadenas son iguales. <0 si la
primera es alfabticamente menor que la segunda >0
si la primera es alfabticamente mayor que la segunda.
Igual que compareTo pero sin considerar maysculas y
minsculas.
Mtodo esttico. Convierte el valor N a String. N puede
ser de diferentes tipos.
Convierte la cadena a un nuevo array de caracteres.
87
SOLUCIN:
Apartado 1)
La funcin pedida imprimeAlReves en el Lenguaje Java sera
public void imprimeAlReves(String palabra, int num) {
int longitud = palabra.length();
if (num == longitud - 1) {
System.out.println(palabra.charAt(longitud - 1 - num));
} else {
System.out.print(palabra.charAt(longitud - 1 - num));
imprimeAlReves(palabra, num + 1);
}
}
imprimeAlReves(palabra, 0);
Apartado 2)
No existe caso mejor ni peor, solamente un caso y para este caso, la complejidad depende del tamao del
problema ser la longitud de la palabra menos uno y menos num, que le llamaremos n. Por tanto, para
calcular la complejidad tenemos que T(n) = T(n - 1) + k y que, segn las frmulas vistas en clase, dara un
orden de complejidad: T(n) (n).
Apartado 3)
La funcin iterativa pedida imprimeAlReves en el Lenguaje Java sera:
public void imprimeAlReves(String palabra, int num) {
int longitud = palabra.length();
while (!(num == longitud - 1)) {
System.out.print(palabra.charAt(longitud - 1 - num));
num = num + 1;
}
System.out.println(palabra.charAt(longitud - 1 - num));
}
imprimeAlReves(palabra, 0);
Apartado 4)
La funcin recursiva para imprimir solamente los k primeros caracteres sera:
public void imprimeAlReves(String palabra, int num, int k) {
if (num == k - 1) {
System.out.println(palabra.charAt(k - 1 - num));
} else {
System.out.print(palabra.charAt(k - 1 - num));
imprimeAlReves(palabra, num + 1);
}
}
imprimeAlReves(palabra, 0, k);
No existe caso mejor ni peor, solamente un caso y para este caso, la complejidad depende del tamao del
problema que podra ser ahora k - 1- num, que le llamaremos n. Por tanto, para el clculo de la complejidad
tenemos que T(n) = T(n - 1) + k y que, segn las frmulas vistas en clase, dara un orden de complejidad
(n). Notar la diferencia con respecto al clculo anterior, pues antes el tamao del problema depende de la
longitud de la palabra y ahora depende del valor de k.
88
=
( < ) (% 0)
( < ) (% = 0)
89
SOLUCIN:
Apartado 1)
int divisores(int n, int k) {
int res;
if (k == n)
res = 0;
else if (n % k == 0)
res = 1 + divisores(n, k + 1);
else
res = divisores(n, k + 1);
return res;
}
Apartado 2)
Tamao = n = n k
Orden de complejidad: T(n) = T(n 1) + c, donde a=1, b=1, c=1, d=0, luego T(n) (n)
Apartado 3)
a)
(, , ) = { (, + 1, )
(, + 1, + 1)
=
( < ) (% 0)
( < ) (% = 0)
b)
Llamada inicial: divisoresFinal(n, 2, 0). Indicamos a partir del 2 porque el 1 no lo contamos.
c)
int divisoresFinal(int n, int k, int ac) {
int res;
if (k == n)
res = ac;
else if (n % k == 0)
res = divisoresFinal(n, k + 1, ac + 1);
else
res = divisoresFinal(n, k + 1, ac);
return res;
}
Apartado 4)
int divisoresIterativo(int n, int k) {
int res;
int ac = 0;
while (k != n) {
if (n % k == 0)
ac++;
k++;
}
res = ac;
return res;
}
90
() = () + ()
Se trata de disear un algoritmo recursivo en el lenguaje C que calcule la integral aproximada de la funcin ya
implementada en C tal que su prototipo es double fo(double x), sabiendo que m = (a + b) / 2 y que se trata de
dividir el intervalo [a, b] por la mitad hasta que sea lo bastante pequeo |b - a| < epsilon, en cuyo caso se acepta
que:
() ( ) ()
Se pide:
1. Disear el correspondiente algoritmo recursivo.
2. Cul es el tamao del problema?
3. Calcular T(n) para el caso mejor y peor del algoritmo diseado para hallar la integral aproximada?
SOLUCIN:
Apartado 1)
double IntegralDe_f(double a, double b, double eps) {
double r, m;
m = (a + b) / 2;
if (fabs(b - a) < eps)
r = (b - a) * fo(m);
else
r = IntegralDe_f(a, m, eps) + IntegralDe_f(m, b, eps);
return r;
}
Apartado 2)
El tamao es: (b - a) / epsilon.
Apartado 3)
Se pide:
1) Expresar el tamao de la funcin m para la llamada inicial: m(vv, 0, TAM-1, c).
2) Definir los casos mejor y peor y calcular el orden del T(n) de cada caso.
92
Apartado 1)
El tamao se define como: n = b - a + 1 = TAM (nmero de elementos del array v)
Apartado 2)
El caso mejor se produce cuando el elemento c est en la primera posicin del array ( v[a] = c ).
El T(n) para el caso mejor ser:
T(n) = k (1)
93
Se pide:
1. Disear las correspondientes funciones
a. recursiva lineal no final
b. recursiva final e indicar cmo sera la llamada inicial a dicha funcin.
2. Cul es el tamao del problema?
3. Calcular T(n) para el caso mejor y peor de cada una de las funciones?
SOLUCIN:
Apartado 1)
// Importante: La precondicin de ambas funciones es n 0
// 1.a > Recursivo Lineal No final
int sumaDigitos(long int n) {
int r;
if (n < 10)
r = n;
else
r = n % 10 + sumaDigitos(n / 10);
return (r);
}
//1.b > Recursivo Final
int sumaDigitos(long int n) {
int r;
r = sumaDigitosAux(n, 0);
return (r);
}
int sumaDigitosAux(long int n, int x) {
int r;
if (n < 10)
r = n + x;
else {
r = sumaDigitosAux(n / 10, x + n % 10);
}
return (r);
}
Apartado 2)
Tamao n = nmero de cifras que tiene el nmero de tipo long.
Apartado 3)
Para las dos funciones no existe caso mejor ni peor, solamente hay un caso y su T(n) = T(n-1) + k, que de
acuerdo con la frmula, si a = 1 entonces T(n) = c1n + c2.
94
!
(, , , ) = ( ) (2[] [])
!
=
(! + 1)
1
(, , , ) = 2[] [] + (
) (
) (2[] [])
1+
!
=
Se pide:
a) Realizar la implementacin en C del esquema propuesto en clase para el diseo recursivo no final para
este problema y los mtodos correspondientes de dicho esquema:
float calculo(
) {
// TO DO
}
logico esCasoBase(
){
// TO DO
}
float solucionBase(
){
// TO DO
}
siguiente(
){
// TO DO
}
float combina(
){
// TO DO
}
b) Cmo debe llamarse a la funcin implementada si queremos calcular el sumatorio pedido desde la
posicin 3 a la 6, de dos vectores A y B dados, de 6 enteros cada uno.
c) Indicar con respecto a la complejidad algortmica: Cul es tamao del problema?
d) Describa el caso mejor y el caso peor de la implementacin realizada de calculo y calcule el T(n) para
cada uno de los dos casos.
95
Solucin:
Apartado a)
//Se supone existe un fichero con los tipos definidos
typedef enum {
false, true
} logico;
typedef struct {
float *a;
float *b;
int i;
int n;
} CuatroDatos;
float calculo (CuatroDatos n) {
float r, rSig;
CuatroDatos nSig;
if (esCasoBase(n)) {
r = solucionBase(n);
} else {
nSig = siguiente(n);
rSig = calculo (nSig);
r = combina(n, rSig);
}
return r;
}
logico esCasoBase(CuatroDatos q) {
return (q.i == q.n);
}
float solucionBase(CuatroDatos q) {
return (2 * q.a[q.i] * q.b[q.i]);
}
CuatroDatos siguiente(CuatroDatos q) {
CuatroDatos qsig = { q.a, q.b, q.i + 1, q.n };
return (qsig);
}
float combina(CuatroDatos q, float rSig) {
float p = (float) 1 / (q.i + 1);
float r = 2 * q.a[q.i] * q.b[q.i] + p * rSig;
return (r);
}
Apartado b)
Se debe hacer la llamada para i uno menos que el valor dado y para n igual. Para el caso de la llamada para
ir de 3 a 6, en q.i debe ser instanciado a 2 y en q.j de CuatroDatos debera instanciarse a 5. Ejemplo:
CuatroDatos d;
float x[7] = {2.0, 3.0, 4.0, 6.0, 1.0, 8.0, 23.0};
float y[7] = {4.0, 6.0, 9.0, 1.0, 6.0, 5.0, 12.0};
d.a = x;
d.b = y;
d.i = 2;
d.n = 5;
float r = calculo (d);
Apartado c)
El tamao del problema es: n - i + 1
Apartado d)
Los caso mejor y peor son el mismo disponer de dos vectores y hacer el recorrido desde i hasta n, y la
complejidad del caso mejor y peor por tanto es la misma: T(n) = T(n - 1) + k
Por tanto T(n) = k1 n + k2 (n). Es un algoritmo lineal
96
=0
[] > (, 1)
97
Solucin:
Aparatado a)
int deco(int *V, int n) {
if (n == 0) {
aux = V[n];
} else if (V[n] > deco(V, n - 1)) {
aux = V[n];
} else {
aux = deco(V, n - 1);
}
return aux;
}
Aparatado b)
int deco(int *V, int n) {
int aux;
if (n == 0) {
aux = V[n];
} else {
aux = deco(V, n - 1);
if (V[n] > aux) {
aux = V[n];
}
}
return aux;
}
Aparatado c)
int deco(int *V, int n) {
// return decoF(V, n, -INF);
return decoF(V, n, V[0]); // si n> 0
}
int decoF(int *V, int n, int aux) {
if (n == 0) {
return aux;
} else if (V[n] > aux) {
aux = V[n];
}
return decoF(V, n-1, aux) {
}
Aparatado d)
int deco(int *V, int n) {
int aux = -1;
while (n > 0) {
if (V[n] > aux) {
aux = V[n];
}
n--;
}
return aux;
}
Aparatado e)
El tamao ser: n = j.
El T(n) para el apartado a) es de orden (2n).
El T(n) para el apartado c) es de orden lineal.
98
2.8.9 Anlisis.
Sean g y f dos funciones definidas como sigue:
[]
=
, , ) = { (, , + ) [] < 1
(, + 2, ) [] 1
= (, , )
=
3
Teniendo en cuenta que el tamao del problema viene especificado por n = j - i, se pide:
99
SOLUCIN:
Aparatado 1)
(, ) = = () =
= =
=0 =0
=0 =0
1 2
(2 )
2
2 3
2. ( ( + 2)) =
3
3
3
3
2+2
3
() = 2 + ( )
3
(2 )
Apartado 3)
Es no final, ya que no hay funcin de combinacin.
Es simple, ya que slo se ejecuta una llamada recursiva cada vez.
Aparatado 4)
int g(int * v, int i, int j) {
int ret;
if (i == j) {
ret = v[i];
} else {
v = f(v, i, j);
if (v[(j - i) / 3] < 1) {
ret = g(v, i, i + (j - i) / 3);
} else {
ret = g(v, i + 2 * (j - i) / 3, j);
}
}
return ret;
}
Aparatado 5)
No convendra utilizar memoria ya que no se repiten problemas (es recursividad simple). Si se utilizara
memoria, se obtendra la misma complejidad.
Aparatado 6)
int g(int * v, int i, int j) {
int ret;
while (i != j) {
v = f(v, i, j);
if (v[(j - i) / 3] < 1) {
j = i + (j - i) / 3;
} else {
i = i + 2 * (j - i) / 3;
}
}
return v[i];
}
100
101
SOLUCIN:
Apartado 1.a )
() = { ( ) + (%10) 10(()1)
10
< 10
10
Apartado 1.b )
long invierteNumero(long numero) {
long r = 0;
long s = 0;
long y = 0;
if (numero < 10) {
r = numero;
} else {
y = numero / 10;
s = invierteNumero(y);
r = (numero % 10) * potencia(10, numeroDigitos(numero) - 1) + s;
}
return r;
}
long potencia(long base, int exponente) {
return pow(base, exponente);
}
// Ya se proporciona implementado
long numeroDigitos(long numero) {
long res;
res = log10(numero);
if (res % 10 != 0)
res = res + 1;
return res;
}
Apartado 2.a )
Para realizar la transformacin no final a final es necesario que el mtodo de combinacin pueda expresarse
como c(x, u), = g(x) u . En este caso puede conseguirse como g(x) = (x%10) 10numeroDigitos(x)-1 y se
correspondera con el operador suma. Es decir:
c(x, u) = (x%10) 10numeroDigitos(x)-1 u = (x%10) 10numeroDigitos(x)-1 + u
La transformacin resultante sera la siguiente:
() {
(, ) {
(, 0)
< 10
10
( , + (%10) 10()1 )
10
< 10
10
Llevando a cabo el procedimiento anterior, observamos que el nmero 235 se invierte como: 5*102 + 3*10 + 2.
Ntese que mediante el algoritmo de Horner, se puede reescribir de la forma: (((5 * 10) + 3) * 10 + 2)
Por tanto, aplicando la transformacin anterior podemos obtener una definicin recursiva ms sencilla:
(, ) {
102
+ 10
( , 10 + (%10))
10
< 10
10
Apartado 3 )
// 1 Solucin usando potencia:
long invierteNumeroIterativo(long numero) {
long r = 0;
long u = 0;
long y = 0;
while (numero > 10) {
y = numero / 10;
u = u + (numero % 10) * potencia(10, numeroDigitos(numero) - 1);
numero = y;
}
r = numero + u;
return r;
}
// 2 Solucin sin usar potencia:
long invierteNumeroIterativo(long numero) {
long r = 0;
long u = 0;
long y = 0;
while (numero > 10) {
y = numero / 10;
u = u * 10 + (numero % 10);
numero = y;
}
r = numero + u * 10;
return r;
}
103
Apartado 4.a )
El tamao del problema es el nmero de dgitos de nmero, por tanto: n = numeroDigitos(numero);
Apartado 4.b )
No hay caso mejor ni peor tanto en el algoritmo recursivo no final como en el algoritmo recursivo final, ya
que todos los casos son dependientes del tamao del problema.
Apartado 4.c )
Algoritmo recursivo no final:
La recurrencia quedara de la siguiente forma:
() = ( 1) + + 0
= 1, = 1, = 1, = 1 , = 1, = 1 (+1 )
(()) = (2 )
Nota: La complejidad del algoritmo puede variar en funcin del coste del mtodo potencia.
104
2.8.11 Funcin.
Dada la siguiente funcin recursiva:
Se pide:
a) Definir el tamao a partir de la llamada inicial func(&A, 0, TAM, 10) donde A es un array de enteros de
tamao TAM.
b) Calcular razonadamente la complejidad de los T(n) para los casos mejor y peor.
SOLUCIN:
Apartado a)
Apartado b)
Caso mejor (a[i] != c para todo i) donde:
() = ( ) +
3
2
)+
3
log3 2
2
) = (17095 )
105
2.8.12 Funcin.
Dada la siguiente funcin recursiva:
double func(int i) {
double s;
if (i < 5) {
s = 1;
for (k = 0; k < i; k++) {
s = s + k * s;
}
} else {
s = 2 * func(i / 2) + i / 4;
}
return s;
}
Se pide:
a) Definir el tamao a partir de la llamada inicial func(N).
b) Calcular razonadamente la complejidad de los T(n) para los casos mejor, peor y medio.
SOLUCIN:
Apartado a)
Apartado b)
() = ( ) +
2
106
En este captulo vamos a estudiar un conjunto de problemas de inters. Veremos problemas resueltos con
algoritmos iterativos y otros con algoritmos recursivos. Usaremos los clculos de la complejidad vistos en el
captulo anterior.
Veremos formas de adaptar algoritmos para resolver problemas para los que no estaban pensados, comenzando
de una forma general por los tratamientos iterativos.
l[i]
l[i j]
l+Lv
v+Ll
l1 + Ll2
Aplanamiento de listas. Es decir, +L[ [a0, , an-1], [b0, , bm-1], [c0, , cp-1], ] = [a0, , an1, b0, , bm-1, c0, , cp-1,].
Para los tratamientos secuenciales de las listas usaremos el concepto de iterador. Un iterador es, en abstracto,
una lista ms una posicin en la misma. Esa posicin podemos definirla por un entero, por el valor de una
posicin (siempre que este identifique una posicin de forma nica) o por ambas cosas dependiendo de las
situaciones. Los iteradores los representaremos por it, it1, it2, y lo modelaremos como it = (l, i). Y segn la
forma concreta de la posicin el iterador podr ser de las formas (l, i) (l, v) (l, i, v).
Un iterador representa de forma nica a la sub-lista que va desde la posicin guardada en el iterador hasta el
final de la lista. En un iterador podemos hablar de posicin actual y valor actual (valor de la lista en la posicin
actual) aunque este ltimo valor estar indefinido si el iterador est vaco.
Hay varias funciones para manipular iteradores cuyos nombres se han estandarizado. Son las funciones next,
hasNext. Las factoras de iteradores se llaman iterables, y tienen una operacin llamada iterator.
108
it = iterator (l)
transform
()
() ()
() ()
()
2 ( , , + ())
()
donde:
itN = next(it) = (l, j, a)
Definamos sobre listas, la funcin accumulate que calcula, a partir de una lista y un operador acumulador, un
valor acumulado. Los operadores acumuladores tienen una serie de propiedades y pueden ser clasificados en
distintos tipos. Cada tipo tendr una forma especfica de obtener el valor acumulador. Asumimos que es el tipo
del valor acumulado y el de los elementos de la lista. En primer lugar tenemos el tipo ms general de operador
acumulador que llamaremos operador acumulador bsico. Este tipo de operador tiene asociado una funcin f0
con signatura (A, A) A y un valor v0 para la lista vaca. Representamos el operador por (f0, v0) Ejemplos de este
tipo de operador son: (+, 0), (*, 1), (+L, []L), (+S, []S), (+M, []M). Las funciones +L, +S, +M aaden un elemento a una
lista, un conjunto o un multiconjunto y los valores []L, []S, []M representan una lista, un conjunto y un
multiconjunto vacos. Asumimos que la funcin f0 tiene la forma f0(a, v). Dnde el primer parmetro es el
acumulador y el segundo el valor a acumular. Algunos operadores acumuladores bsicos tienen la propiedad de
tener un valor de saturacin. Llamamos valor de saturacin para una funcin f0 con signatura (A, A) A si cumple
f0(vs, v) = vs para todos los valores posibles de v. Si una funcin f0 tiene un valor de saturacin vs, el operador de
acumulacin correspondiente lo representamos por (f0, v0, vs). A Este tipo de operadores los llamamos
operadores acumuladores con valor de saturacin. Ejemplos de este tipo son (, true, false) (, false, true). En
este tipo de operadores de acumulacin la iteracin puede terminar si el acumulador alcanza el valor de
saturacin. Un caso particular de los operadores bsicos anteriores son aquellos que no definen un valor para la
lista vaca, los llamamos operadores acumuladores sin valor inicial, y los representamos por (f0, ) para indicar
que no existe valor para la lista vaca.
Ejemplos: max0, min0 que calculan el mximo y el mnimo con respecto a un orden. Podramos considerar otro
caso donde no exista valor para la lista vaca pero s valor de saturacin. Lo representaremos por (f0, , vs). En
este tipo de operadores el acumulador es inicializado con el primer valor de la lista. La definicin recursiva final
de esta funcin es:
= [] 0 =
((0 , 0 , ), ) = { 1 (0 , , (), 0 ) [] 0
1 (0 , , , )
[] 0 =
()
=
1 (0 , , , ) = {
1 (0 , , 0 (, ), )
()
(1 , ) = ()
Hemos representado por a el valor acumulado, y va el valor devuelto por la ltima llamada a next. La funcin f0
tendr la signatura: (A, A) A. El clculo de un acumulador sobre una lista (implcita o explcita) es un clculo
secuencial. Por tanto a un acumulador (operador binario ms lista) podemos asociar una nueva lista, cuyos
elementos son los clculos sucesivos del acumulador y cuyo elemento final contiene el valor devuelto por le
acumulador.
109
3.3 Implementacin.
3.3.1 Implementacin en Java.
Las ideas anteriores de listas, iteradores, iterables, acumuladores, filtros, funciones de transformacin, etc.
pueden ser implementadas en Java usando los tipos List<E>, Set<T>, Multiset<T>, Iterable<E>, Iterator<E>,
Predicate<E>, Function<R, T> y las clases de factora Lists, Iterables, etc. Estas clases las podemos ampliar con
Iterables2, Tupla2, Tupla3, AccumulateOperator, tal como veremos ahora.
Java, junto con el entorno Guava, ya dispone de los tipos Iterator<E> e Iterable<E>. Tambin dispone de los tipos
Predicate<E> y Function<R, T> que ya hemos estudiado. La clase Iterables nos proporciona implementaciones de
la funciones filter y transform, del operador +L en todas sus versiones (los mtodos concat). Tambin, junto con
la clase Ordering<E>, proporciona implementaciones de algunos acumuladores como:
filter
Iterables
transform
Iterables
concat
Iterables
Operador +L
get
Iterables
Operador l[i]
getLast
Iterables
Operador l[n-1]
isEmpty
Iterables
Predicado l = []
any
Iterables
all
Iterables
size
Iterables
accumulate((+1, 0), l)
max
Ordering
accumulate((max, ), l)
min
Ordering
accumulate((min, ), l)
isOrdered
Ordering
isStrictOrdered
Ordering
Junto a lo anterior se ofrecen mtodos de factora para crear iterables a partir de objetos de tipo List, Array y
definiciones implcitas de listas. Esto mtodos, que se encuentran en las clase Iterables2, se proponen para ser
implementados. Estos mtodos son:
Iterable<T> fromArray(T[] a)
110
Por ltimo, los operadores acumuladores, podemos implementarlos en la clase AccumulateOperator<A> con los
siguientes constructores y mtodos:
AccumulateOperator<A>
createBasico(BinaryFunction<A, A,A> f, A initialValue);
AccumulateOperator<A>
createBasicoConValorDeSaturacion(BinaryFunction<A, A, A> f, A initialValue, A saturationValue);
AccumulateOperator<A>
createBasicoSinValorInicial(BinaryFunction<A, A, A> f);
AccumulateOperator<A>
createSinValorInicialConValorDeSaturacion(BinaryFunction<A, A, A> f, A saturationValue);
A accumulate(Iterable<A> it);
111
3.3.2 Implementacin en C.
En el lenguaje C, que no dispone de tipos genricos, podemos implementar una lista explcita de nmeros reales
del tipo [dt, n] mediante (double * dt, int n). Es decir mediante un puntero a double (que seala a los datos) y un
entero que indica el nmero de los mismos. Si es necesario, aadiramos un tercer elemento m, que indique el
nmero mximo de elementos de la lista. Esto es necesario en C si la memoria para los datos se ha reservado
mediante un array. En este caso m es el tamao del array y debe cumplirse que n m.
La funcin para aadir elementos a la lista tiene el prototipo:
void add(double * dt, int * n, int m, double v);
Los prototipos en C de las funciones para gestionar iteradores sobre la lista [dt, n] son:
double next(double * dt, int * p);
int hasNext(int n, int p);
int isEmpty(int n, int p);
int createIt();
Hemos de tener en cuenta que los valores lgicos los representamos por enteros en C y que create actualiza el
valor de la posicin al principio.
Las listas de reales tipo [fl, n] las convertimos en iteradores del tipo:
double next(int * p);
int hasNext(int n, int p);
int isEmpty(int n, int p);
int createIt();
La funcin fl sirve para implementar la funcin next y las del tipo [fl, v0, pl]:
double next(double * a);
int hasNext(double a);
int isEmpty(double a);
double createIt();
Ahora la funcin fl sirve para implementar la funcin next y para implementar hasNext y isEmpty.
Los operadores acumuladores los implementamos como algoritmos iterativos. Veamos el algoritmo equivalente
a accumulate((f0, e0, es), transform(filter([fl, v0, pl], pt), ft)). En primer lugar implementamos el iterador asociado
a la lista. Es decir las funciones next, hasNext, create, asociadas a la lista con las ideas anteriores. Asumimos que
los elementos de la lista son de tipo V y el acumulador es de tipo A.
A execute(List<V> ls){
A a = e0;
Iterator it = create(ls);
while(hasNext(it)){
V v = next(it);
if(pt(v){
a = f0(a, ft(v));
}
if(a==es){
break;
}
}
return a;
}
112
Dada la lista (dt, n) y las funciones (pt(x) = x > 2, ft(x)=x2) pretendemos obtener la lista transformada (da, ,na, ma).
Es decir a partir de una lista (representada como un array) obtener otra (tambin representada como un array)
que contenga los cuadrados de los que sean mayores que dos. El cdigo es una instanciacin del esquema
anterior haciendo que la lista devuelta (da, ,na, ma) sea un parmetro de entrada salida:
void nuevaLista(double * dt, int n, double * da, int * na, int ma) {
*na = 0;
double v, r;
int p = 0;
// createIt()
while (p < n) {
// hasNext
v = dt[p]; // next
p = p + 1; // next
if(v > 2) {
r = v*v;
assert(*na < ma);
da[*na]=r;
//add
*na=*na+1;
//add
}
}
}
Siguiendo los ejemplos anteriores podemos implementar en C todas las ideas incluidas en los conceptos de listas
y operadores acumuladores. La diferencia de implementacin en Java y en C es muy grande. En Java disponemos
de pequeas unidades genricas que pueden ser compuestas para resolver el problema en cuestin. Cada unidad
est diseada para ser reutilizada. En cambio, la reutilizacin es ms compleja y por lo tanto es casi siempre ms
prctico disear un algoritmo que resuelva cada problema concreto. Aunque las ideas abstractas son las mismas:
las listas las manipulamos a travs de iteradores, los operadores de acumulacin instancian esquemas generales.
113
an mod r , es decir, el resto con respecto r de an sin tener que hacer la operacin de potencia y usando
resultados intermedios razonablemente pequeos.
Una recurrencia lineal del tipo fn = a1fn-1 + + akfn-k . Con condiciones iniciales fk-1 = ck-1, , f0 = c0. Este
problema puede ser reducido al segundo puesto que la recurrencia puede ser rescrita como una
ecuacin matricial. As para el caso k = 3 la recurrencia de la forma fn = a1fn-1 + a2fn-2 + a3fn-3 con
condiciones iniciales f2 = c2, f1 = c1, f0 = c0, se puede rescribir como:
1
(1 ) = ( 1
0
2
2
0
1
3 1
0 ) (2 ) ,
0
3
1
= (1
0
2
0
1
3
0)
0
y su solucin es:
2
+2
(+1 ) = (1 ) ,
0
= 0, 1,
Veamos diferentes algoritmos recursivos e iterativos para resolver el problema. El primer algoritmo se basa en
las propiedades de la potencia entera: a0 = 1, an = aan-1. Estas propiedades permiten formular un algoritmo
recursivo y transformarlo a iterativo.
Versin No final ( n ) :
(, ) = {
1
(, 1)
=0
>0
114
( (, ))
2
(, ) =
( (, ))
2
{
=0
> 0 %2 = 0
> 0 %2 0
(, ) = {
(, ) ( (, ))
2
=0
>0
%2 = 0 ( )
%2 0 ( )
Con respecto a la versin inicial, aplicando la ecuacin de recurrencia: T(n) = aT(n/b) + g(n) con a = 1,
grado de g(n) = 0 (constante). El grado de complejidad resultante es: T(n) (log n), que es menor que
el anterior.
() = (2) + [0]
( ) + [1]
{
2
1
> 1 %2 = 0
> 1 %2 0
() = {
[]
( ) + ()
2
[0]
[1]
1
>1
%2 = 0 ( )
%2 0 ( )
Donde +L es el operador de concatenacin de listas, y [n] una lista unitaria que contiene un entero con
un solo dgito. La justificacin del algoritmo se basa en la propiedad de la representacin binaria:
val( r + [d] ) = val(r) * 2 + d
Es decir, a una representacin binaria se le aade (por la derecha) un dgito (0 o 1), el nuevo valor se ha
multiplicado por 2 ms el dgito. Usando esa propiedad vemos que:
( ( ) + ()) = ( ( )) 2 + () = (())
2
2
115
Ahora transformemos la solucin recursiva no final a otra final. Vemos que la funcin de composicin es:
c(n, r) = r+Lf(n).El operador es asociativo, con elemento neutro pero no conmutativo. El esquema transformado
con c(n, r) = f(n)+Lr es:
() = (, [])
(, )
( , (, ))
2
()+
( , ()+ )
2
(, ) = {
>1
o de forma expandida:
(, ) = {
>1
Observemos ahora que la secuencia de problemas es de la forma = [, 2 , 4 , , 0/1]. Siendo |s| = k+1 y la
representacin binaria de n la lista de dgitos [dk, , d1, d0], donde di = 0/1, se cumple por una parte:
n = d020 + d121 + + dk2k
Y por otra se puede extraer de cualquiera de los dos algoritmos que di = f(s[i]). Con estos elementos podemos
volver al problema de la potencia entera para encontrar una versin recursiva.
Observemos que:
= 0 2
0 + 21 ++ 2
1
0 0
1 1
= (2 ) ( 2 )
( 2 )
+1
= 2 2 = ( 2 ) .
Por tanto, los valores entre parntesis pueden ser generados por la lista implcita [f1(r) = r2, a, ] y los valores
de n por la lista implcita [f2(n) = n/2, n, n 1].
0 0
(, , , ) = (, , 2 , (, ))
2
(, , , ) = = 0
1 %2 = 0
(, ) = {
%2 = 1
(, , 2 , (, ))
2
=0
>0
3.4.2 Fibonacci.
El caso de problema de Fibonacci se transforma, por el procedimiento anteriormente visto, en:
1
( +1 ) = (
1
)( ),
0 1
=(
1
1
1
),
0
+1
1
) = ( )
1
( +1 ) = (
1
)( ),
0 1
1
( +1 ) = ( ) ,
=(
1
( 1 ) = ( ) ,
0
0
1
1
1
),
0
= . (0,0)
Las matrices Mm, son denominadas Matrices de Fibonacci. Son de la forma ( + ). Es decir, dependen de
dos parmetros a, b. Esto es debido al hecho de que cualquier matriz cumple su ecuacin caracterstica y por
tanto las potencias de una matriz M de tamao k se pueden expresar mediante k parmetros de la forma:
Mm = ak-1Mk-1 + ak-2Mk-2 + + a0M0
Debemos tener en cuenta, adems, las siguientes propiedades:
2
+ 2
2 + 2 2 + 2)
(
) = (2 +
2
+ 2
2 + 2
+ ( + )( + ) + +
+
+
(
)(
) =(
)
+
+
1 1
= (
)
1 0
1 0
=(
)
0 1
Para resolver el problema hay que modelar el tipo Matriz de Fibonacci. Un valor de este tipo podemos
implementarlo por las propiedades (a, b) y los mtodos:
117
MaxMin
Tcnica:
Tamao:
N=ji
Propiedades Compartidas:
d, List<E>
ord, Comparator<E>
Propiedades Individuales
Solucin:
(M, n)
Instanciacin:
(, ) = {
|| = 0
(0, . ()) || 0
Problema generalizado:
(, )
(, )
(, ) = {
((, ), (, ))
=
=1
=2
>2
= = []
= max(, [], []) , = min(, [], [])
+
2
Recurrencia:
() = 2 ( ) +
2
Complejidad:
(log2 2 ) = ()
Como vemos la complejidad asinttica resultante es la misma que para el clculo iterativo equivalente. El
algoritmo lo podemos implementar de forma directa.
118
Bsqueda Binaria
Tcnica:
Tamao:
N=ji
Propiedades Compartidas:
d, List<E>
ord, Comparator<E>
elto, E
Propiedades Individuales
Solucin:
Instanciacin:
(, , ) = (0, . ())
Problema generalizado:
1
(, ) =
(, )
{ ( + 1, )
=
= []
< []
> []
+
2
Recurrencia:
() = ( ) +
2
Complejidad:
(log )
119
public static <E extends Comparable<? super E>> int binarySearch(List<E> lista, E key) {
Ordering<E> ord = Ordering.natural();
return bSearch(lista, 0, lista.size(), key, ord);
}
public static <E> int binarySearch(List<E> lista, E key, Comparator<? super E> cmp) {
Ordering<? super E> ord = Ordering.from(cmp);
return bSearch(lista, 0, lista.size(), key, ord);
}
private static <E> int bSearch(List<E>
Preconditions.checkArgument(j >=
int r;
int k;
if (j - i == 0) {
r = -i - 1;
} else {
k = (i + j) / 2;
int r1 = ord.compare(key,
if (r1 == 0) {
r = k;
} else if (r1 < 0) {
r = bSearch(lista,
} else {
r = bSearch(lista,
}
}
return r;
}
lista.get(k));
i, k, key, ord);
k + 1, j, key, ord);
Por una parte es conveniente hacer notar la conversin del estilo funcional de la ficha al imperativo del cdigo,
por otra, la generalizacin adicional para tener en cuenta el orden natural o un posible orden alternativo.
120
Para simplificar, supongamos que las propiedades del problema original son una lista dt de enteros, su tamao
n y un entero p que es el pivote. Es decir el problema original es (dt, n, p) y la solucin buscada (r1, r2). Para que
podamos usarlo en algoritmos posteriores vamos a resolver el problema ms general de la Bandera Holandesa
sobre un intervalo de la lista de enteros. El intervalo lo representamos por (i, j). El problema completo es (dt, n,
i, j, p), y el dominio que define los problemas de inters es:
D(dt, n, i, j, p) i 0 i < n j i j < n n 0
Para el diseo del algoritmo lo vamos a generalizar aadiendo los parmetros a, b, c. El problema generalizado
es (dt, n, i, j, a, b, c, p) con un invariante que viene reflejado en el siguiente grfico:
121
Es decir los valores de las casillas en [0, a) son menores que el pivote, los contenidos en [a, b) iguales al pivote,
los contenidos en [b, c) no estn definidos y los contenidos en [c, j) son mayores que le pivote. Con estas ideas
el invariante es:
(, , , , , , , ) ( [] < ) ( [] = ) ( [] > )
[0,)
[,)
[,)
Escogemos como tamao del problema el nmero de elementos en el tercer segmento, es decir, t(dt, n, i, j, a, b,
c, p) = c - b. El problema final es cuando el tamao es cero, cuando c = b. Con estas ideas vamos buscando
transformaciones (paso de un problema generalizado al siguiente), que manteniendo el invariante, vayan
disminuyendo el tamao. Si mantenemos el invariante cuando el tamao sea cero habremos conseguido el
objetivo que buscbamos.
Ahora nos falta un problema inicial que estando dentro del dominio cumpla el invariante. Una eleccin posible
para el estado inicial (conjunto de valores para las propiedades individuales del problema) es (a, b, c) = (i, i, j).
Esta eleccin es debida a que al empezar no tenemos informacin sobre los valores de la lista. Estn vacos los
segmentos 1, 2 y 4. Todos los elementos estn ubicados en el segmento 3. Ya podemos hacer un esqueleto del
algoritmo.
El prototipo de la funcin que queremos disear es en C:
void reordena3(int * dt, int i, int, j, int p, int * r1, int * r2);
Nos falta disear las transiciones para que se mantenga el invariante y disminuya el tamao. Para reducir el
tamao podemos considerar el valor en la casilla b, intentar recolocar el valor, aumentar el valor de en una
unidad y posteriormente actualizar los correspondientes valores de a y b. Segn el valor de la casilla en b
tendremos tres casos posibles: el valor en esa casilla sea menor, igual, o mayor que el pivote. Veamos qu hacer
en cada caso.
Usaremos la funcin que intercambia dos casillas en una lista. La funcin toma una lista y dos enteros y devuelve
otra lista. La definicin de la funcin es:
[]
(, , )[] =
[]
{ []
[, ) [, )
=
=
122
[] <
[] =
[] >
Veamos por qu se mantiene el invariante despus de pasar de un problema al siguiente definido por la funcin
sp1() anterior. Asumimos que se cumple el invariante previamente. En el primer caso debemos tener en cuenta
que en la posicin a haba un valor igual al pivote y se intercambia por un valor menor que el pivote. Al aumentar
los valores de a y b queda restablecido el invariante. En el segundo por ser b igual al pivote es suficiente con
incrementar su valor. En el tercero se colca el valor de la posicin b al principio del ltimo bloque. El valor en esa
posicin se mueve al bloque de los elementos sin informacin.
Si llamamos ro(dt, n, i, j, a, b, c, p) la solucin del problema generalizado y bh(dt, n, i, j, p) la del problema original
tenemos:
1 (, , , , ) = 1 (, , , , , , , )
1 (, , , , , , , ) = {
(, )
1 (1 (, , , , , , , ))
= 0
>0
() ()
El algoritmo en C es:
void bh1(int * dt, int i, int j, int p, int * r1, int * r2) {
int a, b, c;
a = i;
b = i;
c = j;
while (c - b > 0) {
if (dt[b] < p) {
intercambia(dt, a, b);
a++;
b++;
} else if (dt[b] == p) {
b++;
} else {
intercambia(dt, b, c - 1);
c--;
}
}
*r1 = a;
*r2 = b;
}
[,)
1 (, + 1, )
1 ((, , 1), , 1)
[] <
[]
=0
2 (, , , , , , ) = {
1 (2 (, , , , , , )) > 0
Bandera Holandesa
Tcnica:
Tamao:
N=ji
Propiedades Compartidas:
d, List<E>
ord, Comparator<E>
p, entero
i, entero en [0, d.size())
j, entero en (i, d.size()]
(r1, r2) entero en [0, d.size()]
Propiedades Individuales
Solucin:
Instanciacin:
(, ) = {
= (, , )
(, , , )
Problema generalizado:
(, )
0
(, )
[ =+1 ]
[] <
(, , , ) =
=+1
[ =+1 ]
[] =
(, 1)
] [] >
{[
=1
(, ) (, )
124
Recurrencia:
() = ( 1) +
Complejidad:
()
El conjunto de problemas tiene dos propiedades comunes: d (de tipo List<E>) que contiene la lista a ordenar, ord
(de tipo Comparator<E>) que contiene un orden con respeto al cual ordenar. Adems cada problema tiene las
dos propiedades individuales i, entero en [0, d.size()] y j, entero en [i, d.size()]. En este conjunto de problemas,
dos problemas de este tipo son iguales si tienen iguales la propiedad i y j.
El algoritmo usa alguna de las dos versiones del algoritmo de la Bandera Holandesa anterior, en concreto
usaremos la versin 2. El algoritmo generalizado escoge un pivote, reordena las casillas de la lista segn el pivote
(usando el algoritmo de la Bandera Holandesa) y posteriormente pasa a reordenar el bosque de los menores al
pivote y luego el de los mayores o iguales a l.
En lo que para cada algoritmo indicamos sus parmetros de entrada y de salida (que pueden ser varios). Algunos
parmetros, como el orden, los obviamos. Para plantear el algoritmo necesitamos varios algoritmos previos:
e ep(d, i, j): Elige aleatoriamente un elemento de la sublista d definida por el intervalo [i, j).
Esencialmente consiste en elegir aleatoriamente un nmero entero r en el intervalo [i, j) y devolver el
elemento que se encuentra en esa posicin en la lista d. El elemento devuelto se le suele denominar
pivote y el mtodo, elegirPivote. Este mtodo se ejecuta en tiempo (1).
(d1, k) re2(d, i, j, e): Reordena la sublista definida por el intervalo [i, j), alrededor del pivote e. Esto quiere
decir que deja todos los elementos menores que el pivote a su izquierda y los mayores o iguales a su
derecha. El mtodo devuelve la sublista reordenada y posicin del pivote. Este algoritmo puede ser
implementado iterativamente con complejidad (n) segn vimos anteriormente.
d1 = sb(d, i, j): Ordena por algn mtodo iterativo conocido (como el de ordenacin por insercin) la
sublista definida por el intervalo [i, j). Tiene una complejidad de (n2).
125
Tamao:
N=ji
Propiedades Compartidas:
d, List<E>
ord, Comparator<E>
Propiedades Individuales
Solucin:
List<E>
Instanciacin:
() = (, 0, . ())
Problema generalizado:
(, , ) = {
(, , )
(2 , , )
< 0
> 0
= (, , )
(1 , , ) = 1 (, , , )
2 = (1 , , )
Recurrencia:
1
() = + (() + ( ))
=0
Complejidad:
( log )
La ecuacin de recurrencia resultante busca el tiempo medio del algoritmo suponiendo iguales probabilidades
para cada una de las posibles ubicaciones del pivote. El pivote puede caer en cualquier posicin de 0 a n-1.
Reordenar la sublista tiene una complejidad (n). La ecuacin puede ser resuelta numricamente.
Hay otras posibilidades de implementacin del mtodo ep(). Es decir, hay otras formas de elegir el pivote pero
esta implementacin es sencilla y el algoritmo resultante tiene una complejidad media de (n log n)
independientemente del caso a resolver.
public static void quickSort(int[] a, int prim, int ult) {
if (prim < ult) {
int p = pivote(a, prim, ult); // Genero subconjuntos y se aplica recursin sobre ellos
if (p > prim) { quickSort(a, prim, p - 1); }
if (p < ult) { quickSort(a, p + 1, ult); }
} // Caso base prim=ult
}
// Devuelve la posicin del pivote, elemento que por la izqda. slo tiene elementos de valor inferior y por
// la derecha de valor superior. Colocar los elementos a der. o izq. del pivote segn su valor.
private static int pivote(int[] a, int prim, int ult) {
int i = prim + 1;
int l = ult;
while (a[i] <= a[prim] && i < ult) { i++; }
while (a[l] > a[prim]
) { l--; }
while (i < l) {
intercambia(a, i, l);
while (a[i] <= a[prim]) { i++; }
while (a[l] > a[prim]) { l--; }
}
intercambia(a, prim, l);
return l;
}
126
Aqu, las funciones Ordering2.ordenaBase usa algn mtodo bsico para ordenar un problema pequeo y la
funcin List2.reordenaMedianteBanderaHolandesa reordena segn la primera versin de la Bandera Holandesa.
La lista a reordenar, al ser un tipo mutable en Java, se ha convertido en un parmetro de entrada salida, por eso
el algoritmo devuelve void.
Java
/* recursion */
if (left < j)
quickSort(arr, left, j);
if (i < right)
quickSort(arr, i, right);
}
return i;
}
127
En el siguiente ejemplo se marcan el pivote y los ndices i y j con las letras p, i y j respectivamente.
5 - 3 - 7 - 6 - 2 - 1 - 4
p
5 - 3 - 7 - 6 - 2 - 1 - 4
i
1 - 3 - 7 - 6 - 2 - 5 - 4
i
1 - 3 - 2 - 6 - 7 - 5 - 4
i
1 - 3 - 2 - 6 - 7 - 5 - 4
iyj
1 - 3 - 7 - 6 - 2 - 5 - 4
i
1 - 3 - 7 - 6 - 2 - 5 - 4
i
1 - 3 - 2 - 4 - 7 - 5 - 6
p
1 - 3 - 2
1 - 2 - 3
1 - 2 - 3 - 4 - 5 - 6 - 7
128
Tamao:
N=ji
Propiedades Compartidas:
d, List<E>
ord, Comparator<E>
Propiedades Individuales
Solucin:
List<E>
Instanciacin:
(, ) = (, 0, . (), )
Problema generalizado:
(, , , ) = {
=
(, )
(2 , , , , 2 )
< 0
> 0
+
2
(1 , 1 ) = (, , , )
(2 , 2 ) = (1 , , , )
Recurrencia:
() = 2 ( ) + ()
2
Complejidad:
( log )
129
public static <E extends Comparable<? super E>> void mergeSort(List<E> lista) {
Ordering<? super E> ord = Ordering.natural();
List<E> ls = Lists.newArrayList(lista);
mgSort(lista, 0, lista.size(), ord, ls);
}
public static <E> void mergeSort(List<E> lista, Comparator<? super E> cmp) {
Ordering<? super E> ord = Ordering.from(cmp);
List<E> ls = Lists.newArrayList(lista);
mgSort(lista, 0, lista.size(), ord, ls);
}
private static <E> void mgSort(List<E> lista, int i, int j, Ordering<? super E> ord, List<E> ls) {
if (j - i > 1) {
int k = (j + i) / 2;
mgSort(lista, i, k, ord, ls);
mgSort(lista, k, j, ord, ls);
mezcla(lista, i, k, lista, k, j, ls, i, j, ord);
copia(lista, i, j, ls);
}
}
private static <E> void mezcla(List<E> l1, int i1, int j1, List<E> l2, int i2, int j2, List<E> l3,
int i3, int j3, Ordering<? super E> ord) {
int k1 = i1;
int k2 = i2;
int k3 = i3;
while (k3 < j3) {
if (k1 < j1 && k2 < j2) {
if (ord.compare(l1.get(k1), l2.get(k2)) <= 0) {
l3.set(k3, l1.get(k1));
k1++;
k3++;
} else {
l3.set(k3, l2.get(k2));
k2++;
k3++;
}
} else if (k2 == j2) {
l3.set(k3, l1.get(k1));
k1++;
k3++;
} else {
l3.set(k3, l2.get(k2));
k2++;
k3++;
}
}
}
private static <E> void copia(List<E> lista, int i, int j, List<E> ls) {
for (int k = i; k < j; k++) {
lista.set(k, ls.get(k));
}
}
130
Tamao:
N=ji
Propiedades Compartidas:
d, List<E>
ord, Comparator<E>
k, entero en [0, d.size())
Propiedades Individuales
Solucin:
elemento, E
Instanciacin:
(, , ) = (, , . ())
Problema generalizado:
[]
[, 1]
(, , , ) = { ( , , ) <
( , , )
= (, , )
( , , ) = 1 (, , , )
Recurrencia:
1
() = + ()
=0
Complejidad:
()
131
Asumimos que a puede caer en cualquier posicin de [0, n) con probabilidad . Para cada valor de a asumimos
que, con igual probabilidad, k puede ser menor que a o mayor que a. La ecuacin de recurrencia se obtiene
teniendo en cuenta que:
1
=0
=0
1
(() + ( )) = ()
2
1
Casos particulares de este algoritmo son el clculo de la mediana (2 simo elemento), primer cuartil (4 simo
elemento), etc.
132
= 1 2 ,
= (1 + 1 )(2 + 2 ),
= 1 2 + 1 2
= 102 + 10 ( ) +
El entero que hemos modelado tiene las operaciones es, ei y 10ke, con una complejidad (1) y la operacin _+_
con una complejidad (n). A partir de las ideas anteriores podemos disear dos algoritmos: el primero basado
en el caso 1 y el segundo basado en el caso 2. Si el tamao del entero e1 es n1 y el de e2 es n2, entonces el tamao
1
del problema de la multiplicacin de ambos enteros es: n = max(n1, n2). Sea, a su vez, k = 2.
En el caso 1, el problema se divide en cuatro sub-problemas, siendo cada problema de tamao mitad y la funcin
que combina las soluciones de la forma:
(1 , 2 , 3 , 4 ) = 102 1 + 10 (2 + 3 ) + 4
Cuya complejidad es (n) al ser de este orden la suma de enteros y de orden constante la multiplicacin
por potencias de 10. La recurrencia para el tiempo de ejecucin del algoritmo es para el primer caso:
() = 4 ( ) + (n),
2
T(n) (2 )
En el caso 2 el problema se divide en tres sub-problemas de tamao mitad y donde la funcin de combinacin
de las soluciones es:
(1 , 2 , 3 ) = 102 1 + 10 (2 1 3 ) + 3
() = 3 ( ) + (n),
2
El caso 2 es la que tiene complejidad ms baja. En la literatura, el caso 2 se le llama Algoritmo de Karatsuba.
Los detalles de ese algoritmo se muestran en la ficha siguiente:
133
Tamao:
N = max(n1, n2)
Propiedades Compartidas:
Propiedades Individuales
e1, entero
e2, entero
Solucin:
Entero
(1 , 2 ) = {
Recurrencia:
Complejidad:
0 (1 , 2 )
< 0
((1 , 2 ), (1 , 2 ), (1 , 2 ))
(1 , 2 , 3 ) = 102 1 + 10 (2 1 3 ) + 3
() = 3 ( ) + ()
2
(158 )
Para resolver el problema debemos modelar el entero grande. Esto lo podemos hacer con el tipo EnteroGrande
cuya interfaz es:
class EnteroGrande {
int size();
int getK();
void setK(int k);
EnteroGrande por10(int k);
EnteroGrande getS();
EnteroGrande getI();
EnteroGrande getA();
EnteroGrande getM(EnteroGrande e);
}
class Karatsuba {
private final static BigInteger ZERO = new BigInteger("0");
public static BigInteger karatsuba(BigInteger x, BigInteger y) {
// cutoff to brute force
int N = Math.max(x.bitLength(), y.bitLength());
if (N <= 2000) return x.multiply(y);
// optimize this parameter
// number of bits divided by 2, rounded up
N = (N / 2) + (N % 2);
// x = a + 2^N b,
y = c + 2^N d
BigInteger b = x.shiftRight(N);
BigInteger a = x.subtract(b.shiftLeft(N));
BigInteger d = y.shiftRight(N);
BigInteger c = y.subtract(d.shiftLeft(N));
// compute sub-expressions
BigInteger ac = karatsuba(a, c);
BigInteger bd = karatsuba(b, d);
BigInteger abcd = karatsuba(a.add(b), c.add(d));
return
ac.add(abcd.subtract(ac).subtract(bd).shiftLeft(N)).add(bd.shiftLeft(2*N));
}
134
start = System.currentTimeMillis();
BigInteger c = karatsuba(a, b);
stop = System.currentTimeMillis();
System.out.println(stop - start);
start = System.currentTimeMillis();
BigInteger d = a.multiply(b);
stop = System.currentTimeMillis();
System.out.println(stop - start);
System.out.println((c.equals(d)));
}
}
Tamao:
N=ji
Propiedades Compartidas:
d, List<E>
Propiedades Individuales
Solucin:
Instanciacin:
() = (, 0, . ())
Problema generalizado:
(, , ) = {
=
(, , [])
((, , ), (, , ), (, , , ))
=1
>1
+
2
(1 , 2 , 3 ) = (1 , 2 , 3 )
Recurrencia:
() = 2 ( ) + ()
2
Complejidad:
( log )
135
El algoritmo anterior calcula la subsecuencia de suma mxima centrada en la casilla mitad k. Es decir, una
subsecuencia cuyos lmites contienen a k.
Para un diseo iterativo el problema original, definido por (i, j, k) se generaliza a (i, j, k, suma, from, to,
sumaMaxima, i1, j1). Donde la variable suma mantiene la suma de las posiciones recorridas y las variables from,
to y sumaMaxima son los datos de la subsecuencia de suma mxima calculada. El algoritmo mantiene el
invariante que la subsecuencia definida por los lmites (from, to) es la subsecuencia centrada de suma mxima
de la sublista (i1, j1).
El problema generalizado se inicializa a (i, j, k, 0., k, k, -, k, k). El algoritmo tiene por tanto, complejidad lineal.
136
Tamao:
N=ji
Propiedades Compartidas:
ps, Set<Punto>
px, List<Punto>
Propiedades Individuales
py, List<Punto>
i, entero en [0, p.size())
j, entero en (i, p.size()]
Solucin:
(p1, p2) Sea d(p1, p2) la distancia entre los puntos y la relacin de orden
definida por esa distancia.
Instanciacin:
() = ( (), (),0, . ())
Problema generalizado:
(, , , ) = {
=
(, , )
(, )
<4
4
+
2
1 = (, (, < []), , )
2 = (, (, []), , )
= min(1 , 2 )
= (, | < []| < ())
Recurrencia:
() = 2 ( ) + ()
2
Complejidad:
( log )
137
La solucin del caso base la hacemos con el algoritmo mcb. Este busca el par ms cercano de conjunto de puntos
por un mtodo de fuerza bruta. Este algoritmo, como otros de este tipo, se hace recorriendo todos los pares (i,
j) posibles. Es decir todos los valores i, j que cumplen 0 i < j px.size(), y quedndonos con aquel para cuya
distancia sea menor.
Hemos representado por px la propiedad correspondiente de un punto y por filter(py, px < px[k]), donde k es la
mitad, la construccin de una nueva lista filtrando otra mediante un predicado.
El caso recursivo con propiedades i, j se parte en dos problemas del mismo tamao i, k y k, j. Sean s1, s2 las
soluciones de los dos subproblemas. El algoritmo mcc(s, pyC) busca el par de puntos con menor distancia posible
entre los que estn en pyC. Este algoritmo se puede implementar con complejidad constante. Esto es debido a
que para cada punto en pyC los puntos ms cercanos a l que d = d(s) estn en un rectngulo de base 2d y altura
d, como puede ser comprobado.
ParDePuntos s
List<Punto> yCentral
Ordering<ParDePuntos> ordNatural
{
138
Clculo de Maximales
Tcnica:
Tamao:
N=ji
Propiedades Compartidas:
ps, List<Punto>
Propiedades Individuales
i, Integer
j, Integer
Solucin:
Set<Punto>
Instanciacin:
() = ( (), 0, . ())
Problema generalizado:
(, , ) = {
(, , )
<2
((, , ), (, , )) 2
+
2
Recurrencia:
() = 2 ( ) + ()
2
Complejidad:
( log )
La solucin del caso base es el conjunto formado por el nico punto del problema que ser maximal. Si situamos
el umbral en un valor mayor que 2 entonces podemos obtener la solucin del caso base por un mtodo de fuerza
bruta: comprobamos para cada punto si es dominado por alguno de los dems. Si no es dominado por ninguno
lo aadimos al conjunto solucin de puntos maximales. Esto puede hacerse con una complejidad de (n2).
El caso recursivo los resolvemos partiendo el problema en dos subproblemas del mismo tamao y combinado
los dos conjuntos de puntos maximales.
Para hacer la combinacin debemos partir de que los puntos maximales del subproblema derecho no pueden
ser dominados por ninguno de los del izquierdo. Luego todos son maximales del problema. La solucin consiste
en aadir a los puntos maximales del problema derecho los maximales del problema izquierdo que tenga un
valor de Y mayor que el mayor valor de Y de todos los maximales del problema derecho.
139
La implementacin puede hacerse en tiempo lineal. Primero recorremos los maximales del problema derecho,
los aadimos a la solucin, y calculamos ym (el mayor valor de sus coordenadas Y). Posteriormente aadimos a
la solucin los maximales del problema izquierdo cuyos valores de Y sean mayores que ym.
140
Ejercicio 2.
Sea una imagen representada en una matriz cuadrada de tamao 2kx2k, siendo k > 0. Cada elemento de
la matriz, representa un pxel, y puede tomar valores 0 para blanco y 1 para negro, que representa el
color de dicho cuadrado. Disee utilizando la tcnica de Divide y Vencers, un algoritmo para rotar 90
en el sentido de las agujas del reloj una imagen cualquiera. Calcule la complejidad asinttica del
algoritmo, expresndola mediante la notacin , en funcin del nmero de pxeles de la imagen, n.
Ejercicio 3.
Una matriz de rastreo es una matriz cuadrada de n x n nmeros enteros (sin repeticiones), siendo n una
potencia de 2, y en la que se cumple que a1 < b1 < a2 < b2 < a3 < b3 < a4 < b4 y donde adems las cuatro
submatrices componentes son tambin matrices de rastreo (vase la figura). Codificar un programa
utilizando la tcnica de Divide y Vencers que determine si una matriz es o no de rastreo.
Nota: Estas matrices tienen la ventaja de que permiten
bsquedas muy eficientes (de orden logartmico en vez de
cuadrtico). Por ejemplo, una matriz de rastreo de orden 4
podra ser:
141
Ejercicio 4.
En una empresa de fabricacin de vestidos, se dispone de una barra donde se encuentran colgados N
vestidos que corresponden a M modelos (M<<N) (M desconocido), se trata de disear un algoritmo
mediante la tcnica de Divide y Vencers que encuentre el modelo que ms se repite y el nmero de
veces que se repite. La informacin de entrada es un array donde vienen los cdigos de barras (entero)
de los vestidos.
Ejercicio 5.
Dado un array de N movimientos de una cuenta corriente, donde se recogen las fechas y la cantidad en
euros de los ingresos y reintegros:
2/03/04
600.3
5/07/04
-750
9/08/04
1100
16/09/04
-230
13/10/04
420.50
4/11/04
-30.00
27/12/04
-50.20
Si consideramos que saldo parcial de un periodo de tiempo es la suma de ingresos y reintegros durante
ese periodo, disea un algoritmo de Divide y Vencers que determine cul es el saldo parcial mximo de
los posibles periodos a considerar para esa cuenta. En el ejemplo este valor mximo sera 1100-230+420.5
euros. Determinar el orden de complejidad de dicho algoritmo.
142
Utilice la tcnica de Divide y Vencers para resolver el problema de ordenacin de listas mediante el mtodo
QuickSort. Dicha tcnica se basa en la eleccin de un pivote de manera que las dos sub-listas resultantes de
dividir el conjunto original en base a dicho pivote, se ordenarn siguiendo el criterio dado por el comparador, el
cual se supone una propiedad compartida del problema. La lista de datos a ordenar ser declarada como otra
propiedad compartida al igual que el umbral.
Para plantear el algoritmo se suponen implementadas las siguientes funciones estticas (No tiene que
implementar ninguna de ellas), las cuales se encuentran en la clase Utiles:
List<E>
Comparator<E>
Integer
Integer
Integer
datos
comparator
i
j
umbral
Se pide:
1. Complete la ficha que se adjunta en la que se ilustra el comportamiento del algoritmo de bsqueda
mediante la tcnica QuickSort
2. Implementar los siguientes mtodos de la clase ProblemaQuickSortImpl:
a.
b.
c.
d.
e.
f.
Nota:
No est permitido crear listas auxiliares. Se deber trabajar con la propiedad Datos. Como regla general,
los intervalos sern cerrados al inicio y abiertos al final [i, j).
143
Tamao:
N=ji
Propiedades Compartidas:
Propiedades Individuales
Solucin:
Instanciacin:
(, ) = (0, . ())
Problema generalizado:
= (, )
= (, , )
(, ) =
(, )
{ [( + 1, ) ]
>
Recurrencia:
1
() = 1 + (( 1) + ( ))
=0
Complejidad:
( log )
Tamao:
N=ji
Propiedades Compartidas:
Datos, List<E>
Orden, Comparator<E>
Umbral, Integer
Propiedades Individuales
Solucin:
Instanciacin:
List<E>
(, ) = (0, . ())
Problema generalizado:
(, )
= (, )
= (, , )
(, ) =
(, )
{ [( + 1, ) ]
1
Recurrencia:
144
>
1
() = 1 + (( 1) + ( ))
=0
Complejidad:
( log )
Apartado b)
public boolean esCasoBase() {
return (getJ() - getI()) <= getUmbral();
}
Apartado c)
public List<E> getSolucionCasoBase() {
Utiles.sb(datos, i, j, comparator);
return datos;
}
Apartado d)
public int getNumeroSubProblemas() {
return 2;
}
Apartado e)
public ProblemaDyV<List<E>> getSubProblema(int ind) {
ProblemaQuickSort<E> pqs;
E pivote;
Integer k;
pivote = Utiles.pe(datos, i, j);
k = Utiles.rs(datos, pivote, i, j, comparator);
switch (ind) {
case 0:
pqs = new ProblemaQuickSortImpl<E>(datos, comparator, i, k);
break;
case 1:
pqs = new ProblemaQuickSortImpl<E>(datos, comparator, k, j);
break;
default:
throw new SituacionIlegal("Solo hay dos casos!");
}
return pqs;
}
public List<E> combinaSoluciones(List<E>... soluciones) {
return datos;
}
145
3.6.2 Detectar cuntos nmeros aparecen slo una vez en una lista.
Dada una lista no ordenada de nmeros enteros, disee utilizando la tcnica de divide y vencers, un algoritmo
que determine cuantos nmeros aparecen slo una vez en la lista.
Por ejemplo, dada la siguiente lista de nmeros enteros como entrada, el algoritmo devolvera 2 ya que el
nmero 3 y 4 slo aparecen una vez en la lista.
9
de la bandera holandesa para la lista de nmeros de enteros pasada como parmetro para un pivote p entre
los ndices i y j. Dicho mtodo devuelve una lista de enteros de tamao dos que contiene los ndices que
indican las fronteras de los elementos menores, iguales y mayores que el pivote. Por ejemplo, para el caso
anterior, si hicieramos:
List<Integer> solBH = new ArrayList<Integer>();
p = elegirPivote(numeros, 0, numeros.size());
// si por ejemplo p fuera 5
solBH = bh(nmeros, p, 0, nmeros.size());
Se pide:
1. Rellenar la siguiente ficha
2. Disear utilizando la tcnica de divide y vencers los siguientes mtodos:
a. private
Integer
dYV(ProblemaDyV<Integer>
p)
(de
la
clase
AlgoritmoDivideYVencerasSinMemoria<Integer> ). Dicho mtodo permite obtener la solucin a la
tcnica Divide y Vencers sin memoria.
a. public Boolean esCasoBase() (de la clase ProblemaDyV<Integer> ). Indica si el problema que se
est resolviendo es tan simple que no necesita recursin para su resolucin.
b. public Integer getSolucionCasoBase() (de la clase ProblemaDyV<Integer> ). Devuelve la solucin
al problema, resuelto de forma directa.
c. public int getNumeroSubProblemas() (de la clase ProblemaDyV<Integer> ). Indica el nmero de
subproblemas.
d. public ProblemaDyV getSubproblema(int i) (de la clase ProblemaDyV<Integer> ). Devuelve el
subproblema isimo en el que se descompone el problema que se est resolviendo.
e. public Integer combinaSoluciones (Integer[] soluciones) (de la clase ProblemaDyV<Integer>).
Combina cada solucin de la lista de soluciones de manera que juntas resuelvan el problema
completo.
146
Tamao:
N=ji
Propiedades Compartidas:
numeros, List<E>
umbral, Integer
Propiedades Individuales
Solucin:
Instanciacin:
Integer
() = (, 0, . ())
Problema generalizado:
(, , ) =
(, , )
= (, , )
(1, 2) = (, , , )
=0
(2 1 1){ = 1 }
{ [(, , 1) + + (, 2, )]
>
Recurrencia:
1
() = 1 + (( 1) + ( ))
=0
Complejidad:
( log )
147
#include <stdio.h>
int numeros[] = { 9, 9, 1, 1, 8, 8, 3, 5, 5, 4 };
const int TAM = 10;
int main() {
int n = numerosUnaVez();
printf("Existen %d numeros que aparecen una sola vez en el array", n);
scanf("%d", &n);
return 0;
}
int numerosUnaVez() {
int n = numerosUnaVezAux(0, TAM);
return n;
}
int numerosUnaVezAux(int i, int j) {
int r1, r2, p;
int r = 0;
if (j - i <= umbral) {
r = numerosUnaVezBase(nmeros, i, j);
} else {
if (j - i == 0) {
r = 0;
} else {
p = elegirPivote(i, j);
banderaHolandesa(p, i, j, &r1, &r2);
if (r2 - r1 == 1) {
r = r + 1;
}
r = r + numerosUnaVezAux(i, r1) + numerosUnaVezAux(r2, j);
}
}
return r;
}
int elegirPivote(int i, int j) {
int k = (i + j) / 2;
return numeros[k];
}
void intercambia(int i, int j) {
int aux = numeros[i];
numeros[i] = numeros[j];
numeros[j] = aux;
}
int banderaHolandesa(int p, int i, int j, int * r1, int * r2) {
int a, b, c;
a = i;
b = i;
c = j;
while (c - b > 0) {
if (numeros[b] < p) {
intercambia(a, b);
a++;
b++;
} else if (numeros[b] == p) {
b++;
} else {
intercambia(b, c - 1);
c--;
}
}
*r1 = a;
*r2 = b;
}
public Integer numerosUnaVezBase(int i int j) {
quicksort(numeros,i,j);
if (j - i == 1) { //1 elemento
r = 1;
} else if ((j - i == 2) && (a[i] != a[j-1])) { //2 elementos
r = 2;
} else { //al menos 3 elementos
if (a[i] != a[i+1]) {
r++;
}
for (int k = i+1; k <= j-2; k++) {
if (a[k] != a[k-1] && a[k]!=a[k+1]) {
r++;
}
}
if (a[j-2] != a[j-1]) {
r++;
}
}
}
148
El departamento de software de una empresa ha recibido el encargo de realizar un programa que permita
procesar una imagen generada por un Georadar. La imagen recibida consta de diferentes puntos que pueden
tomar diferentes valores. Cada imagen recibida es cuadrada, y adems para cada una se conocen las
coordenadas X e Y que determinan la posicin del punto superior izquierdo de la imagen, y la longitud, que
determina el nmero entero de pxeles del lado del cuadrado que forma la imagen. El lado de la imagen a
procesar es siempre una potencia de 2.
El objetivo del encargo es disear un algoritmo por la tcnica de divide y vencers que dada una imagen inicial y
una propiedad, devuelva una lista de puntos (List<Pixel> solucion) que deben cumplir la siguiente condicin:
La lista de puntos que forman la solucin incluye a todos los puntos que cumplen la propiedad establecida.
List<Pixel> busquedaPuntos(Imagen imagen, Propiedad propiedad)
de la clase Imagen.
Esta funcin comprueba si la propiedad recibida como parmetro se cumple para algn punto incluido en el
subconjunto de puntos delimitado por un cuadrado con size puntos de lado y cuyo punto superior izquierdo es
X e Y. Esta funcin es de orden constante.
Ejemplo: A continuacin se muestra una imagen y la solucin que se debe obtener, suponiendo que cada pixel
se representa con F si no cumple la propiedad y T si la cumple. En este ejemplo la llamada detector(1,1,1,
propiedad) devolvera falso, pero la llamada detector(0, 0, 2, propiedad) devolvera verdadero.
Para resolver este ejercicio suponga que las clases Imagen y Pixel tienen las siguientes propiedades accesibles
por mtodos get y set.
class Imagen {
int x, y, size;
}
class Pixel {
int x, y;
public Pixel(int x, int y) {}
}
Se pide:
1. Rellenar la siguiente ficha.
2. Disear en el lenguaje Java utilizando una funcin que utilice la tcnica de divide y vencers la funcin
que permite resolver el problema. El prototipo de dicha funcin es la indicada anteriormente.
149
Aparatado 1)
Bsqueda de puntos
Tcnica:
Tamao:
Propiedades Compartidas:
imagen, Imagen
propiedad, Propiedad
Propiedades Individuales
i, Integer
j, Integer
size, Integer
Solucin:
List<Pixel>
Instanciacin:
= . , = . , = .
Problema generalizado:
(, ) = (. , . , . )
(, , ) =
{}
! (, , )
{(, )}
(, , ) < 2
) + ( +
, ,
)+
2
2
2
{ [+ ( + + 2 , 2 ) + ( + 2 , + 2 , 2 )]
(, ,
(, , ) > 2
Recurrencia:
En el caso peor: () = 4 ( ) +
4
Complejidad:
En el caso peor: ()
Si slo 1 pixel cumple la propiedad: (log )
Apartado 2)
List<Pixel> busquedaPuntos(Imagen imagen, Propiedad propiedad) {
return dyv(imagen.x, imagen.y, imagen.size, imagen, propiedad);
}
List<Pixel> dyv(int x, int y, int size, Imagen imagen, Propiedad propiedad) {
List<Pixel> solucion = new LinkedList<Pixel>();
boolean existe = imagen.detector(x, y, size, propiedad);
if (existe) {
if (size < 2) {
solucion.add(new Pixel(x, y));
} else {
int newSize = size / 2;
solucion.addAll(dyv(x, y, newSize, imagen, propiedad));
solucion.addAll(dyv(x + newSize, y, newSize, imagen, propiedad));
solucion.addAll(dyv(x, y + newSize, newSize, imagen, propiedad));
solucion.addAll(dyv(x + newSize, y + newSize, newSize, imagen, propiedad));
}
}
return solucion;
}
150
1. Un tipo multimap puede contener valores repetidos asociados a una misma clave?
a) Nunca, si se inserta un valor repetido el nuevo remplaza al viejo.
b) Siempre, precisamente para eso sirve un multimap.
c) Es configurable por el usuario mediante su constructor.
d) El contrato / interfaz multimap deja dicho comportamiento a eleccin de la implementacin.
2. Para resolver un problema con el algoritmo A* es necesario implementar la interfaz WeightedVertexGraph.
Cmo se deben implementar los mtodos getVertexWeight?
a) Solo se implementar el nico mtodo que se use en el problema.
b) Se implementar un nico mtodo debiendo lanzar una excepcin el mtodo sin implementar para
poder descubrir fallos en el programa.
c) Se implementarn todos los mtodos. Los que no se usen en el problema deben devolver valores
que no alteren el clculo.
d) Ninguna de las anteriores.
3. Tal y como se ha visto en clase y en prcticas, el problema de la mochila, resuelto por cualquiera de las
tcnicas vistas, siempre tiene solucin?
a) No, porque puede haber objetos que no quepan en la mochila.
b) S, porque puede quedar espacio vaco en la mochila
c) No, porque no puede sobrar espacio vaco en la mochila
d) S, porque siempre hay objetos para llenar la mochila completamente
4. Durante la ejecucin de un algoritmo de backtracking, cuantas llamadas recursivas se producen
a) Tantas como alternativas haya en un instante determinado.
b) El nmero de llamadas recursivas se indica siempre en la ficha.
c) Tantas como distintas soluciones tenga.
d) BT no es recursiva.
5. Qu interpretacin tiene el hecho de que una funcin f(n) sea de orden lineal?
a) Significa que la funcin est especializada en pintar lneas.
b) Si se incrementa el tamao del problema, el tiempo que tarda la funcin aumenta muy poco
c) Si se incrementa el tamao del problema, el tiempo que tarda la funcin permanece constante.
d) Si se incrementa el tamao del problema, el tiempo que tarda la funcin aumenta en la misma
proporcin.
6. La recursividad mltiple es
a) Cuando una funcin recursiva tiene ms de un parmetro
b) Cuando una funcin recursiva se ejecuta varias veces.
c) Cuando en una llamada recursiva se invoca ms de una vez la propia funcin recursiva.
d) Cuando una funcin recursiva devuelve ms de un valor
7. Cul de las siguientes secuencias de rdenes de complejidad est ordenada de menor a mayor (menos a la
izquierda y mayor a la derecha)?
a) O(na) con a > 1, O(n log n), O(nn)
b) O(1000000000), O(log n), O(na) con a > 1, O(n!)
c) O(log n), O(1000000000), O(an) con a > 1, O(n!)
d) Ninguna de las anteriores.
151
8. Si un algoritmo tiene un orden n y duplicamos el tamao del problema que resuelve dicho algoritmo, qu
sucede?
a) El tiempo que el algoritmo tarda en resolver el problema se duplica.
b) El tiempo que el algoritmo tarda en resolver el problema aumenta ms del doble
c) El tiempo que el algoritmo tarda en resolver el problema aumenta menos del doble
d) El tiempo que el algoritmo tarda en resolver el problema aumenta, pero no se puede saber en qu
proporcin.
9. Cul es la misin de la memoria en un algoritmo de divide y vencers?
a) Asocia a cada problema la solucin calculada si dicho subproblema ya se solucion en un instante
pasado.
b) Hace que la codificacin del algoritmo sea ms sencilla porque ahorra lneas de cdigo.
c) Almacena todos los datos a partir de los cules se va partiendo los problemas
d) Almacena toda la informacin del problema
10. A la vista del siguiente grafo, cul sera el resultado de aplicar el
algoritmo de Kruskall para calcular el rbol de recubrimiento mnimo?
SOLUCIN:
1 - d, 2 - c, 3 - b, 4 - a, 5 - d, 6 - c, 7 - b, 8 - a, 9 - a, 10 - a
152
La sucesin de Jacobsthal-Lucas es la secuencia de nmeros enteros grandes P(n) definida por los siguientes
valores iniciales: P(0) = 2, P(1) = 1, y la siguiente relacin de recurrencia: P(n) = P(n-1)+2*P(n-2). Los primeros
valores de P(n) son: 2, 1, 5, 7, 17, 31, 65, 127, 257, 511, ...
Se pide:
a) Rellene la Ficha adjunta siguiendo la informacin del problema de Jacobsthal-Lucas detallado
anteriormente. Debe decidir cul es la mejor opcin para la tcnica de Divide y Vencers en este
problema concreto: con o sin memoria? Justifquelo brevemente.
b) Implemente los mtodos pertenecientes a la clase ProblemaJacobDyVImpl: para ello, aydese de los
diagramas UML proporcionados a continuacin.
a.
b.
Aparatado a)
Sucesin de Jacobsthal-Lucas
Tcnica:
Tamao:
Propiedades Compartidas:
M, memoria
propiedad, Propiedad
Propiedades Individuales
N, Integer no negativo
Solucin:
BigInteger
Instanciacin:
()
Problema generalizado:
()
2
() = {
1
( 1) + 2 ( 2)
=0
=1
>1
Recurrencia:
Complejidad:
154
() ()
b>
public boolean esCasoBase() {
return getN() <= 1;
}
c>
public BigInteger getSolucionCasoBase() {
BigInteger s;
if (getN().equals(0))
s = new BigInteger("2");
else
s = BigInteger.ONE;
return s;
}
d>
public int getNumeroSubProblemas() {
return 2;
}
e>
public ProblemaDyV<BigInteger> getSubProblema(int i) {
return new ProblemaJacobDyVImpl(getN() - i - 1);
}
f>
public BigInteger combinaSoluciones(BigInteger[] soluciones) {
BigInteger sol = soluciones[0].add(soluciones[1].multiply(new BigInteger("2")));
return sol;
}
155
La sucesin de Padovan es la secuencia de nmeros enteros grandes P(n) definida por los siguientes valores
iniciales: P(0) = P(1) = P(2) = 1, y la siguiente relacin de recurrencia: P(n) = P(n 2) + P(n 3), siendo los primeros
valores de P(n) son: 1, 1, 1, 2, 2, 3, 4, 5, 7, 9, 12, 16, 21, 28, 37,...
Se pide:
a) Rellene la ficha adjunta siguiendo la informacin del problema de Padovan detallado anteriormente.
Debe decidir cul es la mejor opcin para la tcnica de Divide y Vencers en este problema concreto:
con o sin memoria? Justifquelo brevemente.
b) Implemente los mtodos pertenecientes a la clase ProblemaPadovanDyVImpl: para ello, aydese de los
diagramas UML proporcionados a continuacin.
a. private BigInteger dYV(ProblemaDyV<BigInteger> p). Resuelve la tcnica Divide y Vencers.
b. public boolean esCasoBase(). Indica si el problema que se est resolviendo es tan simple que
no necesita recursin para su resolucin.
c. public BigInteger getSolucionCasoBase(). Devuelve la solucin al problema, resuelto de forma
directa.
d. public int getNumeroSubProblemas(). Indica el nmero de subproblemas.
e. public ProblemaDyV<BigInteger> getSubproblema(int i). Devuelve el subproblema isimo en
el que se descompone el problema que se est resolviendo.
f.
156
Tamao:
Propiedades Compartidas:
M, Memoria
Propiedades Individuales
N, Integer no negativo
Solucin:
BigInteger
()
Instanciacin:
Problema generalizado:
()
2
() = { 1
( 2) + ( 3) > 2
Recurrencia:
Complejidad:
() ()
157
Aparatado b)
a>
private BigInteger dYV(ProblemaDyV<S> p) {
BigInteger s;
if (map.containsKey(p)) {
s = map.get(p);
} else if (p.esCasoBase()) {
s = p.getSolucionCasoBase();
map.put(p, s);
} else {
int numeroDeSubProblemas = p.getNumeroSubProblemas();
S[] soluciones = Utiles.creaArray(numeroDeSubProblemas);
for (int i = 0; i < numeroDeSubProblemas; i++) {
ProblemaDyV<S> pr = p.getSubProblema(i);
s = dYV(pr);
soluciones[i] = s;
map.put(pr, s);
}
s = p.combinaSoluciones(soluciones);
map.put(p, s);
}
return s;
}
b>
public boolean esCasoBase() {
return getN() <= 2;
}
c>
public BigInteger getSolucionCasoBase() {
return BigInteger.ONE;
}
d>
public int getNumeroSubProblemas() {
return 2;
}
e>
public ProblemaDyV<BigInteger> getSubProblema(int i) {
return new ProblemaPadovanDyVImpl(getN() - i - 2);
}
f>
public BigInteger combinaSoluciones(BigInteger[] soluciones) {
BigInteger sol = soluciones[0].add(soluciones[1]);
return sol;
}
158
Dadas dos funciones f1 y f2, se desea implementar mediante la tcnica de Divide y Vencers el mtodo
compSimilar que indique si ambas funciones se comportan de manera similar, es decir, son estrictamente
crecientes o estrictamente decrecientes para cualquier intervalo (el mismo en ambas funciones) considerado.
Para el caso base, considere una variable psilon que siempre va a ser lo suficientemente pequea como para
que las funciones en ese intervalo sean solo de un tipo (estrictamente crecientes o decrecientes). Se supone que
ambas funciones son continuas entre x1 y x2 como se indica en el ejemplo grfico. Para ello considere la clase
Funcion ya definida.
Double epsilon = 0,05;
Funcion f1 = Funcion.cargaFuncion1(0, 8);
Funcion f2 = Funcion.cargaFuncion2(0, 8);
Boolean res = compSimilar(f1,f2,epsilon);
Por ejemplo, en la figura (a), el mtodo compSimilar devolvera true, ya que en ningn intervalo de ambas
funciones el comportamiento es diferente (ambas funciones son estrictamente crecientes entre 0 y 2,
estrictamente decrecientes entre 2 y 4 y estrictamente crecientes entre 4 y 8).
Para comprobar si son crecientes basta con
comprobar el valor de la funcin (getValueY) en
cada extremo. Si psiln=005 y estuviramos
en el caso base donde el intervalo es x1=205,
x2= 210 tendramos:
f1.getValueY(205) es 995
f1.getValueY(210) es 990
f2.getValueY(205) es 800
f2.getValueY(210) es 775
f2.getValueY(205) es 800
f2.getValueY(210) es 775
Se pide:
1. Rellene la siguiente ficha.
2. Implemente el mtodo propuesto mediante la tcnica DyV basndose en la ficha.
159
Tamao:
(x2 x1 ) / epsilon
Propiedades Compartidas:
f1, Funcion
f2, Funcion
Propiedades Individuales
Solucin:
Instanciacin:
Problema generalizado:
(1, 2) = {
(1, 2)
(1, ) && (, 2)
(2 1)
(2 1) >
1 + 2
2
Recurrencia:
() = 2 ( ) +
2
Complejidad:
() ()
160
En una gran empresa se vota entre todos los empleados tres propuestas (1,2,3) para solucionar un determinado
problema laboral. Se escoger una propuesta si la votacin supera el 50% de los empleados que votan (las
abstenciones son nmeros naturales mayores de 3).
Sabiendo que los votos se recogen a medida que se producen las votaciones en una lista de enteros, se propone
una vez haya finalizado la votacin disear un algoritmo por la tcnica de divide y vencers que determine
finalmente si hay propuesta que cumpla las condiciones iniciales o hay que volver a negociar (se devuelve un 0).
Por ejemplo, dada la siguiente lista de votaciones como entrada, el algoritmo devolvera 2
5, 1, 2, 2, 1, 3, 2, 1, 2, 2, 2, 3, 2
Puede utilizar los siguientes mtodos:
Integer elegirPivote(List<Integer> votos, Integer i, Integer j)
Este mtodo elige un valor como pivote para una lista de votos entre los ndices i y j.
List<Integer> bh(List<Integer> votos, Integer p, Integer i, Integer j)
Este mtodo implementa el algoritmo de la bandera holandesa para la lista de votos pasada como parmetro
para un pivote p entre los ndices i y j. Dicho mtodo devuelve una lista de enteros de tamao dos que
contiene los ndices que indican las fronteras de los elementos menores, iguales y mayores que el pivote. Por
ejemplo, para el caso anterior, si hacemos:
List<Integer> solBH = new ArrayList<Integer>();
p = elegirPivote(voto, 0, numeros.size());
solBH = bh(nmeros, p, 0, nmeros.size());
Podramos obtener usando los mtodos anteriores una lista que divide el problema en 3 partes, una parte con
los menores que el pivote, otra para los que son iguales que el pivote y otra ltima con los mayores.
1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 5
Se pide:
1. Rellenar la siguiente ficha
2. Disear en el lenguaje Java utilizando una funcin que utilice la tcnica de divide y vencers la funcin
que permite resolver el problema. El prototipo de dicha funcin es:
Integer BuscaPropuestaMayoritaria(List<Integer> votos)
161
Sistema de votacin
Tcnica:
Tamao:
j-i
Propiedades Compartidas:
d, List<Integer>
n, d.size()
Propiedades Individuales
Solucin:
Instanciacin:
Problema generalizado:
0
1[]
(, , ) =
(1, , )
(1, , )
{ 0
> , >
2
2
> , , >
2
2
2
> , , >
2
2
2
= (, , )
(1, , ) = (, , , )
Recurrencia:
1
() = + ()
=0
Complejidad:
() ()
162
Deber implementar un algoritmo que dado un NIF de un alumno (de tipo int) y una lista de pginas (de tipo
List<PDF>), sea capaz de devolver su nota (de tipo double) o -1 si no encuentra al alumno.
Se pide:
1. Disee un algoritmo recursivo, de orden lineal (en el caso peor) con respecto al nmero de pginas (es decir,
que recorra todas las pginas), que encuentre la nota del alumno.
a) D la definicin recursiva. Si necesita realizar generalizacin, indquelo explicando cada variable aadida.
b) Indique cules seran los casos mejor y peor as como sus complejidades. Justifique su respuesta.
2. Disee otro algoritmo recursivo, de orden logartmico (en el caso peor) con respecto al nmero de pginas,
que encuentre la nota del alumno.
a) D la definicin recursiva. Si necesita realizar generalizacin, indquelo explicando cada variable aadida.
b) Indique cules seran los casos mejor y peor as como sus complejidades. Justifique su respuesta.
c) D la implementacin en Java.
163
SOLUCIN:
Apartado 1)
a> Definicin:
Generalizacin: nota(NIF, paginas) = notaAux(NIF, paginas, 0)
double notaAux1(int NIF, List<PDF>paginas, int i)=
getNota(pagina.get(i), NIF) getNota(pagina.get(i), NIF) != -1
-1
i = paginas.size()
{
notaAux1(NIF, paginas, i+1)
eoc
b>
Apartado 2)
a> Algoritmo de bsqueda binaria:
Generalizacin: nota(NIF, paginas) = notaAux(NIF, 0, paginas.size())
double notaAux2(int NIF, List<PDF>paginas, int i, int j)=
getNota(pagina.get(m), NIF) getNota(pagina.get(m), NIF) != -1
-1
i=j
{
notaAux2(NIF, paginas, i, m) getPrimerNIF(paginas.get(m)) < NIF
notaAux2(NIF, paginas, m, j) getUltimoNIF(paginas.get(m)) > NIF
m=
b)
(i+j)
2
164
-0.6003%
Sesin 0
0.75%
Sesin 1
-1.1%
Sesin 2
0.23%
Sesin 3
-0.4205%
Sesin 4
0.3%
Sesin 5
0.502%
Sesin 6
El periodo de mximo riesgo se producir entre la sesiones 2 y 4 y el porcentaje de variacin que se produce en
dicho periodo ser de -1.1 +0.23 -0.4205 = -1.2905%. Este porcentaje de variacin ser el peor que podamos
encontrar entre todos los posibles periodos entre dos sesiones.
SE PIDE:
1) Indicar claramente si para resolver el problema descrito anteriormente debe utilizarse el esquema DyV con
memoria o sin memoria.
2) Implementar el algoritmo dYV visto en clase, es decir, el mtodo private S dYV(ProblemaDyV<S> p).
3) Implementar los siguientes mtodos de la clase ProblemaInversionActivoFinancieroDyV:
a) public boolean esCasoBase()
b) public PeriodoDeMaximoRiesgo getSolucionCasoBase()
c) public int getNumeroSubProblemas()
d) public ProblemaDyV<PeriodoDeMaximoRiesgo> getSubProblema(int i)
e) public PeriodoDeMaximoRiesgo combinaSoluciones
4) (PeriodoDeMaximoRiesgo... soluciones)
a) Determine el orden de complejidad del algoritmo diseado:
b) Defina el tamao de la funcin
c) Defina los casos mejor, peor y medio y calcule el orden del T(n) para cada caso.
165
166
Apartado 1)
Apartado 2)
private S dYV(ProblemaDyV<S> p) {
S s;
if (p.esCasoBase()) {
s = p.getSolucionCasoBase();
} else {
int numeroDeSubProblemas = p.getNumeroSubProblemas();
S[] soluciones = Utiles.creaArray(tipoSolucion, numeroDeSubProblemas);
for (int i = 0; i < numeroDeSubProblemas; i++) {
s = dYV(p.getSubProblema(i));
soluciones[i] = s;
}
s = p.combinaSoluciones(soluciones);
}
return s;
}
Apartado 3)
Apartado 4)
El tamao ser n = j i
Los casos mejor, peor y medio son los mismos y el T(n) = 2 T(n/2) + (n). entonces a=2, b=2 y d=1 y
estaramos en el segundo caso a=bd y el T(n) ser de (n log n) tanto para el caso mejor, peor y medio.
167
GTCGGGATGCACCTGAGAAA
G
2
T
4
C
3
G
2
G
2
G
2
A
1
T
4
G
2
C
3
A
1
C
3
C
3
T
4
G
2
A
1
G
2
A
1
A
1
A
1
En la tabla del ejemplo, tenemos (G 7 veces, A 6 veces, C 4 veces, T 3 veces). Por tanto, el mtodo
devolver 2 ya que corresponde al identificador de la base nitrogenada G, que es la que ms se repite, con un
total de 7 apariciones.
Srvase de los siguientes mtodos para resolverlo:
Integer elegirPivote(List<Integer> adn, Integer i, Integer j): Mtodo que elige un valor como pivote
List<Integer> bh(List<Integer> adn, Integer p, Integer i, Integer j): Mtodo que realiza el algoritmo
de la bandera holandesa para la lista de enteros pasada como parmetro para un pivote p entre los ndices
i y j. Dicho mtodo devuelve una lista de enteros de tamao dos que contiene los ndices que indican las
fronteras de los elementos menores, iguales y mayores que el pivote. Por ejemplo, para el caso anterior, si
hicieramos:
List<Integer> solBH = new ArrayList<Integer>();
p = elegirPivote(adn, 0, adn.size()); // por ejemplo p=3
solBH = bh(adn, p, 0, adn.size());
Se pide:
1. Rellenar la siguiente ficha.
2. Disear utilizando la tcnica de divide y vencers el mtodo: Integer buscaMax(List<Integer> adn) Que
recibe como parmetro una lista de identificadores y mediante la tcnica de divide y vencers devuelve
el nmero de repeticiones del identificador que ms se repite en la secuencia.
168
Aparatado 1)
Secuencia de ADN
Tcnica:
Tamao:
j-i
Propiedades Compartidas:
adn
umbral
List<Integer>
Integer
Propiedades Individuales
i
j
Integer
Solucin:
Instanciacin:
() = (, 0, . ())
Problema generalizado:
(, , )
= (, , );
(1 , 2 = (, , , );
= max(
(, , ) =
(, , 1 ),
2 1 ,
(, 2 , )
)
{
]
>
Aparatado 2)
169
a) Implementar la clase ComparadorPuntos necesaria para realizar la ordenacin del array que almacena
los puntos.
b) Implementar el siguiente mtodo siguiendo la tcnica de divide y vencers. Este mtodo recibe un array
ordenado a partir del orden definido anteriormente y un punto y devuelve la posicin en la que se
encuentra el punto buscado o -1 en el caso de que no exista.
int devuelvePosicion(Punto[] array, Punto p)
c) Implementar un mtodo principal que dado un array que contiene los puntos desordenados y un punto,
haga uso de los apartados anteriores para ordenarlo y encontrar el punto deseado.
d) Indicar el tamao del problema y el orden de complejidad del algoritmo de manera justificada.
170
Apartado a)
public class ComparadorPuntos implements Comparator<Punto> {
public ComparadorPuntos() {
}
public int compare(Punto p1, Punto p2) {
int suma1 = Math.abs(p1.getX()) + Math.abs(p1.getY()) + Math.abs(p1.getZ());
int suma2 = Math.abs(p2.getX()) + Math.abs(p2.getY()) + Math.abs(p2.getZ());
return suma1 - suma2;
}
}
Apartado b)
int devuelvePosicion(Punto[] array, Punto p) {
return devuelvePosicionAux(array, p, 0, array.length, new ComparadorPuntos());
}
int devuelvePosicionAux(Punto[] array, Punto p, int i, int j, Compare<Punto> c) {
int res;
if (i > j) {
res = -1;
} else {
int mitad = (i + j) / 2;
if (c.compare(array[mitad], p) == 0) {
res = mitad;
} else if (c.compare(array[mitad], p) > 0) {
res = devuelvePosicionAux(array, p, mitad + 1, j, c);
} else {
res = devuelvePosicionAux(array, p, i, mitad - 1, c);
}
}
return res;
}
Apartado c)
public static void main(String[] args) {
Comparator<Punto> comparador = new ComparadorPuntos();
Comparator<Punto> comparadorInvertido = Collections.reverseOrder(comparador);
List<Punto> listaPuntos = Lists.newArrayList(puntos);
Collections.sort(listaPuntos, comparadorInvertido);
int posicion = devuelvePosicion((Punto[]) listaPuntos.toArray(), punto);
if (posicion == -1) {
System.out.println("El punto no se encuentra en el array.");
} else {
System.out.println("El punto se encuentra en la posicin " + (posicion + 1));
}
}
Apartado d)
El tamao del problema es el tamao del array y la complejidad es de orden (log n).
171
Se pide:
172
Aparatado 1)
Divide y Vencers.
Tamao:
Propiedades Compartidas:
e1
e2
umbral
Empresa
Empresa
Integer
Propiedades Individuales
x1
x2
Boolean
int
int
Solucin:
( 1, 2, ) = ( 1,
2, , 1, 2)
Instanciacin:
Problema generalizado:
(1 , 2 , , 1 , 2 ) =
(1 , 2 , 1 , 2 )
2 1
(1 , 2 , , 1 , )
]
&& (1 , 2 , , + 1, 2 )
2 1 >
Recurrencia:
() = 2 ( ) +
2
Complejidad:
() ()
Aparatado 2)
public Boolean inversionTendenciasDyV(Empresa e1, Empresa e2, int umbral, int i, int j) {
Boolean res;
if (j - i <= umbral) {
res = inversonBase(e1, e2, i, j);
} else {
k = (j + i) / 2;
res = inversionTendenciasDyV(e1, e2, umbral, i, k)
&& inversionTendenciasDyV(e1, e2, umbral, k + 1, j);
}
return res;
}
173
174
175
176
4.2 Grafos.
El tipo grafo est formado un conjunto de vrtices o nodos unidos por enlaces llamados aristas o arcos, que
permiten representar relaciones binarias entre elementos de un conjunto. Es posible que haya varias aristas
entre cada par de vrtices y aristas que unen un vrtice consigo mismo. Tanto los vrtices como las aristas son
objetos que pueden tener informacin propia. Denominaremos V el tipo de los vrtices y E el tipo de las de las
aristas.
Las aristas pueden ser de dos tipos: aristas dirigidas y aristas no dirigidas. Las primeras tienen una orientacin
es decir tienen un vrtice origen y un vrtice destino. Las segundas no tienen orientacin, slo tienen dos vrtices
que son sus extremos. Los grafos que slo tienen aristas dirigidas se denominan grafos dirigidos y los que tienen
aristas dirigidas y no dirigidas grafos mixtos.
Cuando conocemos a priori el conjunto de vrtices y las aristas de un grafo decimos que tenemos un grafo
explcito. En este caso describimos el grafo de forma global y disponemos en memoria de toda la informacin
sobre el conjunto de vrtices y aristas y sus informaciones asociadas.
Hay otros grafos, que llamaremos grafos implcitos, de los que no conocemos a priori todos sus vrtices y aristas.
Este tipo de grafos estn descritos por las propiedades que cumplen los vrtices y por una funcin que dado un
vrtice nos calcula sus vrtices vecinos y las aristas que lo conectan a ellos. El grafo estar constituido por el
conjunto de vrtices alcanzables desde el vrtice dado. Los vrtices del grafo se irn descubriendo a medida que
el grafo se vaya recorriendo.
Camino: Secuencia de vrtices donde cada uno est conectado con el siguiente por una arista.
Longitud de un camino: Se llama longitud del camino al nmero de aristas que contiene.
Camino Cerrado: Camino donde el vrtice primero coincide con el vrtice ltimo.
Camino Simple: No tiene vrtices repetidos a excepcin del primero que puede ser igual al ltimo.
Ciclo: Camino cerrado donde no hay vrtices repetidos salvo el primero que coincide con el ltimo.
177
Grafos Simples: Desde un vrtice v1 hasta v2 hay como mximo una arista que puede ser dirigida o no
dirigida. No pueden contener bucles.
Multigrafos: Son grafos donde puede haber ms de una arista entre dos vrtices.
Pseudografos: Son grafos donde puede haber ms de una arista entre dos vrtices y bucles.
Grafo Completo: Grafo simple en el que hay un arista entre cada par de vrtices.
Grafo Conexo: Grafo no dirigido en el que hay al menos un camino entre cada dos nodos.
Grafo Dbilmente Conexo: Grafo dirigido en el cual al remplazar cada arista dirigida por una no dirigida
resulta un grafo no dirigido conexo.
Grafo Fuertemente Conexo: Grafo no dirigido dnde para cada par de vrtices u, v existe un camino dirigido
desde u hasta v y otro desde v hasta u.
Subgrafo: Un grafo g1 es subgrafo de otro g2 si los vrtices y aristas de g1 estn incluidos en los vrtices y
aristas de g2.
Componentes Conexas: Subgrafos maximales de un grafo no dirigido formados por los vrtices entre los que
hay al menos un camino, ms las aristas que los conectan.
Una componente fuertemente conexa de un grafo dirigido es un subgrafo maximal fuertemente conexo de
un grafo dirigido.
Una componente dbilmente conexa es un subgrafo maximal dbilmente conexo de un grafo dirigido.
178
<V,E>.
Graph <V,E>
Tipo retorno
addEdge
(V sourceVertex, V targetVertex)
boolean
addVertex (V v)
boolean
containsEdge (E e)
boolean
boolean
containsVertex (V v)
boolean
edgeSet ()
Set<E>
edgesOf (V v)
Set<E>
Set<E>
getEdgeFactory ()
EdgeFactory<V,E>
getEdgeSource (E e)
getEdgeTarget (E e)
gedEdgeWeight (E e)
double
removeAllEdges
(Collection<? extends E> edges)
boolean
boolean
Set<E>
removeEdge (E e)
boolean
removeAllVertices
(Collection<? extends V>
vertices)
boolean
boolean
removeEdge (E e)
removeVertex (V v)
boolean
vertexSet ()
Set<V>
Descripcin
Crea una arista desde v1 hasta v2 y la aade. Devuelve la arista
creada si ha podido ser aadida o null en otro caso. La arista es
creada por la factoria de aristas del grafo.
Aade la arista e desde sv hasta tv. Devuelve cierto si el grafo ha
cambiado.
Aade el vrtice v. Devuelve cierto si el grafo ha cambiado.
Devuelve cierto si la arista e est contenida en el grafo.
Devuelve cierto si existe alguna arista desde sv a tv.
Devuelve cierto si el vrtice v est contenido en el grafo.
Devuelve el conjunto de aristas del grafo.
Devuelve el conjunto de aristas del grafo que tocan el vrtice v.
Devuelve el conjunto de aristas del grafo que conectan ambos
vrtices si existen o null si alguno no existe.
Devuelve la arista que va desde sv hasta tv si existe o null.
Devuelve la factoria que sirve para crear las aristas del grafo.
Devuelve el vrtice origen de la arista.
Devuelve el vrtice destino de la arista.
Devuelve el peso asignado a la arista.
Elimina todas las aristas que se encuentran en la coleccin de
entrada. Devuelve cierto si el grafo ha cambiado.
Elimina todas las aristas que conectan el vrtice sv al tv.
Devuelve las antiguas aristas que conectaban sv a tv.
Elimina todas las aristas que se encuentran el conjunto de
entrada.
Elimina la arista e pasada como entrada.
Elimina todos los vrtices que se encuentran en la coleccin de
entrada y las aristas incidentes. Devuelve cierto si el grafo ha
cambiado.
Elimina todos los vrtices del conjunto de entrada.
Elimina la arista pasada como entrada
Elimina el vrtice v. Devuelve cierto si el grafo ha cambiado.
Elimina tambin todas las aristas incidentes en ese vrtice.
Devuelve el conjunto de vrtices del grafo.
179
Grafos ej JGraphT:
Set<E>
inDegreeOf (V v)
int
outDegreeOf (V v)
int
outgoingEdgesOf (V v)
Set<E>
Tipo retorno
Descripcin
Devuelve el conjunto de aristas que llegan al vrtice v.
Devuelve el nmero de aristas que llegan al vrtice v.
Devuelve el nmero de aristas que salen del vrtice v.
Devuelve el conjunto de aristas que salen del vrtice v.
Tipo retorno
Descripcin
Devuelve el grado del vrtice v. Es decir el nmero de aristas
que lo tocan.
int
Tipo retorno
DEFAULT_EDGE_WEIGHT
double
void
180
Descripcin
Recoge el valor que por defecto se asignar como peso a las
aristas.
Permite actualizar el peso de la arista e con el valor del peso
que se le pasa como entrada.
Subgraph <V,E>
extends Graph <V,E>
Subgraph (G base, Set<V> vs)
Graph
Graph
Descripcin
Devuelve una vista del grafo base que incluye slo los vrtices
en vs. Tampoco incluye las aristas de los vrtices no incluidos.
Devuelve una vista del grafo base que incluye slo los vrtices
en vs y las aristas en es. Tampoco incluye las aristas de los
vrtices no incluidos.
La unin de un grafo g1 = (v1, e1) y otro g2 = (v2, e2) es un nuevo grafo cuyos vrtices son la unin de v1 y v2 y sus
aristas la unin de e1 y e2. Dados dos grafos podemos obtener el grafo unin mediante los siguientes
constructores de cambio de tipo:
Tipo
retorno
Subgraph <V,E>
extends Graph <V,E>
AsUndirectedGraph (DirectedGraph<V,E> g)
AsWeightedGraph ( Graph<V,E> g
, Map<E,Double> weightMap)
Descripcin
Graph
Graph
El tipo WeightCombiner define las constantes de combinacin: FIRST, MAX, MIN, SECOND, SUM.
Es posible construir vistas no dirigidas a partir de grafos dirigidos o grafos con peso a partir de otros sin peso.
Tipo retorno
V
EdgeFactory <V,E>
Tipo retorno
Descripcin
Crea un vrtice.
Descripcin
Crea una arista sin informacin adicional entre los vrtices sv, a tv.
El requisito que deben respetar estas factoras es que cada vrtice y arista debe tener una identidad nica, es
decir, no puede haber dos vrtices o dos aristas que sean iguales.
Las factoras anteriores las extendemos para crear vrtices o aristas a partir de su representacin en forma de
cadenas de caracteres o de arrays de cadenas de caracteres.
Tipo retorno
List <E>
V
Graph<V,E>
V
double
Descripcin
Aristas del camino.
Vrtice final.
Grafo al que pertenece el camino.
Vrtice inicial.
Peso total de las aristas del camino.
181
nodedef>name,label
s1,Sevilla
s2,Crdoba
s3,Cdiz
s4,Mlaga
edgedef>node1,node2,edgeweight
s1,s2,180.
s1,s3,120.
s2,s4,140.
s4,s1,210.
Propiedades
Vrtices
Propiedades
Aristas
A los ficheros con la estructura anterior le damos extensin gdf. A partir de un fichero de ese tipo podemos
construir un objeto de tipo Graph <V, E> o alguno de sus subtipos. El subtipo concreto viene dado por un valor
del tipo GraphType. Los valores de GraphType son: grafos simples, multigrafos, dirigidos, no dirigidos, etc.
Para simplicidad de procesamiento del fichero, extendemos las factoras de vrtices y aristas con nuevos
mtodos, que nos permitan crear (o recuperarlos una vez creados) vrtices y aristas a partir de las lneas del
fichero. Hemos de tener en cuenta que, tanto los vrtices como las aristas creadas, tienen que ser nicos.
StringVertexFactory <V>
extends VertexFactory <E>
Tipo retorno
Descripcin
StringEdgeFactory <V,E>
extends EdgeFactory <V,E>
Tipo retorno
Descripcin
182
Tipo retorno
Descripcin
Graph<V,E>
WeightedGraph<V,E>
La representacin visual de un grafo ya construido es un tema mucho ms complicado pero de gran inters.
Ahora el fichero de partida debe contener informacin grfica especfica para indicar el tipo de vista del grafo
que queremos. Para ello usaremos un fichero con extensin gv (o dot).
graph andalucia {
size="100,100";
Sevilla [style = filled, color = blue];
Vrtice con sus propiedades
Cordoba -- Sevilla [label= "100+2", color= red, style= bold]; Aristas y propiedades
Granada -- Cordoba [label= 10, color= red, style= bold];
Sevilla -- Huelva [label= 15, color= red, style= bold];
Sevilla -- Cadiz [label= 30];
Almeria -- Granada [label= 25, color= red, style= bold];
Granada -- Jaen[label= 32];
Almeria -> Malaga [label= 51, color= red, style= bold];
Arista dirigida
}
Como vemos el fichero se compone de una cabecera y un conjunto de lneas. Cada lnea describe un vrtice con
sus propiedades entre [] o una arista con sus propiedades tambin entre [] e indicando los vrtices que conecta.
El smbolo - - representa una arista no dirigida. El smbolo -> (que no aparece en el texto) una arista dirigida.
Las propiedades y sus valores se representan por pares: nombre = valor. Hay muchas propiedades predefinidas
que tienen una semntica ya fijada.
Para visualizar el grfico, con los detalles indicados en el fichero de extensin gv, es necesario aplicar algunos
algoritmos complejos para calcular la disposicin (layout) ms adecuada de los vrtices y aristas. Podemos usar
la herramienta ZGRViewer (visualizador de Graphviz - Graph Visualization Software) que lee ficheros con esa
extensin y visualiza el grafo correspondiente, siendo posible ver el resultado como una imagen. La
documentacin puede encontrarse en:
http://www.graphviz.org/Download.php
http://sourceforge.net/projects/zvtm/files/zgrviewer/0.8.2/
183
Los grafos construidos mediante JGraphT pueden ser exportados a un fichero con estructura dot (o gv) y
visualizados con la herramienta anterior. Esto puede hacerse con la clase DOTExporter de JGraphT. Esta clase
tiene un constructor de la forma:
public DOTExporter(
,
,
,
,
)
VertexNameProvider<V>
VertexNameProvider<V>
EdgeNameProvider<E>
ComponentAttributeProvider<V>
ComponentAttributeProvider<E>
vertexIDProvider
vertexLabelProvider
edgeLabelProvider
vertexAttributeProvider
edgeAttributeProvider
Donde:
-
vertexIDProvider
vertexLabelProvider
edgeLabelProvider
vertexAttributeProvider
edgeAttributeProvider
184
En la clase Iterables2 el mtodo from(Integer a, Integer b, Integer c) genera los nmeros enteros de a hasta b
con una variacin de c . Es un ejemplo de un iterable virtual: no necesitamos tener en la memoria todos los
enteros que vamos a generar porque tenemos un algoritmo para generar cada entero a partir del anterior.
Otro ejemplo sera el conjunto virtual de los enteros pares comprendidos en el intervalo [a,b). Podemos
implementar fcilmente las operaciones del tipo conjunto sin necesidad de construir una estructura de datos
que contenga todos los elementos del conjunto en memoria.
Un grafo virtual es algo similar. Cuando no podamos (por su tamao) o no queramos (por su complejidad) tener
en memoria todos sus vrtices y aristas pero disponemos de un algoritmo para calcular los vrtices vecinos de
un vrtice dado y las aristas que lo conectan a l, entonces tenemos un grafo virtual. Un grafo virtual puede ser
de los mismos subtipos de un grafo: dirigido, no dirigido, simple, multigrafo, pseudografo, etc. Como en el grafo,
exigiremos que los vrtices y las aristas sean nicos ,y para crearlos, dispondremos de una factora de vrtices y
otra de aristas tal como hemos visto anteriormente.
En un grafo virtual no estamos interesados en aadir ni eliminar aristas ni vrtices. Tampoco en conocer el
conjunto completo de aristas ni de vrtices. Ni tampoco iterar sobre ellos. Aunque s nos interesa decidir si un
vrtice o una arista pertenece al grafo o no. Igualmente puede interesarnos saber si hay una arista entre dos
vrtices dados u obtener todas las aristas que haya entre dos de ellos.
Las operaciones de la factora Graphs son aplicables a este tipo siempre que lo permitan los mtodos
correspondientes del grafo virtual.
Las ideas expuestas podemos concretarlas en una clase abstracta que implementa el tipo
excepcin en aquellos mtodos no disponibles para un grafo virtual.
Graph
y dispara una
185
Tipo retorno
abstract boolean
-
containsEdge (E e)
abstract boolean
abstract boolean
containsVertex (V v)
abstract boolean
edgeSet ()
edgesOf (V v)
abstract Set<E>
abstract Set<E>
abstract E
getEdgeFactory ()
abstract
EdgeFactory<V,E>
getEdgeSource (E e)
abstract V
getEdgeTarget (E e)
abstract V
gedEdgeWeight (E e)
removeAllEdges
(Collection<? extends E> edges)
removeAllEdges (V sv, V tv)
abstract double
Descripcin
Disparar UnsupportedOperationException.
Disparar UnsupportedOperationException.
Disparar UnsupportedOperationException.
Devuelve cierto si la arista e est contenida en el grafo.
Devuelve cierto si existe alguna arista desde sv a tv.
Devuelve cierto si el vrtice v est contenido en el grafo.
Disparar UnsupportedOperationException.
Devuelve el conjunto de aristas del grafo que tocan el
vrtice v.
Devuelve el conjunto de aristas del grafo que conectan
ambos vrtices si existen o null si alguno no existe.
Una de las aristas que van desde sv hasta tv si existe o null.
Devuelve la factoria que sirve para crear las aristas del
grafo.
Devuelve el vrtice origen de la arista.
Devuelve el vrtice destino de la arista.
Devuelve el peso asignado a la arista.
Disparar UnsupportedOperationException.
removeEdge (E e)
removeAllVertices
(Collection<? extends V> vertices)
removeVertex (V v)
Disparar UnsupportedOperationException.
Disparar UnsupportedOperationException.
Disparar UnsupportedOperationException.
vertexSet ()
Disparar UnsupportedOperationException.
Devolver un conjunto vaco o un conjunto con un
elemento. Si el grafo virtual es no conexo entonces el
conjunto devuelto debe contener un vrtice, al menos, de
cada componente conexa si queremos hacer bsquedas
por todos los vrtices del grafo.
186
Como podemos ver, el grafo virtual es simple y no dirigido. Resolver el problema propuesto consiste en, partir
de un vrtice inicial dado y encontrar un camino de longitud mnima hasta otro vrtice final, tambin dado.
Para implementar el grafo debemos implementar el tipo VerticePuzle que modele los vrtices posibles del
problema del puzle. Hay muchas posibilidades de implementacin. Una arista contendr la informacin de los
dos vrtices extremos y del movimiento realizado. Debemos, adems, disear una factora de vrtices y otra de
aristas que aseguren que cada vrtice y cada arista son nicos en el sentido de que no hay otro igual a l.
Con ese diseo debemos implementar los siguiuentes mtodos abstractos:
boolean containsVertex(V v)
Set<E> edgesOf(V v)
V getEdgeSource(E e)
V getEdgeTarget(E e)
double gedEdgeWeight(E e)
EdgeFactory<V,E> getEdgeFactory().
Podemos ver que el nmero de vrtices de este grafo es 9! = 362.880. La forma de verlo es considerar que cada
vrtice puede ser representado por una lista de nueve nmeros (los nmeros del tablero puestos en filas
consecutivamente, por ejemplo). El nmero de estas listas es igual a las permutaciones de los nueve nmeros.
El enorme nmero de vrtices nos aconseja considerar el grafo virtualmente en vez de construirlo
completamente en memoria, que en muchos casos sera inviable.
187
KruskalMinimumSpanningTree
(Graph<V,E> graph)
getEdgeSet ()
Tipo retorno
getSpanningTreeCost ()
Set<E>
double
Descripcin
Conjunto de aristas del rbol de recubrimiento mnimo.
Coste de las aristas del rbol de recubrimiento mnimo.
Tipo retorno
find2ApproximationCover (Graph<V,E> g)
findGreedyCover (UndirectedGraph<V,E> g)
Descripcin
Conjunto de aristas del rbol de recubrimiento
mnimo.
Calcula el coste de las aristas del rbol de
recubrimiento mnimo.
Ambos mtodos implementan algoritmos de aproximacin para resolver el problema. Devuelven el subconjunto
de vrtices buscado o un superconjunto aproximado del mismo.
Los vrtices en rojo son el recubrimiento de vrtices mnimo del grafo.
188
Set <V>
connectedSets ()
List <Set<V>>
isGraphConnected ()
boolean
boolean
Tipo retorno
Descripcin
Conjunto de vrtices conectados al vrtice v.
Lista de componentes conexas.
Si es conexo.
Si existe un camino desde un vrtice al otro.
Tipo retorno
stronglyConnectedSets ()
List <Set<V>>
isStronglyConnected ()
boolean
stronglyConnectedSubgraphs ()
List<DirectedSubgraph<V, E>>
Descripcin
Lista de las componentes fuertemente
conexas.
Si es fuertemente conexo.
Lista de subgrafos fuertemente conexos.
189
La clase BreadthFirstIterator<V,
E>
190
Descripcin
El vrtice inicial es arbitrario.
El vrtice inicial es el vrtice indicado startVertex.
<V,E>
BreadthFirstIterator <V,E>
ClosestFirstIterator (Graph <V,E> g)
ClosestFirstIterator (Graph<V,E> g, V startVertex)
Descripcin
El vrtice inicial es arbitrario.
El vrtice inicial es el vrtice indicado startVertex.
Este iterador va cambiando el rbol de recubrimiento a medida que va avanzando. Al final el rbol de
recubrimiento construido define los caminos mnimos desde cada vrtice al vrtice inicial. Esa informacin puede
ser consultada mediante sus mtodos de la clase anterior:
Tipo retorno
double
getSpanningTreeEdge (V vertex):
Descripcin
Longitud del camino mnimo hasta el vrtice inicial.
Arista que define el camino mnimo hasta el vrtice inicial.
Post:9-10-5-6-2-3-11-12-7-8-4-1
In: 9-5-10-2-6-1-3-11-7-12
En el recorrido en preorden se visita un nodo, luego su primer hijo, luego el primer hijo de este, etc. Es, por lo
tanto un recorrido, como su nombre indica, que avanza en profundidad.
Este recorrido se puede aplicar tanto a grafos dirigidos como a los no dirigidos.
La clase DepthFirstIterator<V,E> implementa un iterador que hace el recorrido en preorden. Constructores:
BreadthFirstIterator <V,E>
DepthFirstIterator (Graph<V,E> g)
DepthFirstIterator (Graph<V,E> g, V startVertex)
Descripcin
El vrtice inicial es arbitrario.
El vrtice inicial es el vrtice indicado startVertex.
Cuando desde un vrtice se alcanza otro por primera vez, a travs de una arista, sta se incluye en el rbol de
recubrimiento.
191
La clase TopologicalOrderIterator
<V,E>
7, 5, 3, 11, 8, 2, 9, 10
3, 5, 7, 8, 11, 2, 9, 10
3, 7, 8, 5, 11, 10, 2, 9
5, 7, 3, 8, 11, 10, 9, 2
7, 5, 11, 3, 10, 8, 9, 2
7, 5, 11, 2, 3, 8, 9, 10
Descripcin
192
La informacin sobre los pesos de las aristas est siempre disponible en mtodo disponible en el tipo Graph<V,E>:
double getEdgeWeight(E e).
La clase DijkstraShortestPath<V,E> implementa el algoritmo. Sus constructores y mtodos relevantes son:
DijkstraShortestPath ( Graph<V,E> graph
, V startVertex
, V endVertex
)
getPath ()
GraphPath<E,V>
getPathEdgeList ()
List<E>
getPathLength ()
double
Tipo retorno
Descripcin
Camino mnimo.
Aristas del camino mnimo.
Longitud del camino mnimo.
Este es el algoritmo ms utilizado para encontrar el camino ms corto entre dos punto de un grafo (aunque sea
virtual), por ejemplo, en grafos que representen mapas, nodos de una red, etc.
List<E>
getCost (V endVertex)
double
Tipo retorno
Descripcin
Camino mnimo desde el startVertex hasta endVertex.
Coste del camino mnimo desde el startVertex hasta
endVertex.
193
GraphPath<V, E>
getShortestPaths(V v)
List<GraphPath<V,E>>
shortestDistance(V a, V b)
double
getShortestPathsCount()
int
getDiameter()
double
getGraph()
Graph<V, E>
Tipo retorno
Descripcin
Los algoritmos A* tratan con una idea ms general que llamaremos ruta que pasa por un vrtice dado. Una ruta
viene definida por un camino que va desde el vrtice inicial a un vrtice intermedio (vrtice actual) y una
estimacin del costo del camino hasta el vrtice final. Si la ruta est definida por el vrtice inicial Vi al vrtice final
Vf y pasando por un vrtice actual Va entonces la longitud de la misma es la suma del coste ya conocido del
camino C que va de Vi a Va ms el coste estimado para llegar al final. El coste de esa ruta es por tanto:
|| = + + +
Donde C es el camino que va de Vi a Va. Si Va es el vrtice final entonces la ruta coincide con el camino y Vaf es 0.
Para aplicar los algoritmos A* incorporamos la informacin anterior al grafo. Un grafo con esa informacin define
el tipo WeightedVertexGraph <V,E>.
WeightedVertexGraph<V, E> extends Graph<V,E>
getVertexWeight (V vertex)
getVertexWeight (V vertex, E edgeIn, E edgeOut);
getWeightToEnd (V startVertex, V endVertex)
Tipo retorno
double
double
double
Usa heursticas admisibles, es decir, heursticas que nunca sobrepasan el costo real para alcanzar la meta. Casos
comunes para ellos son: la distancia Eucldea o la distancia Manhattan.
194
GraphPath<V,E>
getPathEdgeList ()
List<E>
getPathLength ()
double
Tipo retorno
Descripcin
Camino mnimo.
Aristas del camino mnimo.
Longitud del camino mnimo.
Los algoritmos A* son adecuados para buscar un camino mnimo entre un vrtice inicial y un vrtice objetivo.
Un algoritmo de bsqueda de este tipo es admisible si garantiza encontrar el camino a la solucin con el mnimo
costo.
Parte de un nodo origen e intenta dirigirse al destino por el camino ms corto, para ello, visita a su vrtice vecino
que devuelva mejor estimacin para la solucin buscada. En la literatura, este tipo algoritmos tiene asociadas
dos funciones: g(n) y h(n) siendo n el vrtice actual. La primera funcin, g(n), define el costo del camino desde
el vrtice inicial al vrtice actual (el valor |C| definido anteriormente o alguna generalizacin del mismo para un
camino C que va desde el vrtice inicial la actual). La segunda, h(n), es una funcin heurstica que estima el costo
del vrtice actual al final. Asociada a la ruta hay un coste que denominaremos f(n). Tenemos entonces:
f(n) = g(n) + h(n)
g(n) = Costo de llegar desde el nodo inicial al nodo actual n. Representa el coste real del camino recorrido
para llegar a dicho nodo, n.
h(n) = Costo adicional para llegar desde el nodo actual al estado objetivo. Representa el valor heurstico
del nodo a evaluar desde el actual n, hasta el final. Es la estimacin de lo que queda por recorrer.
El algoritmo, como hemos comentado, sigue el orden del siguiente vrtice vecino ms cercano en el sentido que
la ruta que pasa por l es la ms corta. En caso de que dos vrtices definan rutas igual de largas segn la funcin
f(n) entonces se prefiere el vrtice con menor h(n).
Los algoritmos A* tienen asociado el concepto de admisibilidad. Si asumimos que h*(n) es el coste real del
camino ms corto desde el vrtice actual al vrtice final y h(n) cumple la condicin h(n) h*(n), entonces decimos
que el algoritmo A* es admisible.
Los algoritmos A* que cumplen esta condicin, es decir h(n) nunca sobreestima h*(n), son denominados
algoritmos admisibles. Si el algoritmo no es admisible no podemos garantizar que encuentre la solucin ptima.
Si h(x) satisface la condicin adicional h(x) d(x, y) + h(y), para todo x e y conectados por la arista de longitud
d(x, y), la llamaremos montona o consistente. Si h(n) es consistente entonces el algoritmo se puede
implementar de tal forma que cada vrtice sea procesado una sola vez. Los vrtices ya procesados constituyen
el conjunto de vrtices cerrados o visitados.
Si en los algoritmos A* hacemos h(n)=0, y las aristas tienen pesos mayor o igual a cero, la heurstica es
consistente. Este caso particular es el Algoritmo de Dijkstra.
Estos algoritmos son muy adecuados para resolver problemas de caminos mnimos en grafos virtuales.
195
La clase LaberintoCaminoMinimo contiene toda la informacin necesaria para calcular la funcin f(n).
La complejidad del algoritmo es NP, por lo que buscamos un mnimo local, no el ptimo.
HamiltonianCycle ()
getApproximateOptimalForCompleteGraph (SimpleWeightedGraph<V,E> g)
Tipo retorno
List<V>
Adems de la aplicacin de logstica en transporte, otras aplicaciones como la robtica, tratan de minimizar el
nmero de desplazamientos al realizar una serie de movimientos en un circuito impreso.
196
void
getCurrentSink()
getCurrentSource()
getMaximumFlow()
Map<E, Double>
getMaximumFlowValue()
double
Tipo retorno
Descripcin
Set<V>
minCutWeight()
double
vertexWeight(V v)
double
Tipo retorno
Descripcin
La funcin a optimizar es la suma de los flujos sobre cada arista por su coste
unitario ms el flujo producido o consumido en los vrtices respectivos por su
coste unitario (positivo si es productor y negativo si es consumidor). El
problema tiene como objetivo minimizar esa funcin objetivo. Lo que es
equivalente a maximizar los beneficios. De la misma forma se puede definir un
problema que maximice ese coste (minimice los beneficios).
La informacin necesaria para definir el problema la incluimos en el grafo con el tipo FlowGraph<V,
public interface
FlowGraph<V, E>
extends Graph<V,E>
getMinEdgeWeight(E edge)
getUnitEdgeWeight(E edge)
isSource(V vertex)
isSink (V vertex)
getMaxVertexWeight(V vertex)
getMinVertexWeight(V vertex)
getUnitVertexWeight(V vertex)
E>.
Tipo retorno
double
double
boolean
boolean
double
double
double
El peso de la aristas (getEdgeWeight(e)) contiene la cota mxima del flujo sobre un arista. El problema se resuelve
convirtindolo en un problema de restricciones lineales y resolvindolo con el algoritmo del Simplex. La clase
Flow<V,E> resuelve el problema.
Tipo retorno
void
getEdgeFlow()
Map<E,Double>
getSourceFlow()
Map<E,Double>
getSinkFlow()
Map<E,Double>
getCostValue()
double
198
Descripcin
Tipo retorno
Set<V>
getEdgeSet()
Set<E>
getTimeOfVertex(V v)
double
Descripcin
200
Tipo retorno
findGreedyChromaticNumber
(UndirectedGraph<V,E> g)
findGreedyColoredGroups
(UndirectedGraph<V,E> g)
Descripcin
Los dos mtodos implementan algoritmos de aproximacin para resolver el problema. El primero devuelve el
nmero cromtico (nmero de colores distintos). El segundo devuelve, para cada posible color, el conjunto de
vrtices que lo tienen asignado. Cada posible color viene representado por un entero 0, 1,
201
202
0
Capacidad
arista
Conectividad de redes
(n aristas que si se eliminan
desconectan origen y destino)
Transporte
Asignacin
Camino mnimo
Peso arista
Flujo mn.
Flujo mximo
Flujo mx.
ARISTAS
Infinito
Origen y
destino 1
Coste
unit.
arista
Coste
asignar
peronatarea
Long.
arista
Origen y
destino 1
1 (0 si M>N
para
orgenes)
0 (Inter)
1 (inter),
infinito
(origen,
destino)
Infinito
Flujo mn.
Infinito
Infinito
Flujo mx.
Coste
unitario
VRTICES
0 (Fuente)
1 (Sumidero)
1 origen
1 destino
Varios orgenes,
destinos e intermedios
Flujo total = suma flujos
Productores = suma
flujos consumidores
Slo orgenes (M) y
destinos (N)
Flujo productores =
nmero tareas
Flujo mximo
Funcin objetivo
1 origen
1 destino
1 origen
1 destino
1 origen
1 destino
0 (Fuente)
1 (Sumidero)
0 (Inter y
Fuente)
1 (Sumidero)
1 Fuente y
1 Sumidero (o varios)
Nota
0 (Fuente)
1 (Sumidero)
Coste
unitario
Resumen:
SOLUCIN:
203
2. En un juego, existen una serie de planetas. Cada planeta produce un recurso (por ejemplo R) y consume
una serie de recursos (por ejemplo C01 y C02).
Escriba un mtodo que, admita como parmetro un grafo en el que los vrtices representan a los
planetas, y un recurso R. El mtodo devolver un conjunto con aquellos planetas que consumen el
recurso R.
Responda a la siguiente pregunta. qu estructura auxiliar se podra utilizar para conocer en todo
momento los planetas que consumen un recurso sin necesidad de buscarlos en el grafo?
Los planetas estn conectados mediante agujeros de gusano (aristas). Cada agujero de gusano tiene
su propio coste que se ha de pagar para viajar por l. Escriba un mtodo para el que dado un grafo G,
y un planeta P, devuelva el camino con menor coste hasta un planeta (cualquiera) que consuma el
recurso producido por P.
Escriba un ltimo mtodo que reciba como parmetro un grafo y un planeta de origen P, dos planetas
cuales quiera P01 y P02 y un coste C. el mtodo deber devolver cierto si al aadir un agujero de gusano
entre P01 y P02 con un coste de viaje de C, el coste del camino con el coste mnimo a un planeta que
consuma el recurso que produce P es ms pequeo que el coste del menor coste sin que exista ese
agujero de gusano, y falso en caso contrario.
SOLUCIN:
Despus de la ejecucin del mtodo, el grafo debe ser igual a como lo era antes de la ejecucin del mtodo.
Interfaz Planeta:
Recurso getRecursoProducido();
List<Recurso> getRecursosConsumidos();
boolean equals(Object o);
Por simplicidad, se ha utilizado la clase String para modelar los recursos. En el primer mtodo se utiliza un
filtro, definido dentro del propio mtodo, para encontrar los planetas que consumen un recurso
determinado.
Una buena estructura auxiliar sera utilizar un Multimap en el que, para cada recurso (clave) tuviramos
almacenado la coleccin de planetas que consumen dicho recurso. En esta solucin se incluye una Function
que permite crear dicho Multimap a partir de un grafo.
El tercer mtodo aplica el algoritmo de Dijsktra para encontrar los caminos que hay desde el planeta origen
hasta todos los planetas que consumen ese recurso. Despus se queda con el camino ms corto encontrado.
Este problema puede resolverse utilizando el algoritmo de Floyd-Warshall en vez del de Dijsktra.
En el ltimo mtodo se utiliza el algoritmo de Dijsktra para calcular el camino ms corto, despus, se aade
una nueva arista al grafo y s e vuelve a aplicar el algoritmo de Dijsktra para ver si el camino ahora es ms
barato. Por ltimo se elimina la arista aadida para el grafo quede igual que al principio.
204
205
3. La empresa de mensajera PE slo tiene sede en algunas ciudades, sin embargo, sirve paquetes a todo el
territorio.
Disee e implemente una solucin (puede ser un nico mtodo, dos, una clase, dos, etc.) para que,
dado un grafo en el que los vrtices son ciudades y el peso de las aristas es el coste de viajar de una
ciudad a otra, y un conjunto que contenga las ciudades dnde PE tiene sede, devuelva un Map dnde
la clave ser cada una de las ciudades del grafo y el valor ser la ciudad ms cercana dnde PE tenga
una sede.
La ciudad ms cercana a una ciudad C dnde PE tenga sede, ser la propia ciudad C.
SOLUCIN:
En esta solucin se ha utilizado una clase que calcula el Map una nica vez, la primera vez que se necesita.
Para ello se utiliza el algoritmo de Floyd-Warshall ya implementado en JGraphT.
class MapShortestCities {
Map<String, String> m;
SimpleWeightedGraph<String, DefaultWeightedEdge> wg;
public MapShortestCities(SimpleWeightedGraph<String, DefaultWeightedEdge> wg) {
this.wg = wg;
m = null;
}
public Map<String, String> getMap(Set<String> cities) {
if (cities.isEmpty())
return m;
if (m == null)
calculateMap(cities);
return m;
}
private void calculateMap(Set<String> cities) {
m = new HashMap<String, String>();
String sCity = null;
Double cost;
FloydWarshallShortestPaths<String, DefaultWeightedEdge> fwAlg =
new FloydWarshallShortestPaths<String, DefaultWeightedEdge>(wg);
for (String city : wg.vertexSet()) {
if (cities.contains(city))
m.put(city, city);
else {
cost = Double.MAX_VALUE;
for (String myCity : cities) {
if (fwAlg.getShortestPath(myCity, city).getWeight() < cost) {
cost = fwAlg.getShortestPath(myCity, city).getWeight();
sCity = myCity;
}
}
m.put(city, sCity);
} // else
} // for
}
} // Class
206
SOLUCIN:
Con esta Function es posible aplicar algunos algoritmos, como Disjktra, hasta una distancia determinada.
As podramos resolver problemas del tipo: encontrar el camino ms corto de A a B siempre que dicho
camino no recorra ms de N vrtice o N aristas.
public <V> boolean isPathWithSize(Graph<V, DefaultWeightedEdge> wg, V start, V end, double rad)
{
DijkstraShortestPath<V, DefaultWeightedEdge> aDisjktra =
new DijkstraShortestPath<V, DefaultWeightedEdge>(wg, start, end, rad);
return aDisjktra.getPathLength() <= rad;
}
207
208
209
4. En un tablero de ajedrez vaco se pintan varias casillas de color rojo y se coloca una reina sobre una de ellas.
El objetivo planteado es conseguir que a reina visite todas las casillas rojas moviendo el menor nmero de
casillas posibles.
Para solucionar este problema, considere que el tablero se modela como un grafo completo. Los vrtices
sern las casillas rojas mientras que las aristas indicarn el nmero de casillas a recorrer para ir desde una
casilla roja a otra.
5. Una empresa de autobuses tiene andenes en algunas ciudades de un pas y, el mapa de carretera de dicho
pas, est modelado como un grafo en el que los vrtices estn etiquetados con el nombre de la ciudad y las
aristas con la distancia entre dos ciudades.
Desarrolle un mtodo, utilizando el algoritmo de Floyd-Warshall que, dada una ciudad, permita calcular
todas las rutas posibles junto con su distancia.
Despus, desarrolle otro mtodo que, para una ciudad de destino, encuentre cul es la ciudad que tiene un
andn de la compaa desde el cual el viaje es el ms corto posible.
210
211
7. En un juego similar al del ejercicio anterior, un jugador puede establecer una ruta entre dos ciudades, esto
es, construir un conjunto de vas. El coste de la ruta depende de las parcelas que cruce la va, as, al existir
montaas, o ros o pueblos, el coste de la va de dicha ruta aumentar.
El mapa del terreno se organiza en vrtices y aristas. Los vrtice representa una parcela y las aristas
representan las posibles direcciones que puede tomar el trazado de la va, en otras palabras, a qu otra
parcela se puede ir para seguir construyendo. Adems, una arista est etiquetada con el valor de construir
un tramo de va que vaya de una parcela a otra.
Utilice el algoritmo de Dijkstra para encontrar el trazado ms barato entre dos vrtices dados.
public List<Via> trazadoMasBarato ( SimpleDirectedWeightedGraph<Parcela, Via> grafo
, Parcela pOrg
, Parcela pDes)
{
return DijkstraShortestPath.findPathBetween(grafo, pOrg, pDes);
}
8. Resuelva el problema de las Torres de Hanoi mediante grafos virtuales. El juego consiste en tres varillas
verticales. En una de las varillas se apila un nmero indeterminado de discos (elaborados de madera) que
determinar la complejidad de la solucin, por regla general se consideran ocho discos. Los discos se apilan
sobre una varilla en tamao decreciente. No hay dos discos iguales, y todos ellos estn apilados de mayor a
menor radio en una de las varillas, quedando las otras dos varillas vacantes. El juego consiste en pasar todos
los discos de la varilla ocupada (es decir la que posee la torre) a una de las otras varillas vacantes. Para realizar
este objetivo, es necesario seguir tres simples reglas:
1. Slo se puede mover un disco cada vez.
2. Un disco de mayor tamao no puede descansar sobre uno ms pequeo que l mismo.
3. Slo puedes desplazar el disco que se encuentre arriba en cada varilla.
9. Una red de aeropuertos desea mejorar su servicio al cliente mediante terminales de informacin. Dado un
aeropuerto origen y un aeropuerto destino, el terminal desea ofrecer la informacin sobre los vuelos que
hacen la conexin y que minimizan el tiempo del trayecto total. Por un lado, se tiene la informacin de los
vuelos, o lo que es lo mismo, lo que tarda un avin en ir de un aeropuerto origen a uno destino. Adems,
tambin se puede calcular cunto tarda la escala en un aeropuerto concreto ya que se tiene la hora del vuelo
de llegada y la hora del vuelo de salida.
Disear el problema mediante grafos virtuales y resolverlo mediante algoritmos de recorridos sobre grafos
vistos en teora, cul cree que es el ms adecuado?
Notas:
212
11. Un plan de estudios est estructurado para que los alumnos no puedan matricularse libremente de las
asignaturas que deseen. Existen asignaturas que deben haberse cursado (y aprobado) anteriormente para
que un alumno se pueda matricular de una asignatura dada. As, los prerrequisitos de una asignatura pueden
ser 0 o ms asignaturas. Por ejemplo, si los prerrequisitos de la asignatura A3 son las asignaturas A2 y A1, todo
alumno debe haber aprobado estas dos asignaturas antes de matricularse de A3. En el siguiente ejemplo se
muestra el grafo para el caso de un plan de estudios de cinco asignaturas:
Aqu, las asignaturas A1, A2 y A4 no tienen prerrequisitos. Los prerrequisitos de A3 son A1 y A2 y los de A5 son
A3 y A4. En este caso, si un alumno tiene aprobada la asignatura A1, podr cursar las asignaturas A2 y A4, y si
tiene aprobadas A1 y A2, podr cursar las asignaturas A3 y A4.
Implemente un mtodo que, dado el grafo que representa el plan de estudios y la lista de asignaturas que
un alumno tiene aprobadas, devuelva una lista con las asignaturas que puede cursar el prximo ao.
List<Asignatura> asignaturasPosibles (Graph<Asignatura,DefaultEdge> planEstudios, List<Asignatura> aprobadas)
Todas las asignaturas de la lista de entrada aprobadas son correctas (estn en el grafo y no se han producido
incoherencias como que aparezcan como aprobadas slo A1 y A5).
List<Asignatura> asignaturasPosibles
(Graph<Asignatura, DefaultEdge> planEstudios, List<Asignatura> aprobadas)
{
List<Asignatura> posibles = Lists.newArrayList();
for (Asignatura a : planEstudios.vertexSet()) {
boolean esVaida = false;
for (DefaultEdge e : planEstudios.edgeSet())
if ( planEstudios.getEdgeTarget(e).equals(a) && aprobadas.containts(a)
|| planEstudios.inDegreeOf(a) == 0 )
esValida = true;
if (esValida ) { posibles.add(a); }
}
return posibles;
}
213
12. Cree un grafo dirigido que no permita bucles ni mltiples aristas entre un par de vrtices. Los vrtices sern
de tipo String y las aristas no tendrn informacin relevante. Inserte tres vrtices y un par de aristas entre
ellos.
public void metodo() {
SimpleDirectedGraph<String, DefaultEdge> grafo =
new SimpleDirectedGraph<String, DefaultEdge>(DefaultEdge.class);
grafo.addVertex("1");
grafo.addVertex("2");
grafo.addVertex("3");
grafo.addEdge("1", "2");
grafo.addEdge("2", "3");
}
13. Cree un grafo con pesos que no permita aristas mltiples pero s bucles (arista con mismo origen y destino).
Inserte tres vrtices (de tipo String) y un par de aristas entre ellos. Dele peso a ambas aristas.
public void metodo() {
DefaultDirectedWeightedGraph<String, DefaultEdge> grafo =
new DefaultDirectedWeightedGraph<String, DefaultEdge>(DefaultEdge.class);
grafo.addVertex("1");
grafo.addVertex("2");
grafo.addVertex("3");
grafo.setEdgeWeight(grafo.addEdge("1", "2"), 10);
grafo.setEdgeWeight(grafo.addEdge("2", "3"), 20);
}
14. Cree un grafo no dirigido sin bucles ni multiaristas. Los tipos de los vrtices ser Integer y las aristas no
tendrn informacin relevante. Incluya 100 vrtices y haga el grafo completo.
public void metodo() {
SimpleGraph<Integer, DefaultEdge> grafo =
new SimpleGraph<Integer, DefaultEdge>(DefaultEdge.class);
for (Integer v = 0; v < 100; v++) {
grafo.addVertex(v);
}
for (Integer vOrg = 0; vOrg < 100; vOrg++) {
for (Integer vDes = 0; vDes < 100; vDes++) {
if (!vOrg.equals(vDes)) {
grafo.addEdge(vOrg, vDes);
}
}
}
}
214
16. Escriba un mtodo para obtener los vrtices correspondientes a un ciclo hamiltoniano de un grafo dado.
Para ello asegrese previamente que el grafo sea completo.
public List<V> cicloHamiltoniano
(SimpleWeightedGraph<V, DefaultWeightedEdge> grafo)
{
// Hacemos que el grafo sea completo aadiendo las aristas que le falten.
// Para que estas nuevas aristas no se tengan en cuenta, le damos peso mximo.
for (V vOrg : grafo.vertexSet()) {
for (V vDes : grafo.vertexSet()) {
if (!vOrg.equals(vDes) && !grafo.containsEdge(vOrg, vDes)) {
grafo.setEdgeWeight(grafo.addEdge(vOrg, vDes), Double.MAX_VALUE);
}
}
}
// Obtenemos los vrtices del ciclo Hamiltoniano
List<V> cicloH = Lists.newArrayList();
cicloH = HamiltonianCycle.getApproximateOptimalForCompleteGraph(grafo);
// Por definicin, en todo ciclo, el primer vrtice y el ltimo son el mismo.
// Aadimos el primer vrtice obtenido al final de la lista.
cicloH.add(cicloH.get(0));
return cicloH;
}
215
17. Una compaa telefnica est planificando la obra que implica distribuir su infraestructura de fibra ptica
por toda la ciudad. Su intencin es llegar a todos los centros distribuidores para, a partir de ellos, utilizar
cable coaxial hasta cada uno de los hogares que soliciten su servicio. Para ello tienen un plano de los puntos
donde colocarn centros de distribucin y las calles que los unen. Indique qu tipo de grafo utilizar, cmo
modelar el problema y qu algoritmo utilizar. Disee las clases que modelen los vrtices y las aristas.
Adems se quiere disear un camino que permita a un operador visitar todos los centros de distribucin
para hacer comprobaciones rutinarias pasando por cada uno una nica vez. Qu algoritmo y mtodos
deberamos usar?Existe camino para que el operador visite todos los centros de distribucin para hacer
comprobaciones rutinarias pasando por cada uno una nica vez?.
El grafo ser simple, pues entre dos centros distribuidores slo se extender un nico cable coaxial; ser no
dirigido, pues la comunicacin entre el centro distribuidor A y el B es bidireccional; y podr ser ponderado si
interesase determinar rutas ms cortas entre los centros distribuidores.
El problema se modelara, tal como se acaba de indicar, mediante un grafo simple, no dirigido y ponderado.
Los vrtices seran los centros distribuidores y las aristas las calles que los unen.
Grafo:
SimpleWeightedGraph<CentroDistribuidor, Calle> grafo =
new SimpleWeightedGraph<CentroDistribuidor, Calle>(Calle.class);
Vrtices:
import org.jgrapht.VertexFactory;
public class CentroDistribuidor implements VertexFactory<Integer> {
private Integer id;
public CentroDistribuidor(int id) { this.id = id; }
public Integer createVertex() {
this.id++;
return new Integer(this.id);
}
public Integer getId() { return this.id; }
public int hashCode() { return getId().hashCode(); }
public boolean equals(Object obj) {
CentroDistribuidor other = (CentroDistribuidor) obj;
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof VertexFactory)) return false;
if (this.id != other.getId())
return false;
return true;
}
public String toString() {
return "V[" + getId() + "]";
}
}
216
Para disear un camino que permita a un operador visitar todos los centros de distribucin para hacer
comprobaciones rutinarias pasando por cada uno una nica vez, deberamos utilizar el algoritmo
hamiltoniano, tal como sigue:
List<CentroDistribuidor> camino =
HamiltonianCycle.getApproximateOptimalForCompleteGraph(grafo);
217
218
Todo el material est inicialmente en una fbrica concreta a partir de la cual se quiere repartir al resto
de fbricas. (Suponga que el grafo y la fbrica inicial son parmetros de los mtodos que ha de
implementar). Implemente un mtodo que permita calcular y obtener el recorrido ms corto que existe
entre dicha fbrica inicial y una fbrica destino concreta.
Integer identificador;
String nombre;
String pas;
Integer numJuguetes;
Fbrica:
private Integer identificador;
private String nombre;
private Double distancia;
Apartado b)
public double getKmTrayectoTotal(SimpleDirectedWeightedGraph<Fabrica, Trayecto> g) {
double kmTotales = 0;
for (Trayecto trayecto : g.edgeSet()) {
kmTotales += g.getEdgeWeight(trayecto);
}
return kmTotales;
}
Apartado c)
public Set<Fabrica> getFabricasVecinasSalida(final SimpleDirectedWeightedGraph<Fabrica, Trayecto> grafo,
final Integer n) {
return Sets.newHashSet(Iterables.filter(grafo.vertexSet(), new Predicate<Fabrica>() {
@Override
public boolean apply(Fabrica arg0) {
return (grafo.outDegreeOf(arg0)) >= n;
}
}));
}
Apartado d)
public double getkmCubrir(SimpleDirectedWeightedGraph<Fabrica, Trayecto> g) {
KruskalMinimumSpanningTree<Fabrica, Trayecto> recubrimiento = new
KruskalMinimumSpanningTree<Fabrica, Trayecto>(g);
return recubrimiento.getSpanningTreeCost();
}
Apartado e)
public Set<Trayecto> getTrayectosRecubrimiento(SimpleDirectedWeightedGraph<Fabrica, Trayecto> g) {
KruskalMinimumSpanningTree<Fabrica, Trayecto> recubrimiento = new
KruskalMinimumSpanningTree<Fabrica, Trayecto>(g);
return recubrimiento.getEdgeSet();
}
Apartado f)
public List<Trayecto> getRecorridosDesdeFabricaInicialADestino(SimpleDirectedWeightedGraph<Fabrica,
Trayecto> grafo, Fabrica fabInicial, Fabrica fabDestino) {
return DijkstraShortestPath.findPathBetween(grafo, fabInicial, fabDestino);
}
219
Para las estaciones de metro, tendremos el identificador de la estacin, el nombre, los costes de construccinmantenimiento asociados y la lnea a la que pertenecen.
Para los tramos de lneas de metro, conocemos el identificador, las distancias y costes de construccinmantenimiento asociados a los tramos.
Se pide:
1. Indique de qu tipo son los vrtices y las aristas, las propiedades que tendran cada uno, qu tipo de grafo
utilizar y cmo modelar el problema (de qu tipo seran los vrtices y las aristas). La ponderacin del grafo
podra referirse a cada una de las propiedades que poseen los tramos. Por tanto, para este ejercicio indique
todas las propiedades como atributos, de cada tramo, sin tener en cuenta cul usara para su ponderacin.
2. Implemente un mtodo que permita calcular el nmero de kilmetros totales que hay en el mapa del
metro. Diga qu propiedad establecera en el grafo para la ponderacin de las aristas.
3. Implemente un mtodo que permita calcular el mnimo nmero de tramos de lneas de metro que
tendramos que construir para que los ciudadanos puedan llegar a todas y cada una de las estaciones de
tal manera que el coste que suponga su construccin total sea mnimo. Diga qu propiedad establecera
en el grafo para la ponderacin de las aristas.
4. Implemente un mtodo que permita calcular y obtener el recorrido ms corto que existe entre una estacin
origen y una estacin de destino concreta. Diga qu propiedad establecera en el grafo para la ponderacin
de las aristas.
5. Implemente un mtodo que, dado el nombre de una lnea, devuelva el conjunto de estaciones que
pertenecen a la lnea. (Haga uso de la interfaz Predicate e Iterables).
220
String
String
Integer
String
identificador
nombre
coste
linea
Lnea:
private String identificador
private Integer distancia
private Integer coste
Aparatado 2)
En este ejercicio la ponderacin vendr determinada por la distancia de la lnea.
public double getKmLineasTotal (SimpleDirectedWeightedGraph<Estacion, Linea> g) {
double kmTotales = 0;
for (Linea linea : g.edgeSet()) {
kmTotales += g.getEdgeWeight(linea);
}
return kmTotales;
}
Aparatado 3)
En este ejercicio la ponderacin vendr determinada por el coste de la lnea.
public double getCosteTotalLineasMinimo
(SimpleDirectedWeightedGraph<Estacion, Linea> g) {
KruskalMinimumSpanningTree<Estacion, Linea> recubrimiento = new
KruskalMinimumSpanningTree<Estacion, Linea>(g);
return recubrimiento.getSpanningTreeCost();
}
Aparatado 4)
En este ejercicio la ponderacin vendr determinada por la distancia de la lnea.
public List<Estacion> getCaminoDesdeEstacionInicialADestino
(SimpleDirectedWeightedGraph<Estacion, Linea> grafo, Estacion eInicial, Estacion eFin) {
return DijkstraShortestPath.findPathBetween(grafo, eInicial, eFin);
}
Aparatado 5)
public List<Estacion> getEstacionesDeLinea
(String linea1, SimpleDirectedWeightedGraph<Estacion, Linea> grafo)
{
return Lists.newArrayList(Iterables.filter(grafo.vertexSet(),
new Predicate<Estacion>() {
public boolean apply(Estacion arg0) {
return (arg0.getLinea().equals(linea1));
}
}));
}
221
En este ejemplo se muestran dos vrtices: el primero, con 3 fichas usadas y 3 restantes y el segundo, basado en
el primero con 4 fichas usadas y 2 restantes.
Implemente lo siguiente:
Un vrtice es vlido si hay una nica ficha usada o si habiendo ms de una, la penltima y la ltima
encadenan correctamente.
222
223
SOLUCIN:
Apartado 1)
Apartado 2)
224
Se est desarrollando una aplicacin que permita gestionar la cabalgata de los RR.MM. de este ao. La aplicacin
se utilizar para que, en cualquier momento, una persona cualquiera pueda obtener cierta informacin dando
el nombre de la calle y el nmero dentro de ella. La informacin que podr conocer ser:
1) Pasa la cabalgata por mi casa?
2) Cunto tiempo falta para que la cabalgata llegue?
3) Est la cabalgata, ahora mismo, por mi casa?
El sistema tiene informacin sobre qu tramos de calles estn siendo ocupados por la cabalgata (un tramo de
calle tiene un nmero de comienzo y un nmero de fin dentro de la propia calle). Diferentes sensores colocados
en todos los cruces de la ciudad actualizan la informacin de las calles ocupadas cuando la cabalgata entra o sale
de un tramo de calle.
Tendr que terminar de implementar la clase Cabalgata la cual hace uso de un grafo simple: los vrtices sern
Cruces y las aristas sern Tramos.
Atributos de la clase Cabalgata (dispone de mtodos getter y setter):
Graph<Cruce, Calle> mapa;
// grafo que recoge el mapa de la ciudad.
List<Integer> recorrido;
// lista de identificadores de Tramos de calle.
int posicionActualRecorrido; // posicin, dentro de recorrido, de la primera carroza.
id;
nombre_calle;
n_inicio;
n_fin;
hay_cabalgata;
tiempo_estimado;
//
//
//
//
//
//
Nota: Cuando la cabalgata se encuentra en un tramo de calle, se considerar que abarca a todos los nmero de
ese tramo de calle.
225
boolean pasaPorCasa(final String nombreCalle, final int numero): Comprueba si existe algn tramo
de calle en el grafo con el nombre indicado, que incluya el numero de calle indicado y que, adems, est
en el recorrido. (Utilice la librera Guava).
6)
private Tramo getTramoById(int idTramo): Busca en el grafo el tramo cuyo id corresponde con el que se
int tiempoEstimadoHasta(final String nombreCalle, final int numero): Calcula, a partir de la posicin
actual de la cabalgata, el tiempo estimado hasta llegar al tramo de calle con el nombre indicado y que
incluya al nmero indicado. Si la cabalgata no pasa por la casa o ya ha pasado, la funcin devuelve -1.
8)
void sensorEntraEnTramo(int idIn): Actualiza el atributo hayCabalgata del tramo indicado y la posicin
Predicados:
public class PredIdTramo implements Predicate<Tramo> {
int idTramo;
public PredIdTramo(int idT) { idTramo = idT;
}
public boolean apply(Tramo t) { return t.id_tramo == idTramo; }
}
public class PredHayCabalgata implements Predicate<Tramo> {
public boolean apply(Tramo t) {
return t.hayCabalgata; }
}
Constructor de Cabalgata:
public Cabalgata(List<Integer> recorrido) {
mapa = new SimpleGraph<Cruce, Tramo>(Tramo.class);
cargaMapa(vertices.txt, adyacencia.txt);
this.recorrido = recorrido;
posicionActualCabezaRecorrido = 0;
}
Mtodos de Cabalgata:
public boolean estaEnCasa(String calle, int numero) {
return tiempoEstimadoHasta(calle, numero) == 0;
}
boolean esExtremo(Tramo t) {
return Iterables.size(Iterables.filter(
getTramosAdyacentes(t), new PredHayCabalgata())) < 2;
}
226
Apartado 2)
private Tramo getTramoById(int pos) {
return Iterables.find(mapa.edgeSet(), new PredIdTramo(pos));
}
Apartado 3)
int tiempoEstimadoHasta(final String nombreCalle, final int numero) {
int i = -1;
boolean encontrado = false;
if (pasaPorCasa(nombreCalle, numero)) {
i = 0;
for (int pos = posicionActualCabezaRecorrido; pos < recorrido.size() && !encontrado; pos++) {
Tramo calleActual = getTramoById(recorrido.get(pos));
if (calleActual.nombre.equals(nombreCalle) && calleActual.n_inicio <= numero && calleActual.n_fin >= numero) {
encontrado = true;
}
i += calleActual.tiempoEnRecorrerse;
}
if (!encontrado) {
// Ya ha pasado
i = -1;
}
}
return i;
}
Apartado 4)
private Set<Tramo> getTramosAdyacentes(Tramo t) {
Set<Tramo> adyacentes = new HashSet<Tramo>();
adyacentes.addAll(mapa.edgesOf(mapa.getEdgeSource(t)));
adyacentes.addAll(mapa.edgesOf(mapa.getEdgeTarget(t)));
adyacentes.remove(t);
return adyacentes;
}
Apartado 5)
void sensorEntrandoEnTramo(int idIn) {
int siguienteTramo = recorrido.get(posicionActualCabezaRecorrido);
if (idIn != siguienteTramo) {
throw new IllegalArgumentException("Camino erroneo");
}
Tramo t = getTramoById(idIn);
if (!esExtremo(t)) {
throw new IllegalArgumentException("Camino erroneo, se ha roto la cabalgata.");
}
if (t.hayCabalgata)
throw new IllegalArgumentException("Camino erroneo, ya haba cabalgata.");
t.hayCabalgata = true;
posicionActualCabezaRecorrido++;
}
Apartado 6)
void sensorSaliendoDeTramo(int idOut) {
Tramo t = getTramoById(idOut);
if (!esExtremo(t)) {
throw new IllegalArgumentException("Camino erroneo, se ha roto la cabalgata.");
}
if (!t.hayCabalgata)
throw new IllegalArgumentException("Camino erroneo, no haba cabalgata.");
t.hayCabalgata = false;
}
227
El sistema dispone de un grafo con todas las centrales que pertenecen al servicio, as como los trayectos directos
que conectan las centrales. Cada central y cada trayecto estn definidos por un identificador y el nombre
correspondiente. Adems, cada trayecto tiene una determinada longitud, y cada central tiene la informacin
sobre la ciudad en la que se ubica, as como el nmero de paquetes que recibe diariamente para repartir
posteriormente por las localidades que tiene asociadas.
Realice los siguientes apartados:
1. Indique de qu tipo son los vrtices y las aristas, las propiedades que tendra cada uno, qu tipo de grafo
utilizar y cmo modelar el problema, teniendo en cuenta que sera interesante tener implcito en el
propio grafo la distancia que existe entre cada central.
2. Implemente un mtodo que, dado un nmero entero n pasado por parmetro, devuelva el conjunto de
centrales que tienen como mnimo n centrales adyacentes. (Haga uso de la interfaz Predicate e Iterables).
3. Dentro del servicio de mensajera existe una central origen que se encarga de dirigir los envos a cada
una de las centrales destino. Suponga que el grafo y la central origen son parmetros de los mtodos
que ha de implementar.
a. Implemente un mtodo que permita calcular y obtener el recorrido ms corto que existe entre
dicha central origen y cada una de las centrales destinatarias restantes.
b. Implemente un mtodo que permita calcular y obtener la distancia mnima entre la central
origen y cada una de las centrales destinatarias restantes.
4. El sistema tambin se plantea repartir un determinado nmero de paquetes desde una central origen
hasta una central destino utilizando las centrales intermedias para distribuir el reparto. De esta forma,
podra realizarse el reparto en paralelo a travs del sistema de distribucin de cada central y conseguir
que los paquetes lleguen al destino en un tiempo menor.
a. Indique si sera posible calcular el nmero de paquetes que debe circular por cada uno de los
trayectos a partir de la informacin almacenada en el grafo hasta ahora. Sera necesario
modificar la informacin que contienen las centrales? Y los trayectos? Sera necesario
modificar el grafo?
b. Implemente un mtodo que calcule y devuelva el nmero de paquetes que tiene que circular
por cada trayecto, teniendo en cuenta que se quieren repartir n paquetes desde la central origen
hasta la central destino indicada. Suponga que el grafo, la central origen y la central destino son
parmetros del mtodo que ha de implementar.
228
Apartado 1)
Sera necesario emplear un grafo dirigido (puesto que la existencia de un trayecto entre la central A y B
no implica que exista un trayecto entre B y A) y ponderado (para almacenar la distancia de cada trayecto)
(SimpleDirectedWeightedGraph). Podra emplearse un multigrafo ya que puede haber varios trayectos
entre un mismo par de centrales. (DirectedWeightedMultigraph).
Los vrtices seran objetos de tipo Central y las aristas objetos de tipo Trayecto.
El tipo Central tiene las propiedades:
private
private
private
private
Integer
String
String
Integer
identificador;
nombre;
ciudad;
numPaquetes;
Apartado 2)
public Set<Central> getCentralesMinNAdyacentesDirigido
(final SimpleDirectedWeightedGraph<Central, Trayecto> grafo, final Integer n)
{
return Sets.newHashSet(Iterables.filter(grafo.vertexSet(),
new Predicate<Central>() {
public boolean apply(Central arg0) {
return (grafo.inDegreeOf(arg0) + grafo.outDegreeOf(arg0)) >= n;
}
}
));
}
229
Apartado 3)
a>
// Usando FloydWarshallShortestPaths
public List<GraphPath<Central, Trayecto>> getRecorridosDesdeCentralOrigen1
(SimpleDirectedWeightedGraph<Central, Trayecto> grafo, Central centralOrigen)
{
FloydWarshallShortestPaths<Central, Trayecto> recorrido = new
FloydWarshallShortestPaths<Central, Trayecto>(grafo);
return recorrido.getShortestPaths(centralOrigen);
}
// Usando DijkstraShortestPath
public Map<Central, List<Trayecto>> getRecorridosDesdeCentralOrigen2
(SimpleDirectedWeightedGraph<Central, Trayecto> grafo, Central centralOrigen)
{
Map<Central, List<Trayecto>> recorridos = Maps.newHashMap();
for (Central c : grafo.vertexSet()) {
if (!c.equals(centralOrigen)) {
recorridos.put(c, DijkstraShortestPath.findPathBetween(grafo, centralOrigen, c));
}
}
return recorridos;
}
// Otra solucin
public List<GraphPath<Central, Trayecto>> getRecorridosDesdeCentralOrigen3
(SimpleDirectedWeightedGraph<Central, Trayecto> grafo, Central centralOrigen)
{
List<GraphPath<Central, Trayecto>> recorridos = Lists.newArrayList();
DijkstraShortestPath<Central, Trayecto> d = null;
for (Central c : grafo.vertexSet()) {
if (!c.equals(centralOrigen)) {
d = new DijkstraShortestPath<Central, Trayecto>(grafo, centralOrigen, c);
recorridos.add(d.getPath());
}
}
return recorridos;
}
b>
// Usando FloydWarshallShortestPaths
public Map<Central, Double> getDistanciasDesdeCentralOrigen1
(SimpleDirectedWeightedGraph<Central, Trayecto> grafo, Central centralOrigen)
{
Map<Central, Double> distancias = Maps.newHashMap();
List<GraphPath<Central, Trayecto>> recorridos = getRecorridosDesdeCentralOrigen1(grafo,
centralOrigen);
for (GraphPath<Central, Trayecto> ruta : recorridos) {
distancias.put(ruta.getEndVertex(), ruta.getWeight());
}
return distancias;
}
// Si se usa DijkstraShortestPath, sera la misma implementacin
// utilizando getRecorridosDesdeCentralOrigen3
230
a>
Sera necesario aadir, como propiedad del tipo Trayecto, el nmero mximo de paquetes que puede
circular por l.
No sera necesario modificar el tipo Central.
Sera necesario modificar la ponderacin de las aristas considerada en el grafo, de forma que se utilize
como peso la capacidad mxima de cada trayecto, es decir, el nmero de paquetes mximo que puede
circular por cada trayecto. Si no se modifica se considerara como capacidad mximo la distancia entre
cada central.
En caso de que no se haya definido previamente un grafo dirigido, para poder usar el algoritmo de
EdmondsKarpMaximumFlow es necesario que el grafo sea dirigido.
b>
public Map<Trayecto, Double> getFlujoMaximoPaquetes
( SimpleDirectedWeightedGraph<Central,Trayecto> g
, Central centralOrigen
, Central centralDestino
)
{
EdmondsKarpMaximumFlow<Central,Trayecto> e = new EdmondsKarpMaximumFlow<Central,Trayecto>(g);
e.calculateMaximumFlow(centralOrigen, centralDestino);
return e.getMaximumFlow();
}
231
Por ejemplo, la llamada getMovimientos(conjunto, capacidad), siendo conjunto el conjunto formado por
{1,2,3,4}, y capacidad 2, devolvera el conjunto:{ {1},{2},{3},{4},{1,2},{1,3},{1,4},{2,3},{2,4},{3,4} }. Si el
conjunto fuera {lobo, oveja, lechuga} y la capacidad fuera 1, devolvera { {lobo}, {oveja}, {lechuga} }.
Dado que vamos a utilizar el algoritmo de Dijsktra para encontrar el camino mnimo,
5) Cul ser el peso que se le asignar a cada arista?
6) Y a cada vrtice?
233
SOLUCIN:
Apartado 1)
public static void creaDatosLoboOvejaLechuga() {
capacidad = 1;
ObjetoTransporte o = new ObjetoTransporte("Oveja");
ObjetoTransporte l = new ObjetoTransporte("Lobo");
ObjectoTransporte le = new ObjectoTransporte("Lechuga");
tablaDeIncompatibilidades = new Table();
tablaDeIncompatibilidades.put(l, o, true);
tablaDeIncompatibilidades.put(o, l, true);
tablaDeIncompatibilidades.put(le, o, true);
tablaDeIncompatibilidades.put(o, le, true);
estadoInicial = new ProblemaTransporte();
estadoInicial.zona1 = new HashSet();
estadoInicial.zona1.add(o);
estadoInicial.zona1.add(l);
estadoInicial.zona1.add(le);
estadoInicial.zona2 = new HashSet();
estadoInicial.transporte = Zona1;
estadoFinal = new ProblemaTransporte();
estadoFinal.zona2 = new HashSet();
estadoFinal.zona2.add(o);
estadoFinal.zona2.add(l);
estadoFinal.zona2.add(le);
estadoFinal.zona1 = new HashSet();
estadoFinal.transporte = Zona2;
}
Apartado 2)
boolean apply(ProblemaTransporte pt) {
Boolean res = true;
Set<ObjetoTraslado> comprueba;
if (c.posicion == PosicionGuardian.LADO1) {
comprueba = c.lugar1;
} else {
comprueba = c.lugar2;
}
return esCompatible(comprueba, incompatibilidades);
}
234
Apartado 4)
public static Set<Arista> aristasAdyacentes(ProblemaTransporte from) {
Set<Arista> r = Sets.newHashSet();
Function<Set<ObjetoTraslado>, ProblemaTransporte> cs = new EstadoSiguiente(from);
ProblemaTransporte to;
Set<ObjetoTraslado> perms;
if (from.posicion == PosicionGuardian.LADO1) {
perms = from.lugar1;
} else {
perms = from.lugar2;
}
for (Set<ObjetoTraslado> objetos : permutaciones(perms, capacidadTransporte)) {
to = cs.apply(objetos);
if (esEstadoValido.apply(to)) {
r.add(Aristas.create(from, to));
}
}
return r;
}
Apartado 5)
El peso que se le asignar a cada arista es de 1.
Apartado 6)
El peso que se le asignar a cada vrtice es de 0.
235
La red social ETSII+ nos solicita que creemos nuevos mtodos para gestionar los usuarios
de la misma. El grafo sobre el que tenemos que trabajar tiene como vrtices el tipo
Usuario, y como aristas, DefaultWeightedEdge (cada relacin entre vrtices se considera
como distancia 1).
Devuelve el conjunto de usuarios conectados (componente conexa) de mayor tamao dentro del grafo.
Para el ejemplo devolvera el conjunto formado por Fran, Rosa, Miguel, Luisa, Inma y Pepe.
Dado el grafo, devolver los usuarios ms populares del grupo principal es decir, aquellos con el mximo
nmero de amistades (si hay ms de uno los incluiremos todos). En el ejemplo tendramos como usuario
ms popular a Miguel.
Devuelve la suma de distancias del grupo principal, es decir, la suma de las distancias entre todas las
parejas no repetidas de vrtices del grupo principal. En el ejemplo resultara 26.
236
237
Debido a la estructura que forma la sociedad actual, las redes sociales se han convertido en una herramienta
muy potente para multitud de aplicaciones. El departamento de I+D+i de la empresa de comunicaciones
TellMeFnica ha detectado precisamente en esta herramienta una oportunidad para expandir su negocio.
Concretamente, la idea consiste en determinar, de manera ponderada, la relacin existente entre los diferentes
usuarios de una red social en base a los gustos comunes entre dichos usuarios. Este trabajo ya ha sido realizado
por la empresa y ha dado como resultado un grafo semejante al siguiente:
238
Apartado b)
public Integer getAficionesComunes(Usuario usr1, Usuario usr2, SimpleWeightedGraph grafo) {
Integer ret = 0;
Aficion aficion = grafo.getEdge(usr1, usr2);
if (aficion != null) {
ret = aficin.getWeight();
}
return ret;
}
// Otra solucin:
Apartado c)
public Integer getMejorAfinidad(Set<Usuario> usuarios, SimpleWeightedGraph grafo) {
Integer ret = 0;
Integer temp = 0;
for (Usuario usuario : usuarios) {
for (Usuario amigo : usuarios) {
if (!usuario.equals(amigo)) {
temp = getAficionescomunes(usuario, amigo, grafo);
if (temp.compareTo(ret) > 0)
ret = temp;
}
}
}
return ret;
}
Apartado d)
public Set<Usuario> getUsuarioByName(final String nombre) {
return Iterables.filter(mapa.vertexSet(), new Predicate<Usuario>() {
public boolean apply(Usuario usuario) {
return usuario.getNombre().equals(nombre);
}
});
}
Apartado e)
Para la creacin de un rbol de expansin mnimo se debe usar el algoritmo de Kruskal. En este caso se
pide que se obtenga el rbol de expansin mximo, por lo que deber realizarse una modificacin sobre
el algoritmo de Kruskal en el que la eleccin de la mejor arista se basar en la que mayor peso posea, en
lugar del algoritmo original, en el que se eligen aquellas aristas con menor peso.
239
240
Tipo Transporte:
public
private
private
private
private
Aparatado 2)
En este ejercicio la ponderacin vendr determinada por el coste del transporte.
public List<Ciudad> getCaminoDesdeCiudadInicialADestino
(SimpleDirectedWeightedGraph<Ciudad, Transporte> grafo, Ciudad eInicial, Ciudad eFin)
{
return DijkstraShortestPath.findPathBetween(grafo, eInicial, eFin);
}
Aparatado 3)
En este ejercicio la ponderacin vendr determinada por la distancia del transporte.
public Set<Transporte> getCosteTotalTransportesMinimo
(SimpleDirectedWeightedGraph<Ciudad, Transporte> g) {
Set<Transporte> aristas = Sets.newHashSet(Iterables.filter(g.edgeSet(),
new Predicate<Transporte>() {
public boolean apply(Transporte arg) {
return (arg.getTipo().equals(
TipoTransporte.AEREO)||arg.getTipo().equals(TipoTransporte.MARITIMO));
}
}));
g.removeAllEdges(aristas);
KruskalMinimumSpanningTree<Ciudad, Transporte> recubrimiento = new
KruskalMinimumSpanningTree<Ciudad, Transporte>(g2);
return recubrimiento.getEdgeSet();
}
Aparatado 4)
public List<Ciudad> getCiudadesConFabrica
(SimpleDirectedWeightedGraph<Ciudad, Transporte> grafo)
{
return Lists.newArrayList(Iterables.filter(grafo.vertexSet(),
new Predicate<Ciudad>() {
public boolean apply(Ciudad arg0) {
return (arg0.getFabricas().size() > 0);
}
}
));
}
241
4.13.10
Flujo de trabajo.
Se quiere representar el orden de ejecucin de ciertas tareas mediante un grafo. Este grafo estar compuesto de
un conjunto de nodos interconectados mediantes unas aristas de control para formar un flujo de trabajo. Este
grafo se modelar utilizando nodos (clase Node) que pueden ser de tipo: Task, Event, Start, y End, que simularn
los diferentes tipos de tareas del grafo. Los nodos adems poseern un identificador y una mtrica duracin que
recoge la duracin de tiempo en segundos de dicho tarea. Slo los nodos de tipo Task poseern una duracin
mayor que cero, los dems tipos de nodos se supondrn que tienen una duracin de cero segundos.
Para definir la conexin entre los nodos se utilizarn aristas de control (clase AControl), que solo tendrn un
identificador de tipo String. A continuacin se muestra un grafo resultante de la composicin de siete tareas:
A1, A2, A3, A4, A5, A6, y A7; un evento inicial Start: S1; y un evento final End: E1. Notar que adherido a cada
nodo se encuentra anotado las duraciones de cada una de ellas. Observar que la terminacin de una tarea puede
llevar a la ejecucin de varias tareas siguientes (vase la tarea A3). En el grafo siguiente al finalizar la tarea A3
slo una de las tareas que le siguen (A4, A5, o A6) se ejecutar a continuacin. Esto implica que existan diferentes
caminos con diferentes duraciones. Por ejemplo el camino {S1, A1, A2, A3, A4, A7, E1} tiene duracin 31
segundos, mientras que el camino {S1, A1, A2, A3, A5, A7, E1} tiene una duracin de 36 segundos.
Se pide:
1. Indique qu clase de grafo utilizara e indique como implementara las clases Node y AControl
correspondientes a los nodos y aristas del grafo.
2. Justifique de entre los algoritmos que se han explicado en clase: Kruskal, Floyd-Warshall, Dijsktra, Dijsktra
Generalizado (A*), etc. Cul ser el ms adecuado para determinar el recorrido con mnimo tiempo de
duracin desde S1 hasta E1.
3. Implemente el mtodo Graph<Node, AControl> getGrafoEjemplo() que devuelva el grafo que se muestra
en el ejemplo de este enunciado, sin utilizar factorias.
4. Implemente el mtodo mtodo int getDuraciones(Graph<Node, AControl> g) que me devuelva la
duracin del proceso en funcin de la suma de las duraciones de todas las tareas del proceso, utilizando
Predicate e Iterables.
5. Suponiendo que se ha obtenido el camino ptimo del grafo, que vendr definido por un Set<AControl>.
Implemente el mtodo int getDuracion(Graph<Node, AControl> g, Set<AControl> s) que devuelva la
duracin total de dicho camino ptimo.
6. Implemente el mtodo Set<Node> getTareaCrtica(Graph<Node, AControl> g) que devuelva el
conjunto de tareas crticas del grafo. Supondremos que una tarea es crtica cuando sumndole uno a su
duracin, el camino ptimo aumenta su duracin del grafo aumenta su duracin. Suponga que dispone del
mtodo implementado en el apartado 5 y del siguiente mtodo: Set<AControl> getCaminoOptimo
(Graph<Node, AControl> g) que devuelve el conjunto de aristas de control del camino ptimo del grafo
de entrada.
242
Apartado 2)
Dijsktra Generalizado (A*) ya que las duraciones estn en los nodos y no en las aristas.
Apartado 3)
public static Graph<Node, AControl> getGrafoEjemplo() {
SimpleDirectedGraph<Node, AControl> process = new SimpleDirectedGraph<Node,
Sequence>(AControl.class);
// Alternativa vlida:
// SimpleDirectedGraph<Node,DefaultEdge> process=new
// SimpleDirectedGraph<Node, DefaultEdge>(DefaultEdge.class);
Node s16 = new Node("S1", TipoNodo.start, 0, 0);
Node e16 = new Node("E1", TipoNodo.end, 0, 0);
Node
Node
Node
Node
Node
Node
Node
a16
a26
a36
a46
a56
a66
a76
=
=
=
=
=
=
=
new
new
new
new
new
new
new
Node("A1",
Node("A2",
Node("A3",
Node("A4",
Node("A5",
Node("A6",
Node("A7",
TipoNodo.task,
TipoNodo.task,
TipoNodo.task,
TipoNodo.task,
TipoNodo.task,
TipoNodo.task,
TipoNodo.task,
1);
10);
5);
10);
15);
10);
5);
process.addVertex(s1);
process.addVertex(e1);
process.addVertex(a1);
process.addVertex(a2);
process.addVertex(a3);
process.addVertex(a4);
process.addVertex(a5);
process.addVertex(a6);
process.addVertex(a7);
// Se permite crear la arista aparte e incluirla como parmetro en el addEdge
// AControl s1a1 = new AControl(s1a1);
// process.addEdge(s1, a1, s1a1);
process.addEdge(s1,
process.addEdge(a1,
process.addEdge(a2,
process.addEdge(a3,
process.addEdge(a3,
process.addEdge(a3,
process.addEdge(a3,
process.addEdge(a4,
process.addEdge(a5,
process.addEdge(a6,
process.addEdge(a7,
return process;
a1).setId("s1a1");
a2).setId("a1a2");
a3).setId("a2a3");
g1).setId("a3g1");
a4).setId("a3a4");
a5).setId("a3a5");
a6).setId("a3a6");
a7).setId("a4a7");
a7).setId("a5a7");
a7).setId("a7a7");
e1).setId("a7e1");
243
Apartado 4)
public static int getDuraciones(Graph<Node, Sequence> g) {
Iterable<Node> it = Iterables.filter(g.vertexSet(), new Predicate<Node>() {
public boolean apply(Node n) {
return n.getTipo().equals(TipoNodo.task);
}
});
// Otra opcin sera filtrar por las tareas con duracin mayor que 0
/*
Iterable<Node> it = Iterables.filter(g.vertexSet(), new Predicate<Node>() {
public boolean apply(Node n) {
return n.getDuracion() > 0;
}
});
*/
int ac = 0;
for (Node node : it) {
ac += node.getDuracion();
}
return ac;
}
Apartado 5)
public static int getDuracion(Graph<Node, AControl> g, Set<AControl> s) {
int ac = 0;
for (AControl a : s) {
ac += g.getEdgeSource().getDuracion();
}
return ac;
}
Apartado 6)
public static Node getTareaCritica(Graph<Node, Sequence> g) {
Set<Node> s = g.vertexSet();
Set<Sequence> co = null;
int duration = getDurationA(getCaminoOptimo(g));
int max = 0;
Node tcritica = null;
for (Node n : s) {
n.setMetric(n.getMetric() + 1);
co = getCamionOptimo(g);
max = getDuracion(co); // Mtodo calcula duracin de un conjunto de aristas.
if (max > duration) {
tcritica = n;
}
n.setMetric(n.getMetric() - 1);
}
return tcritica;
}
244
4.13.11
0
0
0
0
-1
-1
0
0
-1
-1
0
0
0
0
0
2
3
0
-1
-1
10
0
0
-1
0
2
3
1
1
0
-1
0
0
-1
FIN
0
0
-1
0
0
3
0
0
0
0
1
0
0
0
En el mapa de arriba se han marcado la zona de donde sale el avin (INI) y la zona a donde debe llegar (FIN). Las
zonas marcadas con nmeros negativos representan obstculos por donde el avin no podr pasar y el resto
estn marcadas con el nivel de riesgo de la zona (a mayor valor ms riesgo).
El plan de vuelo debe calcular el camino mnimo desde el origen hasta el destino teniendo en cuenta las
siguientes restricciones:
Los movimientos posibles del avin son norte (arriba), sur (abajo), oste (izquierda) o este (derecha).
Cada vez que el avin realiza un cambio de rumbo (Por ejemplo: [derecha, abajo], [arriba, izquierda], etc) ser
penalizado con 3 puntos de riesgo.
La suma de los riesgos de las zonas por donde pasa el plan de vuelo debe ser lo mnimo posible.
Se utilizar la distancia manhattan, d(x, y) = |x1 y1| + |x2 y2| , como heurstica para estimar el numero mnimo
de movimientos desde la ltima zona visitada a la zonda de llegada.
En el siguiente mapa se ha trazado el plan de vuelo para llegar del origen al destino atendiendo a las restricciones
anteriores.
INI
0
0
0
0
0
0
X
0
0
0
-1
-1
0
X
-1
-1
0
0
0
0
X
2
3
0
-1
-1
10
X
0
-1
0
2
3
1
X
0
-1
0
0
-1
FIN
X
0
-1
0
0
3
X
X
X
X
X
X
X
X
El peso del camino representado en la imagen (casillas con valor X) se calcula de la siguiente forma:
Movimientos
2 Giros
Riesgo
15
6
2
Total:
23
245
246
247
248
4.13.12
El mdulo de gestin de datos (ya implementado) se encargar de confeccionar un grafo con los PITs
seleccionados estando disponible para los mtodos a implementar.
La aplicacin, con los PITs seleccionados, calcular las consultas de los usuarios que podrn ser:
Consulta 1: Cunto se tardara realizar un circuito que recorra y visite todos los PITs seleccionados? Un circuito
termina en el punto que empez; se garantiza que existe dicho circuito en el grafo existente.
Consulta 2: Debido a fenmenos naturales es posible que algunos trayectos queden impracticables. Una vez el
usuario los haya marcado el mdulo de gestin de datos modificar el grafo asignando tiempos
infinitos a esos trayectos. Cunto sera el tiempo mnimo para llegar desde un PIT A a un PIT B
indicados por el usuario? Slo el tiempo de trayecto, no se realizan paradas de visita ni en los PIT A
y B ni en ninguno por los que se pase.
Se pide:
1. Modelar el problema con un grafo. Indique el tipo de grafo utilizado, las representaciones de nodos
y vrtices y razone todas las respuestas.
2. Cree un mtodo que responda a la Consulta 1 y que tendr una signatura similar a la siguiente:
Integer getTiempoTotalConVisita( grafo)
3. Para que siempre est garantizada la existencia de un circuito Qu caractersticas tiene que cumplir
el grafo que se crea partiendo de la lista de PITs elegidos por el usuario?
4. Implemente un mtodo que responda a la Consulta 2 y que tendr una signatura similar a la
siguiente:
Integer getTiempoRecorridoMnimo( grafo, desde, hasta)
249
250
Apartado a)
SimpleWeightedGraph<PIT, Recorrido> grafo;
public class PIT {
Integer identificador;
String nombre;
Integer tiempoVisita;
...
}
public class Trayecto extends DefaultWeightedEdge {
Integer identificador;
...
}
Apartado b)
Integer getTiempoTotalConVisita(SimpleWeightedGraph<PIT, Trayecto> gra) {
List<PIT> vertices = HamiltonianCycle
.getApproximateOptimalForCompleteGraph((SimpleWeightedGraph<PIT, Trayecto>) gra);
Integer tiempo = 0;
PIT desde = null, hasta = null;
for (PIT p : vertices) {
if (desde != null) {
hasta = p;
// Tiempo de trayecto
tiempo = tiempo + (int) gra.getEdgeWeight((gra.getEdge(desde, hasta)));
// Tiempo de visita
tiempo += p.getTiempoVisita();
desde = p;
} else {
desde = p;
// Desde el ltimo al primero
tiempo = (int) gra.getEdgeWeight((gra.getEdge(vertices.get(vertices.size() - 1), desde)));
// Tiempo de visita del primero
tiempo += p.getTiempoVisita();
}
}
return tiempo;
}
Apartado c)
Cada grafo debe ser completo y cumplir la desigualdad triangular.
Apartado d)
Integer getTiempoRecorridoMinimo(SimpleWeightedGraph<PIT, Trayecto> gra, PIT desde, PIT hasta) {
Integer tiempo = 0;
DijkstraShortestPath<PIT, Trayecto> camMinimo = new DijkstraShortestPath<PIT, Trayecto>(gra, desde, hasta);
for (Trayecto t : camMinimo.getPathEdgeList()) {
tiempo += (int) gra.getEdgeWeight(t);
}
return tiempo;
}
251
Se pide:
1. Justifique qu grafo es el ms adecuado para realizar una implementacin del problema anterior. Defina qu
ser un vrtice y qu ser una arista del grafo. Implemente las clases que sean necesarias para modelar los
vrtices y las aristas del grafo.
2. Proporcione una implementacin que devuelva un recorrido que pase por todos los vrtices del grafo una
sola vez utilizando una estrategia de recorrido en profundidad.
3. Proporcione una implementacin que devuelva un recorrido que pase por todos los vrtices del grafo una
sola vez utilizando una estrategia de primero el ms cercano.
4. Proporcione una implementacin que permita obtener el recorrido mnimo que pase por todas las iglesias y
que termine en la misma iglesia en la que comenz. Justifique qu recorrido ser el ms adecuado en este
caso.
5. Proporcione un mtodo que devuelva la distancia total de un determinado recorrido (es decir, una lista de
iglesias) que se recibe como parmetro de entrada. Por ejemplo, dicho mtodo puede recibir los recorridos
obtenidos en los apartados 2, 3 o 4 y devolver la distancia asociada a dicho recorrido.
252
Las cartas agrupadas en categoras dentro de los mazos deben estar ordenadas de menor a mayor valor de sus
habilidades.
Veamos un ejemplo donde se da como entrada un mazo desordenado y se obtiene como salida uno ordenado
segn el criterio que se ha especificado, es decir: primero estn todas las cartas con categora "Equipamiento"
ordenadas de menor a mayor valor de sus habilidades, despus las de "Magia" siguiendo el mismo criterio de
ordenacin, y por ltimo las de "Accin" siguiendo tambin el mismo criterio.
MAZO DESORDENADO
Carta
Carta
Carta
Carta
Carta
Carta
Carta
1
2
3
4
5
6
7
Equipamiento: 2,2,2
Accion: 2,2
Magia: 1,2,1,1
Magia: 1,1,1,1
Accion: 4,2
Equipamiento: 1,1,3
Accion: 1,2
MAZO ORDENADO
Carta
Carta
Carta
Carta
Carta
Carta
Carta
1
2
3
4
5
6
7
Equipamiento: 1,1,3
Equipamiento: 2,2,2
Magia: 1,1,1,1
Magia: 1,2,1,1
Accion: 1,2
Accion: 2,2
Accion: 4,2
Se pide:
1. Implemente un algoritmo de ordenacin de QuickSort que dado un mazo de cartas (representado por una
lista de cartas). Ordene la lista de cartas segn su tipo y de menor a mayor segn el valor de sus habilidades
tal como se ha descrito. Tenga en cuenta que cada carta se representa como un array de enteros que contiene
el valor para sus habilidades.
2. Determine el tamao del problema, calcule el T(n), y determine la complejidad del algoritmo implementado.
3. Cree adecuado utilizar un algoritmo de QuickSort para este tipo de ordenacin? Propondra otras
alternativas?
253
Adems, cada trayecto tiene una determinada longitud, y cada ciudad tiene informacin sobre el pas en el que
se ubica, as como el nmero de clientes que hay que atender.
Se pide:
1. Modele el problema usando el grafo ms apropiado. Explique qu representan los vrtices y las aristas y las
propiedades que tendran.
2. Implemente un mtodo que permita calcular el nmero de km totales que hay en el mapa de trayectos (hay
que tener en cuenta todos los trayectos existentes).
3. Implemente un mtodo que dado un nmero entero n pasado por parmetro, devuelva el conjunto de
ciudades que tienen como mnimo n ciudades vecinas a las que puede ir. (Haga uso de la interfaz Predicate
e Iterables).
4. Implemente un mtodo que permita calcular el mnimo nmero de km que se necesita recorrer para poder
atender a todas las ciudades del grafo.
5. Implemente un mtodo que permita calcular el conjunto de trayectos que se necesita para atender a los
clientes de todas las ciudades del grafo de la manera ms eficiente (recorriendo menos kilmetros).
6. Todas las mercancas estn inicialmente en una ciudad concreta a partir de la cual se quiere repartir al resto
de ciudades. (Suponga que el grafo y la ciudad inicial son parmetros de los mtodos que ha de
implementar). Implemente un mtodo que permita calcular y obtener el recorrido ms corto que existe
entre dicha ciudad inicial y una ciudad destino concreta.
254
255
Ejercicio de ampliacin:
5. Comparar ambas versiones usando diferentes vectores de entrada (con diferentes tamaos y ordenaciones)
haciendo uso de la funcin clock de C. Puede encontrar ms informacin sobre cmo medir el tiempo de
ejecucin del algoritmo en la siguiente direccin http://www.cs.rpi.edu/~musser/gp/timing.html
256
No hay predominante en ninguna de las dos partes. Se devuelve que no hay predominante
La parte derecha tiene predominante, la izquierda no. Se cuenta cuantos elementos tienen ese valor
(predominante derecha) en el vector completo y si es predominante se devuelve. En caso contrario se
devuelve que no hay predominante.
Ambas partes tienen un predominante. Si son distintos se cuentan ambos valores en el vector completo
y se devuelve el mayoritario de haberlo.
Se pide:
1. Calcule la complejidad. Tenga en cuenta que hay un coste recursivo y uno no recursivo, obtenga una cota
superior.
2. Implemente el algoritmo recursivo descrito.
3. Cmo planteara una implementacin iterativa? Usara memoria? Dependera el tamao de la memoria
del tamao del vector? Por qu?
257
En la provincia de Sevilla se dispone de un conjunto de embalses y pantanos para la acumulacin de agua. Dentro
del Plan Hdrico Provincial se desea realizar el estudio de diferentes posibilidades de realizar la interconexin de
los mismos de forma que se pueda trasvasar agua entre cualquiera de ellos. De cada pantano se tiene su nombre
y la altura sobre el nivel del mar a que est situado.
La distribucin de los pantanos, incluyendo su nombre y la altura, as como la distancia de las conexiones posibles
entre ellos (en km), se muestran en el siguiente esquema.
Debe realizar un programa que ayude al equipo directivo en la decisin de las obras a acometer. Para ello deber
1. Modelar el problema con un grafo. Indique el tipo de grafo utilizado, las representaciones de nodos y vrtices
y raznelo.
2. Calcule cul sera el total de kilmetros de canalizacin a realizar si se quisiesen construir todas las
conexiones indicadas. Este ser el objetivo final de la obra.
3. Para conocer cmo acometer la primera fase de ejecucin calcule cul sera el conjunto mnimo de
conexiones que permiten conectar, directa o indirectamente, todos los pantanos. Realice un mtodo para
ste clculo y otro para conocer su longitud.
4. Desarrolle un mecanismo para que la gestin de los trasvases se optimice creando un mtodo que permita
a los gestores encontrar el camino mnimo entre dos pantanos partiendo de las conexiones obtenidas en el
punto anterior.
258
Para el ejemplo anterior el periodo de mximo crecimiento se produjo entre los aos 1999 y 2009.
a) Disee un algoritmo que utilice la tcnica dyv para realizar dicho clculo.
b) Calcule el orden de complejidad del algoritmo.
Pista: Para resolver este problema habr que utilizar el algoritmo de la subsecuencia de suma mxima. Dicho
algoritmo recibir como entrada un array que contendr los porcentajes de crecimiento y decrecimiento
interanuales de la natalidad. Estos porcentajes, pi, sern calculados mediante la frmula (ti+1-ti) / ti donde
ti es la tasa de natalidad en el periodo i.
Ao Tasa
Ao Tasa
Ao Tasa
2012
2011
2010
2009
2008
2007
2006
2005
2004
2003
2002
2001
2000
1999
1998
1997
1996
1995
1994
1993
1992
1991
1990
1989
1988
1987
1986
1985
1984
1983
1982
1981
1980
1979
1978
1977
1976
1975
9,67
10,07
10,42
10,65
11,28
10,86
10,85
10,65
10,57
10,44
10,08
9,95
9,85
9,5
9,17
9,31
9,17
9,23
9,4
9,84
10,15
10,16
10,32
10,52
10,81
11,04
11,37
11,87
12,35
12,72
13,58
14,11
15,21
16,13
17,2
17,93
18,74
18,73
259
260
double funcionDistancia(Via v)
b. Dos mtodos estticos que sirvan como criterios: Uno que sirva para evitar autopistas de peaje
y otra que sirva para evitar vas cuya velocidad media supere los 60 km/h.
i. boolean criterioNoPeages(Via v)
ii.
boolean criterioVelocidadMenor60(Via v)
Tests propuestos:
Usando los datos del ejemplo, podr calcular rutas entre las localidades: Sevilla, Alcal de Guadaria, Utrera,
Montequinto, Dos Hermanas.
a) La ruta ms corta desde Sevilla a Utrera utilizando vas que no superen los 60 km/h de velocidad mxima
es: Sevilla, Montequinto, Dos Hermanas, Utrera.
b) La ruta ms rpida desde Sevilla a Utrera es: Sevilla, Montequinto, Utrera.
261
262
4.14.10
Adornos de Navidad.
El ayuntamiento est planificando adornar la ciudad para las fiestas
de Navidad. Su intencin es decorar todas las zonas importantes de la
ciudad, sin embargo, ste ao tiene un presupuesto menor, as que ha
tomado la determinacin de minimizar el nmero de adornos. Tiene
que tener en cuenta que los monumentos principales hay que
adornarlos obligatoriamente, as que su nica posibilidad es recortar
los adornos que se ponen en las calles: slo va a decorar algunas de
las calles que unen los monumentos importantes. Para ello, dispone
de un mapa con los monumentos desde donde se empezarn los
adornos y las calles (con sus distancias) que los unen.
263
4.14.11
Misin Ninja.
Un guerrero Ninja est planificando la misin de llevar a salvo un paquete con cierta
informacin secreta a un maestro Ninja. El principal problema es que mientras que el
Ninja se encuentra en la aldea A, el maestro se encuentra en la aldea B. Para cumplir
con la misin encomendada, debe pasar por las aldeas de sus amigos Ninjas que le
darn ms informacin y la energa suficiente para alcanzar su destino.
Por los diferentes caminos se va a ir encontrando un nmero de enemigos que quieren evitar que logre su
cometido, por lo que su intencin es llegar al maestro teniendo que luchar contra el menor nmero de enemigos
posibles. Para ello, tiene un mapa de las aldeas donde se encuentran sus amigos y los caminos que los unen,
adems, sus amigos le han dado una estimacin del nmero de enemigos que se encuentran en los diferentes
caminos.
Realice los siguientes apartados:
1. Indique de qu tipo son los vrtices y las aristas, qu tipo de grafo utilizar, cmo modelar el problema y qu
algoritmo utilizar.
2. Implemente todas las clases que estime oportunas para modelar el problema.
3. Escriba un fichero que serialice un grafo ejemplo para este problema y se use posteriormente para resolverlo.
(Considere un mnimo de 5 aldeas).
4. Cree una clase MapaAldeas que modele el grafo (creacin de aldeas y caminos, modificacin, eliminacin,)
5. Cree una clase llamada MisionNinja donde se realicen las diferentes operaciones sobre el mapa de aldeas
que el Ninja necesita para cumplir su misin:
a. Mtodo que disee el camino que le permita visitar a todos sus amigos para obtener la informacin
necesaria pasando por cada aldea una nica vez.
b. Mtodo que le indique cmo llegar al maestro luchando contra el menor nmero de enemigos
posibles, aunque eso signifique que no visite a todos sus amigos.
c. Mtodo que le indique en el camino del apartado b) con cuntos enemigos luchara.
d. Mtodo que devuelva el conjunto de aldeas que tienen acceso por un nmero de caminos dado.
(Haga uso de la interfaz Predicate e Iterables).
e. Invente 2 nuevos mtodos que considere importantes para ayudar al Ninja a cumplir su misin.
6. Implemente una clase TestMisionNinja que compruebe todos los mtodos implementados y muestre por
pantalla el grafo y el resultado de los diferentes mtodos.
264
4.14.12
Para cumplir con la misin encomendada, disponen de un mapa con todas las ciudades que tienen que visitar y
la distancia y caminos entre todas ellas. Cada va tiene una determinada longitud que est asociada directamente
con el tiempo que se tarda en atravesarla para ir de una estacin a otra. Adems, cada ciudad tiene la informacin
del nmero de nios que recibirn regalos.
Realice los siguientes apartados:
1. Indique de qu tipo son los vrtices y las aristas, qu tipo de grafo utilizar, cmo modelar el problema y qu
algoritmo utilizar.
2. Implemente todas las clases que estime oportunas para modelar el problema.
3. Escriba un fichero que serialice un grafo ejemplo para este problema y se use posteriormente para resolverlo.
(Considere un mnimo de 5 ciudades).
4. Cree una clase MapaCiudades que modele el grafo (creacin de ciudades y caminos, modificacin,
eliminacin,)
5. Cree una clase llamada MisionReyesMagos donde se realicen las diferentes operaciones sobre el mapa de
ciudades que los Reyes Magos tienen para repartir los regalos:
a. Mtodo que disee el recorrido que le permita visitar a todas las ciudades una nica vez.
b. Mtodo que le indique el nmero total de nios que recibirn regalos.
c. Mtodo que le indique el recorrido ms corto para llegar a una ciudad determinada.
d. Mtodo que le indique en el recorrido del apartado b) la distancia total recorrida.
e. Invente 2 nuevos mtodos que considere importantes para ayudar a los Reyes Magos a repartir los
regalos.
6. Implemente una clase TestMisionReyesMagos que compruebe todos los mtodos implementados y muestre
por pantalla el grafo y el resultado de los diferentes mtodos.
265
4.14.13
Misin Zombie.
Invente 2 nuevos mtodos que considere importantes para ayudar a los zombies a cumplir su misin.
6. Implemente una clase TestMisionZombie que compruebe todos los mtodos implementados y muestre por
pantalla el grafo y el resultado de los diferentes mtodos.
266
5 Programacin Dinmica.
5.1 Introduccin.
La Programacin Dinmica tiene distintas acepciones en la literatura. Aqu llamaremos Programacin Dinmica
a una generalizacin de la tcnica de Divide y Vencers, en su versin de reduccin, con o sin memoria.
Esencialmente la generalizacin consiste en considerar, para dividir un problema en subproblemas, un conjunto
de alternativas u opciones. Para dividir un problema en subproblemas se considera una de las alternativas, se
divide el problema en subproblemas, se resuelven y luego se combinan estas soluciones obtenidas. Aparecen
nuevos elementos con respecto a la tcnica de Divide y Vencers: alternativas y combinacin de las soluciones
obtenidas tras escoger las diferentes alternativas.
Suponemos que las alternativas estn descritas por un conjunto finito que denominaremos A. Por a, a1, a2,
representaremos valores concretos de las alternativas. El conjunto de alternativas disponibles puede depender
del problema en cuestin. Cuando hay dudas, el conjunto de alternativas disponibles para resolver un problema
X, lo representaremos como Ax, y sin subndice cuando no haya dudas sobre el conjunto en cuestin. Para
resolver un problema dado X, en la tcnica de la Programacin Dinmica, tomamos una de las alternativas y
posteriormente dividimos el problema en subproblemas. Tras elegir una de las alternativas del conjunto Ax, el
problema se divide en los subproblemas 1 , 2 , , . En general, el nmero de subproblemas depende de la
alternativa escogida. Si k = 1 (slo un subproblema), como en el caso de Divide y Vencers, denominaremos a la
tcnica Programacin Dinmica con Reduccin.
() = {
(, (1 ), (2 ), , ( ))
Donde X representa el problema de tamao n, Xi los subproblemas de tamao ni con ni < n, Comb la funcin que
combina las soluciones de los subproblemas (en el contexto del problema a resolver) y Sb la solucin del caso
base de los que puede haber ms de uno. Representaremos por la no existencia de solucin. En la tcnica de
Divide y Vencers, si uno de los problemas no tiene solucin (solucin ), entonces el problema completo
tampoco la tiene.
El esquema de un problema que se resuelve por Programacin Dinmica comparte elementos con la anterior
tcnica, pero es ms general. Su forma es:
() = {
(, , (1 ), (2 ), , ( ))
Donde, como vimos antes, 1 , 2 , , son los subproblemas en los que se puede dividir X tras optar por la
alternativa a del conjunto Ax. Asumimos que los casos base no tienen ningn subproblema y tampoco tienen
subproblemas aquellos problemas para los que Ax = .
Cada problema tiene asociado un tamao n(X) y los tamaos de los subproblema deben ser menores que el
tamao del problema original, esto es: n( ) < n(K) con I [1, k], a Ax.
267
= (, )
= = (, )
Donde hemos denotado por sa la solucin que se obtiene para el problema si seleccionamos la alternativa a.
Vemos que si Ax = , es decir no hay alternativas, entonces las solucin es , es decir no hay solucin. Esta es la
idea que estar implcita en los problemas posteriores.
Como hemos comentado antes, el conjunto de alternativas Ax depende del problema X que estemos
considerando. En cada problema concreto habr que detallar el clculo de este conjunto a partir de las
propiedades del problema.
+ (, )
(, ) = {
+ (, )
+ (, )
Donde
= ( , ), = 1,
= ((, , 1 , 2 , , ))
Como vemos, el problema generalizado anterior devuelve no slo la solucin al problema planteado, sino que
tambin devuelve las soluciones a todos los subproblemas que se han tenido que resolver. Esto nos permite
aprovechar los posibles subproblemas compartidos y resolver un conjunto de problemas en general.
Un conjunto de problemas lo podemos caracterizar por un rango de valores de una o varias propiedades de un
problema. Por tanto, especificar un conjunto de problemas consiste, desde el punto de vista que nos interesa
aqu, en dar un problema y unos rangos donde se movern los valores de alguna de sus variables. Un problema
lo representaremos por (SX, n). El conjunto de problemas estar formado por una secuencia de problemas Xi
cada uno de los cuales est indexado por un ndice i = 1, , n.
Usando el esquema anterior, la solucin de un conjunto de problemas fConjunto(SX, n) (y de los todos los
subproblemas que es necesario resolver) mediante Programacin Dinmica con memoria es de la forma:
(, ) = (, 0, , )
(, , , ) = {
268
(, + 1, , (+1 , ))
(+1 , )
+1 <
+1 =
Como vemos arriba la Programacin Dinmica con memoria (de un problema o un conjunto de problemas)
generaliza el problema aadiendo una propiedad que mantiene en una memoria las soluciones de los problemas
ya resueltos. Resolver un conjunto de problemas es resolver consecutivamente uno tras otro, usando la memoria
devuelta, por los que han sido resueltos previamente. Como vemos, siempre aparece un nuevo caso base que
ocurre si el problema considerado pertenece al dominio de la memoria. Es decir, a los problemas ya resueltos.
Como hemos comentado, si el conjunto de las alternativas est vaco, el problema no tiene solucin. Por defecto
asumimos que la Programacin Dinmica siempre usa memoria aunque puede haber casos concretos en que no
sea as. En las secciones siguientes hablaremos slo de Programacin Dinmica independientemente de que se
haga con memoria o sin ella, aunque por defecto asumiremos que es con memoria.
(, , (1 ), (2 ), , ( ))
Todos los problemas , 1 , 2 , , son problemas del conjunto de problemas considerado. Cada subproblema
debe ser de un tamao menor que el problema original. Esta relacin de tamao nos permite definir una
relacin de orden total entre los problemas del conjunto. En el sentido de que < si el tamao de Y es menor
que el tamao de X. Con esta definicin previa se debe cumplir que < para cada uno de los subproblemas.
El conjunto de problemas ms las relaciones entre problemas y subproblemas definen implcitamente un grafo
dirigido. Este grafo es un grafo bipartito con dos tipos de vrtices: vrtices problemas y vrtices alternativas.
Cada vrtice problema contiene un problema del conjunto de problemas. Cada vrtice alternativa contiene un
problema ms una alternativa escogida de entre su conjunto de alternativas.
Cada problema del conjunto de problemas lo representamos por X, X1, , Xr. Las alternativas para un problema
dado las representaremos por (X, a). Los subproblemas para una alternativa dada los representamos por .
Cada problema se asocia a un vrtice y hay una arista entre los vrtices X y (X, a) si hay una alternativa a Ax , y
otra arista entre (X, a) y para cada entero i que represente un subproblema de X tomando la alternativa a.
Cada arista entre los vrtices X y (X, a) la representamos de la forma X a (X, a) y cada arista entre los vrtices
(X, a) y la representamos de la forma (X, a) i .
269
((, , (1 ))
Cada problema X se reduce al problema 1 despus de tomar la alternativa . En el caso de Reduccin, por
haber un slo subproblema para cada problema tras tomar una alternativa, los subgrafos que definen una
solucin pueden definirse unvocamente por secuencias de alternativas. A partir de un problema, cada
alternativa nos conduce a un slo subproblema, es decir, una solucin en el caso de Reduccin es un camino en
el grafo que conecta el problema con un caso base. Resolver un problema, desde esta perspectiva, es encontrar
un camino, si existe, desde el vrtice que representa el problema hasta una hoja que sea un caso base. Encontrar
todas las soluciones al problema es encontrar todos los caminos desde el vrtice que representa el problema
que acaban en casos base. Conocidas todas las soluciones, la mejor con respecto a una propiedad dada de la
solucin, es aquella que tiene mayor (o menor) valor de esa propiedad entre todas las soluciones encontradas.
En el caso de Reduccin, las soluciones a un problema vienen caracterizadas por caminos en el grafo que,
partiendo de un problema, acaben en un caso base. Estos caminos sern secuencias de alternativas que
representaremos por {a1, a2, , ar}. Por lo que, partiendo de un problema dado X0 y siguiendo la secuencia de
alternativas {a1, a2, , ar}, alcanzaremos un problema que representaremos por Xr.
Desde la perspectiva anterior, una solucin para el problema X0, puede representarse como una secuencia de
alternativas {a1, a2, , ar} con 0 y Xr un caso base.
Asimismo, una secuencia de alternativas (un camino en el grafo), puede acabar en un problema cuyo conjunto
de alternativas est vaco, ese camino no define una solucin. Un problema (en el caso de reduccin) para el que
no es posible encontrar un camino que lo conecte a un caso base, no tiene solucin.
Si el problema, adems de Reduccin es de Optimizacin, entonces el valor de la propiedad ser a optimizar.
271
((, , (, , )))
(1 , )
+ (, ) >
+ (, )
() = max(, + )
= + (, )
Es muy importante el orden de las alternativas. Pues cada vez que alcanzamos un caso base podemos reconstruir
una solucin y por tanto el valor de su propiedad a optimizar. Es muy importante que las alternativas se ordenen
de tal forma que los primeros casos base que se alcancen den lugar a soluciones lo ms cercanas posibles a la
solucin ptima. Esto permitir que el mecanismo de filtro funcione de la mejor forma posible. El orden de las
alternativas es especfico para cada problema y, en general, es una informacin (una heurstica) que cuando se
tiene, permite implementar algoritmos mucho ms eficientes.
La actualizacin del mejor valor, actValMejor(s), se hace una vez que obtenemos una solucin para un problema
(ya sea un caso base, un caso recursivo o tengamos un problema que ya tiene solucin). Eso es posible porque
en ese momento encontramos una solucin al problema original cuyo valor es el acumulado hasta ese punto
ms el valor de la propiedad para el problema ya resuelto (detalle que no aparece explcito en el esquema
272
(, , (1 ), (2 ), , ( ))
() ,
() = {
((, , (1 , ), (2 , ), , ( , ))) () <
() = {
() ()
() ()
Donde a es una de las alternativas posibles escogida aleatoriamente. Esta tcnica es adecuada para resolver
problemas que buscan una solucin para un conjunto de restricciones. Si el problema a resolver es de
Optimizacin, con esta tcnica obtendremos una solucin pero no la mejor. La tcnica puede ser adaptada para
encontrar un nmero dado de soluciones si las hay o encontrar una mejor aproximacin a la mejor solucin.
Si los problemas a resolver tienen varias soluciones entonces los operadores SelecA() y Sb tienen que escoger
aleatoriamente una de ellas.
Ejemplos de este tipo son el Problema de las N-Reinas o el Problema del Sudoku. Ambos se resuelven de
forma similar.
Esta tcnica se puede combinar con la tcnica con Filtro vista antes.
273
((, , (1 ), (2 ), , ( )))
Ahora, STb es el conjunto de las soluciones de un problema que son un caso base. Los operadores CombST y
SelecAT, los podemos definir en base a los antiguos como:
( ) =
(, , 1 , 2 , , ) =
(, , 1 , 2 , , )
1 ,2 ,, 1 2
La idea anterior podemos resumirla diciendo que, en los casos base, el algoritmo obtiene el conjunto de todas
las soluciones de un problema. En un caso recursivo, el operador CombST() escoge todas las posibilidades del
producto cartesiano 1 2 y, para cada una de las posibilidades, obtiene una solucin que aade al
resultado. El operador SelecAT() se queda con el conjunto de soluciones vlidas.
274
Esto es debido a que, supuesto calculados los subproblemas, el tiempo para calcular un problema x es g(x). Luego
el tiempo necesario para calcular un problema es la suma de lo que se requiere para cada uno de los
subproblemas que son necesarios resolver.
Un caso particular, muy usado, se presenta cuando g(x) = k. Entonces la frmula anterior queda:
() = () = 1 = || = (||)
275
El bucle anterior define la transicin del estado e al e siendo f(X) la solucin del problema X en el estado e y
f( ) las soluciones disponibles en el estado e. Es decir, calcula las soluciones de los problemas de tamao n a
partir de las soluciones de los problemas de tamao n-1. El estado tiene que inicializarse con las soluciones de
los casos base.
Las ideas presentadas anteriormente son aplicables a la obtencin de algoritmos iterativos a partir de los
algoritmos recursivos de Programacin Dinmica en los cuales, la solucin para un problema de tamao k, se
exprese a partir de la solucin de problemas de tamao k-1. Para que el Estado sea implementable de manera
adecuada, el nmero de problemas de un tamao dado k, debe ser acotado y suficientemente pequeo para
que sus soluciones puedan guardarse eficientemente.
276
Cundo los problemas de Programacin Dinmica son de Reduccin, esto es, tras escoger una alternativa,
tiene un slo subproblema.
El conjunto de problemas define un grafo implcito donde los vrtices son los problemas.
En este grafo existe una arista desde un problema hasta cada uno de sus subproblemas.
Podemos definir un nuevo problema, el problema final, al cual se reducen todos los casos base.
Cada arista puede ser etiquetada con el valor de la propiedad objetivo (si es un problema de Optimizacin)
que se acumula al pasar de un problema a un subproblema.
Una solucin de un problema de Programacin Dinmica de Reduccin es un camino desde el problema
inicial hasta el que hemos definido como el problema final.
El camino que da la solucin ptima es aquel cuya suma de los valores asignados a las aristas sea ptima.
El problema de Programacin Dinmica tanto de Reduccin como de Optimizacin se transforma en un
problema de Camino Mnimo que puede ser resuelto por el Algoritmo A*.
277
Para hacer el algoritmo ms flexible se han diseado las soluciones parciales como pares <alternativa, valor>
Estos pares son posteriormente implementados por la clase Sp<A,T>.
Veamos cada uno de los mtodos de la interfaz:
278
279
Si queremos usar la tcnica de la Programacin Dinmica con Filtro necesitamos una interfaz que extienda la
anterior y pueda proporcionar la informacin adecuada:
/**
* @param <S> Tipo de la solucin del problema
* @param <A> Tipo de las alternativas del problema
* @param <T> Tipo de la propiedad objetivo
*/
public interface ProblemaPDF<S, A, T extends Comparable<? super T>>
extends
ProblemaPD<S, A, T>
{
/**
* @see AlgoritmoPD.Tipo
* @constraint getObjetivoEstimado(a) <= getObjetivo() si AlgoritmoPD.tipo = Max
* @constraint getObjetivoEstimado(a) <= getObjetivo() si AlgoritmoPD.tipo = Min
* @param a - Alternativa elegida
* @return Valor estimado para propiedad objetivo del problema inicial
* asumiendo que se alcanza el problema actual y se va a seguir la alternativa a
*/
T getObjetivoEstimado(A a);
/**
* @pre esCasoBase()
* @return Valor de propiedad objetivo del problema inicial
*/
T getObjetivo();
}
Ahora, el algoritmo de la Programacin Dinmica puede ser implementado en una clase reutilizable, En concreto,
es la clase AlgoritmoPD<S,A,T>. El ncleo del algoritmo es el algoritmo privado pD que se muestra
posteriormente.
Veamos los detalles del algoritmo. Las soluciones parciales obtenidas para los subproblemas se guardan en la
variable map de tipo Map<ProblemaPD<S,A,T>, T>. Para resolver el problema seguimos los siguientes pasos:
-
Comprobamos si el problema ha sido ya resuelto. Si ha sido resuelto devolvemos su solucin (que puede
ser el valor null que representa la no solucin). Si el problema no ha sido resuelto, preguntamos si el
problema es un caso base y si lo es, lo resolvemos y guardamos el resultado en la memoria.
- Si el problema es un caso recursivo, obtenemos todas las alternativas posibles despus de filtrarlas si
estamos en la modalidad Randomize. Para cada una de las alternativas que pase el filtro (si es en la
modalidad conFiltro) calculamos los subproblemas. Resolvemos cada subproblema y obtenemos la
solucin con el mtodo combinaSoluciones y posteriormente las combinamos todas (con el mtodo
seleccionaAlternativa) para obtener la solucin del problema y despus, guardamos la solucin en la
memoria.
- Para comprobaciones posteriores podemos guardar, para cada problema, la lista de las alternativas
recorridas.
- Si es un problema conFiltro hay que ver si las alternativas pasan el filtro y actualizar el mejor valor.
El mtodo ejecuta resuelve una secuencia de problemas o itera hasta que encuentra solucin para el primero
de ellos.
La clase tiene varias propiedades pblicas que pueden ser consultadas y actualizadas: solucionesParciales,
isRandomize, conFiltro. La primera podemos usarla para consultar la solucin parcial de cada problema y poder
reconstruir la solucin a partir de ella. Las dos ltimas nos sirven para escoger el tipo especfico de Programacin
Dinmica a utilizar.
La factora Algoritmos sirve para crear algoritmos de Programacin Dinmica para un problema o para un
conjunto de problemas. La reconstruccin de la solucin puede hacerse por el mtodo
getSolucionReconstruida(ProblemaPD<S,A,T> pd).
280
281
/**
* Algoritmo que implementa la tcnica de Programacin Dinmica con sus variantes.
* Un problema que se quiera resolver con esta tcnica debe implementar el interface ProblemaPD<S,A>
*
* @param <S> El tipo de la solucin
* @param <A> El tipo de la alternativa
* @param <T> El tipo de la propiedad objetivo
*/
public class AlgoritmoPD<S, A, T extends Comparable<? super T>> extends AbstractAlgoritmo {
/**
* Max: Maximizar, Min: Minimizar, Otro: No se pretende optimizar
*/
public static enum Tipo {
Max, Min, Otro
};
/**
* El tipo del algoritmo segn se pretenda optimizar o no
*/
public static Tipo tipo;
/**
* Si se quiere aplicar la tcnica aleatoria para escoger una de las alternativas
*/
public static boolean isRandomize = false;
/**
* Tamao umbral a partir del cual se escoge aleatoriamente una de las alternativas
*/
public static Integer sizeRef = 10;
/**
* Si se quiere aplicar la tcnica con filtro. En ese caso el Problema debe implementar
* adicionalmente el interface EstadoBTF
*/
public static boolean conFiltro = false;
private
private
private
private
282
283
284
/**
* @param nombre - Fichero dnde se almacenar el grafo para ser representado
* @param titulo - Ttulo del grfico
* @param pd - Problema y sus subproblemas que forman el grafo
*/
public void showAllGraph(String nombre, String titulo, ProblemaPD<S, A, T> pd) {
super.setFile(nombre);
super.getFile().println("digraph " + titulo + " { \n size=\"100,100\"; ");
showAll(pd);
super.getFile().println("}");
}
private void marcarEnSolucion(ProblemaPD<S, A, T> pd) {
if (solucionesParciales.containsKey(pd)) {
Sp<A, T> e = solucionesParciales.get(pd);
if (e != null) {
e.estaEnLaSolucion = true;
A alternativa = e.alternativa;
for (int i = 0; i < pd.getNumeroSubProblemas(alternativa); i++) {
ProblemaPD<S, A, T> pds = pd.getSubProblema(alternativa, i);
marcarEnSolucion(pds);
}
}
}
}
private String problema(ProblemaPD<S, A, T> p, Sp<A, T> e) {
String s = "
" + "\"" + p + "\"";
if (e != null) {
s = s + " [shape=box]";
} else {
s = s + " [shape=diamond]";
}
return s + ";";
}
private String alternativa(ProblemaPD<S, A, T> p, A alternativa) {
String s = "
" + "\"" + p + "," + alternativa + "\"" + " [label=" + alternativa + "]";
return s + ";";
}
private String aristaProblemaToAlternativa(ProblemaPD<S, A, T> p,A alternativa, Sp<A, T> e) {
String s = "
" + "\"" + p + "\"" + " -> " + "\"" + p + "," + alternativa + "\"";
if (e.estaEnLaSolucion && e.alternativa.equals(alternativa)) {
s = s + "[style=bold,arrowhead=dot]";
}
return s + ";";
}
private String aristaAlternativaToProblema(ProblemaPD<S, A, T> p, A alternativa,
ProblemaPD<S, A, T> ps, Sp<A, T> e) {
String s = "
" + "\"" + p + "," + alternativa + "\"" + " -> " + "\"" + ps + "\"";
if (e.estaEnLaSolucion && e.alternativa.equals(alternativa)) {
s = s + "[style=bold,arrowhead=dot]";
}
return s + ";";
}
private void showAll(ProblemaPD<S, A, T> p) {
marcarEnSolucion(p);
for (ProblemaPD<S, A, T> pd : solucionesParciales.keySet()) {
Sp<A, T> e = solucionesParciales.get(pd);
if (e != null)
super.getFile().println(problema(pd, e));
if (e != null && e.alternativas != null) {
for (A alternativa : e.alternativas) {
for (int i = 0; i < pd.getNumeroSubProblemas(alternativa); i++) {
ProblemaPD<S, A, T> pds = pd.getSubProblema(alternativa, i);
if (solucionesParciales.get(pds) == null)
super.getFile().println(problema(pds, null));
super.getFile().println(alternativa(pd, alternativa));
super.getFile()
.println( aristaProblemaToAlternativa(pd, alternativa, e));
super.getFile().println( aristaAlternativaToProblema(pd, alternativa, pds, e));
}
}
}
}
}
285
/**
* Un tipo diseado para representar soluciones parciales a partir de las
* cules se puede reconstruir la solucin del problema. Est formado por un
* par: una alternativa y el valor de una propiedad. La solucin del
* problema es la que se obtendra tomando la alternativa y el valor estara en la propiedad
*
* El valor null para este tipo representar la no existencia de solucin
*
* @param <A1> Tipo de la alternativa
* @param <T1> Tipo de la propiedad objetivo
*/
public static class Sp <A1, T1 extends Comparable<? super T1>>
implements Comparable<Sp<A1, T1>> {
public final A1 alternativa;
public final T1 propiedad;
private List<A1> alternativas = null;
private boolean estaEnLaSolucion = false;
public static <A2, T2 extends Comparable<? super T2>> Sp<A2, T2>
create(A2 alternativa, T2 propiedad)
{
return new Sp<A2, T2>(alternativa, propiedad);
}
private Sp(A1 alternativa, T1 solucionParcial) {
super();
this.alternativa = alternativa;
this.propiedad = solucionParcial;
}
private Sp(A1 alternativa, T1 solucionParcial, List<A1> alternativas) {
super();
this.alternativa = alternativa;
this.propiedad = solucionParcial;
}
public String toString() {
return "(" + alternativa + "," + propiedad + ")";
}
@Override
public int compareTo(Sp<A1, T1> ob) {
return this.propiedad.compareTo(ob.propiedad);
}
}
}
286
Asumiendo que en el problema de la mochila la capacidad es de 123 y los objetos disponibles lo siguientes,
[<1,24,15,2>, <3,24,10,7>, <0,60,24,2>, <2,25,10,5>]
Si planteamos el problema de la mochila con una capacidad de 327 y con los objetos disponibles lo siguientes,
[<1,24,15,2>, <5,2,1,4>, <4,24,11,7>, <3,24,10,7>, <0,60,24,2>, <2,25,10,5>]
287
La idea ha sido ir generando las definiciones de los vrtices del grafo a dibujar, incluyendo los problemas y las
alternativas. Posteriormente se han generado las aristas entre ellos.
Como identificador nico en el fichero de cada vrtice de tipo problema se ha usado toString. El identificador
nico de los vrtices de tipo alternativa ha sido la concatenacin del toString del problema ms el valor de la
alternativa.
Para un problema de la mochila con una capacidad de 35 y con los objetos disponibles los siguientes,
[<2,1,1,10>, <0,60,24,2>, <1,74,15,2>]
El cdigo para generar el fichero a partir de la memoria, del problema de Programacin Dinmica, se realiza
con los siguientes mtodos de la clase AlgoritmoPD:
public void
private void
marcarEnSolucion(ProblemaPD<S,A,T> pd)
288
showAll(ProblemaPD<S, A, T> p)
La lista de objetos disponibles LstObjDisp se mantiene ordenada de menor a mayor respecto por la propiedad
q. Es decir, ordenada por el valor por unidad de peso de cada uno de los objetos, es decir, por q.
Cada objeto de la lista LstObjDisp viene identificado por su posicin i en la misma y lo representamos por
oi, i [0, r-1]. Siendo r el nmero de objetos disponibles diferentes. A partir de las siguientes propiedades
podemos modelar el tipo ObjetoMochila: valori [entero], pesoi [entero], maxUdi el nmero mximo de unidades
del objeto i [entero] y qi el cociente [real] entre el valor y el peso unitario, es decir, =
289
Las alternativas disponibles podemos representar el nmero de unidades (nmero entero) que almacenaremos
del objeto para la capacidad de la mochila, Cap. Las alternativas disponibles estn en el conjunto
, =
{0,1,2, , } = min (
, ) , donde la divisin debe ser entera.
El objetivo del problema es encontrar una solucin cuya propiedad PesoTotal sea menor o igual a la capacidad
Cap de la mochila y adems, tenga el mayor valor posible para la propiedad valorTotal.
Con esos elementos de diseo tenemos que dar los detalles de las funciones de combinacin de soluciones y de
seleccin de alternativas. Si buscamos la solucin directamente Estos son:
((, , ), , ) = + ((), )
Donde por + ((), ) indicamos aadir alt unidades del objeto en la posicin j de LstObjDisp al
multiconjunto s. Por otro lado, podemos considerar en primer lugar la solucin parcial (par formado por la
alternativa elegida y el valor total de la solucin):
((, , ), , ) = (, + )
El operador para seleccionar las alternativas es (en las dos versiones anteriores):
( ) = ( )
Donde Sol alt es el multiconjunto solucin del subproblema resultante cuando escogemos la alternativa alt. Las
soluciones se ordenan de mayor a menor segn su propiedad valor total.
((, )) = ((, ))
Valor de valorTotal.
(Cap, 0, ).
+ ((0), )
( , 0 )
peso unitario y el nmero mximo de unidades del objeto que ocupa la posicin 0 en los objetos disponibles en
LstObjDisp.
Otros casos bases posibles son los de la forma (0, j, LstObjDisp) cuya solucin parcial es (0, 0).
Para cada alternativa hay un slo subproblema que viene dado por:
(, , )
1 = ( , 1, )
A partir de las soluciones parciales, la forma de reconstruir la solucin vendr dada por la funcin donde
asumimos que tenemos disponible la memoria que guarda para cada problema X la informacin asociada a la
solucin parcial. La ms importante para reconstruir la solucin es alternativa escogida en la mejor solucin que
representaremos por maxUdalt(X) (este valor asumimos que ser si el problema no tiene solucin). Adems,
designamos por
1 el subproblema de X cuando se escoge la alternativa alt = maxUdalt(X). La memoria guarda
otra informacin: el valor de la propiedad a optimizar que representaremos por maxUdv(X).
Todas las ideas anteriores las podemos reunir en una ficha para el problema, tal como se muestra a continuacin.
290
Mochila
Tcnica:
Tamao:
Propiedades
Compartidas:
Programacin Dinmica
N=J
LstObjDisp Objetos disponibles, de tipo List<ObjetoMochila>, ordenada de menor a
mayor por el valor unitario e indexada por J, y sin repeticin.
valor
peso
ObjetoMochila maxUd
[
Propiedades
Individuales:
Cap [entero] 0
J
[entero] [0, LstObjDisp.size() )
Solucin:
valor
peso
[MultiSet<ObjetoMochila>]
[entero] Valor total de los objetos almacenados en la mochila.
[entero] Peso total de los objetos almacenados en la mochila.
((, , ), , ) = + ((), ) .
{
((, , ), , ) = (, + )
.
Objetivo:
Alternativas:
Encontrar una solucin Sol tal que pesoToal Cap y que valorTotal tenga el mayor valor posible.
,, = {: . .0},
= min (
, )
Para cada alternativa hay un slo subproblema que viene dado por:
(, , )
1 = ( , 1, )
{
( ) = max ( )
Solucin parcial: (alt, valorTotal) donde alt = Alternativa, valorTotal = Propiedad a optimizar.
(, ) = (, 1, ),
= . ()
Instanciacin:
Problema generalizado:
(0, 0)
() =
= 0
= min (
, 0 ) , = 0
0
(, )
{ ,, ((, , ()))
. . .
= (, , )
= ( , 1, )
(, , ( , )) = (, + )
,, ((, )):
Funcin de reconstruccin:
+ ((0), ())
(, ) = {
()
(1
Complejidad:
, ) + ((), ())
() =
( )
291
Ejemplo: Supongamos que tenemos una mochila con una capacidad mxima de 45. Asimismo, contamos con
estos cuatro objetos de caractersticas segn se detalla en la tabla adjunta. Se trata de obtener la
cantidad de objetos de cada clase que maximice el valor dentro de la mochila.
Objeto
J
0
1
2
3
Valor
v
21
30
76
48
Peso
p
6
8
16
9
Mx. Ud.
m
2
2
2
2
Relacin v/p
q
350
375
475
533
El problema puede resolverse mediante Programacin Dinmica con Filtro. Una buena funcin de cota es:
(, , , ) = + ( , 1)
0
0
(, ) = {
+ ( , 1)
= 0
= 0, = min(//0 , 0 )
> 0, = min (// , )
Donde por Cap//peso0 hemos querido indicar que la divisin hay que hacerla, en este caso, convirtiendo los
enteros a reales y dividiendo, posteriormente, para obtener un nmero decimal.
292
293
compartidas...
List<ObjetoMochila>
objetosDisponibles;
Comparator<ObjetoMochila> ordenObjetos;
Integer
capacidadInicial;
294
295
296
297
@Override
public Iterable<Integer> getAlternativas() {
Iterable<Integer> ite = Iterables2.fromArithmeticSequence(ProblemaMochila.numeroEnteroMaximoDeUnidades(
problema.getIndex(), problema.getCapacidad()), -1, -1);
return ite;
}
@Override
public int getNumeroSubProblemas(Integer a) {
return this.esCasoBase() ? 0 : 1;
}
@Override
public SolucionMochila getSolucion(Integer a, List<SolucionMochila> ls) {
SolucionMochila r = ls.isEmpty() ? SolucionMochila.create() : ls.get(0);
if (a > 0)
r = r.add(ProblemaMochila.getObjeto(problema.getIndex()), a);
return r;
}
@Override
public Integer getObjetivoEstimado(Integer a) {
return this.valorAcumulado + problema.getCotaSuperiorValorEstimado(a);
}
@Override
public Integer getObjetivo() {
return this.valorAcumulado;
}
@Override
public String toString() {
return problema + "," + valorAcumulado;
}
@Override
public int hashCode() { }
@Override
public boolean equals(Object obj) { }
}
298
5.12.2 N-Reinas.
El problema de las reinas se trata de un juego en el que se deben colocar N reinas
en un tablero de ajedrez NxN sin que se amenacen. Fue propuesto por el ajedrecista
alemn Max Bezzel en 1848. En el juego de ajedrez la reina amenaza a aquellas
fichas que se encuentren en su misma fila, columna o diagonal. Las n reinas consiste
en colocar sobre un tablero de ajedrez n reinas sin que estas se den jaques entre
ellas. Las filas y columnas toman valores en [0, N).
Cada reina la representamos por (col, fil, dgPp, dgSc) fil [0, N-1], col [0, N-1].
Siendo coli, fili, dgPpi, dgSci, la columna, la fila, la diagonal principal y la diagonal
secundaria donde est colocada la reina i. La columna y la fila son nmeros enteros. La diagonal principal y la
diagonal secundaria son representadas por nmeros enteros que las identifican de manera nica. Siendo estos
enteros calculados como dgPp = fil-col, dgSc = fil+col. El tipo Reina tiene las propiedades individuales anteriores.
Generalizamos el problema: asumimos ya colocadas col < N reinas en las columnas 0, , col - 1 y resta colocar
las N - col reinas restantes. Las reinas ya colocadas tienen ocupadas un conjunto de filas, de diagonales
principales y de diagonales secundarias.
El problema generalizado tiene la propiedad individual:
-
filOcu [lista de enteros] que es la lista de filas ocupadas. Es decir, en la posicin cero se guarda la fila
donde est colocada la reina ubicada en la columna cero, etc.
col [entero] Columna asignada // ocupada por esa reina (nmero de reinas ya colocadas).
filOcu [lista de enteros] Contiene las filas ya ocupadas.
dgPpOcu [conjunto de enteros] Contiene las diagonales principales ya ocupadas.
dgScOcu [conjunto de enteros] Contiene las diagonales secundarias ya ocupadas.
En este contexto, dos problemas son iguales si tienen iguales la propiedad filOcu .
El tamao del problema es el nmero de reinas que nos quedan por colocar, esto es: N - col
El problema generalizado se instanciar con las propiedades:
(, , , , )
Las alternativas disponibles podemos representarlas con nmeros enteros que representarn la fila donde se
ubicar la siguiente reina. La siguiente reina se colocar en una de las filas disponibles de la de la columna
col = r. Las alternativas disponibles para la fila donde colocar la reina son:
,,,, = { [0, ) | , , + }
299
El caso base es cuando nos quede una reina por colocar, esto es, N - col = 1. La solucin puede obtenerse de las
propiedades del problema. Es decir, la solucin parcial es filOcu directamente.
La solucin buscada es una lista de reinas. La solucin podemos dotarla de varias propiedades derivadas: filOcu,
dgPpOcu, dgScOcu, es decir, lista de filas ocupadas, conjunto de diagonales principales ocupadas y conjunto de
diagonales secundarias ocupadas, respectivamente. Como solucin parcial, a partir de la cual podemos
reconstruir la solucin, podemos escoger una lista de enteros donde en cada posicin col se ubica una fila fil. A
partir de col y fil podemos crear la reina correspondiente que se situar en la columna col y fila fil.
Este problema se adapta bien para usar la tcnica de la Programacin Dinmica Aleatoria. Como hemos indicado,
el problema necesita tener una probabilidad de encontrar la solucin al primer intento para que el esquema
funcione. Ese es el caso para este problema.
ProblemaReina:
Para resolver este problema por la tcnica de Programacin Dinmica, necesitamos crear un conjunto de clases
relacionadas con el problema de las reinas. En primer lugar, tendremos una clase de nuestro problema concreto
ProblemaReina, y por otro, los mtodos propios de la tcnica a utilizar para resolver el problema. Como resultado
se obtiene una clase ProblemaReinaPD donde est todo lo necesario para resolver el problema de las reinas
mediante la tcnica de Programacin Dinmica.
300
301
SolucinReina:
Para resolver este problema por la tcnica de Programacin Dinmica diseamos la clase SolucionReina que
contendr la lista de reinas. Y como propiedades derivadas, el conjunto de filas, diagonales principales y
diagonales secundarias ocupadas.
302
N Reinas
Tcnica:
Tamao:
Propiedades
Compartidas:
Propiedades
Individuales:
col
[entero] [0, N )
filOcu
[Lista<entero>]
dgPpOcu
[Lista<entero>]
dgScOcu
[Lista<entero>]
Solucin:
S(col,filOcu,dgPpOcu,dgScOcu) [Lista<entero>]
Objetivo:
Encontrar una solucin S tal que las posiciones obtenidas para cada reina colocada no
afecte al resto de reinas segn normas del ajedrez, es decir, que:
,,,
= { [0, ) | , , + }
Solucin parcial:
Instanciacin:
Problema generalizado:
= {}
( , ) {} = 1
() = {
((, , ()))
> 1
= (, , , )
= ( + 1, + , + 1 , + 2 ),
1 =
2 = +
(, , ( , )) = (, )
( ):
Funcin de reconstruccin:
(, ) = {
()
(1
{
() =
( + , + 1 , + 2 )
() =
, ) + ( + , + 1 , + 2 )
() >
1 =
2 = +
303
304
305
= [0] =
7 5
(7
3
4
2
)
En la fila Cdesde columna Chasta tenemos la distancia del camino de Cdesde a Chasta para los problemas de tamao
Cinter =- 1 (tamao 0) es decir, aristas en el grafo. Hemos representado por infinito el hecho de no existir arista de
Cdesde a Chasta.
El algoritmo recursivo de Floyd-Warshall nos daba las soluciones de los problemas de tamao Cinter a partir de
los de tamao Cinter -1 mediante la expresin:
( , , 1) + ( , , 1)
( , , ) = min (
)
( , , 1)
Aplicando la expresin al estado representado por la tabla anterior obtenemos el estado siguiente. Recordamos
que CamMin(Cdesde, Chasta, Cinter) nos devolvera el camino mnimo de Cdesde a Chasta para los problemas de tamao
Cinter (estado e) y que CamMin(Cdesde, Chasta, Cinter -1) nos devolvera el camino mnimo de Cdesde a Chasta para los
problemas de tamao Cinter -1 (estado e). Aplicando la frmula de manera iterativa llegaremos a los problemas
de tamao n (siendo n el nmero de vrtices del grafo) que dos da la solucin buscada. Adems, debemos ir
actualizando otra tabla que guarde el camino mnimo de Cdesde a Chasta para los problemas de tamao Cinter. El
esquema concreto de la forma iterativa es de la forma:
1 ;
= 1;
< {
{
{
+1 ( , ) = min ( ( , ) + ( , ), ( , ))
}
}
}
= + 1;
A la hora de implementar el algoritmo slo necesitamos un estado con la tabla . Como cada ndice recorre
n valores la complejidad del algoritmo es (n3) siendo n el nmero de vrtices del grafo. Esta complejidad es la
misma que para el caso recursivo.
Los sucesivos estados asociados al problema del grafo anterior son:
7
(7
4
5
12 2
)
3
9 1
5 7
7
( 7 12 2 )
10 3 5
9 1 11
4
5 7
7
( 7 12 2)
10 3 5
4 1 6
4
7
(6
9
4
5
6
3
4
8
3
6
1
7
2
)
5
6
q [1]
q [2]
q [3]
q [4]
Ya se han hecho todas las iteraciones posibles, por tanto, el camino mnimo entre 2 vrtices cualesquiera del
grafo ser el obtenido en tabla final anterior.
307
Ejemplo:
308
Camino Mnimo
Tcnica:
Programacin Dinmica
Tamao:
numCiu
Propiedades
Compartidas:
G(Ciudad, Camino)
Corigen
[entero]
Ciudad origen.
Cdestino
[entero]
Ciudad destino.
numCiu [entero]
Propiedades
Individuales:
Cdesde
Chasta
Cinter
Ciudad intermedia.
Solucin:
[Lista<Ciudad>]
Objetivo:
Encontrar una solucin S tal que la distancia desde Corigen hasta Cdestino sea la mnima posible.
Alternativas:
,, = {
Solucin parcial:
(a, long)
Instanciacin:
( , ) = ( , , 1)
( , ) + ( , )
( , )
donde: a = Alternativa,
, , = (, )
Problema generalizado:
(, 0)
(, |(, )|)
() =
=
= 1 ( , )
= 1 ( , )
, , (,1 ) + (,2 )
,, ( (
)) 0
(,1 )
{
= ( , , )
,1 = ( , , 1)
,2 = ( , , 1)
,1 = ( , , 1)
(, 1 ) < (, 2 ) = 1 < 2
Funcin de reconstruccin:
(, ) =
()
( , )
(,1 , )
, =
, , = 1
, , 0, () =
{ (,1 , ) + (,2 , ) , , 0, () =
Complejidad:
(numCiu 3)
309
1 0
1
] [0 2
1
2 3
0 1 0
2
0] [2 4]} = {([
4
1 5 1
1 0
5 1
] [0 2
3 1
2 3
0
1 0
2
0]) [2 4]} = {[
4
1
5 1
1 0
5 1
] ([0 2
3 1
2 3
0 1
0 ] [2
1 5
0
35 53
]
4])} = [
29 37
1
Dada una secuencia de matrices M = {m0, m1, , mm-1} donde la matriz mi tiene fi filas y ci columnas con ci = fi+1
queremos obtener el producto de las mismas. Podemos calcular ese producto usando el algoritmo standard de
multiplicacin de pares de matrices como un procedimiento, una vez que hayamos parentizado esa expresin
para evitar ambigedades acerca de cmo han de multiplicarse las matrices.
Un producto de matrices se dice que est completamente parentizado si est constituido por una sola matriz, o
el producto completamente parentizado de dos matrices, cerrado por parntesis. La multiplicacin de matrices
es asociativa, y por tanto todas las parentizaciones producen el mismo resultado. Por ejemplo, si la cadena de
matrices es {m0, m1, m2, m3}, el producto m1m2m3m4 puede parentizarse completamente de estas cinco formas
distintas:
(0 (1 (2 3 ))) , (0 ((1 2 ) 3 )) , ((0 1 ) (2 3 )) , ((0 (1 2 )) 3 ) , (((0 1 ) 2 ) 3 )
El nmero de posibles parentizaciones crece de forma exponencial con el nmero de matrices a multiplicar.
La forma en que parenticemos un producto de matrices puede tener un fuerte impacto en el coste de evaluar el
producto. Consideremos primero el coste de multiplicar dos matrices. Segn el algoritmo standard, si A es una
matriz de dimensin pq y B de dimensin qr, consumira un tiempo proporcional a pqr. Es decir, para
multiplicar dos matrices mi, mi+1 es necesario hacer ficici+1 , o lo que es lo mismo, fifi+1ci+1 multiplicaciones. As,
para ilustrar los diferentes costes asociados a las distintas parentizaciones de un producto de matrices,
consideremos la cadena de tres matrices { m0, m1, m2}, con dimensiones 10x100, 100x5 y 5x50 respectivamente.
Ejemplo: 0 (10100) 1 (10050) 2 (550) {
(0 1 ) 2 10 100 5 + 10 5 50 = 7.500
0 (1 2 ) 5 50 100 + 50 10 100 = 75.000
Queremos obtener la forma de agruparlas para conseguir el nmero mnimo de multiplicaciones es decir,
obtener la parentizacin ptima.
Sean n0,s y ns,k el nmero de multiplicaciones necesarias para multiplicar los grupos de matrices de 0 a s-1 y de s
a k-1 respectivamente. El producto total (m0, m1, , ms-1)(ms, , mk-1) requiere n0,sns,k + f0fsck-1 . Es decir, el
nmero de multiplicaciones necesarias para cada subgrupo ms las que necesitamos para multiplicar las dos
matrices resultantes. Hemos de tener en cuenta que la matriz resultante del subgrupo izquierdo tendr f0 filas y
fs columnas y el subgrupo derecho fs filas y ck-1 columnas.
El problema generalizado pretende encontrar el nmero mnimo de multiplicaciones necesarias para multiplicar
el grupo de matrices comprendido en el intervalo [I, J). Las alternativas disponibles podemos representarlas con
nmeros enteros. Las alternativas para la multiplicacin de matrices encadenadas son el conjunto de las
posiciones donde colocar un parntesis en S (antes de ms) con I < S J.
, = { | > , < }
Adems del nmero de multiplicaciones, queremos encontrar la forma de colocar los parntesis que d lugar a
ese nmero de multiplicaciones. La solucin contendr, tambin, la expresin con los parntesis adecuados. Y la
representaremos como el nmero de operaciones necesarias y la expresin que produce ese nmero de
operaciones.
As, por (1, (2,3)), representaremos el producto de las matriz en la posicin 1 por el resultado de multiplicar las
que estn en las posiciones 2 y 3. La solucin, que representaremos por r, ser de la forma re,n. Donde e es la
expresin con parntesis y n el nmero de operaciones
310
(, ) =
{ , ( (, , (1 ), (2 ))) > 2
= (, )
1 = (, )
2 = (, )
(, , ( , 1 ), ( , 2 )) = (, 1 + 2 + 1 )
, :
( )
Funcin de reconstruccin:
(, ) =
,0
,
,
, )
()
, )
2 = (2
= +1 +1
() , > 2, = (1 , 2 ), = (1 + 2 + () 1 )
()
1 = (1
Complejidad:
() =
() , = 1
() , = 2, = (, 1),
(N3)
311
ProblemaMatriz:
Para resolver este problema por la tcnica de Programacin Dinmica, necesitamos crear un conjunto de clases
relacionadas con el problema de la multiplicacin de matrices encadenadas. Por un lado, tendremos una interfaz
de nuestro problema concreto ProblemaMatriz, y por otro, los mtodos propios de la tcnica a utilizar para
resolver el problema. Como resultado se obtiene una clase ProblemaMatrizPD para resolver el problema de la
multiplicacin de matrices encadenadas mediante la tcnica de Programacin Dinmica.
312
Implementacin:
boolean esCasoBase(): Se trata de un caso base si el ndice j-simo (propiedad j) menos el ndice i-simo
313
314
@Override
public Sp<Integer, Integer> seleccionaAlternativa(List<Sp<Integer, Integer>> ls) {
return Collections.min(ls);
}
@Override
public SolucionMatriz getSolucion(Integer a, List<SolucionMatriz> ls) {
SolucionMatriz r1 = null;
SolucionMatriz r2 = null;
SolucionMatriz r = null;
if (ls.isEmpty()) {
if (problema.getI().equals(problema.getJ() - 1)) {
r = SolucionMatriz.create("" + problema.getI() + "", 0);
} else {
Integer fi = ProblemaMatriz.getFila(problema.getI());
Integer fa = ProblemaMatriz.getFila(a);
Integer cj = ProblemaMatriz.getColumna(problema.getJ() - 1);
Integer numeroMultiplicaciones = fi * fa * cj;
String expresion = "(" + problema.getI() + "*" + (problema.getJ() - 1) + ")";
r = SolucionMatriz.create(expresion, numeroMultiplicaciones);
}
} else {
r1 = ls.get(0);
r2 = ls.get(1);
Integer fi = ProblemaMatriz.getFila(problema.getI());
Integer fa = ProblemaMatriz.getFila(a);
Integer cj = ProblemaMatriz.getColumna(problema.getJ() - 1);
String expresion = "(" + r1.getExpresionConParentesis() + "*" +
r2.getExpresionConParentesis() + ")";
Integer operaciones = r1.getNumeroDeOperaciones() + r2.getNumeroDeOperaciones();
operaciones += fi * fa * cj;
r = SolucionMatriz.create(expresion, operaciones);
}
return r;
}
public String toString() {
return problema.toString();
}
public int hashCode() { }
public boolean equals(Object obj) { }
}
315
siendo = .
El objetivo del problema es encontrar una solucin cuya propiedad Valor sea igual a la cantidad Cant y tenga el
menor valor posible para la propiedad NumeroDeMonedas.
Los operadores SelecA y CombS toman un parmetro adicional, que es el problema actual. En la medida que
tanto SelecA como CombS se implementan como mtodos de los objetos que representan los problemas ese
parmetro adicional est implcito. Un tema importante es el orden con el que tomamos las diferentes
alternativas. En algunos problemas puede ser relevante. En este caso las alternativas las tomamos de mayor a
menor. Es decir, primero la de valor r.
Se han considerado tres casos base, siendo los dos importantes el 2 y el 3. El 1 es un caso particular del 2 que
aadimos para considerar la posibilidad de Cant = 0 para cualquier valor de J. Como es un problema de igualdad
(es decir, hay que devolver un conjunto de monedas de valor igual a la cantidad Cant indicada) entonces debemos
considerar los casos 2 y 3. En el caso 3 indicamos que no hay solucin porque la cantidad Cant no es mltiplo del
valor de la moneda moneda0.
El problema se puede resolver con la tcnica de Programacin Dinmica con Filtro. Habra que aadir dos
propiedades ms: una individual, el nmero de unidades acumulado, y otra compartida, el nmero mnimo de
unidades encontrado. Las alternativas habra que tomarlas de mayor a menor, las monedas ordenarlas de menor
a mayor valor y encontrar una cota inferior del nmero de unidades que faltaran para completar la cantidad
Cant. La cota la podemos obtener tomando un nmero fraccionario de monedas siempre ordenadas de menor
a mayor valor unitario.
316
Cambio de Monedas
Tcnica:
Programacin Dinmica
Tamao:
N=J
Propiedades
Compartidas:
Propiedades
Individuales:
Solucin:
SolucinMoneda, Multiset<Moneda>
Objetivo:
Encontrar S monedas,valor,n tal que valor = Cant y que n tenga el menor valor posible.
Instanciacin:
(, ) = (, . () 1)
Problema generalizado:
{},0,0
(, ) =
, ,
= 0
=
, = , = 0
=
, , = 0
{ , ( (, ( , 1))) . . .
(, ): .
, (,, ): .
Complejidad:
(MonedasCant)
317
public class
ProblemaMonedaPD
implements ProblemaPD<SolucionMoneda, Integer, Integer>
{
/** Propiedades individuales */
private Integer cantidad;
// Indice de la moneda por la que voy
private Integer j;
// Moneda que est en el ndice j
private Moneda monedaJ;
// Orden de las soluciones parciales
private static Ordering<Sp<Integer, Integer>> ordEntry;
/**
* en el constructor es muy importante indicar que la monedaJ es la moneda
* que ocupa la posicin j en la lista de las monedas disponibles luego hay
* que determinar la orden de las soluciones parciales
*/
public ProblemaMonedaPD(List<Moneda> md, Integer cantidad, int j) {
super();
ProblemaMoneda.setMonedasDisponibles(md);
this.j = j;
this.monedaJ = ProblemaMoneda.getMonedasDisponibles().get(j);
this.cantidad = cantidad;
ordEntry = Ordering.natural();
}
@Override
public int size() {
return this.getJ() + 1;
}
@Override
public boolean isSolucion(SolucionMoneda s) {
return this.getCantidad().equals(s.getValor() * s.getNumeroDeMonedas());
}
@Override
public boolean esCasoBase() {
return this.getCantidad() == 0 || this.getJ() == 0;
}
@Override
/**Como que j=0 significa que la MonedaJ devuelta ser la misma, es decir,
* [n = this.getCantidad()/this.getMonedaJ().getValor()=1] por lo tanto
* [cantidad= 1*getMonedaJ().getValor()] */
public Sp<Integer, Integer> getSolucionCasoBase() {
Sp<Integer, Integer> res = null;
Integer monedasNecesarias = cantidad / this.getMonedaJ().getValor();
Integer numeroDeUnidades = getMonedaJ().getNumeroDeUnidades();
Integer n = Math.min(monedasNecesarias, numeroDeUnidades);
if (cantidad.equals(n * getMonedaJ().getValor()) && esCasoBase()) {
res = new Sp<Integer, Integer>(n, getMonedaJ().getValor());
}
return res;
}
public Sp<Integer, Integer> combinaAlternativas( List<Sp<Integer, Integer>> ls) {
Sp<Integer, Integer> res = null;
Iterable<Sp<Integer, Integer>> filtrar = Iterables.filter(ls, Predicates.notNull());
if (Iterables.size(filtrar) > 0) {
res = ordEntry.min(filtrar);
}
return res;
}
318
319
Dada una secuencia X = {x0, x1, , xm-1} decimos que Z = {z0, z1, , zk-1} es una subsecuencia de X (siendo k m)
si existe una secuencia creciente {i0, i1, , ik-1} de ndices de X tales que para todo j = 0, 1, 2, ..., k-1 tenemos que
= . El problema que nos planteamos es: dadas dos secuencias Xm = {x0, x1, , xm-1} y Yn = {y0, y1, , yn-1}
encontrar otra secuencia Zk = {z0, z1, , zk-1} que sea la secuencia comn a ambas ms larga posible.
En el ejemplo de la ilustracin tendremos como subsecuencia ms larga Z = { A, T, M, I, A }.
Como vemos, los datos del problema podemos modelarlos como listas de un tipo genrico T. La solucin buscada
es tambin una lista del mismo tipo T. La solucin tiene las propiedades Z [List<T>] y otras propiedades y
mtodos adecuados para gestionar la lista anterior: Empty, Size, Last, Add. Una solucin la representamos por z,
y por zk , el valor que ocupa la posicin k. La lista vaca la representamos por .
Generalizamos el problema: el problema generalizado pretender encontrar las subsecuencia comn ms larga
entre los prefijos de X e Y que incluyen las casillas menores o iguales a I y J respectivamente.
Las propiedades compartidas del conjunto de problemas son:
X, de tipo [List<T>]
Y, de tipo [List<T>]
En este contexto, dos problemas son iguales si tienen iguales sus propiedades individuales: I y J.
Para encontrar la solucin ptima del problema (I, J) tenemos las siguientes alternativas:
o
Si xi = yi entonces slo tenemos una alternativa posible que llamaremos C. En ese caso, dicho elemento,
debe formar parte de la solucin.
o Si xi yi entonces hay dos alternativas disponibles que representaremos por A y por B segn
disminuyamos el ndice de una u otra lista (lo que supone descartar el ltimo elemento de cada lista para
obtener la solucin).
El objetivo del problema es encontrar una solucin Z que, siendo una subsecuencia comn tanto de X como de
Y, tenga la longitud mxima.
320
Programacin Dinmica
Tamao:
N=I+J
Propiedades
Compartidas:
[Lista<T>]
[Lista<T>]
Propiedades
Individuales:
Solucin:
[Lista<T>]
Objetivo:
Alternativas:
Instanciacin:
{}
{, }
, = {
(, ) = (|| 1, || 1)
Problema generalizado:
(, ) =
{}
< 0 || < 0
, ((, ( 1, 1)))
, (
(, ( 1, ))
(, (, 1))
(, ): .
(, ): .
(, ): + .
, ( ): ||.
321
Dadas dos cadenas de caracteres X e Y, se desea transformar X en Y usando el menor nmero de operaciones
bsicas, que pueden ser de tres tipos: [E] eliminar un carcter, [A] aadir un carcter, y [C] cambiar un carcter
por otro.
Ejemplo 1: Transformar la cadena carro en la cadena casa:
carro carr
carr cara
cara casa
abbac abac
abac ababc
ababc abcbc
Se desea encontrar la mejor de todas las soluciones, es decir, aquella que realice la transformacin en el menor
nmero de operaciones, obtenindose adems la lista de operaciones. Para ello, se dispone del siguiente
diagrama UML.
322
Transformacin de Cadenas
Tcnica:
Representacin
Tamao:
Propiedades
Compartidas:
Programacin Dinmica
Cada problema, un conjunto de parmetros
N=I+J
u
[cadena]
v
[cadena]
Propiedades
Individuales
I
J
Solucin:
Objetivo:
Alternativas:
SolucionTransCad
Encontrar Su, v, l, n tal que n tenga el menor valor posible.
, = {}
=
, = {, , }
Instanciacin:
(, ) = (. (), . ())
Problema generalizado:
,,,0
(, ) =
= 0, = 0
,,{ },
0, = 0
,,{ },
= 0, 0
, ((, ( 1, 1)))
1 = 1
(, ( 1, ))
, ((, (, 1))
)
(
))
(
,
1,
1 1
( , ): ,
, (,,,
):
Complejidad:
(mn)
Nota: la nomenclatura de las operaciones y el significado de la solucin en la ficha se define como sigue:
N No hacer nada.
E Eliminar un carcter.
A Aadir un carcter.
C Cambiar un carcter.
Su, v, l, n u y v son las cadenas, es la lista de operaciones y el nmero de operaciones.
323
SOLUCIN
Apartado a)
public boolean esCasoBase() {
return (getI() == 0) || (getJ() == 0);
}
Apartado b)
public SolucionTransCad getSolucionCasoBase() {
List<OperacionImpl> operaciones = Lists.newArrayList();
if ((getI() == 0) && (getJ() != 0)) {
Character c = null;
for (int i = 0; i < getJ(); i++) {
c = getCadenaFinal().charAt(i);
operaciones.add(new OperacionImpl("A", c, i));
}
} else if ((getI() != 0) && (getJ() == 0)) {
Character c = null;
for (int i = 0; i < getI(); i++) {
c = getCadenaInicial().charAt(i);
operaciones.add(new OperacionImpl("E", c, i));
}
}
return new SolucionTransCadImpl(operaciones);
}
Apartado c)
public int getNumeroDeAlternativas() {
int alternativas = 1;
if (!getCaracterI().equals(getCaracterJ())) {
alternativas = 3;
}
return alternativas;
}
Apartado d)
public int getNumeroSubProblemasAnd(String a) {
return 1;
}
324
Apartado f)
public SolucionTransCad combinaSolucionesAnd(String a, SolucionTransCad... ls) {
SolucionTransCad s = ls[0].clone();
if (a.equals("C")) {
s.getOperaciones().add(new OperacionImpl(a, getCaracterJ(), getI() - 1));
} else if (a.equals("E")) {
s.getOperaciones().add(new OperacionImpl(a, getCaracterI(), getI() - 1));
} else if (a.equals("A")) {
s.getOperaciones().add(new OperacionImpl(a, getCaracterJ(), getI() - 1));
} else if (!a.equals("N")) {
throw new SituacionIlegal("La alternativa no es vlida.");
}
return s;
}
325
326
327
Programacin Dinmica
J
Categorias
PT
[Lista<Categoria>].
[entero] no negativo
Propiedades
Individuales
p
J
[entero] no negativo
Presupuesto Disponible.
[entero] [0, Categorias.size() ).
Solucin:
SolucionCategoria
Objetivo:
Encontrar Sol c,s,t tal que el nmero de trabajadores t tenga el menor valor posible.
Alternativas:
, = {0, 1, , } =
Instanciacin:
(, ) = (, . () 1)
Presupuesto Total.
Problema generalizado:
(, ) =
{ },,,
, = , = 0
, , = 0
Complejidad:
328
( pCategorias.size() )
Nota: Utilice los diagramas UML que se muestran a continuacin para realizar las implementaciones.
329
SOLUCIN:
Apartado a)
public boolean esCasoBase() {
return getJ() == 0;
}
Apartado b)
public SolucionCategoria getSolucionCasoBase() {
SolucionCategoria s = null;
Integer trabajadoresAUtilizar = getPresupuestoDisponible() /
getCategoriaJ().getSueldoCategoria();
Integer presupuestoAUtilizar = getCategoriaJ().getSueldoCategoria() *
trabajadoresAUtilizar;
if (getPresupuestoDisponible().equals(presupuestoAUtilizar)) {
getCategoriaJ().setNumeroDeTrabajadoresCategoria(trabajadoresAUtilizar);
Categoria copiaCategoriaJ = getCategoriaJ().clone();
s = new SolucionCategoriaImpl(Lists.newArrayList(copiaCategoriaJ));
}
return s;
}
Apartado c)
public int getNumeroDeAlternativas() {
Integer posiblesTrabajadores = getPresupuestoDisponible() /
getCategoriaJ().getSueldoCategoria();
return posiblesTrabajadores + 1;
}
Apartado d)
public int getNumeroSubProblemasAnd(Integer a) {
return 1;
}
Apartado e)
public ProblemaPD<SolucionCategoria, Integer> getSubProblemaAnd(Integer a, int i) {
int nuevoPresupuesto = getPresupuestoDisponible()
(a * getCategoriaJ().getSueldoCategoria());
return new ProblemaCategoriaPDImpl(nuevoPresupuesto, getJ() - 1);
}
Apartado f)
public SolucionCategoria combinaSolucionesAnd(Integer a, SolucionCategoria... ls) {
SolucionCategoria s = null;
if (ls[0] != null) {
s = ls[0].clone();
Categoria m = getCategorias().get(getJ());
m.setNumeroDeTrabajadoresCategoria(a);
Categoria copia = m.clone();
s.add(copia);
}
return s;
}
330
bk,f
j
j
entero Identifica a la accin.
entero Cantidad a invertir en la accin j.
.
[k
f(entero x) entero Beneficio al invertir la cantidad x en la accin
SolucionInversiones SLacciones ,
ben
Se desea resolver el problema usando Programacin Dinmica, de acuerdo a la siguiente ficha, para lo que
generalizamos el mismo mediante las propiedades: C (Entero, consultable), cantidad a invertir, y J (Entero,
consultable), que representa al subconjunto de las acciones con ndices en el intervalo [0, J). As pues, en el
problema generalizado se trata de obtener el mximo beneficio al invertir la cantidad C entre las acciones 0..J.
Nota: Considerar que las cantidades destinadas a cada inversin son valores enteros.
Inversiones Acciones
Tcnica:
Tamao:
Propiedades
Compartidas:
Programacin Dinmica
J
Acciones
[Lista<Accion>].
M
[entero] no negativo
Propiedades
Individuales
C
J
Solucin:
SolucionAcciones
Objetivo:
Encontrar Sollacciones,ben tal que el nmero de ben tenga el mayor valor posible.
Alternativas:
, = 0, 1, ,
Instanciacin:
(, ) = (, . () 1)
[entero] no negativo
[entero] [0, Acciones.size() )
Problema generalizado:
,0
0 , 0 ,()
(, ) = {
, (( , ( , 1)))
=0
=0
>0 >0
( , ): > 0
, (,
): ().
Complejidad:
(M2Acciones.size())
331
Se pide:
a) Escribir el cdigo del mtodo S pD(ProblemaPD<S,A>p) de la clase AlgoritmoProgramacionDinamica<S,A>.
b) Suponiendo definida la clase EnteroIterable, implementar de la clase ProblemaInversionPDImpl los
mtodos:
public boolean esCasoBase()
public Iterable<Integer> getAlternativas()
public int getNumeroSubProblemasAnd(Integer a)
public ProblemaPD<SolucionInversion, Integer> getSubProblemaAnd(Integer a, int i)
public SolucionInversion combinaSolucionesAnd(Integer a, SolucionInversion... ls)
332
Apartado a)
private S pD(ProblemaPD<S, A> p) {
S s = null;
if (map.containsKey(p)) {
s = map.get(p);
} else if (p.esCasoBase()) {
s = p.getSolucionCasoBase();
map.put(p, s);
} else {
int numeroDeAlternativas = p.getNumeroDeAlternativas();
S[] solucionesOr = Utiles.creaArray(tipoSolucion, numeroDeAlternativas);
int j = 0;
for (A a : p.getAlternativas()) {
int numeroDeSubproblemas = p.getNumeroSubProblemasAnd(a);
S[] solucionesAnd = Utiles.creaArray(tipoSolucion, numeroDeSubproblemas);
for (int i = 0; i < numeroDeSubproblemas; i++) {
ProblemaPD<S, A> pr = p.getSubProblemaAnd(a, i);
s = pD(pr);
solucionesAnd[i] = s;
}
solucionesOr[j++] = p.combinaSolucionesAnd(a, solucionesAnd);
}
s = p.combinaSolucionesOr(solucionesOr);
map.put(p, s);
}
return s;
}
Apartado b)
public boolean esCasoBase() {
return (getCantidad() == 0) || (getJ() == 0);
}
public int getNumeroDeAlternativas() {
return (getCantidad() + 1);
}
public Iterable<Integer> getAlternativas() {
return new EnteroIterable();
}
public int getNumeroSubProblemasAnd(Integer a) {
return 1;
}
public ProblemaPD<SolucionInversion, Integer> getSubProblemaAnd(Integer a, int i) {
return new ProblemaInversionPDImpl(getCantidad() - a, getJ() - 1);
}
public SolucionInversion combinaSolucionesAnd(Integer a, SolucionInversion... ls) {
SolucionInversion s = ls[0].clone();
if (a > 0) {
Accion b = getAccionJ().clone();
b.setCantidad(a);
s.add(b);
}
return s;
}
333
5.13.4 Embarcaderos.
En un ro hay n embarcaderos, en cada uno de los cuales se puede
alquilar un bote para ir a otro embarcadero que est ms abajo en
el ro, estando prohibido remontar el ro. Los costes de viajar entre
los distintos embarcaderos estn fijados y vienen dados.
Puede ocurrir que un viaje entre i y j salga ms barato parndose
en un embarcadero k intermedio, en lugar de realizarlo sin paradas.
Si para ir de i a j, primero se va directamente de i a k, para realizar
el resto del trayecto ya slo se podrn utilizar como embarcaderos
intermedios los que van de k a j, ya que los botes slo deben ir a
favor de corriente, y por tanto no pueden remontar el ro.
Se desea obtener mediante un algoritmo de Programacin Dinmica el coste mnimo para ir de un embarcadero
i a otro j.
Cada embarcadero se representar por un entero i [0, embarcaderos.size()), que representa la posicin en el ro.
Cuanto mayor es el nmero, ms abajo est el embarcadero en el ro.
El coste de un viaje desde i a j sin pasar por embarcaderos intermedios, se representar por ci,j donde i, j [0,
embarcaderos.size()), siendo i y j los ndices de los embarcaderos origen y destino. Dicho coste vendr dado por
el mtodo esttico getCostTrayecto(int, int) de la clase ProblemasEmbarcadero.
Suponga que los tipos S y E de la interfaz ProblemaPD<S, A, E> son de tipo Integer. Suponga implementadas
todas las clases proporcionadas en el diagrama UML, salvo los mtodos de la clase ProblemaEmbarcaderosPD que
se piden.
334
Se pide,
1) Completar la ficha adjunta del problema a resolver ( //TODO)
2) Implementar los siguientes mtodos teniendo en cuenta la ficha adjunta:
De la clase AlgoritmoProgramacionDinamica:
a) private E pD(ProblemaPD<S, A, E> p). (Segn visto en clase)
De la clase ProblemaEmbarcaderosPD:
b) public boolean esCasoBase()
c) public Integer getSolucionCasoBase()
d) public Iterable<Integer> getAlternativas()
e) public ProblemaPD<Integer, Integer, Integer> getSubProblema(Integer k, int i)
f) public Integer combinaSolucionesAnd(Integer a, List<Integer> ls)
g) public Integer combinaSolucionesOr(List<Integer> ls)
335
SOLUCIN:
Apartado 1)
Embarcadero
Tcnica:
Programacin Dinmica
Propiedades
Compartidas:
Trayectos
[Graph<entero, entero>]
Propiedades
Individuales
I
J
Solucin:
[entero]
Objetivo:
Alternativas:
Instanciacin:
(, ) = (, )
Coste mnimo.
Problema generalizado:
(, ) = {
//
(, ): (, ) +
//
( ):
//
Apartado 2)
@Override
public boolean esCasoBase() {
return getI() == getJ();
}
@Override
public Integer getSolucionCasoBase() {
return 0;
}
@Override
public Iterable<Integer> getAlternativas() {
return Iterables2.from(i + 1, j + 1, 1);
}
@Override
public ProblemaPD<Integer, Integer, Integer> getSubProblema(Integer k, int i) {
return new ProblemaEmbarcaderosPD(k, getJ());
}
@Override
public Integer combinaSolucionesAnd(Integer a, List<Integer> ls) {
return ls.get(0) + ProblemasEmbarcadero.getTrayecto(getI(), a);
}
@Override
public Integer combinaSolucionesOr(List<Integer> ls) {
return ordSolucionParcial.min(ls);
}
336
ETSIIbici est definida por un grafo que contiene las estaciones ubicadas a lo largo de nuestro recorrido
y los trayectos que conectan cada una de las estaciones.
Cada estacin se representar por ei, n donde el identificador i [0, estaciones.size), y n es el nombre.
Cada trayecto se representar por vti,j donde i, j [0, estaciones.size), siendo i y j los ndices de las
estaciones que conecta (origen del trayecto y destino del trayecto), y t representa el tiempo que se tarda
en ir desde la estacin i hasta la estacin j. Considere que el tiempo asignado a un trayecto para llegar
desde una estacin a otra es fijo, independientemente de la velocidad a la que vaya el usuario.
En todo momento vamos a tener acceso al grafo de estaciones y trayectos
[Graph<EstacionBicicletas,Trayecto>], as como a la lista de estaciones ordenada y sin repeticin
[List<EstacionBicicletas>].
Por otro lado, cada problema individual deber tener informacin sobre la estacin origen, I [0,
estaciones.size), la estacin destino, J [0, estaciones.size) y el conjunto de estaciones intermedias
disponibles, K [-1, estaciones.size).
Las alternativas (representadas mediante un entero) son decidir si para ir desde la estacin I a la estacin
J pasamos por la estacin K o no pasamos por la estacin K. El objetivo del problema es encontrar un
recorrido en el grafo cuya primera estacin sea la estacin origen de partida y la ltima sea la estacin
destino hasta la cual queremos llegar, de forma que el tiempo que se tarde sea el mnimo posible.
Se devolver la solucin con las estaciones por las que se ha pasado para realizar todo el recorrido. La
solucin ser de la forma sl, t , donde l es la lista que contiene los trayectos con las estaciones por las que
se ha pasado para realizar el recorrido completo, y t, indica el tiempo que se tarda en realizar dicho
recorrido.
En el siguiente ejemplo, se muestra el grafo de estaciones y el tiempo necesario para realizar cada uno de los
trayectos que comunican las estaciones.
Estacin 2
[20 min]
[50 min]
Estacin 0
[15 min]
[5 min]
Estacin 3
[5 min]
Estacin 1
En este ejemplo, el recorrido que implica el menor tiempo posible para ir desde la Estacin 0 hasta la Estacin 3,
est formado por los trayectos que unen la Estacin 0, con la Estacin 1, la Estacin 1 con la Estacin 2 y la
Estacin 2 con la Estacin 3, respectivamente. Puede observar, que entre la Estacin 0 y la Estacin 3 hay un
trayecto directo, sin embargo, el tiempo que se tarda en recorrerlo es mucho mayor que si pasamos por el resto
de estaciones intermedias.
337
Se pide:
1) Completar la ficha adjunta del problema a resolver. Los apartados a completar, son los sealados
mediante: //TODO
Nota:
-
338
Utilice el diagrama UML que se muestra a continuacin para realizar los distintos apartados.
Suponga implementadas todas las clases proporcionadas en el diagrama UML, salvo los mtodos de la
clase ProblemaEstacionBicicletasPD que se piden para su resolucin.
SolucionParcialEstacionBicicletas y SolucionEstacionBicicletas tienen las mismas propiedades.
339
SOLUCIN:
Apartado 1)
Servicio de Bicicletas
Tcnica:
Programacin Dinmica
Tamao:
Propiedades
Compartidas:
Mapa
Estaciones
Propiedades
Individuales
I
[entero] [0, Estaciones.size() )
J
[entero] [0, Estaciones.size() )
K
[entero] [-1, Estaciones.size() )
SolucionEstacionesBicicletas, S L,t
Solucin:
[Graph<EstacionBicicleta, Trayecto>]
[Lista<EstacionBicicleta>]
L : Lista que contiene los trayectos con las estaciones por las que se ha pasado para realizar
el recorrido completo.
t: Indica el tiempo que se tarda en realizar dicho recorrido.
Objetivo:
Alternativas:
,, = {0, 1}
Instanciacin:
(, ) = (, , . () 1)
Problema generalizado:
(, , ) =
{},0
, = 1, ,
(0, (, , 1))
,, (
) , 0 //
(1, (, , 1), (, , 1))
{
(0, , )
,
, ,
(1, 1 , 2 ): 1 2 {1.}+{2.} , 1.+2.
,, (1, , 2, ): .
340
Punto b)
public boolean esCasoBase() {
return (i == j) || (k == -1);
}
Punto c)
public SolucionParcialEstacionBicicletas getSolucionCasoBase() {
SolucionParcialEstacionBicicletas s = null;
if (i == j)
s = SolucionesEstacionBicicleta.createSolucionParcial();
else { // i != j
Trayecto v = ProblemasEstacionBicicletas.getMapa().
getEdge(ProblemasEstacionBicicletas.getEstaciones().get(i), ProblemasEstacionBicicletas.getEstaciones().get(j));
if (v != null) { // Grafo contiene trayecto
s = SolucionesEstacionBicicleta.createSolucionParcial(v);
} // Grafo no contiene trayecto -> Solucin Parcial nula
}
return s;
}
Punto d)
public Iterable<Integer> getAlternativas() {
return Iterables2.from(0, 2, 1);
}
Punto e)
public ProblemaPD<SolucionEstacionBicicletas, Integer, SolucionParcialEstacionBicicletas> getSubProblema(Integer a, int s) {
ProblemaPD<SolucionEstacionBicicletas, Integer, SolucionParcialEstacionBicicletas> p = null;
switch (a) {
case 0: // No pasar por K (Alternativa 0)
p = ProblemasEstacionBicicletas.createPD(i, j, k - 1);
break;
case 1: // S Pasar por K (Alternativa 1)
if (s == 0) {// Subproblema 0
p = ProblemasEstacionBicicletas.createPD(i, k, k - 1);
} else {
// Subproblema 1
p = ProblemasEstacionBicicletas.createPD(k, j, k - 1);
}
break;
}
return p;
}
341
342
Las alternativas (representadas mediante un entero) son la posibilidad de no poner la cancin en ninguno
de los CDs, representado por el valor 0, o los valores 1 o 2 para ponerlo en el CD 1 y/o CD 2, dependiendo
de si el tiempo de la cancin j cabe en el CD 1 o CD 2 respectivamente.
El objetivo del problema es encontrar la composicin de canciones en ambos CDs de forma que el tiempo
total de la recopilacin, y por tanto, de las canciones que estarn, sea el mximo posible.
La solucin ser de tipo SolucionDiscografia, que tiene la forma lcCD1, lcCD2 , donde lcCD1 es la lista de
canciones del CD 1 y lcCD2 es la lista de canciones del CD 2.
//TODO
2) Completar el mtodo adjunto private Sp<A, E> pD(ProblemaPD<S, A, E> p) perteneciente a la clase
AlgoritmoPD. Los apartados a completar, son los sealados mediante puntos suspensivos ( . . . . .).
private Sp<A, E> pD(ProblemaPD<S, A, E> p) {
Sp<A, E> e = null;
ProblemaPDF<S, A, E> pdf = null;
if (conFiltro)
pdf = (ProblemaPDF<S, A, E>) p;
if (solucionesParciales.containsKey(p)) {
e = solucionesParciales.get(p);
} else if (..............................) {
e = ..............................;
solucionesParciales.put(p, e);
} else {
List<Sp<A, E>> solucionesDeAlternativas = Lists.newArrayList();
List<A> alternativasElegidas = Lists.newArrayList();
for (A a : filtraRandomize(p, ..............................)) {
if (conFiltro && !pdf.pasaFiltro(a)) {
continue;
}
alternativasElegidas.add(a);
int numeroDeSubProblemas = ..............................;
List<Sp<A, E>> solucionesDeSubProblemas = Lists.newArrayList();
for (int i = 0; i < numeroDeSubProblemas; i++) {
ProblemaPD<S, A, E> pr = ..............................;
Sp<A, E> sp = ..............................;
solucionesDeSubProblemas.add(sp);
}
Sp<A, E> sa = ..............................;
solucionesDeAlternativas.add(sa);
}
e = ..............................;
if (e != null)
e.alternativas = alternativasElegidas;
solucionesParciales.put(p, e);
}
if (conFiltro && e != null)
pdf.actualizaMejorValor(e.solucionParcial);
return e;
}
Nota:
-
343
344
Compaa discogrfica
Tcnica:
Programacin Dinmica
Tamao:
N=J
Propiedades
Compartidas:
LC
[Lista<entero>]
// Lista de canciones
capCD1
[entero]
// Capacidad en segundos CD 1
capCD2
[entero]
// Capacidad en segundos CD 2
Propiedades
Individuales
Solucin:
tiempoRestCD1 [entero]
tiempoRestCD2 [entero]
Objetivo:
Encontrar una solucin S tal que el tiempo total de canciones entre los dos CDs sea mximo.
Solucin Parcial:
Alternativas:
Instanciacin:
,1,2 = {0,
1 >
2 >
(, 1, 2) =
(. () 1, 1, 2)
Problema generalizado:
// TODO
// TODO
//TODO
(0, 0)
(0, 0)
() =
= 1
1 0 2 0
(, 0, (0 ))
((, 1, (1 )) ) . . .
(, 2, (2 ))
{
= (, 1, 2)
0 = ( 1, 1, 2)
1 = ( 1, 1 , 2)
2 = ( 1, 1, 2 )
(, , ( , 1 )) = (, 1 + )
, ((, )): .
345
Apartado 2)
346
Punto a)
public boolean esCasoBase() {
return j == -1 || (this.tRestCD1 <= 0 && this.tRestCD2 <= 0);
}
Punto b)
public Sp<Integer, Integer> getSolucionCasoBase() {
return new Sp<Integer, Integer>(0, 0);
}
Punto c)
public Iterable<Integer> getAlternativas() {
List<Integer> alts = Lists.newLinkedList();
alts.add(0);
if (getTiempoCancion(j) <= this.tRestCD1)
alts.add(1);
if (getTiempoCancion(j) <= this.tRestCD2)
alts.add(2);
return alts;
}
Punto d)
public ProblemaPD<SolucionDiscografia, Integer, Integer> getSubProblema(Integer a, int i) {
Integer tiempoEnCD1 = tRestCD1;
Integer tiempoEnCD2 = tRestCD2;
if (a == 1)
tiempoEnCD1 = tRestCD1 - getTiempoCancion(i);
if (a == 2)
tiempoEnCD2 = tRestCD2 - getTiempoCancion(i);
return ProblemaDiscografia.createPD(this.j - 1, tiempoEnCD1, tiempoEnCD2);
}
Punto e)
public Sp<Integer, Integer> combinaSoluciones(Integer a, List<Sp<Integer, Integer>> ls) {
Sp<Integer, Integer> e = null;
if (ls != null)
e = ls.get(0);
if (e != null && e.alternativa != null) {
if (a == 1 || a == 2)
e = new Sp<Integer, Integer>(a, e.solucionParcial +
getCancion(j).getTiempoSeg());
else
e = new Sp<Integer, Integer>(a, e.solucionParcial);
}
return e;
}
347
El objetivo de dicho mago es mezclar todas estas pcimas juntas hasta que slo quede una. En cada paso, el
mago tomar dos pcimas consecutivas, las mezclar entre s para generar una nueva pcima que reemplazar
las dos anteriores.
Cuando dos pcimas de color A y B respectivamente, se mezclan para formar una nueva, la pcima resultante
tendr un nuevo color que se calcula como: (A+B) mod 100. Adems, cuando se mezclan dos pcimas de colores
A y B para formar una nueva, se genera humo en el proceso. La cantidad de humo que se genera al mezclar dos
pcimas consecutivas de colores A y B se calcula como: A x B. De acuerdo a cmo se mezclen las distintas parejas
de pcimas, la cantidad de humo que puede generarse vara.
El mago desea obtener la forma de agruparlas para realizar la mezcla de forma que se consiga la mnima cantidad
de humo posible, es decir, obtener la agrupacin de parejas de pcimas ptima.
En el siguiente ejemplo, se muestra la lista de pcimas y el color asociado a cada una de ellas.
Ejemplo: Si disponemos de la lista de pcimas P = {40, 60, 20}, se pueden realizar dos agrupaciones distintas.
1) ((40,60), 20):
a) Color pcima al mezclar las pcimas de color 40 y 60: (40+60)%100 = 0.
Humo generado: 40x60=2400.
b) La lista resultante sera: (0, 20), que generara la pcima de color 20 y el
humo resultante total sera: 2400 + (0x20) = 2400.
2) (40, (60,20)):
a) Color pcima al mezclar las pcimas de color 60 y 20: (60+20)%100 = 80.
Humo generado: 60x20=1200.
b) La lista resultante sera: (40,80), que generara la pcima de color 20 y el
humo resultante total sera: 1200 + (40x80) = 4400.
La solucin ptima sera la primera opcin ya que el humo total generado es el menor posible.
Por tanto, dada la secuencia de pcimas P = {p0, p1, , pn-1} se desea obtener mediante un algoritmo de
Programacin Dinmica la forma ptima de mezclar las distintas pcimas entre s para conseguir que la cantidad
de humo generado sea mnimo.
En todo momento vamos a tener acceso a la lista de pcimas [Lista<entero>] que contiene el cdigo de color
[entero] de cada una de las pcimas disponibles.
Cada pcima se identifica por un entero p [0, 100) que representa su color.
La pcima que se encuentra en la posicin i de la lista de pcimas se representar por pi donde
i [0, pocimas.size()).
348
349
Se pide:
1) Completar la ficha adjunta del problema a resolver. Los apartados a completar, son los sealados
mediante //TODO
2) Completar el mtodo private Sp<A, E> pD(ProblemaPD<S, A, E> p) perteneciente a la clase
AlgoritmoPD. Los apartados a completar, son los sealados mediante puntos suspensivos ( . . . . .).
private Sp<A, E> pD(ProblemaPD<S, A, E> p) {
Sp<A, E> e = null;
ProblemaPDF<S, A, E> pdf = null;
if (conFiltro)
pdf = (ProblemaPDF<S, A, E>) p;
if (..............................) {
e = ..............................;
} else if (null) {
e = null;
solucionesParciales.put(p, e);
} else {
List<Sp<A, E>> solucionesDeAlternativas = Lists.newArrayList();
List<A> alternativasElegidas = Lists.newArrayList();
for (A a : filtraRandomize(p, ..............................)) {
if (conFiltro && !pdf.pasaFiltro(a)) {
continue;
}
alternativasElegidas.add(a);
int numeroDeSubProblemas = ..............................;
List<Sp<A, E>> solucionesDeSubProblemas = Lists.newArrayList();
for (int i = 0; i < numeroDeSubProblemas; i++) {
ProblemaPD<S, A, E> pr = ..............................;
Sp<A, E> sp = ..............................;
solucionesDeSubProblemas.add(sp);
}
Sp<A, E> sa = ..............................;
solucionesDeAlternativas.add(sa);
}
e = ..............................;
if (e != null)
e.alternativas = alternativasElegidas;
solucionesParciales.put(p, e);
}
if (conFiltro && e != null)
pdf.actualizaMejorValor(e.solucionParcial);
return e;
}
350
351
Apartado 1)
Programacin Dinmica
Tamao:
N=J-I
Propiedades
Compartidas:
[Lista<entero>]
Propiedades
Individuales
I
J
Solucin:
SolucionPocimas, S = (e, n)
e : Expresin con agrupacin de pcimas.
n : Cantidad total de humo generado.
Objetivo:
Solucin Parcial:
(a, n)
a : Punto donde colocamos el parntesis.
n : Cantidad de humo generado.
Alternativas:
, = { | < }
// TODO
Instanciacin:
() = (0, . () )
// TODO
Problema generalizado:
//TODO
(, 0)
=1
=2
() = { ( + 1, +1 )
, ( (, , (1 ), (2 ))) > 2
= (, )
1 = (, )
2 = (, )
..
(, , ( , 1 ), ( , 2 )) = 1 + 2 + (.. .. ) {
..
, ((, )): .
=
{
=
352
( + +1 )%100
+1
(, )
(, )
353
Apartado 3)
Punto a)
public boolean esCasoBase() {
return ((getJ() - getI()) == 1 || (getJ() - getI()) == 2);
}
Punto b)
public Sp<Integer, Integer> getSolucionCasoBase() {
Sp<Integer, Integer> sp = null;
Integer humo = 0;
if (getJ() - getI() == 2) {
int colorA = getPocima(getI());
int colorB = getPocima(getJ() - 1);
humo = colorA * colorB;
sp = new Sp<Integer, Integer>(getI() + 1, humo);
} else {
sp = new Sp<Integer, Integer>(getI(), humo);
}
return sp;
}
Punto c)
public Iterable<Integer> getAlternativas() {
Iterable<Integer> ite = Iterables2.fromArithmeticSequence(getI() + 1, getJ(), 1);
return ite;
}
Punto d)
public ProblemaPD<SolucionPocimas, Integer, Integer> getSubProblema(Integer a, int i) {
ProblemaPD<SolucionPocimas, Integer, Integer> s = null;
if (i == 0)
s = ProblemaPocimas.createPD(getI(), a);
else if (i == 1) {
s = ProblemaPocimas.createPD(a, getJ());
}
return s;
}
Punto e)
public Sp<Integer, Integer> combinaSoluciones(Integer a, List<Sp<Integer, Integer>> ls) {
Sp<Integer, Integer> e1 = null;
Sp<Integer, Integer> e2 = null;
Sp<Integer, Integer> r = null;
if (ls != null) {
e1 = ls.get(0);
e2 = ls.get(1);
}
if (e1 != null && e2 != null && e1.alternativa != null && e2.alternativa != null) {
Integer humoA = e1.solucionParcial;
Integer humoB = e2.solucionParcial;
Integer colorA = getColorGenerado(getI(), a);
Integer colorB = getColorGenerado(a, getJ());
Integer humo = humoA + humoB + colorA * colorB;
r = new Sp<Integer, Integer>(a, humo);
}
return r;
}
354
Para la resolucin del problema mediante programacin dinmica, tenga en cuenta que:
La solucin deber ser del tipo SolucionInvitados, que contiene las siguientes propiedades:
mesaInv: Lista de enteros que almacena en qu mesa se ubica cada invitado, de forma que mesaInv[inv]
almacena la mesa donde se ubica el invitado inv.
nConflictos: Entero que almacena el nmero de conflictos existentes.
inv: Invitado a ubicar en ese paso, de forma que los invitados que ya han sido ubicados son [0, , inv-1].
mesaInv: Lista de enteros que almacena en qu mesa se ubica cada invitado, de forma que mesaInv[inv]
almacena la mesa donde se ubica el invitado inv.
nConflictos: Entero que almacena el nmero de conflictos existentes hasta ese momento.
nInv: Lista de enteros que almacena el nmero de invitados que ya han sido ubicados en cada mesa, de
forma que nInv[m] almacena el nmero de invitados ubicados en la mesa m.
Las alternativas sern de tipo entero, y sern las mesas (0M-1) en las que hay ubicados menos de 10 invitados.
El diagrama UML relacionado con el problema y la resolucin del mismo mediante Programacin Dinmica es el
mostrado a continuacin.
355
356
Solucin:
Ubicacin de Invitados
Tcnica:
Programacin Dinmica
Tamao:
N - Inv
Propiedades
Compartidas:
Solucin:
N
[entero]
Nmero de invitados.
M
[entero]
Nmero de mesas.
Conflictos
[tabla<entero, entero, lgico>]
inv
[entero] [0, N )
mesaInv
[Lista<entero>]
nConflictos
[entero]
nInv
[Lista<entero>]
SolucionInvitados
Objetivo:
Encontrar una solucin SmesaInv,nConflictos tal que nConflictos tenga el menor valor posible.
Alternativas:
Instanciacin:
Propiedades
Individuales
// TODO
Problema generalizado:
(, , , ) = {
,
,,, ((, ))
=
<
= ( + 1, + , + , [] + +)
= (, , )
, .
//
(, ): ,
( ).
): .
,,, (,
357
Apartado 2>
A>
private E pD(ProblemaPD<S, A, E> p) {
E s = null;
if (p.esCasoBase()) {
s = p.getSolucionCasoBase();
} else {
List<E> solucionesOr = Lists.newArrayList();
for (A a : p.getAlternativas()) {
int numeroDeSubProblemas = p.getNumeroSubProblemas(a);
List<E> solucionesAnd = Lists.newArrayList();
for (int i = 0; i < numeroDeSubProblemas; i++) {
ProblemaPD<S, A, E> pr = p.getSubProblema(a, i);
s = pD(pr);
solucionesAnd.add(i, s);
}
s = p.combinaSolucionesAnd(a, solucionesAnd);
solucionesOr.add(s);
}
s = p.combinaSolucionesOr(solucionesOr);
}
return s;
}
B>
public boolean esCasoBase() {
return inv == ProblemasInvitados.getN();
}
C>
public int getNumeroSubProblemas(Integer a) {
return 1;
}
D>
public SolucionInvitados combinaSolucionesOr(List<SolucionInvitados> ls) {
SolucionInvitados s = null;
Iterable<SolucionInvitados> listaFiltrada =
Iterables.filter(ls, Predicates.notNull());
if (Iterables.size(listaFiltrada) > 0) {
s = ordenSolucion.min(listaFiltrada);
}
return s;
}
E>
public Iterable<Integer> getAlternativas() {
Set<Integer> alt = new TreeSet<Integer>();
for (int m = 0; m < ProblemasInvitados.getM(); m++) {
if (nInv.get(m) < 10)
alt.add(m);
}
return alt;
}
358
Ejemplo:
Traducir del espaol al japons disponiendo de los siguientes diccionarios utilizando el menor nmero
de traducciones: espaol-griego (dificultad 2), griego-alemn (dificultad 2), espaol-alemn (dificultad
1), griego-chino (dificultad 3), chino-japons (dificultad 1), alemn-japons (dificultad 3)
Solucin: S es posible la traduccin. La secuencia de traducciones que requiere la menor dificultad sera:
espaol-alemn-japons realizando dos traducciones con dificultad 4 en total.
Se pide:
1) Completar la ficha adjunta del problema a resolver. Los apartados a completar, son los sealados con //TODO
2) De la clase AlgoritmoPD, complete el algoritmo de programacin dinmica visto en clase, es decir, el mtodo
private Sp<A, T> pD(ProblemaPD<S, A, T> p).
3) De la clase ProblemaTraduccionPD, implemente los siguientes mtodos teniendo en cuenta la ficha adjunta:
a)
b)
c)
d)
public
public
public
public
boolean esCasoBase()
Sp<Integer, Integer> getSolucionCasoBase()
Iterable<Integer> getAlternativas()
ProblemaPD<SolucionTraduccion, Integer, Integer> getSubProblema(Integer a, int i)
Nota:
Utilice el diagrama UML que se muestra a continuacin para realizar los distintos apartados.
Suponga implementadas todas las clases proporcionadas en el diagrama UML, salvo los mtodos de la clase
ProblemaTraduccionPD que se piden para su resolucin.
359
360
Programacin Dinmica
Tamao
Propiedades
Compartidas:
grafo
[grafo<Idioma, Diccionario>]
idiomas
[List<Idioma>]
idiomaOrigen [entero] [0, idiomas.size )
idiomaDestino [entero] [0, idiomas.size )
I
[entero] [0, idiomas.size )
J
[entero] [0, idiomas.size )
K
[entero] [-1, idiomas.size )
SolucionTraduccion, S L,d
Propiedades
Individuales
Solucin:
L : Lista que contiene los diccionarios con los idiomas por las que se ha pasado para realizar
la traduccin completa.
d: Indica la dificultad total que implica dicha traduccin.
Solucin Parcial:
Sp <Integer, Integer>
Objetivo:
Encontrar una solucin S L,d tal que la dificultad d sea la menor posible.
Alternativas:
Instanciacin:
Problema generalizado:
(0, 0)
(, , ) =
=
, = 1, ,
(0, )
, = 1, ,
(, 0, (, , 1))
,, (
)
(, 1, (, , 1), (, , 1))
{
. . .
//
(, , )
(, 0, ( , )) = (0, )
(, 1, ( , 1), ( , 2)) = (1, 1 + 2)
//
361
Apartado 2>
362
A>
public boolean esCasoBase() {
return (problema.getI() == problema.getJ()) || (problema.getK() == -1);
}
B>
public Sp<Integer, Integer> getSolucionCasoBase() {
Sp<Integer, Integer> res = null;
if (problema.getI() == problema.getJ()) {
res = Sp.create(0, 0);
} else if (problema.getK() == -1) {
Diccionario diccionario = ProblemaTraduccion.getGrafo().getEdge(
ProblemaTraduccion.getIdiomas().get(problema.getI()),
ProblemaTraduccion.getIdiomas().get(problema.getJ()));
if (diccionario != null) {
res = Sp.create(0, diccionario.getDificultad());
}
}
return res;
}
C>
public Iterable<Integer> getAlternativas() {
return Iterables2.fromArithmeticSequence(0, 2, 1);
}
D>
public ProblemaPD<SolucionTraduccion, Integer, Integer> getSubProblema(Integer a, int i) {
ProblemaTraduccionPD res = null;
ProblemaTraduccion
subp = null;
if (a == 1) {
if (i == 0) {
subp = ProblemaTraduccion.create(problema.getI(),
problema.getK(), problema.getK() - 1);
} else if (i == 1) {
subp = ProblemaTraduccion.create(problema.getK(),
problema.getJ(), problema.getK() - 1);
}
} else {
subp = ProblemaTraduccion.create(problema.getI(), problema.getJ(),
problema.getK() - 1);
}
res = ProblemaTraduccionPD.create(subp);
return res;
}
363
5.13.10
Teniendo en cuenta que cada extensin tiene un tamao diferente, y que protege frente a una cantidad diferente
de amenazas distintas (ninguna amenaza controlada por una extensin es tambin controlada por otra
extensin), se desea instalar el subconjunto de extensiones que maximice el nmero de protecciones contra
amenazas sin sobrepasar el espacio reservado.
Ejemplo:
Extensin
Tamao (MBytes)
Amenazas controladas
Ext-1
12
23
Ext-2
10
13
Ext-3
8
12
Ext-4
14
30
Ext-5
7
15
Ext-6
11
25
Para un espacio limitado a 30 MegaBytes, la solucin ptima implicara instalar las extensiones Ext-3,
Ext-4 y Ext-5, que ocupan 29 MegaBytes y proporcionan proteccin frente a 57 amenazas.
Se pide:
1) Completar la ficha adjunta del problema a resolver.
Los apartados a completar, son los sealados mediante //TODO
364
Apartado 1 >
Programacin Dinmica
Tamao
Nmero de extensiones
Propiedades
Compartidas:
Extensiones
EspacioTotal
[List<Extension>]
[entero] 0
Propiedades
Individuales
I
EspacioRestante
[entero] [0, N )
[entero] 0
Solucin:
Solucin Parcial:
Sp <Integer, Integer>
Objetivo:
Encontrar una solucin s tal que maximice las amenazas protegidas y que el espacio
ocupado sea menor que EspacioTotal.
Alternativas:
// TODO
(a, amenazas)
, = {0, 1}
Instanciacin:
// TODO
pa(EspacioRestante, Extensiones) = pag(EspacioRestante, r - 1, Extensiones)
r = Extensiones.size()
Problema generalizado:
// TODO
(0, 0, 0)
= 0
(
)
=0
() = { ,
, ((, , ()))
. . .
= (, , )
= ( + 1, )
(, , ) = (, + )
Elige la solucin parcial que maximice amenazas.
365
Apartado 2>
366
Para dos secuencias A = a0a1an1, B = b0b1bm1, la ecuacin general de la puntuacin Si,j correspondiente al
alineamiento ptimo de las subsecuencias a0a1ai1 y b0b1bj1, ser:
1,1 + (1 , 1 )
+1
1, 1
, = {
, (, ) = {
1
,1 1
Ntese que:
-
Se pide:
a) Completar la ecuacin en recurrencias para todos los casos, explicando su significado (la expresin
anterior slo es vlida en el caso general, y no para los casos base).
b) Extender dicha ecuacin para determinar el alineamiento ptimo entre dos subsecuencias (prefijos) de
dos secuencias dadas A y B.
c) Desarrollar un algoritmo para determinar el alineamiento ptimo entre dos secuencias dadas A y B, de
longitudes N y M respectivamente.
367
Un algoritmo que obtenga la anchura correspondiente al trayecto de mayor anchura entre cada par de
intersecciones.
Un algoritmo que obtenga el trayecto de mayor anchura desde una interseccin origen hasta otra destino.
Solucin:
Camino de mxima anchura
Tcnica:
Programacin Dinmica
Tamao:
Propiedades
Compartidas:
Solucin:
Grafo
[Graph<Interseccion, Calle>]
Intersecciones [Lista<Interseccion>]
Origen
[entero] [0, Intersecciones.Size() )
Destino
[entero] [0, Intersecciones.Size() )
I
[entero] [0, Intersecciones.Size() )
J
[entero] [0, Intersecciones.Size() )
K
[entero] [-1, Intersecciones.Size() )
SolucionMaximaAnchura
Objetivo:
Alternativas:
,, = {, }
Instanciacin:
(, ) = (, , . () 1)
Propiedades
Individuales
Problema generalizado:
{},
(, , ) =
=
, = 1, (, )
, = 1, (, )
(, (, , 1))
,, (
(, , 1) )
(, {
})
(, , 1)
(, ): .
, ,
(, 1 , 2 ): 1 2 ,
.
, ,
,, ( , ):
368
369
370
371
Ejercicio 1
Sea un grafo dirigido G tal que lij = true indica que existe una arista desde el vrtice vi hasta el vrtice vj y lij = false
en caso contrario. Mediante la tcnica de Programacin Dinmica, desarrollar un programa que obtenga un
camino, si existe, entre dos vrtices vi hasta el vrtice vj dados.
Ejercicio 2
Escriba un algoritmo utilizando la tcnica de Programacin Dinmica, que recibiendo como entrada un natural
n, con n 0, devuelva el nmero de rboles binarios de n elementos topolgicamente distintos (de formas
distintas), que hay. Por ejemplo, si f es la funcin que nos da la solucin, se tendr f(0) = 1, f(1) = 1, f(2) = 2, f(3)
= 5, f(4) = 14, etc.
Ejercicio 3
Disear un algoritmo de Programacin Dinmica que calcule el nmero de rboles ternarios (el nmero mximo
de hijos de un nodo es tres) estructuralmente diferentes que pueden formarse con n nodos, siendo n > 0. Por
ejemplo con f(1) = 1, f(2) = 3, f(3) = 12, etc., tal como se muestra en la figura:
Ejercicio 4
Se desea obtener el nmero de ordenaciones posibles f(n) de n elementos usando las relaciones < y =. Por
ejemplo, para tres elementos, se tienen 13 ordenaciones posibles:
a=b=c
c<a<b
a=b<c
b<a=c
c<a=b
b=c<a
a<b=c
b<a<c
a=c<b
b<c<a
a<b<c
c<b<a
a<c<b
Ejercicio 5
Una red de aeropuertos desea mejorar su servicio al cliente mediante terminales de
informacin. Dado un aeropuerto origen y un aeropuerto destino, el terminal desea ofrecer la
informacin sobre los vuelos que hacen la conexin y que minimizan el tiempo del trayecto
total. Se dispone de una tabla de tiempos tiempos[0..N-1, 0..N-1, 0..23] de valores enteros,
cuyos elementos tiempos[i, j, h] contiene el tiempo que se tardara desde el aeropuerto i al j
en vuelo directo estando en el aeropuerto i a las h horas (el vuelo no sale necesariamente a las h horas). Se desea
obtener la secuencia de aeropuertos por los que hay que pasar para ir desde un aeropuerto origen hasta otro
destino estando en el aeropuerto origen a la hora h si queremos llegar a destino lo antes posible.
Obtener, mediante Programacin Dinmica:
a) Realizar la funcin que obtiene el tiempo mnimo de trayecto total entre el aeropuerto origen y destino
si nos encontramos en el aeropuerto origen a una hora h dada, cuyo prototipo es
func vuelo(origen, destino, h: Entero) dev (t: Entero)
b) Explicar cmo se encontrara la secuencia de aeropuertos intermedios del trayecto de duracin total
mnima entre el aeropuerto origen y destino, estando en el aeropuerto origen a la hora h.
c) Realizar la funcin que obtiene la hora h a la que habra que partir del aeropuerto origen para que el
tiempo de trayecto total hasta el aeropuerto destino sea mnimo, cuyo prototipo es
func hora(origen, destino: Entero) dev (h: Entero)
Notas:
-
373
374
375
0-49
15%
50-99
30%
100-149
45%
+150
50%
En general no existe un beneficio mejor que otro, por lo que el beneficio mximo deber buscarse en un reparto
adecuado de la cantidad total de horas a repartir entre todas las rutas.
Hay un juego de estrategia para dos oponentes que se realiza con una fila de n monedas (siendo n un nmero
par y cada moneda tiene un valor de entre de los valores faciales existentes v(1)...v(k) en caso de jugar con euros
seran 0.01, 0,02, 0.05, 0.10, 0.20, 0.50, 1.00 y 2.00 ). Cada jugador, en turnos alternativos, retira una moneda
de la fila y se la guarda con la restriccin de que slo puede retirar la del extremo izquierdo o la del extremo
derecho. Gana el jugador que al final tenga ms dinero.
Realice un algoritmo de Programacin Dinmica que determine la cantidad mxima de dinero que podemos
conseguir si jugamos primero. Se supondr que el adversario siempre retirar la moneda de mayor valor de las
dos posibles (las que quedan en los extremos).
376
En esta prctica individual se abordar el modelado de la parte ms bsica de este lenguaje. Tendr que
desarrollar un planificador para problemas bsicos de PDDL. En particular, tendr que implementar un mtodo
que encuentre el plan mnimo (mnimo nmero de acciones) que lleve el estado inicial a cumplir el objetivo.
Tendr la siguiente cabecera (Dentro de la clase FachadaEjecucinPDDL):
public static <E extends EstadoPDDL> List<Accion<E>>
solve(E estado_inicial, List<Accion<E>> acciones, Predicate<E> objetivo, int numeroMaximoDePasos)
Las interfaces Accion y EstadoPDDL as como parte de clase ProblemaPDDL se encuentran en el paquete
us.lsi.pd.PDDL.modelado.
Parte de las clases FachadaEjecucinPDDL y ProblemaPDDLPD se encuentran en el paquete us.lsi.pd.PDDL.
No olvide implementar los mtodos equals y hashCode para la clase que defina sus problemas.
Para evitar complicaciones con los tipos genricos, implemente las propiedades compartidas como
atributos finales en vez de como atributos estticos.
377
5.15.5 Bancos.
Le acaba de tocar x millones de euros en la lotera de navidad y N bancos diferentes le
ofrecen un depsito para que invierta su dinero en ellos, de los cuales, desea obtener el
mximo beneficio.
Para ello, se dispone de una tabla de intereses de depsitos bancarios que representa
la cantidad de dinero que se obtiene al invertir en el depsito del banco i la cantidad x.
Las funciones de inters cumplen que si x > y, entonces TD(x, i) > TD(y, i). En general no existe una oferta mejor
que ninguna otra para cualquier cantidad, por lo que el beneficio mximo deber buscarse en un reparto
adecuado de la cantidad total a invertir entre todas las ofertas. Realice mediante la tcnica de Programacin
Dinmica un algoritmo que determine el mximo beneficio que puede obtenerse al invertir la cantidad total M
mediante un reparto adecuado entre las ofertas de los N bancos considerados.
378
Dao
60
74
1
Consumo
24
15
1
Repeticiones
2
2
10
Bonus
1
10
1
379
380
Donde todos los problemas , 1 , 2 , , son problemas del conjunto de problemas considerado. Cada
subproblema debe ser de un tamao menor que el problema original. Esta relacin de tamao nos permite
definir una relacin de orden total entre los problemas del conjunto. En el sentido de que Y <n X si el tamao de
Y es menor que el tamao de X. Con esta definicin previa se debe cumplir que < para cada uno de los
subproblemas.
El conjunto de problemas ms las relaciones entre problemas y subproblemas definen implcitamente, como
vimos, un grafo dirigido. Cada problema se asocia a un vrtice y hay un camino entre los problemas X y si hay
una alternativa a Ax y un entero i que represente un posible subproblema. Cada camino entre los vrtices ,
la representamos de la forma >, .
En el caso particular de Reduccin, es decir cuando el nmero de subproblemas para una alternativa dada es
igual a uno (k = 1) el esquema para este caso particular es:
() = {
(, , (
))
En este caso el problema X se reduce al problema Xa (donde podemos eliminar el subndice al solo haber un
subproblema) despus de tomar la alternativa a Ax. De nuevo, como en el caso general, el conjunto de
problemas ms las relaciones >a (ahora est omitido el nmero del subproblema porque siempre hay 1) definen
un grafo dirigido implcito. Cada vrtice es un problema del conjunto de problemas y existe una arista de X1 a X2
si X1 >a X2 para algn a 1 . Ahora en el grafo (a diferencia de en el caso general de la Programacin Dinmica)
todos los vrtices estn asociados a problemas y las aristas etiquetadas con alternativas. Las hojas de este grafo
son casos base o problemas que no tienen solucin. Como vimos, resolver un problema, desde esta perspectiva,
es encontrar un camino, si existe, desde el vrtice que representa el problema hasta una hoja que sea un caso
base. Encontrar todas las soluciones al problema es encontrar todos los caminos desde el vrtice que representa
el problema que acaban en casos base. Conocidas todas las soluciones la mejor, con respecto a una propiedad
dada de la solucin, es aquella que tiene mayor (o menor) valor de esa propiedad entre todas las soluciones
encontradas.
381
Los caminos en el grafo sern secuencias de alternativas que representaremos por {a1, a2,, ar}. Partiendo de un
problema dado X0 y siguiendo la secuencia de alternativas {a1, a2,, ar} alcanzaremos un problema que
representaremos por Xr. Desde la perspectiva anterior una solucin para el problema X0 puede representarse
como una secuencia de alternativas {a1, a2,, ar} con a1 0 y Xr un caso base. Por otro lado, una secuencia de
alternativas (un camino en el grafo), puede acabar en un problema que no tenga solucin.
En la Programacin Dinmica los casos base se tratan explcitamente. Por tanto en esa tcnica no es necesario
definir el conjunto de alternativas para esos problemas. En algunos casos es ms sencillo permitir tambin
reducciones de los casos base con solucin a un nuevo problema que llamaremos problema final. Siguiendo esta
posibilidad completamos el conjunto de problemas con un problema ficticio adicional que denominaremos el
problema final y representaremos por . Este problema final tendr un tratamiento especial. Con este problema
final tenemos una forma alternativa de caracterizar las soluciones de un problema. Ahora las hojas del grafo son
el problema y problemas que no tienen solucin. Las soluciones vienen definidas por caminos desde el
problema original hasta el problema final . La solucin al problema original la podemos representar por
s = a1 + a2 + + ar. Donde es la alternativa que hemos tomado en el caso base para alcanzar el problema fina.
Podemos estar seguros que la secuencia de alternativas que conducen al estado final define una solucin. Las
secuencias que conducen a un problema sin alternativas, pero no final, no es una solucin.
382
Bsico: Es el esquema de partida. Es adecuado para buscar todas las soluciones, la mejor o slo una de
ellas. En este ltimo caso el algoritmo para cuando encuentra la primera solucin. Esta variante es
adecuada, tambin, para problemas que buscan una solucin solamente o la existencia o no de solucin.
Ramifica y Poda: El algoritmo filtra (poda) el conjunto de alternativas disponible usando informacin de
las decisiones tomadas y cotas del valor esperado. Es la versin correspondiente de las mismas ideas
vistas en la Programacin Dinmica con Filtro. Es adecuada para resolver problemas de Optimizacin.
Aleatoria: Escoge, sin volver atrs, una de las alternativas (un nmero fijado de veces) de forma aleatoria
y despus contina normalmente como un algoritmo de Vuelta Atrs.
Relacionados con los algoritmos de Vuelta Atrs estn unos algoritmos iterativos denominados los Algoritmos
Voraces que estudiaremos con ms detalle en el captulo siguiente y haremos la introduccin en este. Si en un
algoritmo de Vuelta Atrs escogemos con una poltica fija una y slo una de las alternativas posibles y eliminamos
la posibilidad de volver atrs tenemos un Algoritmo Voraz. Esto algoritmos recorren, sin vuelta atrs, un camino
que va desde el problema original hasta una de las hojas del grafo de problemas. Se pueden especificar
escogiendo en cada estado, mediante una funcin, una de las alternativas disponibles. Al seguir un solo camino
los Algoritmos Voraces son muy rpidos. Pero hay que tener en cuenta algunas diferencias importantes con los
de Vuelta Atrs:
Los algoritmos Voraces son siempre mucho ms rpidos que los de Vuelta Atrs.
Los algoritmos Voraces puede que no encuentren la solucin aunque exista. Los de Vuelta Atrs siempre
la encuentran, si existe.
Si estamos resolviendo un problema de optimizacin los algoritmos Voraces puede que encuentren una
solucin pero que no sea la ptima. Los de Vuelta Atrs siempre encuentran la ptima, si existe.
Si podemos demostrar que un algoritmo Voraz encuentra la solucin (o la solucin ptima si es lo que
buscamos) entonces es preferible al algoritmo Voraz al de Vuelta Atrs correspondiente. Pero esa
demostracin hay que hacerla especficamente para algoritmos Voraces concretos.
383
Aade una alternativa al estado y lo actualiza para que pueda informar sobre el
problema actual.
toma el valor a.
int size(): Informa sobre el tamao del problema actual.
Boolean isFinal(): Devuelve si el estado es final.
Iterable<A> getAlternativas(): Devuelve las alternativas disponibles en ese estado.
S getSolucion(): Transforma el estado en solucin.
Podemos usar la tcnica equivalente a la Programacin Dinmica con Filtro que llamaremos Ramifica y Poda. En
esta tcnica necesitamos que el estado proporcione ms informacin. Para ello diseamos el tipo EstadoBTF que
depende de un parmetro ms, el parmetro E, que representa el tipo de la propiedad a optimizar.
384
Los mtodos tienen el mismo objetivo que en la Programacin Dinmica con Filtro. El mtodo
esUnValorMejorDeLaPropiedad decide si el valor actual de la propiedad a optimizar es mejor que el mejor valor
encontrado previamente.
385
extends AbstractAlgoritmo {
Tipo{Max,Min,Otro};
tipo;
solucion = null;
soluciones = Sets.newHashSet();
numeroDeSoluciones = 1;
isRandomize = false;
sizeRef = 10;
conFiltro = false;
soloLaPrimeraSolucion = true;
problema;
estado;
estadoF;
exito = false;
mejorValor;
386
La clase Metricas mide propiedades de un algoritmo en las que podemos estar interesados como por ejemplo
TiempoDeEjecucion. Las propiedades que pueden medirse las iremos viendo a medida que sean necesarias. Por
ltimo, la factora Algoritmos sirve para crear algoritmos de Vuelta Atrs para un problema.
En el siguiente UML se muestra el diagrama completo de la clase AlgoritmoBT<S,
A, E>.
El algoritmo, dado un estado genera las alternativas disponibles, las filtra si estamos en el modo con filtro, las
aade al estado y se llama recursivamente en el nuevo estado.
El mtodo filtraRandomize es el mismo que en el caso de la Programacin Dinmica Aleatoria.
El algoritmo, como podemos ver, dado un estado genera las alternativas disponibles, las filtra si estamos en el
modo con filtro, las aade al estado y se llama recursivamente en el nuevo estado. Cuando vuelve de la llamada
recursiva coloca el estado en la situacin previa e intenta la siguiente alternativa.
Cuando alcanza un estado final actualiza la solucin, el conjunto de soluciones y posteriormente decide indicar
que hay que parar (colocando xito a true).
Si del conjunto de alternativas escoge mediante una funcin slo una de ellas y cuando alcanzamos el problema
final o uno sin alternativas paramos el algoritmo tendramos la implementacin de un Algoritmo Voraz.
387
N Reinas
Tcnica:
Tamao:
N - col (nmeros de reinas y dimensin del tablero, menos las reinas ya colocadas).
Propiedades
Compartidas:
[entero] [0, N )
[Lista<entero>]
[Lista<entero>]
[Lista<entero>]
Solucin:
Objetivo:
Encontrar una solucin S tal que las posiciones obtenidas para cada reina colocada no
afecte al resto de reinas segn normas del ajedrez, es decir, que:
|col| = |filOcu| = |dgPpOcu| = |dgScOcu| = N y ninguna amenace a otra.
Estado Inicial:
Estado Final:
col = N
Alternativas:
,,,
= { [0, ) | , , + }
Avanza:
(f) = [col + 1, filOcu + fil, dgPp + d1, dgSc + d2] con d1 = fil-col, d2 = fil+col
Retrocede:
388
=
=
=
=
=
columna;
Sets.newHashSet();
Sets.newHashSet();
Sets.newHashSet();
Lists.newArrayList();
389
6.6.2 Mochila.
[Vase el enunciado completo del problema en el captulo 5]
El estado, adems de extender el tipo EstadoBTF<SolucionMochila,Integer,Integer>, tendr las propiedades
CapAct (capacidad actual de la mochila), J (entero que indica que en el problema actual podemos usar los objetos
ubicados en las posiciones que van de cero a J) y ValAcu (valor acumulado al escoger con las alternativas ya
escogidas).
Para poder usar la tcnica de Backtracking con Filtro (Ramifica y Poda) aadimos a los problemas una propiedad
compartida ValMax (valor mximo).
Como vemos ahora debemos especificar si un problema (o lo que es equivalente un estado dado) es inicial o
final. El conjunto de alternativas es el mismo que en el caso de la Programacin Dinmica con Filtro.
Mochila
Tcnica:
Tamao:
N=J
Propiedades
Compartidas:
CapIni
[entero]
ValMax
[entero]
valor
peso
Encontrar una solucin S tal que el peso total pesoTotal CapIni y que el valor total valorTotl
tenga el mayor valor posible.
Estado Inicial:
Estado Final:
J<0
Alternativas:
,, = {: 0}
= min (
,, = {}
= min (
, ) , > 0
, ) , = 0
Retrocede:
390
Cambio de Monedas
Tcnica:
Tamao:
N=J
Propiedades
Compartidas:
Monedas
CantidadIni
Solucin:
SolucinMoneda [MultiSet<Moneda>]
Objetivo:
Encontrar una solucin S tal cuya propiedad Valor sea igual a Cantidad y tenga el menor
valor posible para la propiedad NumeroDeMonedas.
Estado Inicial:
Estado Final:
J<0
Alternativas:
>0
, = {}
=0
, = {}
=0
, = {}
<0
Avanza:
Retrocede:
J-1]
391
6.6.4 Asignacin.
Dados n personas y n tareas, con un coste Coste(i, J) si la persona i ejecuta la tarea j. Buscar una asignacin de
tareas a personas (donde cada tarea es asignada a una persona y cada persona tiene una sola tarea) para que el
coste total sea mnimo.
Asignacin
Tcnica:
Propiedades
Compartidas:
Coste
[Tabla<entero,entero,entero>] Coste de una tarea ejecutada por una persona.
N
[entero]
Nmero de personas y de tareas.
CosteMejor [entero]
Coste de la mejor solucin.
Estado Inicial:
(Asig, i, CosteAcu) ([ ], 0, 0)
Estado Final:
iN
Alternativas:
Retrocede:
Si entre las alternativas posibles elegimos una, entonces tenemos un algoritmo Voraz:
Asignacin
Tcnica:
Voraz
Propiedades
Compartidas:
Coste
N
Estado Inicial:
(Asig, i, CosteAcu) ([ ], 0, 0)
Estado Final:
iN
Alternativas:
Eleccin:
= ((, )) = min((, ))
Avanza:
392
Tareas y Procesadores
Tcnica:
Propiedades
Compartidas:
TareasTiempo [Lista<Tarea>]
Proc
[entero]
TiempoMejor [entero]
Encontrar una solucin tal que el tiempo de ejecucin sea el mnimo posible.
Estado Inicial:
Estado Final:
J<0
Alternativas:
Avanza:
Retrocede:
, J-1)
393
Una forma simple de implementar el filtro F(a) es agrupar las alternativas con el mismo valor de TiempoXProc(a)
(por ejemplo mediante un mapa) y luego escoger aleatoriamente una alternativa de cada conjunto.
De nuevo, si escogemos slo una de las alternativas posibles, tenemos un algoritmo Voraz:
Tareas y Procesadores
Tcnica:
Voraz
Propiedades
Compartidas:
TareasTiempo [Lista<Tarea>]
Proc
[entero]
Encontrar una solucin tal que el tiempo de ejecucin sea el mnimo posible.
Estado Inicial:
Estado Final:
J<0
Alternativas:
Eleccin:
Avanza:
394
Disee un algoritmo basado en la tcnica Vuelta Atrs (Backtracking) que resuelva el problema.
395
Caballo de Ajedrez
Tcnica:
Tamao:
Propiedades
Compartidas:
posInicialX
[entero]
posInicialY
[entero]
movX
[Lista<entero>] {-1, m , 1, }
movY
[Lista<entero>] {2, 2 , -2, }
Propiedades del casillasProcesadas
[entero]
Estado:
posX
[entero]
posY
[entero]
tablero
[tabla<entero, entero, entero>] PosX, PosY, Caballo pasa por celda.
Solucin:
[Tabla<entero, entero, entero>]
Objetivo:
Estado Inicial:
casillasProcesadas = 1
posX = posInicioX
posY = posInicioY
tablero[posX, posY] = 1
Estado Final:
casillasProcesadas = 1
Alternativas:
Avanza:
posX + movX[m]
posY + movY[m]
tablero[posX + movX[m], posY + movY[m]] = casillasProcesadas
casillasProcesadas + 1
Retrocede:
casillasProcesadas - 1
posX - movX[m]
posY - movY[m]
tablero[posX - movX[m], posY - movY[m]] = null
Complejidad:
= (2n)
396
397
398
6.6.7 Sudoku.
El Sudoku es un pasatiempo que fue ideado por un arquitecto estadounidense Howard Garns a finales de los
setenta y se populariz en Japn en 1986, dndose a conocer en el mbito internacional en 2005 cuando
numerosos peridicos empezaron a publicarlo en su seccin de pasatiempos.
El problema del Sudoku consiste en, dada una matriz de 9x9 casillas subdivididas en cajas de 3x3, rellenarlas con
las cifras del 1 al 9 cada una de las casillas de la matriz que estn vacas, con la restriccin de que no se debe
repetir ninguna cifra en la misma fila, columna o caja. Alcanzamos la solucin cuando la matriz est
completamente rellena de nmeros (9x9=81 en total) y no se repite ninguna cifra del 1 al 9 en ninguna fila,
columna o caja.
Por tanto, el objetivo de sta prctica ser rellenar la matriz del Sudoku colocando en cada casilla un nmero de
1 a 9 sin que se repita en ninguna fila, columna o caja.
Cada casilla ser representada por una tupla (fil, col, caja, valor). Todas las propiedades estn representadas por
nmeros enteros que comprendidos entre 1 y 9.
Generalizamos el problema: asumiendo ya instanciadas las r < N casillas vacas el objetivo ser instanciar las
N - r casillas vacas restantes. Las casillas ya instanciadas tienen ocupadas un conjunto de instancias por cada fila,
columna y caja de la matriz. El problema generalizado tiene un conjunto de propiedades filas, columnas y cajas
de tipo Map donde la clave indica la fila, la columna o la caja y el valor es un conjunto con los nmeros ya
instanciados que contiene cada fila, columna o caja de la matriz del Sudoku.
Las alternativas disponibles sern representadas por los posibles valores que puede tomar una casilla. Una casilla
solo podr tomar aquellos valores que no incumpla ninguna de las condiciones que debe cumplir la matriz del
Sudoku. Los valores que podr tomar una casilla son:
= { [1 9] | ,,, [] [] [] }
A continuacin se detalla el modelado de cada una de las partes necesarias para resolver el problema del Sudoku
mediante la tcnica de Vuelta Atrs.
399
400
Sudoku
Tcnica:
Tamao:
casillasVacias.size() - siguienteCasillaVacia
Propiedades
Compartidas:
casillasVacias
casillasOcupadas
[Lista<CasillaSudoku>]
[Lista<CasillaSudoku>]
Objetivo:
Estado Inicial:
siguienteCasillaVacia = 0
filas[fil] = (m | c fil,col,caja,valor casillasOcupadas)
columnas[col] = (m | c fil,col,caja,valor casillasOcupadas)
cajas[caja] = (m | c fil,col,caja,valor casillasOcupadas)
Estado Final:
siguienteCasillaVacia = casillasVacias.size()0
Alternativas:
= { [1 9] | ,,, [] [] [] }
Avanza:
filas[fil] + m
columnas[col] + m
cajas[caja] + m
siguienteCasillaVacia + 1
Retrocede:
siguienteCasillaVacia - 1
filas[fil] - m
columnas[col] - m
cajas[caja] - m
Complejidad:
= (9n)
401
1. Estudie la interfaz y las propiedades que implementa la clase EstadoSudoku del paquete us.lsi.pd.sudoku:
EstadoSudoku<S, A, E>
int siguienteCasillaVacia :
402
Integer
Integer
Integer
Integer
caja;
fila;
columna;
valor;
403
int tamaoSudoku = 0;
List<CasillaSudoku> casillasVacias = null;
List<CasillaSudoku> casillasOcupadas = null;
int siguienteCasillaVacia = 0;
public EstadoSudoku() {
this( ProblemaSudoku.getCasillasVacias(),
ProblemaSudoku.getCasillasOcupadas(),
ProblemaSudoku.getTamao());
}
private EstadoSudoku(List<CasillaSudoku> casillasVacias,
List<CasillaSudoku> casillasOcupadas,
int tamaoSudoku) {
super();
this.siguienteCasillaVacia = 0;
this.tamaoSudoku = tamaoSudoku;
this.casillasVacias = casillasVacias;
this.casillasOcupadas = casillasOcupadas;
this.filas = Maps.newHashMap();
this.columnas = Maps.newHashMap();
this.cajas = Maps.newHashMap();
Set<Integer> s = null;
for (int i = 0; i < tamaoSudoku; i++) {
s = Sets.newTreeSet();
this.filas.put(i, s);
s = Sets.newTreeSet();
this.columnas.put(i, s);
s = Sets.newTreeSet();
this.cajas.put(i, s);
}
for (CasillaSudoku c : casillasOcupadas) {
this.filas.get(c.getFila()).add(c.getValor());
this.columnas.get(c.getColumna()).add(c.getValor());
this.cajas.get(c.getCaja()).add(c.getValor());
}
}
public void avanza(Integer a) {
CasillaSudoku c = casillasVacias.get(siguienteCasillaVacia);
filas.get(c.getFila()).add(a);
columnas.get(c.getColumna()).add(a);
cajas.get(c.getCaja()).add(a);
c.setValor(a);
siguienteCasillaVacia++;
}
public void retrocede(Integer a) {
siguienteCasillaVacia--;
CasillaSudoku c = casillasVacias.get(siguienteCasillaVacia);
filas.get(c.getFila()).remove(a);
columnas.get(c.getColumna()).remove(a);
cajas.get(c.getCaja()).remove(a);
c.setValor(null);
}
404
405
static
static
static
static
Integer tamao;
List<CasillaSudoku> casillasVacias;
List<CasillaSudoku> casillasOcupadas;
Table<Integer, Integer, Integer> sudokuInicial;
private ProblemaSudoku() {
}
public static void cargaDatos(String fichero) {
sudokuInicial = HashBasedTable.create();
Iterable<String> is = Iterables2.fromFile(fichero);
for (String s : is) {
s = s.trim();
String[] split = s.split("[ \t,]");
if (split.length == 1) {
tamao = new Integer(split[0]);
}
if (split.length == 3) {
int fila = new Integer(split[0]);
int columna = new Integer(split[1]);
int valor = new Integer(split[2]);
sudokuInicial.put(fila, columna, valor);
}
}
casillasVacias = Lists.newArrayList();
casillasOcupadas = Lists.newArrayList();
Integer tamCaja = tamao / 3;
Integer caja = null;
if (tamao == 4) tamCaja = tamao / 2;
for (int f = 0; f < tamao; f++) {
for (int c = 0; c < tamao; c++) {
Integer v = sudokuInicial.get(f, c);
caja = ((int) (f / tamCaja)) + tamCaja * ((int) (c / tamCaja));
if (v == null) {
casillasVacias.add(CasillaSudoku.create(f, c, caja));
} else {
casillasOcupadas.add(CasillaSudoku.create(f, c, v, caja));
}
}
}
}
public static Integer getTamao() {
return tamao;
}
public static List<CasillaSudoku> getCasillasVacias() {
return casillasVacias;
}
public static List<CasillaSudoku> getCasillasOcupadas() {
return casillasOcupadas;
}
public static Table<Integer, Integer, Integer> getMatriz() {
return sudokuInicial;
}
406
407
El problema se modelar a travs de la clase ProblemaMercanciasImpl. Esta clase almacena el conjunto de clientes
que esperan el envo en el conjunto ordenado denominado conjuntoClientes. Adems, se conoce la hora inicial
en la que el vehculo comienza a servir pedidos, y la hora final, a partir de la cual el vehculo deja de servir
pedidos. Por ejemplo, suponiendo que el vehculo empieza su recorrido a las 8:00, y puede entregar las
mercancas hasta las 18:00, trabajando sin interrupcin, se dispondra de 10 horas.
408
SortedSet<Cliente> clientesPendientes:
Se pide:
a) Completar el apartado avanza y retrocede de la ficha proporcionada. Tenga en cuenta que tal como se
puede ver en la ficha proporcionada, las alternativas posibles son los clientes para los cules es posible
realizar la entrega de la mercanca antes de la hora final fijada para el problema.
b) Escribir el cdigo completo del mtodo ryp() de la clase ProblemaMercanciasImpl.
c) Escribir el cdigo completo de los mtodos avanza(Cliente) y retrocede(Cliente) de la clase
EstadoMercanciasImpl.
d) Completar el cdigo de la clase interna IterableBT, que ser utilizada en el mtodo getAlternativas()
para devolver las alternativas para cada estado del problema.
e) Escribir el mtodo getAlternativas() de la clase ProblemaMercanciasImpl. Tenga en cuenta que tendr
que utilizar la clase interna IterableBT creada en el apartado anterior.
409
Solucin:
Apartado a)
Envo de Mercancas
Tcnica:
Tamao:
N = tamao (clientesPendientes)
Propiedades
Compartidas:
conjuntoClientes
horaInicial
horaFinal
[SortedSet<Cliente>]
[entero]
[entero]
[Lista<Cliente>]
[SortedSet<Cliente>]
[entero]
[real]
Alternativas:
Estado Inicial:
clientesAtendidos
= {}
clientesPendientes
= copia(conjuntoClientes)
horaDisponible
= horaInicial
beneficioTotal
= 0.0
horaDisponible = horaFinal
( ci clientesPendientes : horaDisponible + duracion(ci) > horaFinal)
Estado Final:
Avanza:
Retrocede:
clientesProcesados
clientesPendientes
horaDisponible
horaEnvio(ci)
beneficioTotal
clientesProcesados
clientesPendientes
horaDisponible
beneficioTotal
horaEnvio(ci)
=
=
=
=
=
=
=
=
=
=
clientesAtendidos + ci
clientesPendientes - ci
horaDisponible + duracion(ci)
horaDisponible
beneficioTotal + precioFinal(ci)
clientesAtendidos - ci
clientesPendientes + ci
horaDisponible - duracion(ci)
beneficioTotal - precioFinal(ci)
-1
Donde:
-
410
Apartado c)
411
Apartado d)
Class
IterableBT
implements Iterable<Cliente>, Iterator<Cliente>
{
Iterator<Cliente> itClientes;
Cliente nextCliente;
public IterableBT() {
this.itClientes = Lists.newArrayList(estado.getClientesPendientes()).iterator();
}
public Iterator<Cliente> iterator() {
return this;
}
public boolean hasNext() {
// TODO COMPLETAR SLO ESTE MTODO
boolean enc = false;
while (this.itClientes.hasNext() && !enc) {
nextCliente = itClientes.next();
if (estado.getHoraDisponible() + nextCliente.getDuracion() <= getHoraFinal()){
enc = true;
}
}
return enc;
}
public Cliente next() {
return nextCliente;
}
public void remove() { throw new UnsupportedOperationException...)}
}
Apartado e)
412
6.7.2 Ayuntamiento.
El alcalde de un determinado pueblo dispone de un presupuesto para realizar las diversas
actividades que estaban contempladas en su programa electoral. Con el fin de maximizar
el nmero de habitantes que se vean beneficiados por esas medidas, los asesores
pretenden disear un algoritmo de Vuelta Atrs para escoger aquellas actividades que
hagan mximo el nmero de habitantes que se veran afectados por dicha actividad.
Ejemplo: La siguiente tabla muestra un ejemplo de datos de entrada. Para un presupuesto de 2500 unidades la
mejor opcin es realizar las actividades 1 y 4, que agruparan 60 habitantes y tendra un coste de 2500 unidades.
El objetivo es encontrar, a travs de un algoritmo de Vuelta Atrs, qu actividades que deben realizarse para
alcanzar al mximo nmero de habitantes para un presupuesto total concreto.
Para modelar el problema, se supondr que existen m actividades (a1, a2, ..., am). El tipo Actividad permite
almacenar toda la informacin sobre una actividad. Los mtodos ms importantes son:
Para calcular los habitantes afectados por las actividades se sumarn los habitantes afectados por cada
actividad, es decir, no se contemplar si varias actividades afectan al mismo habitante.
Las actividades no se pueden fraccionar.
413
Integer actividadesProcesadas:
no subvencin.
List<Actividad>
actividadesAtendidas:
subvencin.
listaActividades:
problema.
Se pide:
a) Escribir el cdigo completo del mtodo private void ryp() de la clase AlgoritmoRyP<Integer, Boolean,
EstadoActividades>.
b) Escribir el mtodo public boolean isAlternativa(EstadoActividades e) de la clase
ProblemaActividadesImpl.
c) Escribir el mtodo public Iterable<Boolean> getAlternativas(EstadoActividades e) de la clase
ProblemaActividadesImpl. Tenga en cuenta que tal cmo se puede ver en la ficha proporcionada, las
alternativas sern valores lgicos que indicarn si la actividad recibir subvencin o no.
d) Escribir el cdigo completo de los mtodos public boolean avanza(Boolean e) y public Boolean
retrocede() de la clase EstadoActividadesImpl, teniendo en cuenta la ficha proporcionada.
Solucin:
Ayuntamiento
Tcnica:
Tamao:
N = listaActividades.size() - actividadesProcesadas
Propiedades
Compartidas:
presupuestoTotal
actividadesPrevistas
[entero]
[Lista<Actividad>]
[entero]
[Lista<Actividad>]
[entero]
[entero]
[Lista<Actividad>]
Estado inicial:
=
=
=
=
=
414
actividadesProcesadas
actividadesAtendidas
presupuestoDisponible
habitantesTotal
listaActividades
0
{}
presupuestoTotal
0
actividadesAtendidas
415
El objetivo ser obtener el nmero y tipo de objetos a introducir en cada mochila de forma que se maximice el
valor total de las dos mochilas con la restriccin de que deben tener el mismo peso. La solucin se almacenar
en una lista de Pair<Integer, Integer> donde se indica el nmero de objetos que se introducirn en la mochila
A y B de cada elemento en el orden indicado en la lista listaObjetos.
Se pide:
1) Completar la ficha adjunta del problema de las dos mochilas. Los apartados sealados mediante //TODO.
2) Implementar los siguientes mtodos teniendo en cuenta la ficha adjunta:
-
Nota: El tipo Pair<Integer, Integer> permite almacenar dos valores de tipo Integer a travs de su constructor.
Tambin es posible obtener los valores su valor usando los mtodos getP1() y getP2().
416
Propiedades
Compartidas:
// TODO
listaObjetos:
capacidadMaximaA
capacidadMaximaB
Propiedades del // TODO
Estado:
posActual
pesoActualA
pesoActualB
valorAcumulado
objetosAnadidos
Solucin:
SolucionMochilas
Objetivo:
[Lista<Vaso>]
[entero]
[entero]
[entero] [0, listaObjetos.size() )
[entero]
[entero]
[entero]
[Mapa<ObjetoMochila, Pair<entero,entero>>]
// TODO
Encontrar una solucin tal que posActualA = posActualB y valorAcumulado tenga el mayor
valor posible.
Alternativas:
Estado inicial:
Pair<Integer, Integer> p |
p.getP1() + p.getP2() < listaObjetos[posActual].numeroMaximo
pesoActualA + listaObjetos[posActual].pesoUnitario * p.getP1() pesoMaximoA
pesoActualB + listaObjetos[posActual].pesoUnitario * p.getP2() pesoMaximoB
// TODO
posActual = listaObjetos.size() - 1
valorAcumulado = 0;
pesoActualA = 0;
pesoActualB = 0;
objetosAnadidos = {}
Estado final:
posicion = 0
Avanza:
// TODO
Retrocede:
pesoActualA += a.getP1()*listaObjetos[posActual].pesoUnitario
pesoActualB += a.getP2()*listaObjetos[posActual].pesoUnitario
valorAcumulado += a.getP1()*listaObjetos[posActual].valorUnitario +
a.getP2()*listaObjetos[posActual].valorUnitario
objetosAnadidos.put(listaObjetos[posActual], a)
posActual - // TODO
posActual ++;
objetosAnadidos.remove(listaObjetos[posActual])
pesoActualA -= a.getP1()*listaObjetos[posActual].pesoUnitario
pesoActualB -= a.getP2()*listaObjetos[posActual].pesoUnitario
valorAcumulado -= a.getP1()*listaObjetos[posActual].valorUnitario +
a.getP2()*listaObjetos[posActual].valorUnitario
417
Apartado 2>
418
419
Dispone de una clase con mtodos estticos llamada UtilesOrganizacion , que no hay que implementar, con:
a. static double calculaDesviacion(List<Recurso> organizacion). Calcula la desviacin con respecto a
la media de una lista de recursos basndose en la asignacin de actividades de cada uno de ellos.
b. static List<Recurso> getLibres(List<Recurso> recursos, int inicio, int fin). Dada una lista de
recursos, con una asignacin de actividades devuelve el subconjunto de ellos que no estn ocupados
dentro del intervalo de tiempo [inicio, fin].
Se pide:
1) Rellene la ficha.
2) Implemente los siguientes mtodos (detecte en qu clases estn dichos mtodos). Compruebe que su
implementacin es coherente con la traza de la figura.
a. private void bt()
b. public Iterable<Recurso> getAlternativas(EstadoBacktrackingOrganizador e)
c. public SolucionOrganizador getSolucion(EstadoBacktrackingOrganizador e)
d. public boolean isFinal(EstadoBacktrackingOrganizador e)
e. public void avanza(Recurso r)
f. public void retrocede(Recurso r)
Solucin:
420
Propiedades
Compartidas:
plan
recursos
[Lista<Actividad>]
[Lista<Recursos>]
[Lista<Recurso>]
[entero]
Solucin:
SolucionOrganizacion
Alternativas:
Estado inicial:
// TODO
organizacionRecurso = recursos
i=0
Estado final:
i tamao(plan)
Avanza:
// TODO
a.organizacionRecurso.add( plan[i] )
i++
Retrocede:
// TODO
i-a.organizacionRecurso.remove( plan[i] )
421
Apartado 2>
a>
private void bt() {
if (problema.isFinal(estado)) {
S solucion = problema.getSolucion(estado);
if (problema.isSolucion(solucion)) {
soluciones.add(solucion);
}
}
if (buscaSoloLaPrimeraSolucion && !soluciones.isEmpty()) {
exito = true;
} else {
for (A a : problema.getAlternativas(estado)) {
estado.add(a);
bt();
estado.remove(a);
if (exito)
break;
}
}
}
b>
public Iterable<Recurso> getAlternativas(EstadoBacktrackingOrganizador e) {
List<Recurso> res;
if (e.getI() >= ProblemasOrganizador.getPlan().size()) {
res = new ArrayList<Recurso>(); // Ya ha terminado, no hay ms alternativas
} else {
Actividad a = ProblemasOrganizador.getPlan().get(e.getI());
res = UtilesOrganizador.getLibres(
a.getPosibles(), a.getHoraInicio(), a.getHoraInicio() + a.getDuracion());
res.retainAll(e.getOrganizacion());
}
return res;
}
c>
public SolucionOrganizador getSolucion(EstadoBacktrackingOrganizador e) {
return new SolucionOrganizador(
e.getOrganizacion(), UtilesOrganizador.calculaDesviacionEstandar(e.getOrganizacion()));
}
d>
SOLUCIN 1:
public boolean isFinal(EstadoBacktrackingOrganizador e) {
return Iterables.size(getAlternativas(e)) == 0;
}
SOLUCIN 2:
public boolean isFinal(EstadoBacktrackingOrganizador e) {
return e.getI() >= ProblemasOrganizador.getPlan().size();
}
e>
public void avanza(Recurso r) {
Actividad a = ProblemasOrganizador.getPlan().get(i);
r.getOrganizacionRecurso().add(a);
i++;
}
f>
public void retrocede(Recurso r) {
i--;
Actividad a = ProblemasOrganizador.getPlan().get(i);
r.getOrganizacionRecurso().remove(a);
// O bien: r.getOrganizacionRecurso().remove(r.getOrganizacionRecurso().size()-1)
}
422
6.7.5 La contrasea.
Existen muchos dispositivos para los cuales se permite establecer una contrasea de bloqueo relacionada con
un conjunto de 9 puntos que se distribuyen como se muestra en la figura:
Observe que cada punto est representado por un entero comprendido entre 1 y 9.
La contrasea, que se puede representar como una lista de enteros, debe cumplir los siguientes requisitos:
No debe contener elementos repetidos (por lo tanto nunca contendr ms de 9 elementos).
Cada punto en la lista debe ser vecino del punto anterior, y no haber sido incluido previamente en la solucin
parcial. Un punto P1 es vecino de otro P2 cuando P1 est encima, debajo, a la derecha, a la izquierda, o en
alguna de las 4 diagonales de P2. Todas las relaciones de vecindad se muestran en la figura (b). Para obtener
el conjunto de puntos vecinos de p se ha desarrollado el mtodo Set<Integer> vecinos(Integer p) de la
clase EstadoContraseaImpl. Cuando dicho mtodo recibe un 0 devuelve el conjunto formado por los 9
puntos (alternativas disponibles inicialmente).
Un ejemplo de contrasea vlida se muestra en la figura (c).
Se desea romper la seguridad de un determinado dispositivo usando el esquema de Vuelta Atrs. La nica forma
de saber si la contrasea conseguida es correcta es usando el mtodo boolean apply(List<Integer>) de la clase
EsContraseaValida (clase interna de ProblemasContrasea) que implementa Predicate<List<Integer>>.
Dicho mtodo devuelve cierto en caso de que la contrasea representada por dicha lista de enteros sea correcta,
y falso en caso contrario. Tenga en cuenta que:
El diagrama UML relacionado con el problema y la resolucin del mismo mediante backtracking se muestran a
continuacin.
423
424
La contrasea
Tcnica:
Propiedades
Compartidas:
esContraseaValida
[Lista<entero>]
Lgico.
[Lista<entero>]
[entero]
Puntos ya visitados.
ltimo punto visitado.
Solucin:
SolucionContrasea
Objetivo:
Alternativas:
Estado inicial:
Estado final:
esContraseaValida(puntos-0)
Avanza:
Retrocede:
anterior = puntos.get(puntos.size()-2)
// penltimo elemento
De la clase AlgoritmoBacktracking:
a) El algoritmo de backtracking visto en clase, es decir, el mtodo private void bt().
De la clase EstadoContraseaImpl:
b) public EstadoContraseaImpl(). Recuerde que aqu debe inicializar las Propiedades del
Estado segn el estado inicial.
c) public void avanza(Integer a)
d) public void retrocede(Integer a)
De la clase ProblemaContraseaBT:
e) public Iterable<Integer> getAlternativas(EstadoContrasea e). Este mtodo puede
devolver directamente un objeto de tipo Set<Integer>, ya que es un iterable sobre elementos
de tipo Integer.
425
Solucin:
Apartado a>
private void bt() {
if (problema.isFinal(estado)) {
S solucion = problema.getSolucion(estado);
soluciones.add(solucion);
}
if (buscaSoloLaPrimeraSolucion && !soluciones.isEmpty()) {
exito = true;
} else {
for (A a : problema.getAlternativas(estado)) {
estado.add(a);
bt();
estado.remove(a);
if (exito)
break;
}
}
}
Apartado b>
public EstadoContraseaImpl() {
puntos = new ArrayList<Integer>();
puntos.add(0);
actual = 0;
}
Apartado c>
public void avanza(Integer a) {
puntos.add(a);
actual = a;
}
Apartado d>
public void retrocede(Integer a) {
Integer anterior = puntos.get(puntos.size() - 2);
actual = anterior;
puntos.remove(a);
}
Apartado e>
public Iterable<Integer> getAlternativas(EstadoContrasea e) {
Set<Integer> res = new TreeSet<Integer>(e.vecinos(e.getActual()));
res.removeAll(e.getPuntos());
return res;
}
426
La solucin:
La empresa AcmeRayos se dedica a instalar estaciones de suministro asegurando un correcto funcionamiento
incluso cuando algn error ocurre (i.e. un fallo en una estacin). Para ello, dispone de dos tipos de estaciones:
1) Las estaciones bsicas, con un coste de 10.000 .
2) Las estaciones avanzadas, con un coste de 35.000 . Esta estacin funciona como dos estaciones bsicas.
AcmeRayos ha dividido el complejo PocasLuces en grupos de viviendas dada su distribucin geogrfica. Su
cometido ahora es colocar un conjunto de estaciones dentro de los grupos de viviendas, slo pudiendo haber
una estacin por grupo. Una estacin colocada en un grupo de viviendas, es capaz de dar suministro al propio
grupo y a todos los grupos adyacentes.
Para garantizar un buen nivel de servicio, cada grupo de viviendas debe poder acceder, al menos, a dos
suministros (por ejemplo, a dos estaciones bsicas o a una avanzada).
AcmeRayos ha contratado sus servicios para la implementacin de un algoritmo que le indique dnde colocar
cada estacin para que, minimizando el coste de la instalacin, cumpla todos los requisitos. As se le podr dar
un presupuesto al complejo PocasLuces.
Implementacin:
Se requiere que implemente este algoritmo utilizando la tcnica de Vuelta Atrs (Backtracking con Filtro)
Ramifica y Poda, teniendo en cuenta las siguientes consideraciones:
1) En todo momento se dispondr de un Multimap<String,String> grColind que,
Key Values
para cada String que identifica a un grupo de viviendas, indica qu otros
1
{2, 3, 4}
grupos son colindantes a ste. Para el ejemplo de la figura, grColind contendr
2
{1, 3, 5}
los elementos mostrados en la siguiente tabla:
3
{1, 2, 3, 4, 5, 6}
2) La solucin al problema (clase SolucionSuministros) se compone de dos
4
{2, 6, 8}
List<String>, estBasic y estAvanz con las estaciones bsicas y avanzadas
5
{2, 3, 4}
colocadas, adems de un atributo Integer costeTotal. Para el ejemplo de la
6
{3, 4, 5, 6, 7, 8}
figura: estBasic = {5, 6, 8}
estAvanz = {1}
costeTotal = 65000
7
{6, 8}
3) Los estados del problema (EstadoSuministrosBTF) contienen, adems de los
8
{4, 6, 7}
atributos de la solucin, un Map<String, Integer> grSumi, en el que para cada
grupo almacena el nmero de suministros accesibles y un atributo Set<String> grSinSumi con los grupos con
menos de dos suministros. Segn se puede observar en la figura:
grSumi = {1x2, 2x3, 3x4, 4x4, 5x2, 6x3, 7x2, 8x2}
grSinSumi = {}
//Todos acceden, al menos, a dos suministros
427
4) Las alternativas sern de tipo AlternativaSuministros, que tiene dos atributos: String grupo e Integer
tipoEstacion. El grupo indica qu grupo (an sin estacin) alojar la estacin y el tipoEstacion indica si sta
ser bsica (0) o avanzada (1).
Nota: La solucin mnima para la figura sera colocar estaciones bsicas en los grupos: 1, 3, 6 y 7.
Se pide:
1) Rellene la ficha (Se rellenar en la hoja entregada para ello).
2) Rellenar los huecos del algoritmo bt().(Se rellenar en la hoja entregada para ello).
3) Implemente los siguientes mtodos de la clase EstadoSuministrosBT.
a. public Iterable<AlternativaSuministros> getAlternativas().
b. public void add(AlternativaSuministros alt).
c. public boolean isFinal().
4) Implemente los siguientes mtodos de la clase EstadoSuministrosBTF. Vea el UML.
d. public Integer getCotaValor(AlternativaSuministros a).
e. public boolean pasaFiltro(AlternativaSuministros a).
Solucin:
428
Propiedades
Compartidas:
grColind
[MultiMapa<cadena,cadena>]
// TODO
A grupo, tipo de tipo AlternativaSuministros, A grupo, tipo | grupo grSinSumi tipo [0, 1]
Estado inicial:
// TODO
grSumi = ;
estBasic =;
estAvanz =;
costeTotal =0;
grSinSumi = grColind.keySet
Estado final:
// TODO
grSinSumi =
Avanza:
// TODO
costeTotal += (a.tipo== 0) ? 10000 : 35000;
a.tipo == 0 ? estBasic.add(a.grupo) : estAvanz.add(a.grupo);
grSumi.add(a.grupo, (a.tipo== 0) ? 1 : 2);
grupo grColind.get(a.grupo) : grSumi.add(grupo, (a.tipo== 0) ? 1 : 2);
grSumi.count(a.grupo) 2 ? grSinSumi.remove(a.grupo) : null ;
grupo grColind.get(a.grupo) : grSumi.count(grupo) 2 ? grSinSumi.remove(grupo) : null;
Retrocede:
// TODO
costeTotal -= (a.tipo== 0) ? 10000 : 35000;
a.tipo == 0 ? estBasic.remove(a.grupo) : estAvanz.remove (a.grupo);
grSumi.remove(a.grupo, (a.tipo== 0) ? 1 : 2);
grupo grColind.get(a.grupo) : grSumi.remove(grupo, (a.tipo== 0) ? 1 : 2);
grSumi.count(a.grupo) < 2 ? grSinSumi.add(a.grupo) : null ;
grupo grColind.get(a.grupo) : grSumi.count(grupo) < 2 ? grSinSumi.add(grupo) : null ;
429
Apartado 2>
430
b>
public void add(AlternativaSuministros alt) {
Integer costeEstacion;
Integer repeticionesAAadir;
if (alt.tipoEstacion == 0) {
estBasic.add(alt.grupo);
repeticionesAAadir = 1;
costeEstacion = 10000;
} else {
estAvanz.add(alt.grupo);
repeticionesAAadir = 2;
costeEstacion = 35000;
}
costeTotal += costeEstacion;
grSumi.add(alt.grupo, repeticionesAAadir);
if (grSumi.count(alt.grupo) >= 2) {
grSinSumi.remove(alt.grupo);
}
for (String grupo : ProblemaSuministros.getGrColind().get(alt.grupo)) {
grSumi.add(grupo, repeticionesAAadir);
if (grSumi.count(grupo) >= 2) {
grSinSumi.remove(grupo);
}
}
}
c>
public boolean isFinal() {
return grSinSumi.isEmpty();
}
d>
public Integer getCotaValor(AlternativaSuministros a) {
Integer costeEstacion = 0;
if (a.tipoEstacion == 0) {
costeEstacion = 10000;
} else {
costeEstacion = 35000;
}
return costeEstacion;
}
e>
public boolean pasaFiltro(AlternativaSuministros a) {
boolean r = true;
Integer valorEstimado = null;
if (mejorValorEncontrado != null) {
valorEstimado = costeTotal + getCotaValor(a);
r = valorEstimado.compareTo(mejorValorEncontrado) < 0;
}
return r;
}
431
6.7.7 Hangares.
Debido al incremento de trfico areo privado en el aeropuerto de Dubi, el
pas se ha encontrado con un grave problema a la hora de estacionar los casi
1500 Jets privados que recibe semanalmente procedente de todo el mundo.
Cada Jet es almacenado en un hangar, los cuales suelen tener una capacidad
de almacenamiento de entre 2 y 5 aeronaves. El principal problema de la
organizacin actual no es la falta de hangares, que podra convertirse en un
problema real dentro de varios meses, sino la falta de criterio a la hora de
asignar un lugar de aparcamiento a los Jets.
No todas las aeronaves poseen las mismas dimensiones (expresadas en m2), as como tampoco todos los
hangares tienen las mismas caractersticas de alojamiento. Todas las plazas de un determinado hangar poseen
las mismas medidas (expresadas tambin en m2), pero no todos estos hangares tienen ni el mismo nmero de
plazas ni las mismas dimensiones. Por este motivo, un avin puede ser estacionado en un hangar siempre y
cuando su tamao sea menor o igual que el tamao de las plazas del hangar.
Otro problema con el que se encuentra la organizacin del aeropuerto de Dubi es con el elevado coste de
mantenimiento de los hangares. La iluminacin y la limpieza que conlleva el uso de un hangar hacen que
minimizar el nmero de ellos que son usados sea crtico. Por supuesto, no todos los hangares tienen el mismo
coste de mantenimiento, sino que depende de su tamao y caractersticas.
El objetivo es encontrar, a travs de un algoritmo de Vuelta Atrs (Backtracking con Filtro) Ramifica y Poda,
una asignacin de hangares a las diferentes aeronaves de manera que se consigan aparcar todos los aviones con
el menor coste posible de hangares.
Para ello partimos de la siguiente informacin:
-
List<Aeronave> aeronaves.
Por otro lado, los tipos Aeronave y Hangar contienen los siguientes mtodos:
Aeronave:
- Double getDimensiones(). Obtiene el tamao de la aeronave expresado en m2.
- Double getIdentificador(). Recupera el identificador unvoco de la aeronave.
Hangar:
- Integer getPlazas(). Capacidad total del hangar, es decir, nmero de aeronaves que puede almacenar.
- Double getDimensiones(). Obtiene el tamao de cada una de las plazas en m2. Recuerde que todas las
plazas de un determinado hangar tienen el mismo tamao.
- Double getCosteMantenimiento(). Coste en que conlleva el uso de este hangar. Recuerde que este
coste se pagar siempre y cuando el hangar sea usado por al menos una aeronave.
Por ltimo, para facilitar el modelado, tenga en cuenta que se han definido las siguientes propiedades del estado:
-
432
Map<Hangar, List<Aeronave>> organizacion. Funcin que asocia a cada hangar las aeronaves que se le
han asignado. Inicialmente el mapa contendr todos los hangares disponibles con todas las listas
asociadas vacas.
Double costeTotalMantenimiento. Almacena el coste total de mantenimiento, que no es ms que la suma
del coste de mantenimiento de todos los hangares usados.
Se pide:
a)
b)
c)
d)
e)
f)
boolean isFinal(EstadoBTFHangares e)
Iterable<Hangar> getAlternativas(EstadoBTFHangares e)
void avanza(Hangar h)
void retrocede(Hangar h)
Implementar las funciones public boolean pasaFiltro(Hangar h) y public Double
getCotaValor(Hangar h) de la interfaz EstadoBTF que indican si la alternativa h es una alternativa
vlida dado un estado y el valor de la funcin de cota para el estado actual con la alternativa elegida
respectivamente. Considere que el valor de la funcin de cota ser el coste total de mantenimiento
actual si el hangar actual posee plazas libres y en caso contrario ser el coste total de mantenimiento
actual ms el coste del hangar ms econmico con plazas libres.
433
Solucin:
Apartado a>
Hangares
Tcnica:
Tamao:
aeronaves.size()
Propiedades
Compartidas:
aeronaves
hangares
[Lista<Aeronave>]
[Lista<Hangar>]
Propiedades del I
[entero]
Estado:
organizacion
[Mapa<Hangar, Lista<Aeronave>>]
CostetotalMantenimiento
[real]
Solucin:
Mapa<Hangar, Lista<Aeronave>>
Alternativas:
Ae = { hi hangares
hi.getDimensiones() a.getDimensiones()
usoHangar(hi) < hi.getPlazas()
}
donde a = aeronaves[i]
Estado inicial:
i = aeronaves.size()
usoHangar = {}
organizacin = {}
costeTotalMantenimiento = 0.0
Estado final:
i=0
Avanza:
// TODO
organizacion.put(a, organizacion[a].add(aeronaves[i])
costeTotalMantenimiento += a.getCosteMantenimiento();
Si no est en uso
i -Retrocede:
// TODO
i ++;
costeMantenimiento -= a.getCosteMantenimiento();
organizacion.put(a, organizacion[a].remove(aeronaves[i]);
Nota: Supuesto que el nmero de plazas en los hangares en suficiente para acoger todas las aronaves.
434
c>
d>
e>
f1>
f2>
435
public Integer
Nota: Para obtener la funcin de cota, fjese en la propiedad del estado Srestante, que contiene la suma de
todos los elementos que restan por recorrer en el conjunto E, teniendo en cuenta en cada caso la alternativa
escogida.
4. Implemente: public
5. Implemente: public
Solucin:
Apartado 1>
Inversiones Acciones
Tcnica:
Propiedades
Compartidas:
E
C
Alternativas:
A = {sumar, descartar}
Estado inicial:
Estado final:
Cacumulada = C || I E.size()
Avanza:
Retrocede:
Nota: El tipo AlternativaSumaParcialRyP es un enumerado con dos valores Sumar y Omitir. Suponemos
igualmente que el tipo EstadoSumaParcialRyP posee mtodos get y set para cada una de las propiedades
definidas en la ficha.
436
Apartado 3>
public Integer getCota(AlternativaSumaParcialRyP a, EstadoSumaParcialRyP e) {
if (a.equals(AlternativaSumaParcialRyP.sumar)) {
return e.getCantidadAcumulada() + e.getSumaRestante();
} else {
return e.getCantidadAcumulada() + e.getSumaRestante()
ProblemasSumaParcial.getNumeros().get(e.getPosicion());
}
}
Apartado 4>
public Iterable<AlternativaSumaParcialRyP> getAlternativas(final EstadoSumaParcialRyP e) {
List<AlternativaSumaParcialRyP> listOperadores = Lists.newArrayList();
if (e.getCantidadAcumulada() <= ProblemasSumaParcial.getObjetivo()) {
if (getCota(AlternativaSumaParcialRyP.descartar, e) >=
ProblemasSumaParcial.getObjetivo()) {
listOperadores.add(AlternativaSumaParcialRyP.descartar);
}
if (getCota(AlternativaSumaParcialRyP.sumar, e)>= ProblemasSumaParcial.getObjetivo())
listOperadores.add(AlternativaSumaParcialRyP.sumar);
}
return listOperadores;
}
Apartado 5>
public void avanza(AlternativaSumaParcialRyP a) {
Integer valor = ProblemasSumaParcial.getElementos().get(posicion);
if (a.equals(AlternativaSumaParcialRyP.sumar)) {
usados.add(valor);
cantidadAcumulada += valor;
}
sumaRestante -= valor;
posicion++;
}
437
EstadoPresupuesto getEstadoInicial()
{ //
Iterable<ServicioConexion> getAlternativas(EstadoPresupuesto e) { //
boolean isAlternativa(EstadoPresupuesto e)
{ //
boolean esSolucion(SolucionPresupuesto s)
{ //
TODO
TODO
TODO
TODO
}
}
}
}
438
439
Solucin:
440
T1
24
56
90
32
T2
45
56
67
23
T3
12
12
32
12
T4
34
76
54
23
Se pide:
1) Completar la siguiente ficha, marcado con //TODO
2) Implementar los siguientes mtodos:
public
public
public
public
Iterable<Integer> getAlternativas()
void avanza(Integer idCiudad)
Integer getObjetivo()
Integer getObjetivoEstimado(Integer idCiudad)
Nota: No es necesario usar predicados ni iteradotes para implementar el cdigo de los mtodos anteriores.
Apartado 1>
Inversiones Acciones
Tcnica:
Propiedades
Compartidas:
C
N
Propiedades del AS
[Map<Integer, Integer>]
Estado:
I
[Integer]
CA
[Integer]
TA = values(AS)
Alternativas:
A AS, i, CA = { j : 0N - 1 / j TA }
( {}, 0 , 0)
// TODO
Estado final:
IN
// TODO
Avanza:
// TODO
Retrocede:
// TODO
441
Apartado 2>
SOLUCIN 1
A>
public Iterable<Integer> getAlternativas() {
List<Integer> ls = Lists.newArrayList();
for (int idCiudad = 0; idCiudad < N; idCiudad++) {
if (TA.contains(idCiudad))
continue;
ls.add(idCiudad);
}
return ls;
}
B>
public void avanza(Integer idCiudad) {
AS.put(i, idCiudad);
CA = CA + C.get(i, idCiudad);
i = i + 1;
}
C>
public Integer getObjetivo() {
return CA;
}
D>
public Integer getObjetivoEstimado(Integer idCiudad) {
Integer ve = CA;
ve = ve + C.get(i, idCiudad);
for (Integer idCamion = i + 1; idCamion < N; idCamion++) {
Integer min = null;
for (Integer idCiudadAux = 0; idCiudadAux < N; idCiudadAux++) {
if (TA.contains(idCiudadAux))
continue;
Integer valorAux = C.get(idCamion, idCiudadAux);
if (min == null || valorAux < min) {
min = valorAux;
}
}
if (min != null) {
ve = ve + min;
}
}
return ve;
}
442
B>
public void avanza(Integer idCiudad) {
Integer idCamion = siguienteCamionPorAsignar;
asignacionDeCamiones.put(idCamion, idCiudad);
coste = coste + ProblemaAsignacion.tablaAsignacion.get(idCamion, idCiudad);
siguienteCamionPorAsignar = siguienteCamionPorAsignar + 1;
}
C>
public Integer getObjetivo() {
return coste;
}
D>
public Integer getObjetivoEstimado(Integer a) {
avanza(a);
Integer valorEstimado = getObjetivo();
if (isFinal()) {
retrocede(a);
return valorEstimado;
}
Iterable<Integer> itCamionesPorAsignar =
Iterables2.fromArithmeticSequence(siguienteCamionPorAsignar, ProblemaAsignacion.N, 1);
Iterable<Integer> itCiudadesPorAsignar =
Iterables.filter(Iterables2.fromArithmeticSequence(0, ProblemaAsignacion.N, 1), new
Predicate<Integer>() {
public boolean apply(Integer idCiudad) {
return !asignacionDeCamiones.containsValue(idCiudad);
}
});
for (int idCamion : itCamionesPorAsignar) {
Integer min = null;
for (int idCiudad : itCiudadesPorAsignar) {
Integer valorAux = ProblemaAsignacion.tablaAsignacion.get(idCamion, idCiudad);
if (min == null || valorAux < min) {
min = valorAux;
}
}
if (min != null) {
valorEstimado = valorEstimado + min;
}
}
retrocede(a);
return valorEstimado;
}
443
6.8.1 Laberinto.
Se tiene un laberinto bidimensional (M, N), representado por un objeto de tipo
laberinto en el que cada casilla guarda un entero, de forma que en cada casilla
puede haber un obstculo (valor - 1), un objeto de valor valor > 0, o no haber
nada (valor 0). La entrada al laberinto se produce por la casilla (1, 1) (esquina
superior izquierda), y la salida por la casilla (M, N). Para atravesar el laberinto,
los nicos movimientos posibles son realizar un paso hacia la derecha o hacia
abajo en la matriz, sin pasar dos veces por la misma casilla y sin pasar por los
obstculos.
a) Codificar un algoritmo basado en Vuelta Atrs, para obtener una de las posibles soluciones ptimas (que
maximicen el valor total obtenido). Cmo modificara el algoritmo si se quieren obtener todas las soluciones
ptimas?
b) Teniendo en cuenta que el valor mximo de un objeto es VMAX, cmo cambiara el algoritmo para
minimizar el nmero de soluciones exploradas? Utilcese como estrategia de bsqueda la seleccin de la
casilla vecina con mayor valor en primer lugar.
444
445
446
<0
0
Es decir, se acepta con total seguridad si el incremento es negativo (estamos asumiendo problemas de
Minimizacin) y con la probabilidad indicada si es positiva. La probabilidad depende de un concepto llamado
temperatura. La temperatura, que intenta emular la temperatura de un sistema, toma un valor inicial y
posteriormente va disminuyendo hasta acercarse a cero. Una cuestin clave en la estrategia de enfriamiento. Es
decir el mecanismo de disminucin de la temperatura. Se han propuesto varias alternativas. Aqu, en un primer
momento, escogeremos T = T0 i , 0 < < 1, donde es un parmetro, i es el nmero de iteracin y T0 es la
temperatura inicial. Tambin ser necesario establecer n como el nmero de iteraciones y m como el nmero de
iteraciones sin cambiar la temperatura.
El algoritmo comienza a dar pasos y en las primeras iteraciones acepta incrementos positivos con una
probabilidad de . Al final acepta incrementos positivos con probabilidad 0 . Para ajustar el algoritmo
debemos escoger los parmetros anteriores. Para ello escogemos la probabilidad de aceptacin al principio de
incrementos positivos p0 y al final pf. La primera debe ser alta (098 por ejemplo) y la segunda baja (001 por
ejemplo). A partir de lo anterior vemos que debe cumplirse:
= 0 ,
Si conocemos el tamao tpico de y escogemos n (el nmero de iteraciones con cambio de temperatura en
cada una de ellas) podemos despejar tanto T0 como :
0 =
,
ln(0 )
0 ln( )
ln (
,
)
0 ln( )
ln()
447
Posteriormente diseamos tipos adecuados para representar los estados de los algoritmos Voraces y Simulated
Annealing.
public interface EstadoVZ <EstadoVZ<E,S,A>, S, A>{
E next(A a);
A getAlternativa();
S getSolucion();
boolean condicionDeParada();
}
449
Estos tipos estn parametrizados por S y A. S representa el tipo de las soluciones del problema y A el tipo de las
alternativas posibles. Los mtodos tienen el siguiente funcionamiento:
Fijados los tipos anteriores podemos implementar el esquema de los algoritmos de aproximacin en una clase.
450
451
452
453
454
private
private
private
private
private
ProblemaBT<S, A>
EstadoBT<S, A>
EstadoBTF<S, A, T>
boolean
T
problema;
estado;
estadoF;
exito = false;
mejorValor;
null
&& estadoF.getObjetivoEstimado(a).compareTo(mejorValor) > 0
&& estadoF.getObjetivoEstimado(a).compareTo(mejorValor) < 0;
455
456
Problemas y Estados.
Los problemas que se quieran resolver por las tcnicas Voraz y Simulated Annealing vendrn modelados
mediante la interfaz ProblemaVZ y ProblemaSA. Para cada problema habr que implementar los tipos necesarios
en los cuales estn definidos los detalles del problema. ProblemaVZ y ProblemaSA sern de tipo genrico y sus
parmetros se definen como E, S, A para indicar el tipo de estado, el tipo de solucin y el tipo de alternativa del
problema.
Un estado debe ser diseado para contener la informacin sobre el problema actual y la secuencia de
alternativas elegidas para llegar hasta l desde el problema inicial. Necesita, adems los mtodos para aadir
alternativas. El estado inicial debe tener la informacin sobre el problema inicial y una secuencia vaca de
alternativas escogidas.
Con estas ideas podemos disear los tipos genricos, EstadoVZ y EstadoSA, que depende de los parmetros E, el
propio estado, A, tipo de las alternativas, y S, tipo de las soluciones del problema a resolver, que nos sirva para
construir objetos que contengan un estado. Igualmente diseamos los tipos ProblemaVZ y ProblemaSA que
contendrn las propiedades de los problemas a resolver y, a partir de ellas, crear el estado inicial. En el caso
particular de la tcnica Voraz deber implementar las interfaces EstadoVZ y ProblemaSA y para la tcnica de
Simulated Annealing habr que implementar la interfaz EstadoSA y ProblemaSA.
457
E getEstadoInicial():
Devuelve el estado inicial. Es decir, un estado vaco pero con la informacin del
problema inicial.
E next(A a): Calcula el estado al siguiente dada una alternativa.
A getAlternativa(): Escoge una alternativa entre las posibles.
S getSolucion(): Calcula la solucin asociada al estado.
boolean condicionDeParada(): Implementa la condicin de parada.
double getObjetivo(): Calcula el valor de la funcin objetivo para el estado actual.
E copia(): Obtiene una copia del estado.
Los algoritmos que estudiaremos sern implementaciones del tipo AlgoritmoAbstracto. Los algoritmos Voraz y
Simulated Annealing pueden ser implementados en una clase reutilizable. En concreto, el algoritmo ejecuta, de
la clase AlgoritmoVZ<E,S,A>, que se muestra a continuacin es el ncleo del algoritmo Voraz:
Como podemos ver, este algoritmo, escoge una alternativa y pasa al estado siguiente. Repite estos pasos hasta
que encuentre el criterio o condicin de parada.
El algoritmo ejecuta que se muestra se encuentra en la clase AlgoritmoSA<E,S,A>, y es el ncleo de la tcnica
Simulated Annealing.
public void ejecuta() {
mejorSolucionGlobalObtenida = problema.getEstadoInicial();
for (Integer n = 0; n < numeroMaximoDeIntentos
&& soluciones.size() < numeroDeSoluciones; n++) {
temperatura = temperaturaInicial;
estado = problema.getEstadoInicial();
for (numeroDeIter = 0; numeroDeIter < numeroDeIterPorIntento
&& !estado.condicionDeParada(); numeroDeIter++) {
for (int s = 0; s < numeroDeIteracionesALaMismaTemperatura; s++) {
A a = estado.getAlternativa();
nextEstado = estado.next(a);
double incr = nextEstado.getObjetivo() - estado.getObjetivo();
if (aceptaCambio(incr)) {
estado = nextEstado.copia();
actualizaMejorValor();
numeroDeCambiosAceptados++;
} else {
numeroDeCambiosNoAceptados++;
}
}
}
nexTemperatura();
}
solucion = estado.getSolucion();
if (solucion != null)
soluciones.add(solucion);
}
458
459
7
7
10
3
5
15
15
8
30
30
1
5
4
4
6
10
8
3
3
25
20
6, 7
9
5
3
4
5
3
1
2
25
10
Adems, debemos tener en cuenta que el beneficio de la emisin del anuncio no slo depende de la oferta de
la empresa anunciadora, sino tambin depender del lugar que ocupe el anuncio en la emisin. Donde posicin
es el segundo en que comienza el anuncio asumiendo que los segundos se numeran desde 1. Esta relacin entre
la posicin de emisin y la oferta del anuncio viene determinada por la siguiente expresin:
beneficio =
oferta
1000 + 50000
posicin
De esta forma, cuanto antes se emita el anuncio, mayor ser el beneficio que aporte. Adicionalmente, tenemos
otra restriccin en cuanto a los anuncios que pueden ser emitidos en el tiempo publicitario, ya que por motivos
de competencia entre compaas, ciertos anuncios no pueden ser emitidos si entre los anuncios emitidos existen
algunos en concreto. Dichas restricciones ser una propiedad compartida del problema.
En los siguientes apartados disearemos un algoritmo Voraz y otro de Simulated Annealing para resolver el
problema.
460
461
fm) {
new Integer(fm[0]);
new Integer(fm[1]);
new Integer(fm[2]);
462
static
static
static
static
List<Anuncio>
Integer
Set<ParInteger>
Set<Integer>
todosLosAnunciosDisponibles;
tiempoTotal;
restricciones;
todosLosAnuncios;
public ProblemaAnuncios() {
super();
}
public static void leeYOrdenaAnuncios(String file) {
List<String> ls = Lists.newArrayList(Iterables2.fromFile(file));
int index = ls.indexOf("#");
List<String> ls1 = ls.subList(0, index);
List<String> ls2 = ls.subList(index + 1, ls.size());
todosLosAnunciosDisponibles = Lists.newArrayList();
Anuncio a;
for (String s : ls1) {
String[] at = s.split(",");
Preconditions.checkArgument(at.length == 3);
a = Anuncio.create(at);
todosLosAnunciosDisponibles.add(a);
}
restricciones = Sets.newHashSet();
for (String s : ls2) {
String[] at = s.split(",");
Preconditions.checkArgument(at.length == 2);
Integer n1 = new Integer(at[0]);
Integer n2 = new Integer(at[1]);
restricciones.add(ParInteger.create(n1, n2));
restricciones.add(ParInteger.create(n2, n1));
}
Comparator<Anuncio> cmp = new Comparator<Anuncio>() {
@Override
public int compare(Anuncio arg0, Anuncio arg1) {
return arg0.compareTo(arg1);
}
};
Ordering<Anuncio> ord = Ordering.from(cmp);
ord = ord.reverse();
Collections.sort(ProblemaAnuncios.todosLosAnunciosDisponibles, ord);
todosLosAnuncios =
Sets2.toSet(0, ProblemaAnuncios.todosLosAnunciosDisponibles.size());
}
public static ProblemaAnuncios create() {
return new ProblemaAnuncios();
}
}
463
List<Integer>
Set<Integer>
Integer
Integer
Double
SortedSet<Integer>
anunciosDecididosParaEmitir;
anunciosDecididosParaEmitirSet;
tiempoConsumido;
tiempoRestante;
valor;
anunciosDisponibles;
464
465
466
PrecioUnitario =
PrecioBase
Duracin
1000
Posicion
+ 50000
Propiedades compartidas:
AnuncionesDisponibles [List<Anuncio>] : Anuncios disponilbes ordenados por el precio unitario.
TiempoTotal [Integer] : Tiempo mximo para la emisin de la secuencia de anuncios.
Restricciones [Set<ParInteger>] : Restricciones para un anuncio dado.
Propiedades bsicas del Estado:
AnunciosDecididosAEmitir [List<Integer>] Anuncios ya decididos a emitir.
Propiedades derivadas del Estado:
TiempoConsumido [Integer] : Suma de la duracin de los tiempos de los anuncios a emitir.
TiempoRestante [Double] : Diferencia entre el tiempo total y el consumido.
AnunciosDisponibles [List<Anuncio>] : Lista de anuncios restantes que no tienen incompatibilidades con
los ya decididos y cuya duracin es menor que el tiempo restante. Ordenada de mayor a menor segn
su precio unitario.
Valor [Double] : Valor de los anuncios ya decididos a emitir.
Dadas las propiedades bsicas es posible calcular las propiedades derivadas. El estado queda concretado cuando
fijamos las propiedades bsicas.
467
Este algoritmo Voraz, en un caso general, no nos asegurar que la solucin sea ptima. Los detalles del problema
de los anuncios de televisin resuelto con algoritmo Voraz se muestran en la siguiente Ficha:
Anuncios de Televisin
Voraz
Propiedades
Compartidas:
AnuncionesDisponibles
TiempoTotal
Restricciones
[List<Anuncio>]
[Double]
[Set<ParInteger>]
AnunciosDecididosAEmitir
TiempoConsumido
TiempoRestante
AnunciosDisponibles
Valor
[List<Integer>]
[Integer]
[Double]
[List<Anuncio>]
[Double]
Propiedades
del Estado
Tcnica:
Bsicas:
Derivadas:
Solucin:
ListaDeAnunciosAEmitir
Objetivo:
Alternativas:
Estado inicial:
AnunciosDecididosAEmitir = {}
Estado final:
AnunciosDecididosAEmitir
Next:
AnunciosDecididosAEmitir + {a}
468
public class
EstadoAnunciosVZ
implements EstadoVZ<EstadoAnunciosVZ, ListaDeAnunciosAEmitir, Integer>
{
public static EstadoAnunciosVZ create() {
return new EstadoAnunciosVZ(ListaDeAnunciosAEmitir.create());
}
private ListaDeAnunciosAEmitir lista;
private EstadoAnunciosVZ(ListaDeAnunciosAEmitir lista) {
super();
this.lista = ListaDeAnunciosAEmitir.create(lista);
}
// El algoritmo voraz parar cuando no existan ms anuncios
// en la lista anunciosParaEscoger.
public boolean condicionDeParada() {
return lista.getNumAnunciosDisponibles() == 0;
}
// Devuelve como alternativa el primer anuncio de anunciosParaEscoger.
public Integer getAlternativa() {
return lista.getAnunciosDisponibles().first();
}
// Devuelve un objeto ListaDeAnunciosAEmitir con los anuncios a emitir.
public ListaDeAnunciosAEmitir getSolucion() {
return lista;
}
// Aade la siguiente alternativa al estado.
// Tendr que eliminar el anuncio que se encuentra en la posicin a
// de anunciosParaEscoger y aadir dicho anuncio en anunciosAEmitir.
// Devuelve el siguiente estado.
public EstadoAnunciosVZ next(Integer a) {
// return new EstadoAnunciosVZ(lista.insertarUltimo(a));
lista = lista.insertarUltimo(a);
return this;
}
}
469
Para implementar la tcnica de Simulated Annealing reutilizaremos las ideas utilizadas para disear la tcnica
Voraz. El nico cambio necesario ser que las alternativas estarn formadas por la unin de tres tipos de
alternativas: una primera de la forma (inserta, p1, p2) que representa el insertar en la posicin p1 de
AnunciosDecididosAEmitir el anuncio que se encuentre en la posicin p2 de AnunciosDisponibles; una segunda
de la forma (intercambia, p1, p2) que representa intercambiar el anuncio de AnunciosDecididosAEmitir que se
encuentre en la posicin p1 por el anuncio que se encuentre en la posicin p2 de AnunciosDecididosAEmitir, y
una tercera (elimina, p1) que representa eliminar el anuncio en la posicin p1 de AnunciosDecididosAEmitir.
Anuncios de Televisin
Simulated Annealing
Propiedades
Compartidas:
AnuncionesDisponibles
TiempoTotal
Restricciones
[List<Anuncio>]
[Double]
[Set<ParInteger>]
AnunciosDecididosAEmitir
TiempoConsumido
TiempoRestante
AnunciosDisponibles
Valor
[List<Integer>]
[Integer]
[Double]
[List<Anuncio>]
[Double]
Propiedades
del Estado
Tcnica:
Bsicas:
Derivadas:
Solucin:
ListaDeAnunciosAEmitir
Objetivo:
Alternativas:
Estado inicial:
AnunciosDecididosAEmitir
Next:
470
public class
ProblemaAnunciosSA
extends
ProblemaAnuncios
implements ProblemaSA<EstadoAnunciosSA, ListaDeAnunciosAEmitir, AlternativaAnuncios>
{
public static ProblemaAnunciosSA create() {
return new ProblemaAnunciosSA();
}
private ProblemaAnunciosSA() {
super();
}
public EstadoAnunciosSA getEstadoInicial() {
ListaDeAnunciosAEmitir ls = getSolucionVoraz();
return EstadoAnunciosSA.create(ls);
}
public static ListaDeAnunciosAEmitir getSolucionVoraz() {
ListaDeAnunciosAEmitir e = ListaDeAnunciosAEmitir.create();
while (!e.getAnunciosDisponibles().isEmpty()) {
Integer a = e.getAnunciosDisponibles().first();
e = e.insertarUltimo(a);
}
return e;
}
}
471
472
473
474
7.4.2 N - Reinas.
[Vase el enunciado completo del problema en el captulo 5]
NumR
col
filOcu
dgPpOcu
dgScOcu
N- Reinas
Simulated Annealing
Tamao:
NumR - col
Propiedades
Compartidas:
NumR
[entero]
Bsicas:
filOcu
[Lista<entero>]
Derivadas:
dgPpOcu
dgScOcu
[Multiconjunto<entero>]
[Multiconjunto<entero>]
Propiedades
del Estado
Tcnica:
Solucin:
[Lista<Reina>]
Objetivo:
Alternativas:
Estado inicial:
Next:
475
476
List<Integer>
Set<Integer>
Set<Integer>
double
yOcupadas;
diagonalesPrincipalesOcupadas;
diagonalesSecundariasOcupadas;
objetivo;
private TableroDeReinas() {
this(new ArrayList<Integer>());
}
private TableroDeReinas(Integer... ls) {
this(Arrays.asList(ls));
}
private TableroDeReinas(List<Integer> ls) {
this.yOcupadas = new ArrayList<Integer>(ls);
this.calculaPropiedadesDerivadas();
}
private TableroDeReinas(TableroDeReinas t) {
this(t.yOcupadas);
}
private void calculaPropiedadesDerivadas() {
this.diagonalesPrincipalesOcupadas = Sets.newHashSet();
this.diagonalesSecundariasOcupadas = Sets.newHashSet();
for (Reina r : getReinas()) {
this.diagonalesPrincipalesOcupadas.add(r.getDiagonalPrincipal());
this.diagonalesSecundariasOcupadas.add(r.getDiagonalSecundaria());
}
}
public double getObjetivo() {
return objetivo;
}
public void setDiagonalesSecundariasOcupadas(Set<Integer> diagonalesSecundariasOcupadas) {
this.diagonalesSecundariasOcupadas = diagonalesSecundariasOcupadas;
}
public TableroDeReinas add(Integer y) {
List<Integer> ls = new ArrayList<Integer>(this.yOcupadas);
ls.add(y);
return create(ls);
}
477
478
public class
ProblemaReinasVZ
implements ProblemaVZ<EstadoReinasVZ, List<Reina>, Integer> {
public static int numeroDeReinas = 8;
public EstadoReinasVZ getEstadoInicial() {
List<Integer>li = Lists.newArrayList();
TableroDeReinas tab = TableroDeReinas.create(li);
return EstadoReinasVZ.create(tab);
}
public static ProblemaReinasVZ create() {
return new ProblemaReinasVZ();
}
}
public class
EstadoReinasVZ
implements EstadoVZ<EstadoReinasVZ, List<Reina>, Integer> {
public static EstadoReinasVZ create(TableroDeReinas tablero) {
return new EstadoReinasVZ(tablero);
}
public TableroDeReinas tablero;
private EstadoReinasVZ(TableroDeReinas tablero) {
super();
this.tablero = tablero;
}
public EstadoReinasVZ next(Integer a) {
tablero = tablero.add(a);
return EstadoReinasVZ.create(tablero);
}
public Integer getAlternativa() {
return Iterables2.elementRandom(tablero.getFilasPosibles());
}
public List<Reina> getSolucion() {
return (tablero.getNumDeReinas() == ProblemaReinasVZ.numeroDeReinas) ? tablero
.getReinas() : null;
}
public boolean condicionDeParada() {
return tablero.getNumDeReinas() == ProblemaReinasVZ.numeroDeReinas
|| Iterables.isEmpty(tablero.getFilasPosibles());
}
public int hashCode() { }
public boolean equals(Object obj) { }
}
479
public class
ProblemaReinasSA
implements ProblemaSA<EstadoReinasSA, List<Reina>, ParInteger> {
public static int numeroDeReinas = 8;
public EstadoReinasSA getEstadoInicial() {
List<Integer> li = Lists.newArrayList();
for (int i = 0; i < numeroDeReinas; i++) {
li.add(i);
}
TableroDeReinas tab = TableroDeReinas.create(li);
return EstadoReinasSA.create(tab);
}
public static ProblemaReinasSA create() {
return new ProblemaReinasSA();
}
}
public class
EstadoReinasSA
implements EstadoSA<EstadoReinasSA, List<Reina>, ParInteger> {
public static EstadoReinasSA create(TableroDeReinas tablero) {
return new EstadoReinasSA(tablero);
}
public TableroDeReinas tablero;
private EstadoReinasSA(TableroDeReinas tablero) {
super();
this.tablero = tablero;
}
// Intercambia las posiciones de las reinas en ParInteger
public EstadoReinasSA next(ParInteger a) {
// TableroDeReinas tab = TableroDeReinas.create(this.tablero);
// tab.intercambia(a.getP1(), a.getP2());
return create(tablero.intercambia(a.getP1(), a.getP2()));
}
// Devuelve un Par Integer de reinas distintas y que existan
public ParInteger getAlternativa() {
return Math2.getParAleatorioYDistinto(0, tablero.getNumDeReinas());
}
public List<Reina> getSolucion()
return tablero.getReinas();
return create(tablero.copia());
return tablero.getObjetivo();
480
Igualmente, dada un arista e, representaremos por el peso de la arista y por el otro vrtice de la arista e si uno
de ellos es a.
Trabajaremos con T = (V, E, D, C):
V : Vrtice actual.
E: Arista que indica su camino ms corto hacia el origen.
D : Distancia hasta el origen siguiendo la arista anterior E.
C : Indica si el camino representado ya es o no el mnimo posible.
481
Camino mnimo
( Iterable: Siguiente Vrtice ms Cercano )
Tcnica:
Voraz
Propiedades
Compartidas:
g
v0
Propiedades del m
Estado:
f
Invariante:
Estado inicial:
Estado final:
f={}
Next:
next() = actualizaTodos(A);
f = f - r;
r.f = true;
actualizaTodos(A) = for (E e: A) { actualizaUno(vo(e, a), e) }
actualizaUno(v, e)=
m' = m + t f' = f + t
m' = m
f' = f
m' = m
f' = f
m' = m
{
f' =
vm
v m m(v).c = true
v m m(v).d < da
f
v m m(v).d da
(m(v), t)
Tal como podemos observar, el algoritmo, mantiene un agregado de tuplas de la forma T = (V, E, D, C) indexadas
mediante la componente v en un Map. Para cada v en el dominio del Map tenemos, en la tupla asociada, la distancia
hacia el origen, la arista que seala el camino hacia el origen y si el camino mnimo es ya o no el mnimo posible.
Las tuplas se mantienen ordenadas en un montn de Fibonacci segn la distancia al origen.
Partimos de un estado inicial que contiene slo el vrtice origen v0. En un estado cualquiera seguimos explorando
las aristas salientes del vrtice actual. Para cada arista e y cada vrtice opuesto al actual v actualizamos el estado
segn la funcin actualizaUno(v, e). La funcin actualizaTodos(A) actualiza el estado para todas las aristas
salientes.
La funcin actualizaUno(v, e) actualiza el camino al origen desde el vrtice v. Si v no haba sido encontrado
previamente (v m) o el camino a travs de a es ms corto que el camino previamente encontrado (v m,
m(v).d da) entonces el camino hasta el origen empieza por la arista e y su distancia es da = m(a).d + w(e) . En
los restantes casos el camino previamente encontrado permanece (distancia a travs de a ms larga o del vrtice
que ya haba encontrado el camino mnimo).
482
class FibonacciHeapNode<T> {
T
getData();
double getKey();
String toString();
}
Los objetos en el montn de Fibonacci estn ordenados segn el valor de la propiedad Key. El constructor
FibonacciHeapNode(T data).
La operaciones y propiedades ofrecidas por el montn de Fibonacci son:
class FibonacciHeap<T> {
void decreaseKey(FibonacciHeapNode<T> x, double key);
void delete(FibonacciHeapNode<T> x);
void insert(FibonacciHeapNode<T> node, double key);
boolean isEmpty();
FibonacciHeapNode<T> min();
FibonacciHeapNode<T> removeMin();
static FibonacciHeap<T> unin(FibonacciHeap<T> h1, FibonacciHeap<T> h2);
}
Con el constructor FibonacciHeap(), los nombres de los mtodos describen suficientemente la funcionalidad
ofrecida.
La implementacin en jGrapht de este algoritmo est dado en la clase:
ClosestFirstIterator(Graph<V,E> g, V startVertex)
483
El nuevo problema se resuelve como una generalizacin del Algoritmo de Dijsktra. De nuevo partimos de un
vrtice inicial y vamos recorriendo los vrtices segn el iterable anterior hasta que encontrar todos los vrtices
del conjunto proporcionado. Posteriormente reconstruye el camino mnimo para cada vrtice.
La implementacin en jGrapht de este algoritmo viene dado en la clase:
DijkstraShortestPath(Graph<V,E> graph, V startVertex, V endVertex)
484
() + () + (, , )
Donde:
Junto a la anterior usaremos una estimacin H(a) del coste del camino desde vrtice actual a hasta el final. El
coste calculado de un camino, desde el vrtice inicial hasta el final que pasa por el vrtice actual a, ser:
F(a) = G(a) + H(a)
Asumimos que si H*(a) es el coste real del camino ms corto desde el vrtice actual al vrtice final entonces H(a)
cumple la condicin H(a) H*(a). Es decir, asumimos que el algoritmo A* es admisible.
Asumimos tambin que H(x) cumple H(x) H(x, y)+ H(y)para todo vrtice x e y conectados por una arista e. Por
d(x, y) representamos del camino de x a y (segn el G anterior). Es decir d(x, y) = w(x, ee, es) + w(e) + w(y). Diremos
que H(a) es montona o consistente.
Ahora aumentamos la informacin mantenida en las tupas para que sean de la forma (V, E, D1, D2, C) donde V es
un vrtice, E la arista que indica su camino ms corto hacia el origen, D1 su distancia hasta el origen siguiendo la
arista anterior, D2 peso del camino que pasa por el vrtice actual y C si el camino representado ya el el mnimo
posible. Es decir D1 = G(a), D2 = F(a).
Por simplicidad, si t es una tupla de la forma T = (V, E, D1, D2, C) representaremos respectivamente por t.v, t.e,
t.d1, t.d2 y t.c las sucesivas componentes. El agregado de tuplas anterior lo mantendremos organizado mediante
un mapa<V, T> y un montn de Fibonacci<T> ordenado segn la componente t.d2.
Definimos da como el peso del camino desde el origen hasta v pasando por a. Y definimos ca como el peso
estimado del camino del origen hasta el final pasando por v y previamente por a.
Los algoritmos A* resuelven de forma eficiente el problema del camino mnimo. Usan el Iterable del Siguiente
Vrtice ms Cercano Generalizado visto previamente. Parten de un vrtice inicial y van recorriendo los vrtices
segn el iterable anterior hasta que encuentra el vrtice final. Posteriormente reconstruye el camino mnimo.
485
Algoritmo A*
( Iterable: Siguiente Vrtice ms Cercano Generalizado )
Tcnica:
Voraz
Propiedades
Compartidas:
g
v0
Propiedades del m
Estado:
f
Invariante:
Estado inicial:
Estado final:
f={}
Next():
next() = actualizaTodos(A);
f = f - r;
r.f = true;
actualizaTodos(A) = for (E e: A) { actualizaUno(v0 (e, a), e) }
actualizaUno(v, e) =
m' = m + t f' = f + t
m' = m
f' = f
m' = m
f' = f
m' = m
{
f' =
f
v m m(v).D2 ca
(m(v), t)
486
vm
v m m(v).c = true
v m m(v).D2 < ca
Las propiedades compartidas son el grafo de partida y la lista de aristas de este grafo ordenadas por peso de
menor a mayor. Las propiedades del estado son un dato (U) de tipo UnionFind, un valor de un entero (J) y un
conjunto de aristas (SE).
Algoritmo de Kruskal
Tcnica:
Voraz
Propiedades
Compartidas:
g
l
Propiedades del u
Estado:
j
se
Solucin:
Conjunto<E>
Objetivo
[Graph<V, E>]
[Lista<E>]
Grafo.
Aristas del grafo ordenadas por peso.
[UnionFind<V>]
[entero]
[Conjunto<E>]
j [0, |l|-1]
Conjunto de Aristas.
Estado inicial:
Encontrar un subgrafo de g que sea un bosque y el peso de sus aristas sea mnimo. El grafo
puede ser no conexo.
(u, j, se) ( gv, 0, {} )
Estado final:
j = |ge|
Next:
487
des
En la notacin anterior hemos representado por eorg
j , ej el origen y el destino de la arista que est en la posicin
j de la lista de aristas. Por u(v) el representante del conjunto donde se encuentra v. Si es igual el representante
des
de dos vrtices entonces estn en el mismo conjunto. Por u(eorg
j + ej ) representamos la operacin sobre u de
des
fusin de los conjuntos donde estaban eorg
j , ej .
En cada paso se escoge la arista con menor peso. Si la arista conecta dos conjuntos distintos estos se funden en
uno y la arista se aade a SE. Si la arista une dos vrtices del mismo conjunto U y SE no se modifican. Cuando el
algoritmo termina SE contiene las aristas que definen el recubrimiento mnimo.
El nmero de componentes conexas y los vrtices adscritos a cada componente conexa pueden obtenerse a
partir de U.
488
Algoritmo de Prim
Tcnica:
Voraz
Propiedades
Compartidas:
Propiedades del c
Estado:
se
cola
r = min (q)
Solucin:
Conjunto<E>
Objetivo
[Graph<V, E>]
Grafo.
[Conjunto<V>]
[Conjunto<E>]
[PriorityQueue<E>]
Estado inicial:
Encontrar un subgrafo de g que sea un bosque y el peso de sus aristas sea mnimo. El grafo
debe ser conexo. Si no lo es entonces devuelve el recubrimiento mnimo de la componente
donde est el vrtice inicial.
({v0}, A ) con v0 un vrtice cualquiera y A el conjunto de aristas salientes del mismo.
Estado final:
cola = { }
Next:
La clase DepthFirstIterator<V,E> de jGrapht implementa un iterador que hace el recorrido en preorden. Sus
constructores son:
DepthFirstIterator(Graph<V,E> g)
DepthFirstIterator(Graph<V,E> g, V startVertex)
Cuando desde un vrtice, se alcanza otro por primera vez a travs de una arista, sta se incluye en el rbol de
recubrimiento.
490
Voraz
Propiedades
Compartidas:
g
v0
[Graph<V, E>]
[V]
Grafo.
Vrtice origen
Propiedades del m
[Mapa de V sobre (V, E)]
Estado:
pila
[Pila <V>]
a = { primero(p) | null si p est vaco }
A
[Conjunto<E>]
Aristas que salen de a
Estado inicial:
m = { v0, null }
pila = { v0 }
Estado final:
pila = { }
Next:
next() = recorreTodas(A)
pila = pila - a
recorreTodas(A) = for (E e : A) { recorreUna(VrticeOpuesto(e, a), e }
m' = m + (v, e) pila'' = pila' + v
vm
recorreUna(v, e) = {
''
m' = m
pila = pila'
vm
El recorrido en postorden, que se presenta abajo, es similar al anterior pero el vrtice actual slo se saca de la
pila cuando todos sus vrtices adyacentes han sido visitados.
Recorrido iterable de un grafo en profundidad: Postorden
Tcnica:
Voraz
Propiedades
Compartidas:
g
v0
Propiedades del m
[Mapa de V sobre (V, E)]
Estado:
pila
[Pila <V>]
a = { primero(p) | null si p est vaco }
A
[Conjunto<E>] Aristas que salen de a
B
[Conjunto<V>] Vrtices vecinos de a, segn las aristas en A,
que no han sido visitados (no pertenecen al dominio de m).
Estado inicial:
m = { v0, null }
pila = { v0 }
Estado final:
pila = { }
Next:
pila' = pila - a
B = {}
m' = m
B {}
recorreTodas(A) = for (E e : A) { recorreUna(VrticeOpuesto(e, a), e }
m' = m + (v, e) pila'' = pila' + v
vm
recorreUna(v, e) = {
''
m' = m
pila = pila'
vm
next() = {
491
En el recorrido en profundidad en postorden, a partir del vrtice actual se recorren las aristas salientes (o
simplemente incidentes si el grafo es no dirigido). En cada una de ellas se busca el vrtice v opuesto al actual
(proporcionado por la funcin VrticeOpuesto(e, a)). Si v ha sido encontrado previamente entonces desechamos la
arista. Si v no ha sido encontrado previamente entonces aadimos la arista al rbol de recubrimiento (aadiendo
el par (v, e) al map) y el vrtice v a la pila. Si el vrtice actual tiene todos sus vecinos ya visitados entonces se
saca de la pila.
El iterable en postorden va proporcionando solamente los sucesivos vrtices actuales en los cuales B = {}. Es decir
solamente aquellos que se sacan de la pila.
Vemos que en el recorrido en postorden anterior cada vrtice puede estar dos veces como vrtice actual: una
vez donde B {} que llamamos de preVisita y otro donde B {} que es la postVisita.
492
v es blanco si time < d(v). Un vrtice es blanco en todo tiempo anterior al momento de ser el vrtice actual
en el recorrido en preorden o en la preVisita en el recorrido en postorden. Al principio, todos los vrtices son
blancos.
v es gris si d(v) time < f(v). Es decir, desde el momento de ser el vrtice actual hasta el momento en el cual
l y todos sus descendientes han sido visitados. Es decir, hasta el tiempo de la postVisita en el recorrido en
postorden.
v es negro si f(v) time. Es decir, despus de que l y todos sus descendientes han sido visitados.
Con estas ideas podemos caracterizar el tipo de arista que parte del vrtice actual. Si v es el vrtice actual y
e(v, w) es una arista que se explora por primera vez entonces esta arista puede ser clasificada en funcin del
color de w:
-
493
El esquema de recorrido en postorden anterior puede ser fcilmente ampliado para calcular las funciones, siendo
la ltima el tipo de la arista e. Aqu, por complementariedad, presentaremos la versin recursiva del recorrido
en profundidad y mediante l, calcularemos las funciones anteriores.
El esquema recursivo del recorrido en profundidad, la clasificacin de las aristas y el preorden y postorden
obtenidos se ver a continuacin. El algoritmo usa varias variables globales de tipo funcin (color, d, f) que
mantienen el color de cada vrtice, el tipo de descubrimiento del vrtice y el tiempo de finalizacin. Otra funcin
global (tipo) donde cada arista devuelve su tipo. Por ltimo, el algoritmo devuelve el conjunto de aristas que
definen el rbol de recubrimiento en la variable global edgeSet. Tambin calcula las funciones (va, lc) que
devuelven el vrtice anterior y el camino hasta la raz de la componente correspondiente. Esto nos puede
permitir preguntar a cada vrtice a qu componente pertenece. Las funciones previsit y postvisit generan las
secuencias en preorden, postorden y postorden inverso y los devuelven en las listas globales prev, posv y invpos.
Previsit aade el vrtice v al final de la lista prev. La funcin postvisit aade el vrtice v al final de posv y al
principio de invpos. El esquema es:
dfsAll(G){
hacer que todos los vrtices sean blancos;
hacer que edgeSet est vaco;
inicializar va(v) a null y lc(v) a cero;
time = 0;
for(cada v V)
if (v es blanco) dfs(v);
}
dfs(v,G){
color[v] = gris;
d[v] = ++time;
previsit(v);
for (cada w adjacente a v){
if(w es blanco){
tipo(v, w) = treeEdge;
aadir la arista (v, w) a edgeSet;
va(w) = v;
lc(w) = lc(v) + |(v,w)|;
} else if(w es gris) {
tipo(v, w) = backEdge;
} else if(d[v] < d[w]) {
tipo(v, w) = forwardEdge; // w es negro
} else {
tipo(v, w) = crossEdge;
// w es negro
}
}
f[v] = ++time;
postvisit(v);
color[v] = black;
}
El algortimo, tal como est diseado, obtiene un bosque de recubrimiento del grafo completo, la clasificacin de
todas las aristas del grafo y las funciones d(v) y f(v) para cada vrtice. Si la funcin color la implementamos como
un Map entonces no es necesario el color blanco que vendra definido por los vrtices que no estn en su dominio.
Casos particulares del algoritmo anterior son visitar los vrtices en preorden o en postorden sin hacer ningn
clculo adicional. A esto casos particulares los llamaremos: dfsPreorder(v, G) y dfsPostorder(v, G) cuyas versiones
iterativas vimos ms arriba.
En el ejemplo siguiente se muestran los valores de d(v) y f(v) obtenidos por el algoritmo para cada vrtice, el tipo
de arista y el bosque de recubrimiento para un grafo dirigido concreto.
494
A partir del grfico es sencillo obtener los valores de va(v) y lc(v). Aplicando el algoritmo podemos obtener,
tambin el preorden, postorden y postorden inverso. Como hemos dicho estos rdenes dependen del recorrido
en particular y en concreto del bosque de recubrimiento construido. En este caso concreto el preorden viene
dado por: 1, 2, 3, 4, 5, 6. El postorden por 4, 3, 2, 1, 6, 5. Y el postorden inverso por 5, 6, 1, 2, 3, 5, 4.
Donde GT es el grafo traspuesto a G. Es decir, aquel otro con las aristas invertidas.
En el ltimo grafo observamos que el algoritmo empieza por D (el primer vrtice en el postorden inverso) y todos
los vrtices que se alcanzan pertenecen a la misma componente fuertemente conexa (SCC) que D.
Las componentes fuertemente conexas son SCC(D) = {D, F, G, H}, SCC(C) = {C}, SCC(A) = {A}, SCC(B) = {B, E}.
495
Voraz
Propiedades
Compartidas:
g
v0
Propiedades del m
[Mapa de V sobre (V, E)]
Estado:
cola
[Cola <V>]
a = { primero(p) | null si p est vaco }
A
[Conjunto<E>] Aristas que salen de a
Estado inicial:
m = { v0, null }
cola = { v0 }
Estado final:
cola = { }
Next:
next() = recorreTodas(A)
cola = cola a
recorreTodas(A) = for (E e : A) { recorreUna(VrticeOpuesto(e, a), e }
recorreUna(v, e) = {
496
vm
vm
497
La clase TopologicalOrderIterator<V, E> implementa un iterador que hace el recorrido en orden topolgico.
Sus constructores son:
TopologicalOrderIterator(DirectedGraph<V,E> dg)
TopologicalOrderIterator(DirectedGraph<V,E> dg, Queue<V> queue) // La cola, y su
posible implementacin, permiten elegir uno de los posibles recorridos.
Voraz
Propiedades
Compartidas:
g
v0
Propiedades del m
[Mapa de V sobre (V, E)]
Estado:
pila
[Pila <V>]
a = { primero(p) | elemento cualquiera de se si p est vaco | null si pila y se estn vacos }
A
[Conjunto<E>] Aristas que salen de a.
se
[Lista<V>]
Vrtice maximales.
Estado inicial:
m = { v0, null } , pila = { v0 }
Estado final:
pila = { }
Next:
next() = {
se' = se - a
recorreTodas(A)
pila ' = pila - a recorreTodas(A)
pila = {} a se
pila {}
El orden topolgico sigue un recorrido similar al recorrido en profundidad. Parte de los vrtices maximales
(conjunto de vrtices sin aristas de entrada) y sigue un recorrido en profundidad. Cuando la pila se agota toma
otro de los vrtices maximales.
498
499
Ejemplo de ejecucin:
Dados los datos de accesibilidad de la imagen, teniendo como pistasInciales la 1 y la 2 y un tiempoTotal de 60
minutos, la solucin obtenida utilizando un esquema Voraz, debera ser.
-
Lo que representa que se han esquiado 12 km por las pistas indicadas y que, cuando se termin de esquiar, los
telesillas haca ya 5 minutos que no dejaban entrar a ms gente.
Se pide:
1. Rellenar las partes de la ficha que no estn rellenas.
2. Implementar los mtodos (Deber corresponderse con lo que escriba en la ficha):
a) private void voraz().
b) public boolean isAlternativa(EstadoPista e). De ProblemaPistasImpl.
c) public Pista getAlternativa(EstadoPista e). De ProblemaPistasImpl.
d) public boolean next(Pista p). De EstadoPistasImpl.
e) public boolean condicionDeParada(SolucionPista s). De ProblemaPistasImpl.
Tendr que comprobar que los 3 atributos de la solucin (ver UML) son vlidos.
500
Apartado 1)
Esquiador
Tcnica:
Voraz
Tamao:
t = tiempoDisponible
Propiedades
Compartidas:
pistasIniciales
accesibilidad
tiempoTotal
[SortedSet<Pistas>]
Ordenada descendentemente por tiempo/longitud.
[Map<Pista,SortedSet<Pista>>]
Ordenada descendentemente por tiempo/longitud.
[entero]
[Lista<Pista>]
[entero]
[entero]
// TODO: Slo tendrn en cuenta las pistas accesibles desde la ltima aadida.
Aestado = { ai accesibilidad.get(estado.pistasRecorridas.last) }
Eleccin:
Estado Inicial:
Estado Final:
pistasRecorridas += pistasIniciales.first
distanciaRecorrida = pistasIniciales.first.longitud
tiempoDisponible = tiempoTotal - pistasIniciales.first.tiempo
// TODO: Se tendrn que indicar cundo el esquiador no puede seguir ms.
tiempoDisponible 0
Next:
// TODO: Para (pi) Todos los valores del estado tendrn que actualizarse con la nueva pista.
pistasRecorridas += pi
distanciaRecorrida += pi.longitud
tiempoDisponible -= pi.tiempo
501
Apartado 2)
a>
private void voraz() {
while (problema.isAlternativa(estado)) {
Pista alternativa = problema.getAlternativa(estado);
estado.add(alternativa);
}
solucion = estado.getSolucion();
}
b>
public boolean isAlternativa(EstadoPistas e) {
// Quedar tiempo, para coger otro telesillas?
return e.getTiempoDisponible() > 0;
}
c>
public Pista getAlternativa(EstadoPistas e) {
SortedSet<Pista> pistasAccesibles = accesibilidad.get(e.getLast());
return pistasAccesibles.first();
}
d>
public boolean next(Pista p) {
pistasRecorridas.add(p);
distanciaRecorrida += p.getLongitud();
tiempoDisponible -= p.getTiempo();
return true;
}
e>
public boolean condicionDeParada(SolucionPistas s) {
// No queda tiempo para coger otro telesillas
boolean ret = s.getTiempoRestante() <= 0;
// Los kilometros esquiados son positivos
ret &= s.getLongitudEsquiada() >= 0;
// Cada pista da acceso a la siguiente
if (ret) {
List<Pista> pistas = s.getPistasRecorridas();
Pista anterior = pistas.get(0);
for (int i = 1; i < pistas.size() && ret; i++) {
Pista actual = pistas.get(i);
ret &= accesibilidad.get(anterior).contains(actual);
anterior = actual;
}
}
return ret;
}
502
Se pide:
a) Rellenar la siguiente ficha para que resuelva el problema descrito utilizando la tcnica Voraz. Para ello,
dispone de los siguientes tipos (notacin compacta):
Accion
bk,f
j
j
entero Identifica a la accin.
k
entero Cantidad a invertir en la accin j.
.
[
f(entero x) entero Beneficio al invertir la cantidad x en la accin.
SolucionInversiones SLacciones ,
ben
y de la funcin max((, )) que devuelve el j del conjunto T que hace mximo g(i, j)
Inversiones Acciones
Tcnica:
Voraz
Propiedades
Compartidas:
N
M
Propiedades
del Estado:
L
[Lista<Accion>]
B
[entero]
C
[entero]
I
[entero]
SolucionInversiones
Solucin:
[entero]
[entero]
Nmero de acciones.
Cantidad de dinero a invertir.
Lista de inversiones.
Beneficio total.
Cantidad restante.
ID de la siguiente accin.
Objetivo:
Alternativas:
Eleccin:
Estado inicial:
Estado final:
Next:
Complejidad:
503
b) Dado el siguiente esquema UML implementar la clase ProbemaVZImpl, slo los mtodos de la interfaz
ProblemaVZ. Suponga implementados el resto de clases e interfaces.
504
Voraz
Propiedades
Compartidas:
N
M
[entero]
[entero]
Nmero de acciones.
Cantidad de dinero a invertir.
Propiedades del L
[Lista<Accion>]
Estado:
B
[entero]
C
[entero]
I
[entero]
Solucin:
SolucionInversiones
Lista de inversiones.
Beneficio total.
Cantidad restante.
ID de la siguiente accin.
Objetivo:
Alternativas:
A(L, B, C, I) = {0, 1, 2, , C}
Eleccin:
= ((, , , )) = max (
()
)
Estado inicial:
(L, B, C, I) ({}, 0 , M , 0)
Estado final:
iNoC0
Next:
Complejidad:
(N x M)
Apartado b)
public class ProblemaInversionVZImpl implements ProblemaVZ<SolucionInversion, ObjetoInversion, EstadoInversion>,
AlgoritmoConUnaSolucion<SolucionInversion>, ProblemaInversion {
ProblemaInversion problema;
public EstadoInversion getEstadoInicial() {
return new EstadoInversionImpl(problema.getCantidadParaInvertir());
}
public boolean isAlternativa(EstadoInversion e) {
return e.getSiguienteAccion() < problema.getAcciones().size() && e.getCantidadRestante() > 0;
}
public Accion getAlternativa(EstadoInversion e) {
Accion a = problema.getAcciones().get(e.getSiguienteAccion());
int crMax = 0;
int rMax = 0;
for (int cr = 1; cr <= e.getCantidadRestante(); cr++) {
r = a.getBenficio(cr) / cr;
if (rMax < r) {
crMax = cr;
rMax = r;
}
}
a.setCantidad(crMax);
return a;
}
public boolean esSolucion(SolucionMochila s) {
return problema.esSolucion(s);
}
...
}
505
506
507
Se pide:
1) Completar la ficha adjunta del problema a resolver. Los apartados sealados mediante //TODO
2) Implementar los siguientes mtodos teniendo en cuenta la ficha adjunta que ha de completar:
De la clase AlgoritmoVoraz:
a)
De la clase ProblemaApuestaVZ:
b)
c)
De la clase EstadoApuestaVZ:
d)
Notas:
-
Utilice el diagrama UML que se muestra a continuacin para realizar los distintos apartados.
Suponga implementadas todas las clases proporcionadas en el diagrama UML, salvo los mtodos de la clase
AlgoritmoVoraz, ProblemaApuestaVZ y EstadoApuestaVZ que han de resolverse.
508
Voraz
Propiedades
Compartidas:
vasosDisponibles
[Lista<Vaso>]
Eleccin:
// TODO
h(estado) = Escoger alternativa 0 si el vaso del extremo izquierdo tiene mayor cantidad de
agua que el vaso del extremo derecho, en otro caso escoger la alternativa 1.
Estado inicial:
Estado final:
( vasoExtremoDerecha - vasoExtremoIzquierda 0 )
Next:
// TODO
si a = 0 entonces
vasoingenioso = vasosDisponibles(vasoExtremoIzquierda + 1)
vasosUsadosIngenioso = vasosUsadosIngenioso + vasoingenioso
cantidadIngenioso = cantidadIngenioso + vasoingenioso.cantidad
vasoperezoso = vasosDisponibles(vasoExtremoIzquierda)
vasosUsadosPerezoso = vasosUsadosPerezoso + vasoperezoso
cantidadPerezoso = cantidadPerezoso + vasoperezoso.cantidad
vasoExtremoIzquierda = vasoExtremoIzquierda + 2
si a = 1 entonces
vasoingenioso = vasosDisponibles(vasoExtremoDerecha)
vasosUsadosIngenioso = vasosUsadosIngenioso + vasoingenioso
cantidadIngenioso = cantidadIngenioso + vasoingenioso.cantidad
vasoperezoso = vasosDisponibles(vasoExtremoIzquierda)
vasosUsadosPerezoso = vasosUsadosPerezoso + vasoperezoso
cantidadPerezoso = cantidadPerezoso + vasoperezoso.cantidad
vasoExtremoIzquierda = vasoExtremoIzquierda + 1
vasoExtremoDerecha = vasoExtremoDerecha - 1
509
Apartado 2>
a>
b>
c>
d>
510
Funcionalidad: Funcin que desempear en las olimpiadas (por ejemplo, estadio, piscina,
villa, etc.).
Importancia: Relevancia que tendr el edificio dentro de las olimpiadas. Ser una
puntuacin entre 0 y 10 dada por el JOC, siendo 0 la mnima puntuacin y 10 la mxima.
El JOC ha decidido contratar sus servicios para la realizacin de un Algoritmo Voraz que les permita decidir qu
edificios construir para que la suma total del valor, en relacin a la calidad por importancia de los edificios sea
mxima. Adems, hay que tener en cuenta que slo se puede elegir un edificio que tenga la misma funcionalidad
(por ejemplo, no se pueden construir 2 piscinas olmpicas para la natacin) y que est dentro del presupuesto
disponible.
Por ejemplo, con un presupuesto de 20 millones de Yenes y la siguiente lista de proyectos ordenados por la
funcin objetivo: [(Importancia * Valor de Calidad)/Precio]
Nombre
Funcionalidad
Importancia
Sakura
Natsuki
Akira
Hikari
Mizuki
Ryu
Sora
Ai
ESTADIO PPAL
ESTADIO PPAL
ESTADIO PPAL
PISCINA
VILLA
PISCINA
VILLA
VILLA
10
10
10
7
8
7
8
8
Precio
(mil. de yenes)
7
8
9
6
8
7
10
9
Valor
Calidad
8
9
10
9
10
8
10
8
Valoracin
Final
1142
1125
1111
1050
1000
800
800
711
La solucin obtenida sera la siguiente tabla, con un presupuesto restante de 6 millones de Yenes y un valor de
Calidad-Importancia Total de 143.
Nombre
Funcionalidad
Importancia
Sakura
Hikari
ESTADIO PPAL
PISCINA
10
7
Precio
(mil. de yenes)
7
6
Valor
Calidad
8
9
Valoracin
Final
1142
1050
511
Se pide:
1) Completar la ficha adjunta siguiendo la informacin del problema detallado anteriormente.
2) Implemente los siguientes mtodos pertenecientes a la clase EstadoJOCAP:
a. private EstadoJOCAP()
b. public SolucionJOC getSolucion()
c. public boolean condicionDeParada()
d. public Proyecto getAlternativa()
e. public void next(Proyecto p)
3) Implemente el siguiente mtodo perteneciente a la clase ProblemaJOCAP:
f. private class DistintaFuncionalidadYDentroPresupuesto implements Predicate<Proyecto>
NOTA: Recuerde que si necesita una clase con funcionalidad adicional (por ejemplo, Predicate, Comparator,
Function, etc.) debe implementarla.
512
Voraz
Propiedades
Compartidas:
proyectosDisponibles
presupuestoTotal
[Lista<Proyecto>]
[entero]
[Lista<Proyecto>]
[Lista<Proyecto>]
[entero]
[Real]
Objetivo:
Alternativas:
Estado inicial:
proyectosElegidos
= {}
proyectosPorElegir
= proyectosDisponibles
presupuestoDisponible
= presupuestoTotal
valorCalidadImportanciaTotal = 0.0
Next:
proyectosElegidos
proyectosElegidos + pry
proyectosPorElegir
presupuestoDisponible
presupuestoDisponible - pry.precio
513
Apartado 2>
a>
private EstadoJOCAP() {
super();
this.proyectosElegidos = Lists.newLinkedList();
this.presupuestoDisponible = ProblemaJOC.getPresupuestoTotal();
this.proyectosPorElegir = Lists.newLinkedList(ProblemaJOC.getProyectosDisponibles());
this.valorCalidadImportanciaTotal = 0.0;
}
b>
@Override
public SolucionJOC getSolucion() {
return SolucionJOC.create(proyectosElegidos);
}
c>
@Override
public boolean condicionDeParada() {
return (proyectosPorElegir.isEmpty() || presupuestoDisponible <= 0);
}
d>
@Override
public Proyecto getAlternativa() {
Proyecto proyecto = null;
if (!proyectosPorElegir.isEmpty()) {
proyecto = proyectosPorElegir.get(0);
}
return proyecto;
}
e>
@Override
public void next(Proyecto p) {
this.proyectosElegidos.add(p);
this.presupuestoDisponible = this.presupuestoDisponible - p.getPrecio();
this.valorCalidadImportanciaTotal = this.valorCalidadImportanciaTotal +
(p.getValorCalidad() * p.getImportancia());
// Actualizar y filtrar la lista de proyectosPorElegir
this.proyectosPorElegir.remove(p);
this.proyectosPorElegir = Lists.newLinkedList
( Iterables.filter(proyectosPorElegir, new DistintaFuncionalidadYDentroPresupuesto(p)) );
}
Apartado 3>
f>
514
La idea es hacer un recorrido desde una estacin origen (primera estacin) hasta una estacin destino distinta
(ltima estacin) cambiando de bicicleta el menor nmero de veces posible. Para ello, se desea obtener
mediante un algoritmo Voraz la secuencia de estaciones que ms se acerque a la ptima en las cuales debemos
cambiar de bicicleta para llegar a la estacin destino sin agotar el tiempo permitido durante todo el recorrido.
Las propiedades compartidas son la lista de estaciones de bicicletas que estarn ubicadas a lo largo del recorrido
y el tiempo mximo permitido que se dispone para poder usar una misma bicicleta.
Cada estacin se representar por ei,t,b donde el identificador i [0, estaciones.size), t es el tiempo que se tarda
en llegar a la siguiente estacin ms cercana y b el nmero de bicicletas disponibles en dicha estacin.
Un determinado Estado del problema est representado por cuatro propiedades: estacionesUsadas,
tiempoRestante, tiempoRecargado y estacionActual, que contendrn respectivamente, la lista de estaciones en
las cuales realizaremos un cambio de bicicleta, el tiempo restante para seguir utilizando una misma bicicleta sin
realizar cambio, la suma total del tiempo recargado cada vez que se ha cambiado de bicicleta en una estacin
(calculado a partir de la diferencia del tiempo mximo permitido y el tiempo restante) y la posicin de la estacin
actual dentro de la lista de estaciones.
Las Alternativas son decidir si se cambia de bicicleta siempre y cuando sea necesario y queden bicicletas
disponibles en una determinada estacin, o no cambiar de bicicleta en dicha estacin. Si se cambia de bicicleta,
se renovar el tiempo restante con el tiempo mximo permitido, se calcular el tiempo recargado y se actualizar
la lista de estaciones donde realizamos un cambio. Para el clculo del tiempo restante hay que tener en cuenta
el tiempo que se tarda en llegar a la siguiente estacin.
Si llegamos a una estacin con tiempo negativo, no se podr cambiar de bicicleta y por tanto, no se puede
continuar el recorrido. El objetivo es encontrar una solucin en la que se consiga llegar a la ltima estacin de
forma que el nmero de cambios de bicicleta sea el menor posible. En la ltima estacin (estacin destino),
obligatoriamente se dejar la bicicleta y no se realizar un cambio en la misma.
Se devolver la Solucin con la lista de estaciones en las que hemos cambiado de bicicleta as como el tiempo
total recargado durante el recorrido.
En el siguiente ejemplo, se muestra la lista de estaciones y el tiempo necesario para llegar a la siguiente. Cada
bicicleta representa una estacin. Si disponemos de 30 min como tiempo mximo permitido, el algoritmo voraz
determinar que cambiaremos de bicicleta en la estacin 0 y 1, para llegar a la estacin 3.
515
Se pide:
1. Completar la ficha adjunta del problema a resolver. (El apartado Next).
2. Implementar los siguientes mtodos teniendo en cuenta la ficha adjunta que ha de completar:
De la clase AlgoritmoVoraz:
a. El mtodo ejecuta del algoritmo Voraz visto en clase, es decir, el mtodo: public void ejecuta()
De la clase ProblemaEstacionBicicletasVZ:
b. public boolean hayAlternativa(EstadoEstacionBicicletasVZ e)
c. public boolean getAlternativa(EstadoEstacionBicicletasVZ e
De la clase EstadoEstacionBicicletasVZ:
d. public void next(Boolean a)
516
Apartado 1>
Servicio de bicicletas
Tcnica:
Voraz
Propiedades
Compartidas:
estaciones
tiempoMaximo
[Lista<entero>]
[real]
Eleccin:
heurstica(Estado) siempre que sea necesario y sea posible escoger la alternativa Y, en otro
caso escoger la alternativa N.
Estado inicial:
estacionesUsadas
tiempoRestante
tiempoRecargado
estacionActual
Estado final:
Next:
// TODO
{}
0.0
0.0
0
si a = Y entonces
estacionesUsadas estacionesUsadas + estaciones(estacionActual)
tiempoRecargado tiempoRecargado + (tiempoMaximo - tiempoRestante)
tiempoRestante tiempoMaximo - estaciones(estacionActual).tiempoSiguienteEstacion
estacionActual estacionActual + 1
si a = N entonces
tiempoRestante tiempoRestante - estaciones(estacionActual).tiempoSiguienteEstacion
estacionActual estacionActual + 1
517
Apartado 2>
a>
public void ejecuta() {
estado = problema.getEstadoInicial();
while (problema.hayAlternativa(estado)) {
A a = problema.getAlternativa(estado);
estado.next(a);
}
solucion = problema.getSolucion(estado);
if(!problema.isSolucion(solucion){
solucion = null;
}
}
b>
public boolean hayAlternativa(EstadoEstacionBicicletasVZ e) {
return e.getTiempoRestante() >= 0.0 &&
e.getEstacionActual() < ProblemasEstacionBicicletas.getEstaciones().size()-1;
}
c>
public Boolean getAlternativa(EstadoEstacionBicicletasVZ e) {
Boolean ret = false;
if (e.getEstacionActual() < ProblemasEstacionBicicletas.getEstaciones().size() 1
&& e.getTiempoRestante() <
ProblemasEstacionBicicletas.getEstaciones().get(e.getEstacionActual()).getTiempoSiguienteEstacion()
&&
ProblemasEstacionBicicletas.getEstaciones().get(e.getEstacionActual()).getNumBicicletasDisponibles()
> 0) {
ret = true;
}
return ret;
}
d>
public void next(Boolean a) {
EstacionBicicletas estacion =
ProblemasEstacionBicicletas.getEstaciones().get(estacionActual);
if (a) {
Double tiempoRenovado =
ProblemasEstacionBicicletas.getTiempoMaximo()
tiempoRestante;
this.estacionesUsadas.add(estacion);
this.tiempoRestante =
ProblemasEstacionBicicletas.getTiempoMaximo()
estacion.getTiempoSiguienteEstacion();
this.tiempoRecargado = this.tiempoRecargado + tiempoRenovado;
} else {
this.tiempoRestante = this.tiempoRestante
estacion.getTiempoSiguienteEstacion();
}
this.estacionActual++;
}
518
7.5.6 Inmobiliaria.
Llegan las vacaciones y una inmobiliaria de una
determinada ciudad costera desea encontrar la mejor
asignacin entre la lista de posibles clientes y la lista de
apartamentos disponibles en alquiler en dicha ciudad.
Cada cliente ha informado a la inmobiliaria sobre las
caractersticas del apartamento que deseara alquilar para
sus vacaciones (terraza, ascensor, piscina y vistas al mar).
Por ejemplo, un cliente puede desear alquilar un apartamento con terraza y vistas al mar, pero no le importa si
tiene ascensor, o piscina.
La inmobiliaria desea realizar una asignacin de un apartamento a cada uno de sus clientes de forma que se
maximice la suma total de las caractersticas satisfechas a todos los clientes.
Por ejemplo, dado el siguiente conjunto de clientes (NO significa que al cliente no le importa si no tiene la
caracterstica):
Clientes
C1
C2
Terraza
S
No
Ascensor
No
S
Piscina
No
S
Mar
S
S
Y los siguientes apartamentos disponibles (NO significa que el apartamento no dispone de dicha caracterstica):
Apartamentos
A1
A2
A3
Terraza
S
S
No
Ascensor
No
No
S
Piscina
S
No
No
Mar
S
No
S
En este ejemplo, la solucin ptima sera asignar el apartamento A1 al cliente C1 (2 caractersticas satisfechas) y
el apartamento A3 al cliente C2 (tambin 2 caractersticas satisfechas), lo que hace un total de 4 caractersticas
satisfechas que es el mximo valor que se puede obtener para estos conjuntos de clientes y apartamentos.
El objetivo del problema ser encontrar la solucin que contenga un apartamento asignado a cada uno de los
clientes y que la suma total de las caractersticas satisfechas de los clientes teniendo en cuenta los apartamentos
asignados sea mxima. Para obtener tal solucin, se pide resolver el problema mediante la tcnica Simulated
Annealling.
Para modelar las alternativas se dispone de la clase AlternativaInmobiliaria la cual contiene dos tipos de
operaciones que pueden aplicarse con la misma probabilidad:
Intercambia: Intercambia el apartamento del cliente C1 por el apartamento del cliente C2 y viceversa.
519
Se pide:
1) Complete la ficha adjunta, siguiendo la informacin del problema detallado anteriormente para la
tcnica Simulated Annealing.
2) Implemente los siguientes mtodos pertenecientes a la clase EstadoInmobiliariaAP para la tcnica
Simulated Annealing:
-
satisfechas
para
cada
cliente
el
caractersticasSatisfechas a optimizar.
Notas:
520
Puede hacer uso del mtodo public static <T> T elementRandom(Iterable<T> it) de la clase
Iterables2.
Suponga implementadas todas las clases proporcionadas en el diagrama UML, salvo los mtodos de la
clase EstadoInmobiliariaAP que se piden para su resolucin.
En el estado inicial, partimos de una asignacin de apartamentos para cada uno de los clientes.
Por tanto, en cada estado, todos los clientes han de tener un apartamento asignado.
Cada cliente y cada apartamento slo almacenan las caractersticas indicadas con S. Aquellas que NO
son importantes para el cliente o bien que NO estn disponibles en el apartamento, no sern
almacenadas.
521
Solucin:
Apartado 1)
Inmobiliaria
Tcnica:
Simulated Annealing
Propiedades
Compartidas:
clientes
apartamentos
totalSatisfacciones
Propiedades del asignaciones
Estado:
apartamentosPorAsignar
caractersticasSatisfechas
Solucin:
SolucionInmobiliaria
Alternativas
[List<Cliente>]
[List<Apartamento>]
[Integer]
[Map<Cliente, Apartamento>]
[List<Apartamento>]
[Integer]
: c1 asignaciones.keySet(), a1 apartamentosPorAsignar }
Estado final:
Next:
Apartamento a2 = asignaciones(c1)
Apartamento a1 = asignaciones(c1)
Apartamento a2 = asignaciones(c2)
apartamentosPorAsignar - a1
apartamentosPorAsignar + a2
calculaSatisfacciones();
522
Apartado 2.b)
public void next(AlternativaInmobiliaria a) {
Apartamento a1 = null;
Apartamento a2 = null;
switch (a.getTipo()) {
case Modifica:
Cliente c = a.getC1();
a1 = a.getA1();
a2 = asignaciones.remove(c);
apartamentosPorAsignar.add(a2);
apartamentosPorAsignar.remove(a1);
asignaciones.put(c, a1);
calculaSatisfacciones();
break;
case Intercambia:
a1 = asignaciones.get(a.getC1());
a2 = asignaciones.get(a.getC2());
asignaciones.put(a.getC1(), a2);
asignaciones.put(a.getC2(), a1);
calculaSatisfacciones();
break;
default:
break;
}
}
Apartado 2.c)
private void calculaSatisfacciones() {
Integer aux = 0;
for (Entry<Cliente, Apartamento> e : asignaciones.entrySet()) {
List<Caracteristica> cclientes = Lists.newArrayList(e.getKey().getCaracteristicas());
List<Caracteristica> capartamentos = Lists.newArrayList(e.getValue().getCaracteristicas());
cclientes.retainAll(capartamentos);
aux += cclientes.size();
}
caracteristicasSatisfechas = aux;
}
Apartado 2.d)
public double getIncremento(EstadoSA<SolucionInmobiliaria, AlternativaInmobiliaria> e) {
EstadoInmobiliariaAP copia = (EstadoInmobiliariaAP) e;
return copia.caracteristicasSatisfechas - this.caracteristicasSatisfechas;
}
523
Nombre:
Precio:
Equipo actual:
Valor de Calidad:
Posicin:
La junta directiva ha decidido contratar sus servicios para la realizacin de un Algoritmo Voraz que les permita
decidir qu jugadores tienen que fichar para que la suma total del valor en calidad de los jugadores sea mxima.
Adems, para que los fichajes sean lo ms heterogneos posible la junta no permite fichar a dos jugadores que
ocupen la misma posicin en el terreno de juego.
Por ejemplo, con un presupuesto inicial de 500 y la siguiente lista de jugadores ordenadas por la funcin
objetivo: Valor de Calidad / Precio (columna VC/P)
Equipo Actual
Nombre
Posicin
Precio
Valor de Calidad
VC/P
Jan
Mara
POR
100
9.3
0.093
Almera
Marta
POR
150
6.5
0.043
Granada
Andrs
DEF
200
8.5
0.042
Ana
DEL
300
6.4
0.033
Crdoba
Pablo
DEF
300
7.1
0.023
Mlaga
Pepe
MED
600
6.2
0.010
Recreativo
Laura
MED
800
5.5
0.006
Cdiz
La solucin obtenida sera la siguiente tabla, con un presupuesto restante de 200 y un Valor de Calidad Total
de 17.8.
Equipo Actual
Nombre
Posicin
Precio
Valor de Calidad
VC/P
Jan
Mara
POR
100
9.3
0.093
Granada
Andrs
DEF
200
8.5
0.042
Se pide:
a) Completar la ficha adjunta siguiendo la informacin del problema detallado anteriormente.
b) Implemente los siguientes mtodos pertenecientes a la clase EstadoFichajesAP:
-
private EstadoFichajesAP()
public static EstadoFichajesAP create()
public Fichaje getAlternativa()
public boolean condicionDeParada()
public void next(Fichaje fichaje)
524
525
Solucin:
Apartado a)
Equipo de Fltbol
Tcnica:
Voraz
Propiedades
Compartidas:
Catalogo
presupuestoInicial
[List<Fichaje>]
[List<Fichaje>] ordenada por Calidad/Precio.
[Integer]
[Integer]
Objetivo:
Alternativas:
Estado inicial:
fichajesPorDecidir
fichajesDecididos
presupuesto
valorTotalCalidad
Catalogo
{}
presupuestoInicial
0
Next:
(fichaje)
fichajesPorDecidir
fichajesDecididos
Presupuesto
valorTotalCalidad
fichajesPorDecidir - {fichaje}
fichajesDecididos + {fichaje}
presupuesto - fichaje.precio
valorTotalCalidad + fichaje.valorCalidad
526
private EstadoFichajesAP() {
super();
this.fichajesDecididos = new ArrayList();
this.fichajesPorDecidir = Lists.newArrayList(ProblemaFichajesAP.catalogo);
this.presupuesto = ProblemaFichajesAP.presupuestoInicial;
this.valorCalidadTotal = 0.0;
}
public static EstadoFichajesAP create() {
return new EstadoFichajesAP();
}
public Fichaje getAlternativa() {
Fichaje fichaje = null;
if (fichajesPorDecidir.size() != 0) {
fichaje = fichajesPorDecidir.get(0);
}
return fichaje;
}
public boolean condicionDeParada() {
return ((getAlternativa() == null) || (fichajesPorDecidir.size() == 0));
}
public void next(Fichaje fichaje) {
fichajesDecididos.add(fichaje);
presupuesto = presupuesto - fichaje.getPrecio();
fichajesPorDecidir.remove(fichaje);
fichajesPorDecidir = Lists.newArrayList
(Iterables.filter(fichajesPorDecidir, new DistintaPosicionYDentroPresupuesto(fichaje)));
valorCalidadTotal = valorCalidadTotal + fichaje.getValorCalidad();
}
private class DistintaPosicionYDentroPresupuesto implements Predicate<Fichaje> {
private Fichaje fichaje;
public DistintaPosicionYDentroPresupuesto(Fichaje fichaje) {
this.fichaje = fichaje;
}
@Override
public boolean apply(Fichaje arg0) {
return ((arg0.posicion != fichaje.getPosicion()) && (arg0.getPrecio() <= presupuesto));
}
}
Apartado c)
527
Se pide:
1) Completar la ficha que se proporciona.
2) Indicar de manera justificada los tipos E, S y A de la interfaz: EstadoSA<E extends EstadoSA<E, S, A>, S, A>.
3) Implementar los siguientes mtodos del algoritmo de Simulated Annealing:
a) public double getObjetivo(): Calcula el valor
de la funcin objetivo para el estado actual.
b) public A getAlternativa(): Escoge una
alternativa entre las posibles.
c) public E next(A a): Calcula el estado siguiente
dada una alternativa.
d) public S getSolucion(): Devuelve la solucin
del problema.
528
getInconsistencias(): Devuelve el
nmero de par vrtices conectados con el
mismo color.
Integer
getColoresUsados(): Devuelve el
nmero de colores usados en el grafo.
Integer
Vrtice coloracin
Tcnica:
Simulated Annealing
Propiedades
Compartidas:
NVERTICES
NMAXCOLORES
[Integer]
[Integer]
Propiedades
bsicas del
Estado:
GRAFO
[GrafoColoreado]
Grafo.
Propiedades
derivadas del
Estado:
NCOLORES
NINCONSISTENCIAS
[Integer]
[Integer]
Solucin:
GRAFO
NCOLORES
grafo
Nmero de colores
Objetivo:
Minimizar la funcin:
Estado inicial:
GRAFO
= Asignar a cada vrtice del grafo un color diferente.
NCOLORES
= NVERTICES
NINCONSISTENCIAS = 0
A = { (vertice, color)
Alternativas:
// TODO
// TODO
// TODO
Apartado 2>
Para los tipos E y S, lo importante es indicar qu atributos van a contener, por tanto, asumiendo que la respuesta
al nombre del tipo E y S es EstadoXXXSA y SolucionXXX, cada uno debe contener lo siguiente:
Si incluye algn atributo derivado, ste debe ser un Integer que indique el nmero de colores.
Para el tipo A, se permiten las respuestas ParInteger y Tupla2<V, Integer>, a la que tambin deben justificar lo
siguiente segn el caso:
ParInteger:
El primero indica la posicin que ocupa el vrtice en la lista devuelta por el mtodo
getVertices() de la clase GrafoColoreado y el segundo el color.
Tupla2<V, Integer>: El primero indica el vrtice y el segundo el color.
529
Apartado 3>
530
Impacto
2
1
5
El nivel de satisfaccin de la madre respecto a la asignacin de cama ser la suma de los impactos asociados a
cada uno de los factores que se cumplan. De forma que, por ejemplo, para dos madres con la misma nacionalidad
de dos nias, el nivel de satisfaccin ser de 3 (2 + 1). Para ello, el hospital cuenta con una serie de habitaciones
con una capacidad mxima de 2 pacientes por habitacin. Por supuesto, no hay ningn inconveniente en no
cubrir esta capacidad al completo. En caso de que en una habitacin tan slo haya alojada una madre (habitacin
single), el nivel de satisfaccin de sta ser 5.
Un planteamiento para resolver el problema mediante Simulated Annealing es usar estados inconsistentes, es
decir, permitxir que una misma habitacin contenga ms de dos pacientes, penalizando tal situacin en la
funcin objetivo a optimizar. De esta forma, para la transicin entre estados basta con un nico operador, que
asignar a una madre una habitacin determinada. Una manera de garantizar que el algoritmo devuelve una
solucin vlida es partir de un estado consistente, y definir la funcin objetivo de forma que cualquier solucin
inconsistente sea peor que cualquier solucin consistente. De forma trivial, un estado inicial consistente se
puede definir asignando a cada habitacin un par aleatorio de madres, independientemente del nivel de
satisfaccin como consecuencia de la asignacin.
El objetivo es encontrar, a travs de un algoritmo de Simulated Annealing, una asignacin de habitaciones a las
diferentes madres de manera que se consigan alcanzar un nivel de satisfaccin global mximo.
Se define el nivel de satisfaccin global como la suma de la satisfaccin de cada madre en base a la asignacin
realizada. Para ello partimos de la siguiente informacin:
-
Integer habitaciones. Nmero de habitaciones totales del hospital. La numeracin de las habitaciones
List<Madre> madres.
Almacena todas las madres que estn alojadas en el hospital. El tipo Madre
contiene las propiedades nacionalidad, edad y sexo del beb.
531
Se pide:
1. Completar la ficha del problema. ( // TODO ).
2. Implementar los siguientes mtodos
public
public
public
public
EstadoAsignacionHospitalSA next(AlternativaAsignacionHospital a)
double getObjetivo()
AlternativaAsignacionHospital getAlternativa()
int getNumeroPacientesHabitacion(final Integer habitacion)
Nota: Puede hacer uso de las funciones recogidas en el siguiente UML excepto aquellas funciones que deba
implementar.
532
Apartado 1>
Habitaciones de hospital
Tcnica:
Simulated Annealing
Propiedades
Compartidas:
// TODO
habitaciones
madres
Propiedades
bsicas del
Estado:
Propiedades
derivadas del
Estado:
[Integer]
[List<Madre>]
// TODO
asignacin
[Map<Madre, Integer]
satisfaccinGlobal
[Integer]
Solucin:
Objetivo:
// TODO
Maximizar el valor de la propiedad satisfaccionGlobal.
Estado inicial:
Alternativas:
Next:
// TODO
Next((r, m, h) ) : (Reasignar)
Saca a la madre m de la habitacin donde se encontraba y se le asigna la habitacin h.
( asignacin.put(m, h) )
533
Apartado 2>
534
535
Caracterstica
P1
P2
P3
P4
P5
P6
P7
Incompatibilidades
{P2, P3}, 5%
{}, 0%
{P4, P5}, 1%
{P2 P7}, 19%
{P1, P2}, 8%
{P4, P5}, 15%
{P1, P3, P4}, 50%
Precisin
10 %
5%
7%
30 %
17 %
5%
50 %
Combinacin
P1 + P2
P2 + P4 + P7
P1 + P4 + P7
P4 + P5 + P7
Penalizacin
5%
19% + 19% + 50%
19% + 50% + 50%
19% + 50%
Resultado
10 %
0%
0%
28 %
Lo que se pretende encontrar es un conjunto mnimo de caractersticas (compatibles o no entre ellas) que
permitan obtener la mayor precisin posible.
536
Proveedor
P1
P2
P3
P4
P5
P6
Aplicaciones cubiertas
{A, B, D}
{B, C, E}
{A, D}
{E, G}
{F}
{F, G}
537
41000
13000
56000
110000
109000
90000
149000
58000
21000
3400
5200
6200
5400
5600
6000
7400
2800
6400
Tiempo
(calculado)
10,2
15,6
18,6
16,2
16,8
18
22,2
8,4
19,2
Ejemplo de soluciones (todas menores de 80 minutos puesto que 80 + 40 del recorrido base = 120)
Monumentos
Tiempo Votos
8 4 9 7 6
75
502000
5 4 6 9 7 79,2 504000
8 4 6 3 1 77,4 451000
8 6 5 4 9 73,2 522000
8 7 5 4 9 74,4 503000
538
afinidad_alineacion
41
Edad
18
18
25
31
30
22
18
21
25
30
30
25
29
Nacionalidad
Espaa
Espaa
Espaa
Francia
Francia
Italia
Alemania
Alemania
Alemania
China
China
Japn
Brasil
Posiciones
DF
DF,M
P
DF
M,DL
P
DF
DL
M,DL
M,DL
DF,DL
DF,M,DL
M,DL
Calidad
90
60
95
40
70
80
30
80
90
70
60
20
70
Casillas
Pepe, Antonio, Andres, Carlos
Cristina, Ramn, Marcos, Juan
Roberto, Sara
539
Id
Numero
Coste
Actividad de Clientes Actividad
1
2
3
4
5
15
25
25
45
50
1100
1300
1200
1500
1000
Por ejemplo, para la tabla anterior que contiene cinco actividades propuestas. Para un presupuesto de 2500
una de las soluciones posibles es realizar las actividades 3 y 5, que agruparan 75 potenciales clientes y tendra
un coste de 2200
[idActividad=5, numClientes=50, coste=1000]
[idActividad=3, numClientes=25, coste=1200]
costeTotal=2200.0, numTotalClientes=75
El objetivo es encontrar, a travs de un algoritmo de Simulated Annealing, que actividades se deben realizar para
dar cobertura a un nmero mximo de potenciales clientes para un presupuesto dado. Ntese que el coste total
no debe sobrepasar el presupuesto establecido.
540
Una banda de ladrones de guante blanco planean el robo a un importante museo de europa. Para llevar acabo
su fechora estn han desarrollado un minucioso plan del robo. El problema que tienen es que no son capaces
de decidir que piezas deben robar. Implemente un progama que le diga a los los ladrones que piezas robar,
teniendo en cuenta que:
a) Las piezas tiene un valor en el mercado negro y un peso.
b) Al se ser piezas de nicas e irrepetibles no existen ms de una pieza igual.
c) Y que para la huida dispone de una furgoneta que tiene una tara mxima.
541
idCliente
Oferta
Horas
1000
800
900
1000
1500
2000
3000
Por ejemplo, para una jornada laboral de N = 8 horas y la tabla de ofertas anterior, una de las posibles soluciones
sera:
[idCliente=3,
[idCliente=1,
[idCliente=7,
[idCliente=5,
[idCliente=6,
oferta=900 ,
oferta=1000,
oferta=3000,
oferta=1500,
oferta=2000,
horas=1]
horas=2]
horas=3]
horas=1]
horas=1]
horasTotales=8, beneficioTotal=15400
Ntese que la suma total de horas de las ofertas elegidas en la solucin no superan las 8 horas.
542
7.6.8 Editorial.
Una gran editorial tiene sobre la mesa una serie de N propuestas de diferentes libros
para ser editados y publicados por la editorial. Cada libro propuesto tiene asociado
una duracin de edicin, un plazo lmite, y un beneficio estimado de ventas en caso
de que se publique. Por ejemplo, podramos tener siete propuestas como las de la
siguiente tabla:
IdLibro
Duracin
de edicin
(meses)
Plazo lmite
Publicacin
(meses)
Beneficio estimado
Ventas
(miles de euros)
13
12
20
18
10
30
30
30
Implemente un algoritmo de Simulated Annealing que determine la secuencia en la que deben ejecutarse la
edicin de los diferentes libros (suponiendo que el primer libro empezar en el mes 0) de forma que el beneficio
estimado de ventas total sea mximo. Si se decide no realizar la edicin de algn libro, se no formar parte de
dicha secuencia.
Tenga en cuenta que la edicin de varios libros no puede solaparse en el tiempo y que es posible que la edicin
de algn libro no pueda realizarse y por tanto no genere beneficios de venta.
Por ejemplo para la tabla anterior, una de las posibles soluciones sera la siguiente:
[idLibro=5,
[idLibro=1,
[idLibro=4,
[idLibro=7,
[idLibro=6,
duracion=3,
duracion=4,
duracion=9,
duracion=2,
duracion=2,
plazoLimite=5, beneficio=10]
plazoLimite=13, beneficio=6]
plazoLimite=18, beneficio=8]
plazoLimite=30, beneficio=30]
plazoLimite=30, beneficio=5]]
duracionTotal=20, beneficioTotal=59
Obsrvese que el plazoLmite nunca sobrepasa la suma de la duracin de la tarea actual y de las anteriores. Por
ejemplo, hasta la tarea 4, la duracin de tareas suman 3 + 4 + 9 = 16 y no sobrepasa el plazo lmite de 18.
543
544
8 Implementacin de Tipos.
8.1 Diseo de tipos.
Un buen diseo de tipos es bsico para que los programas sean comprensibles y fciles de mantener. Veamos
algunas pautas para este diseo y algunos ejemplos que puedan servir de gua.
Al disear un tipo nuevo debemos partir de los ya existentes. Es necesario decidir a qu otros tipos extender. El
nuevo tipo puede usar los tipos disponibles para declarar variables parmetros formales, etc. Decimos que el
nuevo tipo usa esos tipos. Tambin puede disearse el nuevo tipo extendiendo algunos de los disponibles. En
este caso decimos que el nuevo tipo es un subtipo de los tipos de los que hereda o tambin decimos que refina
esos tipos.
Todo tipo tiene, adems de las heredadas de los tipos que refina, unas propiedades y posiblemente unas
operaciones nuevas. Cada propiedad tiene un nombre, un tipo, puede ser consultada y adems modificada o
slo consultada, y puede ser una propiedad simple o una propiedad derivada. Adems las propiedades pueden
ser individuales y compartidas. Cada tipo tiene una poblacin. La poblacin de un tipo es el conjunto de objetos
que podemos crear de ese tipo. Como ya sabemos, las propiedades individuales son especficas de un objeto
individual. Las propiedades compartidas son comunes a todos los objetos de la poblacin del tipo. Las
propiedades derivadas pueden ser calculadas a partir de las otras propiedades. Las simples o bsicas no. Las
propiedades son usualmente consultables y pueden ser tambin modificables.
Las propiedades pueden tener parmetros y una precondicin. Una precondicin es una expresin lgica que
indica en qu condiciones es posible obtener el valor de la propiedad. Segn sean modificables o slo
consultables, deduciremos un conjunto de mtodos. De las operaciones deduciremos otro conjunto de mtodos.
Con todos ellos definiremos un nuevo tipo. Este nuevo tipo lo podemos crear mediante una interfaz y
posteriormente implementarlo mediante una clase o directamente mediante la parte pblica de una clase.
Cuntos atributos y de qu tipos? Una primera idea es poner un atributo por cada propiedad no
derivada. Cada atributo tiene como nombre el de la propiedad pero empezando por minscula y el tipo
de la misma. Si la propiedad asociada es compartida por toda la poblacin del tipo entonces el atributo
llevar el modificador static.
El estado de los objetos debe representar de forma mnima las propiedades de los objetos. Esto quiere
decir que tenemos que implementar una forma concreta de guardar las propiedades en los atributos de
la forma ms simple y eficiente posible. Es lo que denominamos forma normal de los valores de ese tipo.
Elegir adecuadamente esta forma normal es muy importante, tal como hemos visto anteriormente, para
implementar la igualdad, los mtodos hashCode, toString y el orden natural. Desde este punto de vista
escogemos la forma normal para que guarde los valores del representante cannico de cada una de las
clases de equivalencia definidas por la igualdad.
Cuntos constructores? Uno con todos los datos suficientes para dar valor a los atributos, otro sin
parmetros y en muchos casos uno que tome un String como parmetro. En algunos casos puede ser
interesante algn constructor ms.
Los constructores deben disparar excepciones si no pueden construir un objeto en un estado vlido con
los parmetros dados. Por ejemplo, si un constructor RacionalImpl toma un parmetro que obligue a
hacer cero el denominador.
Debemos tener en cuenta el estado inicial y el invariante del contrato (deben cumplirse al finalizar la
ejecucin del constructor).
546
Debemos tener en cuenta la precondicin, postcondicin e invariante. Debe asegurarse que si se cumple
la precondicin, se cumplirn tras la ejecucin del mtodo tanto la postcondicin como la invariante.
Si se verifica alguna condicin de disparo de excepcin, entonces hay que disparar las excepciones
definidas en el contrato.
Operaciones:
add(E e): boolean, Aade el elemento e al final de la lista y por lo tanto, dicho elemento e, se convierte
Constructor:
547
548
549
return element;
this.element = element; }
return next;
this.next = next;
}
}
}
}
Y las operaciones:
Y los constructores:
550
DynamicArray(int capacity): Construye un nuevo capacidad capacity y todas las casillas a null.
DynamicArray(int capacity, DynamicArray<E> d):
int capacity;
int size;
E[] elements;
final int INITIAL_CAPACITY = 10;
final int GROWING_FACTOR = 2;
public DynamicArray() {
super();
this.capacity = INITIAL_CAPACITY;
this.size = 0;
this.elements = null;
}
public DynamicArray(int capacity) {
super();
Preconditions.checkArgument(capacity > 0);
this.capacity = capacity;
this.size = 0;
this.elements = null;
}
public DynamicArray(DynamicArray<E> a) {
super();
this.capacity = a.capacity;
this.size = a.size();
this.elements = Arrays.copyOf(a.elements, a.capacity);
}
public DynamicArray(E[] a) {
super();
this.capacity = a.length;
this.size = capacity;
this.elements = Arrays.copyOf(a, capacity);
}
private void grow(int newCapacity) {
E[] oldElements = elements;
capacity = newCapacity;
elements = Arrays.copyOf(oldElements, capacity);
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public E get(int index) {
Preconditions.checkElementIndex(index, size);
return elements[index];
}
public E set(int index, E e) {
if (size == 0) {
if (index >= capacity) {
capacity = index + 1;
}
elements = (E[]) Array.newInstance(e.getClass(), capacity);
}
if (index >= capacity) {
grow(index + 1);
}
if (index >= size) {
size = index + 1;
}
E r = get(index);
elements[index] = e;
return r;
}
551
Como vemos las principales dificultades estn en la creacin de un array genrico de forma dinmica. Esto lo
hacemos al conocer la clase del primer elemento a aadir a la lista y usando la clase Array de Java y las
posibilidades reflexivas del lenguaje. La otra dificultad es la sustitucin del array que contiene los elementos por
otro de mayor capacidad cuando el tamao es igual a la capacidad y queremos aadir un elemento nuevo. Esto
lo resolvemos con el mtodo adecuado de la clase Arrays.
El tipo ArrayList, ofrecido por Java, es una implementacin del tipo List basndose en el tipo BasicArrayList
anterior. El tipo ArrayList implementa, adems de List, otros tipos.
552
Descripcin
and(BitSet set)
Realiza la operacin lgica and entre cada bit de this y los de set.
andNot(BitSet set)
Pone a false en this todos los bits que estn a true en set.
cardinality()
clear()
get(int bitIndex)
flip(int bitIndex)
isEmpty()
length()
Devuelve el ndice ms alto (mas uno) de los bits con valor true.
or(BitSet set)
Realiza la operacin lgica or entre cada bit de this y los bits de set.
set(int bitIndex)
size()
xor(BitIndex bitIndex)
Realiza la operacin lgica xor entre cada bit de this y los bits de set.
Es decir lleva a cabo la operacin lgica or exclusivo.
intersect(BitSet set)
Descripcin
clear()
containsKey(Object key)
get(int index)
get(Object key)
getEntrySize()
Nmero de entradas.
getLength()
getLengthInitial()
getLoadFactor()
getLoadFactorMax()
getLoadFactorMin()
hash(int key)
isInitial()
rehash()
Remove(Object key)
size()
554
BasicHashTable<K,V> (): Construye un atabla hash vaca con una capacidad por defecto
BasicHashTable<K,V)(int capacity): Construye una tabla hash vaca con capacidad capacity.
Las sucesivas llamadas a los mtodos put irn cambiando el factor de carga. Para mantener el invariante la
implementacin deber invocar al mtodo rehash() cuando se supere un factor de carga de referencia. Tambin
se podra llamar al mtodo rehash(), aunque no se ha hecho en la implementacin de abajo, cuando el factor
de carga baja por debajo de un valor.
555
public K1 getKey() {
return key;
}
public void setKey(K1 key) {
this.key = key;
}
public V1 getValue() {
return value;
}
public void setValue(V1 value) {
this.value = value;
}
public String toString() {
return "(" + key + "," + value + ")";
}
}
556
Descripcin
void clear()
E get(int index)
int getCapacity()
Devuelve la capacidad.
E remove(int key)
int size()
557
8.3 rboles.
Descripcin
int getNumChildren()
Nmero de hijos.
int size()
boolean isEmpty()
boolean isRoot():
boolean isLeaf()
E getLabel()
Tree<E> getParent()
int getDepth()
int getHeight()
El rbol hijo que ocupa la posicin index. Los hijos tienen los ndices de 0 a
getNumChildren()-1. Tiene como precondicin que index sea mayor o igual
que cero y menor que getNumChildren() y que el rbol no est vaco.
Tree<E> setElement
(int index, Tree<E> element)
void add
(int index, Tree<E> element)
558
E label;
final int INITIAL_CAPACITY = 2;
DynamicArray<Tree<E>> elements;
Tree<E> parent;
public Tree() {
super();
this.label = null;
this.elements = new DynamicArray<Tree<E>>(INITIAL_CAPACITY);
this.parent = null;
}
public Tree(E label) {
super();
this.label = label;
this.elements = new DynamicArray<Tree<E>>(INITIAL_CAPACITY);
this.parent = null;
}
public Tree(E label, Tree<E>[] elements) {
super();
this.label = label;
this.elements = new DynamicArray<Tree<E>>(elements);
this.parent = null;
}
public Tree(E label, DynamicArray<Tree<E>> elements) {
super();
this.label = label;
this.elements = elements;
this.parent = null;
}
public int getNumChildren() {
int r = 0;
if (!isEmpty()) {
r = elements.size();
}
return r;
}
private int size(Tree<E> t) {
int r;
if (t == null || t.isEmpty()) {
r = 0;
} else if (t.getNumChildren() == 0) {
r = 1;
} else {
r = 1;
for (int i = 0; i < t.getNumChildren(); i++) {
r = r + size(t.getElement(i));
}
}
return r;
}
public int size() {
return size(this);
}
public boolean isEmpty() {
return label == null;
}
public boolean isRoot() {
return parent == null;
}
559
560
561
Descripcin
Devuelve un iterador en anchura que va recorriendo todos
los subrboles de t, incluido l mismo.
Devuelve un iterable general en profundidad que va
recorriendo todos los subrboles de t, incluido l mismo.
Toma un segundo parmetro que indica la posicin en la
que se visitar el rbol actual: 0 delante del primer hijo, 1
delante del segundo hijo, etc.
Devuelve verdadero si t1 es igual a t2. Dos rboles son
iguales si ambos son vacos o si ambos no son vacos tienen
iguales sus etiquetas y cada uno de sus hijos.
Devuelve verdadero si el rbol t est ordenado segn el
orden del comparator.
Devuelve un iterable en inorden que va recorriendo todos
los subrboles de t, incluido l mismo.
Devuelve un iterador en postorden que va recorriendo
todos los subrboles de t, incluido l mismo.
Devuelve un iterador en preorden que va recorriendo
todos los subrboles de t, incluido l mismo.
Construye una copia del rbol. El tipo de la etiqueta deber
ser copiable.
Crea una representacin del rbol como cadena de
caracteres.
Los diferentes tipos de recorridos sobre rboles binarios podemos definirlos recursivamente de la siguiente
forma:
562
Recorrido en preorden: Se visita primero el rbol actual luego el primer hijo, luego el segundo hijo, etc.
Recorrido en postorden: Se visita primero el primer hijo, luego el segundo hijo derecho, etc. y por ltimo
el rbol actual.
Recorrido en inorden: Se visita primero el primer hijo, luego el rbol actual y por ltimo el segundo hijo.
Recorrido general en profundidad: Es una generalizacin de los anteriores donde se indica la posicin el
momento en el que se visitar el rbol actual.
Recorrido en anchura o por niveles: Se visita primero el rbol actual (nivel 1), luego los rboles de nivel
2 de izquierda a derecha, etc.
Descripcin
Nmero de etiquetas del agregado.
Devuelve Verdadero si el nmero de etiquetas es 0.
El primer elemento segn el orden del agregado o null si estaba vaco.
Aade la etiqueta e al agregado. Si ya estaba devuelve false y si no estaba true.
Elimina la etiqueta e del agregado. Devuelve la etiqueta eliminada o null si no
estaba en el rbol.
Elimina la primera etiqueta del agregado. Devuelve la etiqueta eliminada o
null si no estaba en el rbol.
Devuelve Verdadero si el agregado contiene la etiqueta.
El orden asociado al agregado.
Por simplicidad asumimos que en un rbol de bsqueda no hay dos etiquetas iguales aunque esta restriccin
podra eliminarse. Aadiendo diversos invariantes obtenemos diversos tipos de rboles de bsqueda.
Los rboles de bsqueda binarios pueden ser vacos o tener cero o dos hijos.
563
Como podemos ver los rboles binarios de bsqueda tienen sus operaciones con una complejidad en el caso
pero de (n). Si el rbol estuviera equilibrado la complejidad sera menor. En efecto en un rbol binario
equilibrado (cada rbol tiene dos hijos exactamente) el nmero total de etiquetas en un rbol de altura k es:
= 2 =
=0
2+1 1
2+1 ,
1
(log )
Por lo tanto si el rbol esta equilibrado las operaciones anteriores tienen una complejidad en el caso peor de
(log n). debido a que el camino ms largo es de log n si hay n etiquetas. Pero las operaciones de insercin y
eliminacin desequilibran un rbol previamente equilibrado. Son necesarios rboles con invariantes ms fuertes
para conseguir esa complejidad.
Propiedades especficas de BinarySortedTree respecto a Tree:
Invariante
contains(E e)
remove(Object e)
size()
564
Como vemos los rboles AVL son casi equilibrados. Como hemos comentado arriba las operaciones de insercin
y eliminacin desequilibran el rbol. Para mantenerlo casi equilibrado (segn el invariante establecido para los
rboles AVL) debemos llevar a cabo, despus de la insercin o la eliminacin operacin de requilibrado.
Para implementar las operaciones de reequilibrado se disean dos transformaciones bsicas sobre rboles
binarios: leftRotation, rightRotation. Cada una de ellas reordena un rbol dado tal como se muestra en la
figura siguiente:
565
Adems de las propiedades de los rboles binarios de bsqueda, cumplen las siguientes restricciones:
1. Todo nodo es o bien rojo o bien negro.
2. La raz es negra.
3. Las hojas son rboles vacos y son negras.
4. Los dos hijos de todo nodo rojo son negros.
5. Cada camino desde un nodo a una hoja descendiente contiene el mismo nmero de nodos negros. Para
un rbol dado el nmero de nodos negros desde la raz a las hojas es constante para todos los caminos
y se denomina Altura negra del rbol.
Estas reglas producen una caractersticas producen una regla crucial para los rboles rojo-negro: el camino ms
largo desde la raz hasta una hoja no es ms largo que dos veces el camino ms corto desde la raz a una hoja. El
resultado es que dicho rbol est aproximadamente equilibrado.
Dado que las operaciones bsicas como insertar, borrar y encontrar valores tienen un peor tiempo de ejecucin
proporcional a la altura del rbol, esta cota superior de la altura permite a los rboles rojo-negro ser eficientes
en el peor caso, a diferencia de los rboles binarios de bsqueda.
Para comprobarlo basta ver que ningn camino puede tener dos nodos rojos seguidos debido a la propiedad 4.
El camino ms corto posible tiene todos sus nodos negros, y el ms largo alterna entre nodos rojos y negros.
Dado que todos los caminos mximos tienen el mismo nmero de nodos negros por la propiedad 5, no hay
ningn camino que pueda tener longitud mayor que el doble de la longitud de otro camino.
En muchas presentaciones de estructuras arbreas de datos, es posible para un nodo tener solo un hijo y las
hojas contienen informacin. Es posible presentar los rboles rojo-negro en este paradigma, pero cambian
algunas de las propiedades y se complican los algoritmos. Por esta razn, este artculo utiliza hojas nulas.
566
Usos y ventajas:
Los rboles rojo-negro ofrecen un peor caso con tiempo garantizado para la insercin, el borrado y la bsqueda.
No es esto nicamente lo que los hace valiosos en aplicaciones sensibles al tiempo como las aplicaciones en
tiempo real, sino que adems son apreciados para la construccin de bloques en otras estructuras de datos que
garantizan un peor caso. Por ejemplo, muchas estructuras de datos usadas en geometra computacional pueden
basarse en rboles rojo-negro.
El rbol AVL es otro tipo de estructura con O(log n) tiempo de bsqueda, insercin y borrado. Est equilibrado
de forma ms rgida que los rboles rojo-negro, lo que provoca que la insercin y el borrado sean ms lentos
pero la bsqueda y la devolucin del resultado de la misma ms veloz.
Los rboles rojo-negro son particularmente valiosos en programacin funcional, donde son una de las
estructuras de datos persistentes ms comnmente utilizadas en la construccin de arrays asociativos y
conjuntos que pueden retener versiones previas tras mutaciones. La versin persistente del rbol rojo-negro
requiere un espacio O(log n) para cada insercin o borrado, adems del tiempo.
Los rboles rojo-negro son isomtricos a los rboles 2-3-4. En otras palabras, para cada rbol 2-3-4, existe un
rbol correspondiente rojo-negro con los datos en el mismo orden. La insercin y el borrado en rboles 2-3-4
son tambin equivalentes a los cambios de colores y las rotaciones en los rboles rojo-negro. Esto los hace ser
una herramienta til para la comprensin del funcionamiento de los rboles rojo-negro y por esto muchos textos
introductorios sobre algoritmos presentan los rboles 2-3-4 justo antes que los rboles rojo-negro, aunque
frecuentemente no sean utilizados en la prctica.
567
Los subrboles hojas no son nunca rboles vacos y todos los niveles
estn completos excepto posiblemente el ltimo que est lleno de
izquierda a derecha.
La etiqueta asociada a la raz de un rbol es siempre menor que las
etiquetas contenidas en los rboles hijos.
Cada subrbol es un montculo binario.
Los montculos suelen denominarse montculos de mnimos como el de arriba (cuando la raz es menor que cada
uno de los hijos) o de mximos (como abajo) cuando la raz es mayor que cada uno de los hijos. Por ejemplo, la
insercin de la etiqueta 15 se hace incluyndola en la siguiente posicin libre del ltimo nivel del rbol (posicin
sealada con X). Posteriormente para mantener el invariante se va intercambiando la etiqueta por la raz del
rbol correspondiente.
Si bien se puede utilizar un rbol binario para representar un montculo, la condicin de rbol completo permite
representar fcilmente un montculo en un vector colocando los elementos por niveles y en cada nivel, los
elementos de izquierda a derecha.
Dado que el rbol es completo, no es necesario almacenar apuntadores en el rbol. Siempre se puede calcular
la posicin de los hijos o la del padre a partir de la posicin de un nodo en el vector (contando las posiciones del
vector a partir de cero):
-
Por tanto, el padre de un nodo que est en la posicin k ( k > 0 ) est almacenado en la posicin (k - 1) / 2.
568
Un rbol-B de orden M (el mximo nmero de hijos que puede tener cada nodo) es un rbol que satisface las
siguientes propiedades:
-
Cada subrbol tiene como mximo M hijos, como mnimo (excepto posiblemente raz y hojas) tiene
como mnimo M/2 hijos.
La raz tiene al menos 2 hijos si no es un nodo hoja.
Todos los nodos hoja aparecen al mismo nivel.
En cada subrbol con m hijos el nmero de etiquetas asociadas a la raz es m-1.
Sean e0, e1, e2, , em-2 las etiquetas asociadas a la raz y h0, h1, h2, , hm-1 los hijos, entonces se cumple
que para cada i en [0, m - 2] la etiqueta ei es mayor que las contenidas en le subrbol hi y menor que las
contenidas en hi+1.
Los rboles-B o B-rboles son estructuras de datos de rbol que se encuentran comnmente en las
implementaciones de bases de datos y sistemas de archivos. Son rboles balanceados de bsqueda en los cuales
cada nodo puede poseer ms de dos hijos.1 Los rboles B mantienen los datos ordenados y las inserciones y
eliminaciones se realizan en tiempo logartmico amortizado.
La idea tras los rboles-B es que los nodos internos deben tener un nmero variable de nodos hijo dentro de un
rango predefinido. Cuando se inserta o se elimina un dato de la estructura, la cantidad de nodos hijo vara dentro
de un nodo. Para que siga mantenindose el nmero de nodos dentro del rango predefinido, los nodos internos
se juntan o se parten. Dado que se permite un rango variable de nodos hijo, los rboles-B no necesitan
rebalancearse tan frecuentemente como los rboles binarios de bsqueda auto-balanceables, pero por otro lado
pueden desperdiciar memoria, porque los nodos no permanecen totalmente ocupados. Los lmites una superior
e inferior en el nmero de nodos hijo son definidos para cada implementacin en particular. Por ejemplo, en un
rbol-B 2-3 (A menudo simplemente llamado rbol 2-3 ), nodo slo puede tener 2 3 nodos hijo.
Un rbol-B se mantiene balanceado porque requiere que todos los nodos hoja se encuentren a la misma altura.
Los rboles B tienen ventajas sustanciales sobre otras implementaciones cuando el tiempo de acceso a los nodos
excede al tiempo de acceso entre nodos. Este caso se da usualmentea cuando los nodos se encuentran en
dispositivos de almacenamiento secundario como los discos rgidos. Al maximizar el nmero de nodos hijo de
cada nodo interno, la altura del rbol decrece, las operaciones para balancearlo se reducen, y aumenta la
eficiencia. Usualmente este valor se coloca de forma tal que cada nodo ocupe un bloque de disco, o un tamao
anlogo en el dtispositivo. MientrasEA que los rMboles B 2-3 pueden ser tiles en la memoria principal, y
adems ms fciles de explicar, si el tamao de los nodos se ajustan para caber en un bloque de disco, el
resultado puede ser un rbol B 129-513.
569
Curiosidad: Los creadores del rbol B, Rudolf Bayer y Ed McCreight, no han explicado el significado de la letra
B de su nombre. Se cree que la B es de balanceado, dado que todos los nodos hoja se mantienen al mismo
nivel en el rbol. La B tambin puede referirse a Bayer, o a Boeing, porque sus creadores trabajaban en los
Boeing Scientific Research Labs por ese entonces.
570
Aridad: indica la cantidad de operandos que requiere el operador. Por ejemplo la suma (+) tiene aridad
2, mientras que la aridad del operador cambio de signo (-) es 1. Observe que el operador resta y cambio
de signo utilizan el mismo smbolo. Inclusive es comn que un mismo smbolo sea utilizado para realizar
operaciones diferentes y tipos de datos diferentes.
Asociatividad: puede ser izquierda o derecha e indica la direccin en que se realizarn las operaciones
cuando una expresin involucra el mismo operador.
Precedencia: indica que operaciones se realizarn primero o despus cuando en una expresin se
combinan operadores distintos.
2 ** 3 ** 2
Orden de resolucin
Se calcula la suma de 2 y 3, el resultado 5 se
suma a 4. El nuevo resultado 9 se suma a 5.
Se calcula 3 elevado a la 2, el resultado 9 es
tomado como el exponente en 2 ** 9. As el
resultado final es 512.
Notas:
-
Expresin
4+2+31
Orden de evaluacin
((4 +2) + 3) 1
Resultado
8
571
b
c
2 * 3 10 / 5 / 2
100 / 5 ** 2
(2 * 3 ) ((10 / 5) / 2)
100 / (5 ** 2)
5
4
El orden de aplicaciones de los operadores se puede representar grficamente por medio de un rbol sintctico.
Un rbol sintctico es una representacin grfica en donde los operandos y los operadores son nodos. Los nodos
de los operadores estn ligados a los nodos de sus operandos.
Los rboles sintcticos para las expresiones anteriores son:
El nodo superior de un rbol sintctico recibe el nombre de nodo raz. Los otros nodos etiquetados con
operadores reciben el nombre de nodos internos. Los nodos internos tienen uno o ms nodos hijo o
descendientes, segn sea su aridad. Un nodo interno de un operador con aridad 2 tiene 2 nodos hijo. Un nodo
interno de un operador con aridad 1 tiene un nodo hijo.
Los nodos sin descendientes reciben el nombre de nodos terminales (los hemos representado por medio de
rectngulos). En los rboles sintcticos anteriores los nodos terminales correspondes a valores numricos.
La evaluacin de un rbol procede de las hojas y se propaga hacia la raz.
Ejemplo:
Paso 1
572
Paso 2
Paso 3
573
El tipo operador es tiene mtodos para calcular el valor a partir del de sus operandos, devolver el tipo del
resultado de la expresin y comprobar la consistencia de la expresin. Es decir cada operador se aplica a los
operandos adecuados en nmero y tipo.
public interface Operator {
Object getValue(Object[] values);
Class<?> getResultType();
boolean check(Class<?>[] typesOfValues);
}
Como hemos dicho arriba un operando vamos a representarlo por un operador sin operandos. La clase Id
implementa un operando en general.
public class Id implements Operator {
private Object value;
private Class<?> type;
private String name;
public Id(String name, Object value) {
super();
this.name = name;
this.value = value;
this.type = value.getClass();
}
@Override
public Object getValue(Object[] values) {
return value;
}
public void setValue(Object value) {
this.value = value;
this.type = value.getClass();
}
@Override
public Class<?> getResultType() {
return type;
}
@Override
public boolean check(Class<?>[] typesOfValues) {
boolean r = true;
if (typesOfValues != null) {
r = false;
}
return r;
}
public String toString() {
return name;
}
}
574
575
En estos rboles no se suele estar interesado en el valor del rbol, pero s en la consistencia del mismo, o en el
tipo resultado, etc.
576
Diagnostico medico.
Anlisis de riesgo en crdito.
Clasificador de objetos para manipulador de robots.
577
En la prctica, casi siempre se utiliza el rbol para obtener todos los cdigos de una sola vez; luego se guardan
en tablas y se descarta el rbol.
Ejemplo:
578
Significado
Nmero de nodos (etiquetas o claves) en el montn.
Nmero de hijos del nodo x.
Rango mximo de los nodos en el montn H.
Nmero de rboles en el montn H.
Nmero de nodos marcados en el montn H.
Tamao del rbol (nmero nodos).
rbol cuya raz tiene k hijos.
Ejemplo de un montculo de Fibonacci. Tiene tres rboles de rangos 0, 1 y
3. Tres vrtices estn marcados (mostrados en azul).
La estructura mantiene un invariante sobre el grado de los nodos de los
rboles (el nmero de hijos). El grado de los nodos se tiene que mantener
bajo cada nodo tiene un grado mximo de (log n), donde n el nmero
de etiquetas del montculo. En concreto:
r(H) log n ,
1 + 5
2
El tamao (nmero de claves) de un subrbol cuya raz tiene grado k es por lo menos Fk+2, donde Fk es un nmero
de Fibonacci (pero considerando F0 = 1, F1 =2).
( ) +2
Cuando la estructura est consolidada se cumple:
() () + 1
La estructura es adecuada para implementar eficientemente las operaciones del tipo SearchTree y dos
operaciones ms: Unin y Decrementar el valor de la clave.
La operacin Encontrar Mnimo es trivial. La Unin se implementa simplemente concatenando las listas de races
de rboles de los dos montones. La operacin Insertar trabaja creando un nuevo montculo con un elemento y
haciendo la Unin. En ambos casos la estructura queda sin consolidar. La operacin de Consolidacin, explicada
ms abajo, se pospone hasta la siguiente llamada a la operacin de Eliminar el Mnimo.
579
La operacin Eliminar Mnimo opera en tres fases. Primero cogemos la raz con el elemento mnimo, la borramos
y convertimos sus hijos en races de nuevos rboles. En la segunda fase decrementamos el nmero de races
agrupando sucesivamente las races con el mismo grado.
Cuando dos races u y v tienen el mismo grado, hacemos que la que tenga la clave mayor sea hija de la otra. Por
lo tanto colocamos la ms pequea como raz. Esto se repite hasta que todas las races tienen un grado diferente.
Esta segunda fase se denomina Consolidacin (consolidate). Para implementarla eficientemente se mantienen
varias listas cada una de las cuales enlaza los rboles cuyas races tienen el mismo nmero de hijos. Por ltimo,
comprobamos cada una de las races restantes y encontramos el mnimo.
La secuencia de eliminacin se muestra en la figura:
La operacin Decrementar Clave decrementar la clave del nodo y si viola la propiedad del montculo (la nueva
clave es ms pequea que la clave del padre), el nodo se corta de su padre. Si el padre no es una raz, se marca.
Si ya estaba marcado, se corta tambin y su padre se marca. Continuamos subiendo hasta que, o bien alcanzamos
la raz o un vrtice no marcado. En el proceso creamos un nmero k de nuevos rboles. Los nodos marcados en
cada momento son aquellos de los que se ha eliminado un hijo.
Por ltimo, la operacin Borrar puede ser implementada simplemente decrementando la clave del elemento a
borrar a menos infinito, convirtindolo en el mnimo de todo el montculo, entonces llamamos a Extraer Mnimo
para borrarlo.
Mtodo
Descripcin
insert(T elem)
minimum()
removeMin()
union(FibonacciHeap<T> h)
decreaseKey(T elem, int key)
remove(T elem)
580
El constructor
UnionFind(Set<T> elements): Construye la estructura con un conjunto por cada elemento en elements.
Por las operaciones disponibles podemos representar todos los objetos que pertenecen a un mismo conjunto
como un rbol. La raz ser la representante de todos ellos. Para buscar el representante de un elemento se trata
de seguir el camino de cada nodo a su padre hasta uno, la raz, que la hacemos padre de si mismo. Para conseguir
que el rbol que representa cada conjunto tenga la mnima altura posible, en al operacin find, se hace cada
nodo hijo del antecesor de su padre hasta conseguir que todos los nodos de esa rama sean hijos de la raz.
La unin se consigue haciendo que el rbol de menor altura sea hijo del de mayor altura. En este caso concreto
slo nos interesan de los rboles la propiedad de padre de cada nodo y altura del mismo. Por esta razn todos
los rboles se pueden representar por dos funciones: parentMap y rankMap. El primero nos da la etiqueta padre
de una dada y el segundo una aproximacin a la altura del rbol con una etiqueta dada.
Mtodo
Descripcin
addElement(T elem)
find(T elem)
union(T elem1, T elem2)
removeUnion(T elem1, T elem2)
removeElement(T elem)
removePart(T elem)
getNumberOfParts()
findInteger(T elem)
findElementPart(int i)
581
582
Se cumple que: n2 = n1 + n0 + 1
Se pueden generar 4 rboles de Fibonacci con altura dos. Existen adicionalmente varios rboles AVL de altura
dos (los con 5, 6, y 7 nodos) pero se consideran ms balanceados que los de Fibonacci.
Esto es,
ImagenEspecular(rbol(izq, elto, der)) = rbol(ImgenEspecular((der), elto, ImagenEspecular(izq))
583
Implementacin:
t1
t2
t3
t4
t5
t6
t7
t8
t9
=
=
=
=
=
=
=
=
=
new
new
new
new
new
new
new
new
new
Tree<Integer>(1);
Tree<Integer>(2);
Tree<Integer>(3);
Tree<Integer>(4);
Tree<Integer>(5);
Tree<Integer>(6);
Tree<Integer>(7);
Tree<Integer>(8);
Tree<Integer>(9);
t5.add(t6);
t5.add(t4);
t8.add(t9);
t5.add(t2);
t5.add(t1);
t3.add(t5);
t3.add(t7);
t3.add(t8);
System.out.println(Trees.toString(t3));
t3.imagenEspecular(t3);
System.out.println(Trees.toString(t3));
}
public static <E> Tree<E> imagenEspecular(Tree<E> I) {
E label = I.getLabel();
DynamicArray<Tree<E>> invElements = new DynamicArray<Tree<E>>();
for (int pos = 0; pos < t.getNumChildren(); pos++) {
Tree<E> e = t.getElement(pos);
invElement.add(0, imagenEspecular(e));
}
return new Tree<E>(label, invElements);
}
}
584
Un primer tipo es la vista con invariante. En este caso la vista y objeto ligado son agregados de objetos y
donde los objetos de la vista son los del objeto ligado pero filtrados por un invariante. Desde este punto
de vista una variable s es una vista de otra r cuando ambas son agregados de objetos y estn ligadas en
el sentido de que en cada momento los objetos en s (la vista) son los de r que cumplan un invariante
dado. Es decir si modificamos la variable r los valores de la s quedan actualizados a los de r que cumplan
el invariante. Igualmente ocurre si modificamos s pero a esta no podemos aadir objetos que no cumplan
el invariante. Esta idea de vista con invariante puede ser extendida a tipos que no sean agregados de
datos.
Un segundo tipo donde los valores de la vista y el original son los mismos pero la vista slo ofrece un
subconjunto de los mtodos disponibles. Son las vistas restringidas. Ejemplos de vistas del segundo tipo
son las vistas no modificables de un agregado dado.
Un tercer tipo donde los valores y los mtodos son los mismos en la vista y en el objeto ligado pero los
mtodos de la vista tienen alguna semntica adicional. Ejemplo de este tipo son las vistas concurrentes.
Veamos un ejemplo. Vamos a implementar la vista devuelta por el mtodo subList del tipo List. El primer paso
es tener claro cules son las restricciones que va introducir la vista. A continuacin, se muestran dichas
restricciones expresndolas como ejemplos. A partir de estos ejemplos sera automtico generar casos de prueba
que validaran la implementacin de la vista. Por simplicidad no se muestra el tipo de los objetos en la lista aunque
se puede deducir del contexto.
Cdigo
Integer[] i = {1, 2, 3, 4, 5, 6};
List l = new ArrayList(Arrays.asList(i));
List vista = l.subList(2, 5);
System.out.println(vista);
System.out.println(vista.get(0) + ", " + vista.get(1));
System.out.println(vista.size());
vista.add(0, 0);
System.out.println(vista);
System.out.println(l);
vista.remove(2);
vista.remove(2);
System.out.println(vista);
System.out.println(l);
Resultado esperado
Vaco
[3, 4, 5]
3, 3
3
[0, 3, 4, 5]
[1, 2, 0, 3, 4, 5, 6]
[0, 3]
[1, 2, 0, 3, 6]
Despus, creamos una nueva clase que implemente la interfaz List. Ahora es el momento de implementar cada
uno de los mtodos del contrato (en este caso List), utilizando el patrn decorador. Para emplear este patrn,
todos los mtodos de la implementacin de la vista deben llamar a mtodos de la clase original aadiendo el
cdigo necesario. Una posible implementacin, basada en el propio cdigo fuente de Java, para la clase interna
que implementa la sublista se muestra a continuacin.
class SubList<E> {
private List<E> l;
private int offset;
private int size;
private int expectedModCount;
SubList(List<E> list, int fromIndex, int toIndex) {
checkElementIndex(fromIndex, list.size(), "Index: " + fromIndex + ",Size: " + list.size());
checkPositionIndex(toIndex, list.size(), "Index: " + toIndex + ",Size: " + list.size());
checkArgument(fromIndex > toIndex, "fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
l = list;
offset = fromIndex;
size = toIndex - fromIndex;
}
public E get(int index) {
checkElementIndex(index, size, "Index: " + index + ",Size: " + size);
return l.get(index + offset);
}
public int size() {
return size;
}
public void add(int index, E element) {
checkPositionIndex(index, size);
l.add(index + offset, element);
size++;
}
public E remove(int index) {
checkElementIndex(index, size, "Index: " + index + ",Size: " + size);
E result = l.remove(index + offset);
size--;
return result;
}
// Contina
}
La implementacin anterior usa la clase Preconditions de Guava. El mtodo subList en una clase que
implemente List<E> sera:
public List<E> subList(int fromIndex, int toIndex) {
return new SubList(this, fromIndex, toIndex);
}
586
8.4.3.1
La API de Java ofrece muchos tipos de vistas. El contrato Collection define una nica vista, que es un iterador. El
contrato List define dos nuevas vistas. Ambas se describen en la siguiente tabla:
Vistas propias del contrato List<E>
ListIterator<E> listIterator()
List<E> subList(int from, int to)
El contrato Set no define ninguna nueva vista. Sin embargo, los conjuntos ordenados s definen un conjunto de
vistas adicionales.
Vistas del contrato SortedSet< E>
SortedSet<E> headSet(E toElement)
SortedSet<E> tailSet(E toElement)
SortedSet<E> subSet(E fromElement, E toElement)
587
8.4.3.2
Se ha visto en la seccin anterior que para implementar vistas se utilizan delegados. Para facilitar el desarrollo
de estos delegados a la hora de trabajar con tipos de datos, el paquete Guava incluye un conjunto de clases
llamadas forward. Guava incluye unas clases forward para cada tipo de dato definido en el API de Java y para
cada tipo de dato definido en Guava.
Todas las clases forward de Guava siguen un mismo patrn. Todas implementan un contrato y delegados de ese
contrato. Estas clases son tiles cuando se crean vistas con restricciones que afectan slo a algunos mtodos.
Esencialmente se trata de heredar de esas clases y sobrescribir el mtodo correspondiente.
Como ejemplo, vamos a implementar una vista del contrato List<E> que imponga como restriccin que no se
puede decrementar el tamao de la lista. Eso significa que los mtodos remove, removeAll y clear deben lanzar
una excepcin mientras que el resto de los mtodos se comportar normalmente. El cdigo resultante utilizando
la clase ForwardingList<E> de Guava se muestra a continuacin.
Como puede verse, gracias a ForwardList slo es necesario volver a implementar los mtodos que modifican su
comportamiento, en vez de todos los mtodos del contrato. Esta solucin no est completa, ya que es necesario
modificar todas las vistas para no poder eliminar elementos a partir de ellas.
588
Guava ya ofrece un mecanismo para crear colecciones cuyos elementos cumplen una restriccin. Para definir
una restriccin se usa la interfaz Constraint y la clase Constrains que incluye mtodos factoras para crear
colecciones con restricciones.
El tipo Constraint<E> tiene el mtodo E checkElement(E e) verifica si el elemento recibido como parmetro
cumple la restriccin. Si no la cumple, el mtodo debe lanzar una excepcin y, si la cumple, el mtodo debe
devolver el mismo elemento que recibe por parmetro. Las diversas restricciones se obtienen implementando
esa interfaz. La clase Constraints incluye mtodos para crear colecciones con restricciones pasndole como
parmetro un objeto que implemente Constraint<E>. Los mtodos de la clase Constraints se implementan
siguiendo ideas similares al caso de las vistas.
A modo de ejemplo, el siguiente cdigo crea una lista que no permite valores nulos:
List<String> l = Constraints.constrainedList(new ArrayList<String>(), Constraints.notNull());
Descripcin
Aade un objeto interesado en el evento correspondiente
Elimina el objeto como interesado en el evento correspondiente.
Descripcin
Actualiza su estado al recibir el evento e.
La idea central es que el objeto observado (Listenable) enva un mensaje a todos los objetos que se han suscrito
cuando un evento previamente diseado ocurre. Por ese mecanismo el objeto observador (Listener) puede
actualizar su estado para mantener una vista del objeto observado o simplemente tener en cuenta los eventos
ocurridos el objeto observado. Un ejemplo lo tenemos en el tipo ListenableGraph<V,E> que ofrece el API de
jgrapht.
589
590
591
Y el programa principal:
public static void main(String[] args) {
ListenableSet<Double> s = new ListenableHashSet<Double>();
Counter<Double> c = new Counter<Double>();
s.addSetAddListener(c);
s.addSetRemoveListener(c);
for (double r = 0.; r < 5.; r = r + 0.5) {
s.add(r);
}
for (double r = 1.; r < 5.; r = r + 0.5) {
s.remove(r);
}
System.out.println(s);
System.out.println(c);
}
592
Conjunto de enteros.
Implementar un tipo en base a otro consiste en utilizar la estructura de un tipo usando las utilidades que nos
ofrecen el tipo sobre el que se representa. En este problema vamos a modelar el tipo Set con una serie de
restricciones. En concreto, se desea implementar un conjunto de enteros cuyos elementos pertenezcan a un
rango dado.
La eleccin de un tipo para la implementacin de otro, tiene una serie de ventajas y de inconvenientes en funcin
de las caractersticas deseadas. En esta problema, vamos a implementar un conjunto de enteros cerrados a un
rango utilizando array de bits, en concreto, el tipo BitSet. El BitSet ofrece unos tiempos constantes en las
operaciones de insercin, borrado, bsqueda y en operaciones de gran relevancia para los conjuntos como la
insercin, la unin, etc. Sin embargo, tiene la limitacin de que delimitamos el conjunto, en principio una
estructura de rango libre, a un rango concreto.
Cuando se implementa un tipo en funcin de otro es necesario establecer las ventajas e inconvenientes que esto
tiene y delimitar las restricciones que puede darnos.
En el array de booleanos, el hecho de que el bit i-simo del vector sea verdadero indica que el entero i es un
elemento del conjunto. La principal ventaja de esta representacin es que las operaciones de insercin, consulta
o eliminacin se realizan en un tiempo constante por direccionamiento directo al bit apropiado, es decir, tienen
una complejidad de (1). Por otra parte, las operaciones de unin, interseccin y diferencia se realizan en tiempo
proporcional al tamao del conjunto universal. El inconveniente es que todos los conjuntos ocupan la misma
cantidad de almacenamiento que el conjunto universal.
De esta forma, cada uno de los elementos tendr asignada una posicin en el array. Sin embargo, dado que un
array de bits comienza en la posicin 0 y en nuestro caso tenemos un lmite inferior y superior que limitan el
rango de nuestro conjunto de enteros, habr que tener en cuenta que, para acceder a la posicin de un
determinado elemento, habr que hacer un pequeo clculo para saber a qu posicin corresponde un
determinado elemento del rango. En concreto, la posicin se calcular mediante la diferencia del valor del
elemento y el lmite inferior del rango.
Modelar el tipo ConjuntoEnteros implementando el
tipo Set<Integer>, implica que todas las funciones del
tipo Set deben realizarse internamente con las
funciones del tipo BitSet. En la figura siguiente se
muestra el comportamiento general que tendr
nuestro conjunto de enteros basado en un BitSet.
593
Dado que se va a implementar un Set<Integer>, el modelado de clases para implementar el conjunto de enteros
con un rango dado ser el siguiente, y la interfaz del tipo BitSet, cuyos mtodos utilizaremos internamente en
la implementacin del ConjuntoEnteros es, igualmente, el siguiente:
Vamos a implementar cada uno de los mtodos de la interfaz Set adaptados a las restricciones del problema, es
decir, limitar el rango a uno dado y utilizando un BitSet para la implementacin (ConjuntoEnteros no implementa
BitSet). Recordemos que ConjuntoEnteros implementa Set.
Las propiedades del tipo ConjuntoEnteros son:
Inf:
Entero, Consultable. Lmite inferior del rango, intervalo al que pertenece el conjunto de enteros.
Valor incluido dentro del intervalo.
Sup: Entero, Consultable. Lmite superior del rango, intervalo al que pertenece el conjunto de enteros.
Hay que tener en cuenta que se trata de intervalos cerrados por la izquierda y abiertos por la derecha,
por lo que este valor no estara incluido dentro del intervalo abierto.
Conjunto: Array de bits, Consultable. BitSet donde se indica la pertenencia de los elementos al conjunto
de enteros. Si el valor de una posicin, es verdadero, indica que ese elemento pertenece al conjunto.
Hay que tener en cuenta que la posicin de un elemento se calcula a partir de la diferencia del mismo y
el lmite inferior.
A continuacin se detallan pasos propuestos para el modelado del tipo ConjuntoEnteros que implementa la
interfaz Set<Integer> y utilizando internamente un BitSet para indicar la pertenencia de los valores del rango.
594
Mtodos
Descripcin
and(BitSet set)
Realiza la operacin lgica AND entre cada bit de this y los bits de set.
andNot(BitSet set)
Pone a false en this todos los bits que estn a true en set.
cardinality()
clear()
flip(int bitIndex)
Hace el complemento sobre el bit indicado (operacin lgica not sobre el bit).
get(int bitIndex)
intersect(BitSet set)
Verdadero si existen bits en this y en set indexados por el mismo entero y con
valor true.
isEmpty()
length()
Devuelve el ndice (ms uno) del bit con valor true con ndice ms alto.
nextSetBit(int fromIndex)
Devuelve el ndice del primer bit que est establecido a true a partir del ndice
especificado fromIndex.
or(BitSet set)
Realiza la operacin lgica OR entre cada bit de this y los bits de set.
set(int bitIndex)
size()
xor(BitSet set)
Realiza la operacin lgica XOR entre cada bit de this y los bits de set. Es decir
lleva a cabo la operacin lgica or exclusivo.
595
A continuacin daremos paso a la implementacin del tipo ConjuntoEnteros. Recordemos que este tipo
implementa la interfaz de Set<Integer>, por lo que los mtodos del tipo a implementar sern los mismos que
tiene Set<Integer>.
1. Definir las propiedades del tipo ConjuntoEnteros anteriormente descritas.
2. Implementar el constructor donde se inicialicen todas las propiedades. El constructor recibe nicamente
los lmites del rango al que pertenece el conjunto de enteros. Debe comprobar que el lmite superior sea
mayor que el lmite inferior. En caso contrario, deber lanzar una excepcin (IllegalArgumentException).
Para inicializar la propiedad conjunto, utilice el constructor de BitSet al que se le pasa el nmero de
elementos, en nuestro caso ser sup menos inf.
3. Implementar los mtodos get y set para las propiedades definidas. Tenga en cuenta si son consultables
y/o modificables.
4. Hacer que la clase ConjuntoEnteros implemente la interfaz Set. Tenga en cuenta que los elementos que
almacenar dicho conjunto son de tipo Integer.
5. Implementar los mtodos de la interfaz Set<Integer>. Si pulsamos sobre el error que aparece en la clase
ConjuntoEnteros, y se selecciona Add unimplemented methods, se crearn todas las cabeceras de los
mtodos de la interfaz Set<Integer> que faltan por implementar.
Aade un elemento al conjunto de enteros. Comprobar si pertenece al rango definido, en caso contrario,
lanzar una excepcin. Comprobar si existe el elemento, en caso contrario, aadirlo al conjunto. El mtodo
devolver verdadero si se modifica el conjunto, falso en caso contrario.
public void clear()
Elimina un elemento del conjunto de enteros. Comprobar si el elemento est contenido antes de
borrarlo, en caso contrario no se har nada. El mtodo devolver verdadero si se modifica el conjunto,
falso en caso contrario.
596
Aade todos los elementos de la coleccin al conjunto de enteros. En este mtodo existen dos
situaciones, para ello, debe comprobar si se trata de una instancia del tipo ConjuntoEnteros.
a. En caso de que sea as, comprobar si el rango del mismo coincide con el rango del
ConjuntoEnteros que estamos modelando. Adems tendr que utilizar mtodos del BitSet para
que la insercin de todos los elementos sea constante.
b. En caso contrario, aadir uno a uno cada uno de los elementos de la coleccin de entrada.
La diferencia entre el primer caso y el segundo es que cuando lo que se recibe es un ConjuntoEnteros,
podemos utilizar los mtodos del BitSet. Slo en este caso se pueden asegurar los rdenes de
complejidad constantes. Sin embargo, en el segundo caso, al tratarse de una Collection, no se puede
aplicar ningn mtodo de BitSet directamente. La nica solucin es aadir uno a uno cada uno de los
elementos del Collection en el ConjuntoEnteros. En tal caso, no podemos asegurar el rden de
complejidad.
El mtodo devolver verdadero si se modifica el conjunto, falso en caso contrario.
Elimina todos los elementos de la coleccin en el conjunto de enteros. En este mtodo existen dos
situaciones al igual que en el mtodo anterior, para ello, debe comprobar si se trata de una instancia del
tipo ConjuntoEnteros.
a. En caso de que sea as, comprobar si el rango del mismo coincide con el rango del
ConjuntoEnteros que estamos modelando. Adems tendr que utilizar mtodos del BitSet para
que el borrado de todos los elementos sea constante.
b. En caso contrario, eliminar uno a uno cada uno de los elementos de la coleccin de entrada.
La diferencia entre el primer caso y el segundo es la misma que en el mtodo addAll. Cuando lo que se
recibe es un ConjuntoEnteros, podemos utilizar los mtodos del BitSet. Slo en este caso se pueden
asegurar los rdenes constantes. Sin embargo, en el segundo caso, al tratarse de Collection, la nica
solucin es eliminar uno a uno cada uno de los elementos del Collection en el ConjuntoEnteros.
El mtodo devolver verdadero si se modifica el conjunto, falso en caso contrario.
597
En este punto tendremos casi completado la clase ConjuntoEnteros. Nos habremos dado cuenta de que nos falta
un mtodo por implementar. En concreto se trata del mtodo iterator(), ya que toda clase que implemente
Set debe ser iterable. Sin embargo, no existe ningn iterador para el tipo BitSet. Para ello, completaremos la
clase interna y pblica que se llama IteradorConjuntoEnteros y que implementa Iterator<Integer>.
Disponemos de un mtodo privado llamado siguiente que devuelve la posicin del prximo ndice del BitSet
cuyo valor es verdadero. Si no hay ms elementos, devuelve -1.
Tambin tenemos implementado el constructor que inicializa el ndice i (propiedad privada de la clase
IteradorConjuntoEnteros. Utiliza el mtodo siguiente. Tenga en cuenta que el valor que se le pasa al mtodo
siguiente es -1 ya que la primera sentencia de este mtodo es incrementar en 1 el valor. De esta forma
comenzaramos en 0.
Debemos implementar los siguientes mtodos:
public boolean hasNext()
Devuelve el siguiente elemento. Tenga en cuenta que la posicin siguiente, no es el valor real del
entero a devolver, por lo que deberemos calcular su valor.
public void remove()
Como no se permite borrar elementos desde el iterador, deber lanzar una excepcin.
A continuacin vamos a disear los casos de prueba para el tipo implementado. Utilizaremos un test de JUnit,
llamado TestConjuntoEnteros que completaremos.
1. Analizar y ejecutar los test ya implementados. Existe algn error? Por qu? Modifiar los casos de test
que consideremos oportunos para que no lance ninguna excepcin.
2. Implementar un caso de test llamado testAddAllCollection(). Definir una coleccin con los elementos
0,2,3,4,5,6 y adirlos a la propiedad conjunto de enteros definida en el test. Comprobar que el conjunto
de enteros contiene todos los elementos de la coleccin de entrada y que el tamao es igual a 7. Qu
caso del mtodo addAll est realizando? Cul es la complejidad de esta operacin en este caso?
3. Implementar un caso de test llamado testAddAllConjuntoEnteros(). Definir un conjunto de enteros del
tipo que acaba de modelar con los elementos 0,2,3,4,5,6 y adilos a la propiedad conjunto de enteros
definida en el test. Comprobar que el conjunto de enteros contiene todos los elementos del conjunto de
enteros de entrada y que el tamao es igual a 7. Qu caso del mtodo addAll est realizando? Cul es
la complejidad de esta operacin en este caso?
598
599
@Override
public boolean containsAll(Collection<?> c) {
boolean res = true;
if (c instanceof ConjuntoEnteros) {
if (((ConjuntoEnteros) c).getInf() != getInf()
|| ((ConjuntoEnteros) c).getSup() != getSup())
throw new IllegalArgumentException();
ConjuntoEnteros cAux = new ConjuntoEnteros(inf, sup);
cAux.addAll((ConjuntoEnteros) c);
((ConjuntoEnteros) cAux).getConjunto().andNot(this.getConjunto());
// RemoveAll en c de los elementos de this.
res = cAux.size() == 0;
// Otra opcin: Hacer la interseccin de c y this y comprobar que el
// tamao de la interseccin sea igual que el tamao de c.
} else {
for (Object i : c) {
res = contains(i) && res;
if (!res)
break;
}
}
return res;
}
@Override
public boolean retainAll(Collection<?> c) {
boolean res = false;
if (c instanceof ConjuntoEnteros) {
if (((ConjuntoEnteros) c).getInf() != getInf()
|| ((ConjuntoEnteros) c).getSup() != getSup())
throw new IllegalArgumentException();
int size = size();
this.getConjunto().and(((ConjuntoEnteros) c).getConjunto());
res = size != size();
} else {
for (Integer i : this) {
if (!c.contains(i))
res = remove(i) || res;
}
}
return res;
}
@Override
public Object[] toArray() {
return Sets.newHashSet(this).toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return Sets.newHashSet(this).toArray(a);
}
@Override
public boolean isEmpty() {
return conjunto.isEmpty();
}
@Override
public Iterator<Integer> iterator() {
return new IteratorConjuntoEnteros();
}
600
No es del
@Override
public boolean removeAll(Collection<?> c) {
if (c instanceof ConjuntoEnteros) {
ConjuntoEnteros ce = (ConjuntoEnteros) c;
for (Integer obj : ce)
remove(obj);
return true;
} else
return false;
}
@Override
public int size() {
/*
* int res = 0; for(int i=inf; i<sup; i++){ if(conjunto.get(i)) {res ++;} }
*/
return conjunto.cardinality();
}
private class IteratorConjuntoEnteros implements Iterator<Integer> {
private int i;
public IteratorConjuntoEnteros() {
i = siguiente(-1);
}
private int siguiente(int i) {
i++;
return conjunto.nextSetBit(i);
}
@Override
public boolean hasNext() {
return i != -1;
}
@Override
public Integer next() {
int it = i + inf;
i = siguiente(i);
return it;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Error:
ConjuntoEnteros.IteratorConjuntoEnteros.remove operacin no soportada.");
}
}
@Override
public String toString() {
return "ConjuntoEnteros [a=" + inf + ", b=" + sup + ", conjunto=" + conjunto + "]";
}
@Override
public int hashCode() { }
@Override
public boolean equals(Object obj) { }
@Override
public ConjuntoEnteros clone() { }
}
601
Set<PartidoPolitico> getPartidosPoliticos():
Set<Municipio> getMunicipios():
en la consulta popular.
602
getPartidosPoliticos(Municipio
m):
603
Solucin:
604
Solucin:
public static <T> List<T> primos(Tree<T> root, T elemento) {
List<T> primos = new ArrayList<T>();
List<T> aux = new ArrayList<T>();
Tree<T> abuelo = null;
if (!elemento.equals(root.getLabel())) {
abuelo = buscaAbuelo(root, elemento);
if (abuelo != null) {
for (Tree<T> hijo : abuelo.getSubTrees()) {
boolean sonHermanos = false;
aux.clear();
for (Tree<T> nieto : hijo.getSubTrees()) {
if (!nieto.getLabel().equals(elemento))
aux.add(nieto.getLabel());
else
sonHermanos = true;
}
if (!sonHermanos)
primos.addAll(aux);
}
}
}
return primos;
}
605
Se pide:
a) Implemente el mtodo buscaAbuelo que recibiendo un Tree<T> y un elemento de tipo String
(correspondiente al nombre del abuelo), devuelva el rbol cuyo nodo raz es el abuelo.
b) Implemente el mtodo misPrimos que recibiendo un Tree<T> y dos elemento de tipo String, uno
correspondiente al nombre de mi abuelo y otros correspondiente a mi nombre, devuelve una lista con mis
primos. (Utilice el mtodo buscaAbuelo del apartado anterior). Por ejemplo, la salida del mtodo para este
rbol y el elemento Manuel y Carlos ser: { [Carmen, 8] }
c) Devuelva aquellos primos que son menores de edad. Para ello:
a. Cree e implemente la clase PredicateMenorEdad.
b. Implemente el mtodo primosMenoresEdad que devuelva una lista con aquellos primos que son
menores de edad. Puede utilizar los mtodos implementados en los apartados anteriores.
606
607
Solucin:
Apartado a)
private static Tree<Persona> buscaAbuelo (Tree<Persona> arbolGenealogico, String elemento) {
Tree<Persona> abuelo = null;
String nombre = arbolGenealogico.getLabel().getNombre();
if (elemento.equals(nombre)) {
abuelo = arbolGenealogico;
} else {
Iterator<Tree<Persona>> it = arbolGenealogico.getSubTrees().iterator();
while (it.hasNext() && abuelo == null) {
Tree<Persona> st = it.next();
nombre = st.getLabel().getNombre();
abuelo = buscaAbuelo(st, elemento);
}
}
return abuelo;
}
Apartado b)
private static List<Persona> misPrimos (Tree<Persona> arbolGenealogico, String nomAbuelo, String yo) {
List<Persona> misPrims
= Lists.newLinkedList();
List<Persona> misHermanos = Lists.newLinkedList();
Tree<Persona> ret = null;
ret = buscaAbuelo(arbolGenealogico, nomAbuelo);
boolean yoEnc = false;
if (ret != null) {
for (Tree<Persona> hijos : ret.getSubTrees()) {
misHermanos.clear();
for (Tree<Persona> nietos : hijos.getSubTrees()) {
if (!nietos.getLabel().getNombre().equals(yo)) {
misHermanos.add(nietos.getLabel());
} else
yoEnc = true;
}
if (!yoEnc) {
misPrims.addAll(misHermanos);
yoEnc = false;
}
}
}
return misPrims;
}
Apartado c.a)
public class PredicadoMenorEdad implements Predicate<Persona> {
@Override
public boolean apply(Persona arg0) {
return arg0.getEdad() < 18;
}
}
Apartado c.b)
private static Iterable<Persona> primosMenoresEdad
(Tree<Persona> arbolGenealogico, String nomAbuelo, String yo)
{
List<Persona> l = misPrimos(arbolGenealogico, nomAbuelo, yo);
return Iterables.filter(l, new PredicadoMenorEdad());
}
608
Se pide:
1. Realizar el mtodo:
Public Tree<Directory> updateSizeDirectories(Tree<Directory> dir): Que actualice lo que ocupan cada uno de los
directorios (atributo Size) tras haber realizado cambios. Para la realizacin del mtodo considere alguno de los
esquemas de recorrido en profundidad que se han visto en clase. Tenga en cuenta que para calcular el tamao
de un nodo primero tiene que calcular el tamao de sus nodos hijos. Por ejemplo, para el rbol de directorios
de la figura A, el resultado sera el rbol de directorios de la figura B.
609
Solucin:
public Tree<Directory> updateSizeDirectories(Tree<Directory> dir) {
Tree<Directory> res = new Tree<Directory>();
if (dir != null && !dir.isEmpty()) {
if (dir.isLeaf()) {
res = new Tree<Directory>(dir.getLabel());
} else {
Integer totalSize = 0;
res = new Tree<Directory>(dir.getLabel());
for (int i = 0; i < dir.getNumChildren(); i++) {
Tree<Directory> arb = updateSizeDirectories(dir.getElement(i));
res.add(arb);
totalSize = totalSize + arb.getLabel().getSize();
}
res.getLabel().setSize(totalSize);
}
}
return res;
}
610
2. Realice una segunda implementacin del tipo Set<E> a partir del tipo BasicHashTable<K, V> con la restriccin
adicional de que la iteracin sobre los elementos mantenga el orden de insercin. Calcule las complejidades
de las diferentes operaciones en funcin del tamao del conjunto.
3. Implemente el tipo List<E> a partir del tipo DynamicArray<E>. Calcular las complejidades de las diferentes
operaciones en funcin del tamao del conjunto. Considere el concepto de coste amortizado como el coste
promedio de ejecutar una operacin un gran nmero de veces.
4. Implemente el tipo List<E> a partir del tipo BasicLinkedList<E>. Calcule las complejidades de las diferentes
operaciones en funcin del tamao del conjunto.
5. Implemente el tipo Set<Integer>, donde los enteros que forman el conjunto estn en el rango [a1, a2), a partir
del tipo Bitset. Calcule las complejidades de las diferentes operaciones en funcin del tamao del conjunto.
6. Implemente el tipo Set<E> a partir del tipo BasicHashTable<K, V>. Calcular las complejidades de las diferentes
operaciones en funcin del tamao del conjunto.
7. Implemente el tipo Map<K, V> a partir del tipo BasicHashTable<K, V>. Calcular las complejidades de las
diferentes operaciones en funcin del tamao del conjunto de las claves.
8. Implemente los tipos Queue<E>, Stack<E>, PriorityQueue<E>. Sumimos que implementan la interfaz
BasicCollection<E> siguiente con la semntica correspondiente. Eleja en cada caso cul es mejor tipo para
llevar a cabo la implementacin.
interface BasicCollection<E> {
int size();
boolean isEmpty();
E element();
boolean add(E e);
E remove();
boolean contains(E e);
}
611
10. Implemente los mtodos para realizar rotaciones a la izquierda o a la derecha en la clase AVLTree. Esta clase
se implementar usando el tipo Tree<E> anterior.
11. Implemente los mtodos para realizar inserciones, eliminar elementos o encontrarlos en la clase AVLTree.
13. Implemente los mtodos containsValue, get, put, remove, size y values del tipo Multimap<K,V> basndose
en el tipo Map<K,V>.
14. Dada las siguientes secuencias de nodos obtenidos en preorden, inorden y postorden de un rbol binario
cuyas etiquetas son caracteres:
preorden: A-D-K-E-B-G-L-M-O-F-U-Y
inorden: K-D-E-G-B-A-M-O-L-U-F-Y
postorden: K-G-B-E-D-O-M-U-Y-F-L-A
Se pide dibujar el correspondiente rbol binario. Disee un mtodo que recibiendo la secuencia de nodos
en preorden o en inorden, devuelva el Tree correspondiente a dichas secuencias.
16. Escriba un algoritmo que dados dos rboles, compruebe si dichos rboles (Tree<T>) son exactamente iguales
(tanto en estructura como en contenido), dando el correspondiente mensaje. Slo se pueden usar todas las
operaciones del tipo Tree<T> salvo el mtodo equals.
612
18. Realice una funcin recursiva que aplicada a un rbol, devuelva un nuevo rbol sea la imagen especular del
primero.
20. Realice un mtodo que recibiendo un Tree<T> y un elemento de tipo T, devuelve una lista con los hermanos
de dicho elemento. Suponga que en el rbol no hay elementos repetidos (cada elemento slo aparece una
vez en el rbol completo).
Ejemplo:
21. Proponga una implementacin posible para el tipo Graph<V, E>. Discuta los casos posibles segn el subtipo
de grafo considerado.
613