Anda di halaman 1dari 615

Escuela Tcnica Superior de Ingeniera Informtica

Anlisis y Diseo de Datos y Algoritmos


Departamento de Lenguajes y Sistemas de Informacin

Miguel Angel Cifredo Campos

SEGUNDO

--- REVERSO DE PORTADA ---

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1 Introduccin a la Recursividad y su relacin con la Iteracin.


1.1 Introduccin.
Objetivos del tema:

Aprender a definir e implementar algoritmos recursivamente sin y con memoria.


Introducir el concepto de generalizacin y tamao del problema.
Discutir la relacin y posible transformacin de los algoritmos recursivos con los algoritmos iterativos.
Aprender cmo se puede comprobar la correccin de los algoritmos recursivos

1.2 Elementos de recursividad.


La definicin recursiva de un problema es una especificacin de la solucin del mismo en base a la de otros
problemas de la misma naturaleza pero de un tamao menor. En estas definiciones aparecen los conceptos de:

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

La idoneidad de la definicin podemos verla con un ejemplo:


3! = 3 2! = 3 2 1! = 3 2 1 0! = 3 2 1 1

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
= !

Pero juntas no forman una definicin recursiva:


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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


definen) se reduzca el tamao. Como este deber ser mayor o igual a cero para todos los problemas de conjunto
considerado que en algn momento llegaremos al caso base. Esto no ocurre en la definicin incorrecta anterior.
Junto con las definiciones recursivas de problemas tenemos algoritmos recursivos. Un algoritmo es recursivo
cuando se llama a s mismo.

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, )

Veamos una traza de la solucin propuesta:


Pila de llamadas (call stack):

edad(2010, 2013) = 3
1+edad(2011, 2013) = 1+2 = 3

nac act

1+edad(2012, 2013) = 1+1 = 2

nac act

1+edad(2013, 2013) = 1+0 = 1

nac act

nac = act

int edad(int nac,


int r;
assert(nac
if (nac ==
r =
} else {
r =
}
return r;
}

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));
}
}

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Otro ejemplo, dada una lista de enteros, obtener el valor mayor, definida como: valMayor(lista, tamao, pos):
{ }
(, , ) = { []
max( [], (, , 1) )

= 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));
}
}

1.3 El operador de Asignacin Paralela.


Se denotada como: (x, y) (y, x) y asigna el antiguo valor de x a y, y viceversa.
x=y

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


En el caso particular que el aserto A(x) no dependa de una variable xi la sustitucin simblica anterior debe
entenderse como: A(x)[xi|ei] A(x) xi = ei(x).
Veamos ahora cmo conseguir un bloque bsico equivalente a una asignacin paralela. La idea general es usar
variables nuevas, asignar a estas variables los valores de las expresiones, posteriormente asignar las nuevas
variables a las antiguas y simplificar el bloque bsico. El esquema es entonces:
a1 = e1 ;
a1 = e2 ;

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)

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.4 Generalizacin de problemas.


En muchos casos es mucho ms fcil encontrar una definicin recursiva de un problema si lo consideramos como
un caso particular de un conjunto de problemas ms amplio (es decir, definidos por ms parmetros). Desde
este punto de vista el problema que tenemos que resolver se obtiene dando valores concretos a algunos
parmetros del problema ms general (problema generalizado).
Para ello se hace una definicin general del problema que luego se ajusta en el problema concreto que queramos
resolver dando valores a los parmetros correspondientes.
Habr que diferenciar entre:

Parmetros compartidos: Comunes a todos los problemas particulares de un conjunto de problemas.

Parmetros individuales: Especficos de cada problema concreto.

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)

Resultado: R = tipo de elementos de dt

Con estos datos es casi imposible plantear una definicin recursiva. Por lo tanto, generalizamos:

Definicin generalizada: Calcular el mximo valor de una lista dt considerando


los elementos comprendidos entre i hasta el tamao n de la lista: mv (i, dt, n)
Problema: E = (i, dt, n)

Resultado: R = tipo de elementos de dt

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

El algoritmo correspondiente es:


int maxVal(int * dt, int n) {
return mv(0, n, dt, n);
}
int mv(int i, int j, int * dt, int n) {
int r;
assert(j > i && i >= 0 && j <= n);
if (j - i == 1) {
r = dt[i];
} else {
r = max(dt[i], maxVal(i + 1, j, dt, n));
}
return r;
}
int max(int a, int b) {
return a >= b ? a : b;
}

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)

=
=
=
=
=

j>i && i>=0 && j<=n


j-i == 1
dt[i]
<i+1, j, dt, n>
max(dt[i], 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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.5 Esquemas Recursivos sin Memoria.


En general, los algoritmos recursivos siguen el siguiente esquema:
R f(E x) {
R r;
LR s;
LE y;
assert(D(x));
if(b(x)){
r = sb(x);
} else {
y = sp(x);
assert(t(y) < t(x));
s = f(y);
r = c(x,s);
}
return r;
}

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;
}

Si queremos hacer explcito el caso de un nmero variable de sub-problemas:


R f(E x) {
R r;
S s;
T y;
assert(D(x));
if(b(x)){
r = sb(x);
} else {
y = sp(x);
s = [];
for(E z: y){
assert(t(z) < t(x));
s = s+f(z);
}
r = c(x,s);
}
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]

s = [s1, s2, , sk]

sp(x) = [sp1(x), sp2(x), , spk(x)]

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

Los algoritmos simples siguen el siguiente esquema:


R f(E x) {
R r;
R s;
E y;
if(b(x)){
r = sb(x);
} else {
y = sp(x);
s = f(y);
r = c(x, s);
}
return r;
}

E: Tipo de las propiedades del problema.


R: Tipo del resultado de la solucin.

Los algoritmos mltiples siguen el siguiente esquema:


R f(E x) {
R r;
LR s;
LE y;
if(b(x)){
r = sb(x);
} else {
y = sp(x);
for(E z: y){
s = s + f(z);
}
r = c(x, s);
}
return r;
}

12

E: Tipo de las propiedades del problema (individuales


<entrada> y compartidas <E/S>).
R: Tipo del resultado de la solucin.
LE: Lista de tipo E, de los diversos sub-problemas.
LR: Lista de tipo R, de resultados intermedios de los subproblemas.
+: Smbolo que indica la unin de un elemento a una lista.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


En general los algoritmos recursivos siguen el siguiente esquema:
T f(T1 p1, , Tn pn) {
T r, rp1, ;
assert(d(p1,,pn));
if(b1(p1,,pn)){
r = sb1(p1,,pn);
}

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;
}

Las funciones anteriores son:


d(n)
t(n)
b1(n)
sb1(n)
sp1(n)
c(n,r1)

=
=
=
=
=
=

n>=0
n
n==0
1
n-1
n*r1

Y en el problema del mximo comn divisor:


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;
}

Las funciones del esquema son:


d(a,b)
t(a,b)
b1(a,b)
sb1(a,b)
sp1(a,b)
c(a,b,rp1)

=
=
=
=
=
=

a>=0 && b>=0 && !((a==0) && (b==0))


b
b==0
a
<b,a%b>
rp1

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.6 Esquemas Recursivos con Memoria.


Recordemos el problema definido recursivamente del clculo de la serie de Fibonacci. Comienza con un 0, luego
con un 1, y a partir de ah, cada nmero es la suma de los dos siguientes. Esto ltimo nos indica recursividad. Por
tanto, la funcin de recursividad puede ser:
0
() = { 1
( 1) + ( 2)

=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):

Ha sido resuelto el problema x?

R r = memo.get(x):

Obtiene la solucin del problema ya resuelto x.

void memo.put(x, r):

Almacena la solucin obtenida r para el problema x.

Los algoritmos recursivos con memoria siguen un esquema similar a este:


R f(E x) {
Map<E,R> m = inicializar;
return f1(x,m);
}

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

Veamos un ejemplo de la implementacin de la serie de Fibonacci con memoria:

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));
}
}

1.7 Algoritmos iterativos.


La idea es obtener algoritmos iterativos (esquemas secuenciales) a partir de los algoritmos recursivos.
Los conceptos que necesitamos son:

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.

Los algoritmos iterativos siguen un esquema similar a este:


R f(T p) {
E x = i(p);
while(g(x)){
x = s(x);
}
return r(x);
}

T: Tipo de problema original.


E: Tipo del problema generalizado.
R: Tipo de la solucin.
i(p): Instanciacin.
g(x): Verdadero si no es estado final.
s(x): Siguiente problema.
r(x): Clculo de la solucin.

16

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.8 Transformacin Recursin Iteracin.


La recursividad lineal puede ser:

Final: Cuando la forma de la funcin de combinacin es de la forma c(x, r) = r, es decir, no depende de


las propiedades del problema.
No final: En cualquier otro caso.

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:
() = (())
() = {

()
(())

()
! ()

Si el problema generalizado es x, el original y, y la funcin de instanciacin i(y), entonces el algoritmo iterativo


resultante es de la forma:
R f(T y){
E x = i(y);
while(!b(x)){
x = sp(x);
}
return sb(x);
}

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.

Dada una definicin recursiva no final:


() = {

()
(, (()))

()
! ()

La definicin final quedara:


() = (, )
(, ) = {

(, )
((), (, ))

()
! ()

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

1.9 Correccin de los algoritmos recursivos.


La correccin de los algoritmos recursivos depende de la especificacin del problema que estemos intentando
resolver. En ellos debemos observar los datos de entrada y los resultados esperados, as como la relacin entre
ellos.
Podemos utilizar como mtodos para probar que un algoritmo recursivo es correcto la Induccin o bien Pruebas
por contradiccin.

Para el mtodo de induccin debemos estudiar:


o
o

Casos base: Verificar que lo que se implementa responde a lo especificado.


Paso inductivo: Dado que lo especificado se cumple para la k primeras instancias, probar que se cumple
tambin para la instancia k+1.

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!
-

Si n > 0, la prueba de la condicin n = 0 falla y x = n * factorial(n-1).

Por hiptesis inductiva, factorial(n-1) devuelve (n-1)! , por lo que factorial(n) devuelve n*(n-1)!,
que en realidad es n!

Para la correccin total n-1 < n y si n es positivo siempre nos acercaremos a n = 0.

Truco: Pasos para transformar una funcin recursiva no final en otra s final.

Funcin Recursiva No Final:


() {

1
=0
( 1) 0

Funcin Recursiva Final:


(, ) {

=0
( 1, ) 0

Paso 1: Se mantienen los mismos casos bases y casos recursivos.


Paso 2: Se copia la misma variacin de n de cada llamada recursiva (i.e. n-1)
Paso 3: La misma operacin que se realiza sobre la llamada recursiva no final, se aplica sobre el acumulador.
Paso 4: El caso base se convierte en el acumulador.

18

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.10 Ejercicios resueltos.


1.10.1 Clculo de la edad de una persona.
Dados el ao actual y el de nacimiento de una persona, determinar la edad que tiene.

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;

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.10.2 Mximo Comn Divisor (Algoritmo de Euclides).


Calcular el mximo comn divisor de dos nmeros, mcd(a, b), utilizando el Algoritmo de Euclides:

=
<
(, ) = { (, )
(, %) >

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.10.3 Factorial de un nmero.


Calcular el factorial de un nmero n (n!) para nmeros enteros positivos.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.10.5 Potencia segn la paridad del exponente.


Dada una base (a) y un exponente (n), calcular la potencia de an teniendo en cuenta si el exponente es un
nmero par o impar, segn se ilustra a continuacin:
8 = (4 )2
9 = (4 )2

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.10.6 Sumar los dgitos de un nmero.


Dada un nmero entero positivo, n, calcular la suma aritmtica de sus dgitos.
Ejemplo:

Sea el nmero n = 2347

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.10.7 Nmeros de la Serie de Fibonacci.


Calcular los nmeros de la serie de Fibonacci, siendo su secuencia como sigue:
() = {

( 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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.10.8 Combinaciones sin repeticin.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.10.9 Divisin por sucesin de restas.


Obtener la divisin aplicando la operacin resta de manera recursiva. As, si se desea realizar A / B y obtener
el cociente C y el resto R, el proceso consistir en restar a A la cantidad B hasta que el resultado de la resta
sea menor que el propio B. El nmero de operaciones de restas realizadas es C y el resultado de la ltima
resta es A.
Nota: La funcin recursiva necesita conocer ms de un parmetro: necesita saber cul es el A actual, cul es
el B actual y cul es el nmero de restas que se han realizado, C.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.10.10

VECTOR. Sumar los elementos.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.10.11

VECTOR. Comprobar si todos son pares.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.10.12

VECTOR. Determinar el valor ms grande.

Determinar cul es el valor ms grande entre los elementos de un vector.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.10.13

VECTOR. Determinar la posicin donde se encuentra el valor ms pequeo.

Determinar en qu posicin se encuentra el valor ms pequeo de un vector.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.10.14

VECTOR. Producto Escalar.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.10.15

Resolver una funcin (1).

Implemente recursivamente e iterativamente la siguiente funcin:


() = {

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.10.16

Resolver una funcin (2).

Implemente recursivamente e iterativamente la siguiente funcin.


2
1
() = {
1
2 ( 1) + 3 ( 2) ( 3)

=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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.10.17

CADENA. Obtener una cadena de caracteres a partir de un nmero.

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:

public class Int2txt {


public static void main(String[] args) {
int n = 2468;
System.out.println("El nmero " + n + " es como cadena >" + int2txt_N(n, "") + "<");
}
public static String int2txt_N(int n, String txt) {
txt = n % 10 + txt;
if ((n / 10) == 0) {
return txt;
} else {
return int2txt_N(n / 10, txt);
}
}
}

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

1.10.18

CADENA. Invertir una cadena.

Dada una cadena de caracteres cad, de tamao tam, obtener su cadena inversa.

SOLUCIN:

Algoritmo No Final:
(, , ) = {

[]
[] & (, , 1)

= 0
> 0

public class Ejercicio {


public static void main(String[] args) {
String texto = "Hola, estamos probando la inversin de una cadena.";
System.out.println("Cadena normal : " + texto);
System.out.println("Cadena invertida: " + inverso(texto, texto.length(), texto.length()-1));
}
public static String inverso(String cad, int tam, int pos) {
String res = "";
if (pos == 0) {
res = "" + cad.charAt(pos);
} else {
res = "" + cad.charAt(pos) + inverso(cad, tam, pos - 1);
}
return res;
}
}

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

FIGURA. Contabilizar tringulos.

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

Reduzcamos la complejidad de la solucin planteada, que sale cuadrtica, redefiniendo la funcin


tringulos(n) de la siguiente forma:
() = 2 1

luego tenemos:
() = {

1
(2 1) + ( 1)

=1
>1

de esta forma hemos pasado de:


public int triangulos(int n) {
return n == 1 ? 1 : (2 + triangulos(n - 1));
}
public int sumador(int n) {
return n == 1 ? 1 : (triangulos(n) + sumador(n - 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

public int sumador(int n, int acu) {


return n == 1 ? 1 : sumador(n - 1, (2 * n - 1) + acu);
}

Algoritmo Iterativo:
public int sumador(int n) {
int i = 1;
int acu = 0;
while (i <= n) {
acu += (2 * n - 1);
i--;
}
return acu;
}

54

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

2 Anlisis de la eficiencia de algoritmos.


2.1 Introduccin.
Cuando diseamos algoritmos es necesario demostrar en primer lugar que acaban y que hacen el cometido
especificado, adems que resulte fcil de entender, codificar, depurar, verificar y de mantener, al mismo tiempo
que usan eficientemente los recursos del ordenador y se ejecutan en el menor tiempo posible. Pero en segundo
lugar es conveniente estimar el tiempo que tardarn en ejecutarse en funcin del tamao del problema a
resolver. Deberemos por tanto analizar la complejidad de los algoritmos y estimar el tiempo de ejecucin.

2.2 rdenes de complejidad.


En general estamos interesados en el tiempo de ejecucin como una funcin del tamao del problema cuando
el tamao es grande. Representaremos el tamao de un problema por n. En general n ser una funcin de los
valores de las propiedades x del problema. Es decir n = f(x). Representaremos por T(n) la funcin que nos da el
tiempo de ejecucin en funcin del tamao. En los estudios de complejidad de algoritmos asumimos que todas
las funciones T(n) son montonas crecientes y normalmente slo estaremos interesados en los aspectos
cualitativos de T(n), es decir en su comportamiento para valores grandes de n. Para ello clasificamos las funciones
segn su comportamiento para grandes valores de n. Esta clasificacin agrupar las funciones T(n) en rdenes
de complejidad. Cada orden de complejidad es un conjunto de funciones con comportamiento equivalente para
grandes valores de n.
Para concretar lo anterior introduciremos una relacin de orden total entre las funciones. Este orden define
implcitamente una relacin de equivalencia y unas operaciones de mnimo y mximo asociadas.
Representaremos este orden por el smbolo < . Una funcin h(n) es menor que otra g(n) segn este orden
cuando el lmite de su cociente es cero. Es decir:
() < () 0 = lim

()

()

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.

2.2.1 Constante multiplicativa k.


() () + lim

()
=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.

2.2.2 Jerarqua de rdenes de complejidad exactos.


Como hemos explicado anteriormente los rdenes de complejidad exactos son conjuntos de funciones entre los
cuales se puede definir una relacin de igualdad y otra de orden. En el anlisis de algoritmos hay varios rdenes
de complejidad exactos que reciben nombres especiales. stos, a su vez, podemos organizarlos en una jerarqua
de menor a mayor:
(k)
(log(n))
(n)
(nlog(n))
(n2)
(nk)
(kn)
(n!)
(nn)

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

2.2.3 Otros rdenes de complejidad.


Al comparar dos algoritmos de distinto orden hay que tener muy en cuenta las constantes multiplicativas. Junto
con el orden de complejidad exacto, (g(n)), se usan otras notaciones O(g(n)) (cota superior), (g(n)) (cota
inferior). Todos definen conjuntos de funciones:
: () (())
: () (())

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 (

2.3 Recurrencias Lineales.


Para calcular la complejidad de algoritmos recursivos es necesario plantear ecuaciones de recurrencia que
relacionan la complejidad del problema con la de los sub-problemas y la de la obtencin de los sub-problemas y
la combinacin de las soluciones. Usualmente aparecen recurrencias lineales de la forma:
0 () + 1 ( 1) + + ( ) = 1 1 () + 2 2 () + + ()

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

En el clculo de la complejidad slo nos interesa el orden de complejidad de la expresin anterior:


1

(()) = ( ) = ( 1 )
=1 =0

donde rh es la solucin de la ecuacin caracterstica generalizada con mayor valor.


Ejemplo:
El problema de Fibonacci es, como hemos visto en el tema anterior, f(n) = f(n-1) + f(n-2).
El tiempo de ejecucin, T(n), verifica la ecuacin de recurrencia: T(n) = T(n-1) + T(n-2) + k.
La ecuacin caracterstica ser: (x2 x 1) (x 1) = 0
Sus races son: 1 =

1+5
2

= 1618 y 2 =

15
2

= 0618

Y como |1 | > 1, |2 | < 1


Entonces: (()) = (1 ) = ((

1+5
2

) )

Casos particulares:
1er caso:
Un caso particular de este tipo de ecuaciones que tiene mucha utilidad es:
() = ( ) + ()

Siendo g(n) un polinomio de grado d. La ecuacin caracterstica generalizada es:


( ) ( )+1 = 0

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}
( )
() { ( ())
( )

>
=
<

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

2.4 Recurrencias con Sumatorios.


Nos podemos encontrar con la necesidad de calcular sumatorios de expresiones cuyas variables siguen
progresiones aritmticas o geomtricas. Estando interesados en el orden de complejidad de los sumatorios
cuando el lmite superior tiende a infinito.
Los sumatorios ms usuales son del tipo:

() = () + ( + 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

De la misma forma, por la definicin de integral, tenemos las siguientes relaciones:


1

1 ()

1 ()

() (())

(()) =

=,
= ()

=0

(()) ()
()

1
()
( ())

En general, por quedar claro el contexto, simplificamos la notacin:

Para progresiones aritmticas donde h(x) = a + rx, h(x) = r tenemos:

()

(1 ())

=+

()

1
()

Para progresiones aritmticas donde h(x) = arx, h(x) = aln rrx, h(h-1(x)) = xln r tenemos:

()

()
(1 ())

1 ()

ln

Casos particulares de sumatorios son:

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

2.5 Complejidad de los algoritmos.


2.5.1 Tamao de problemas y casos de ejecucin.
En lo que sigue representaremos los problemas por p, p1, p2, , pr . Los problemas se agruparn en conjuntos de
problemas. Un conjunto de problemas lo representaremos por P. Cada problema tendr unas propiedades x.
Cada propiedad especfica la representaremos mediante un superndice: x = x1, , xk. Dentro de un conjunto de
problemas P los valores de sus propiedades identifican al problema de manera nica.
Asociado a un problema podemos asociar el concepto de tamao, como una nueva propiedad derivada del
problema. 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
n = t(x) o bien n = t(p). Como hemos dicho, un problema dentro de un conjunto de problemas, puede
representarse por un conjunto de propiedades x. Entonces el tamao es una nueva propiedad del problema que
se calcula a partir de esas propiedades n = t(x). Por tanto, cada problema dentro de un conjunto de problemas,
tendr un tamao.
En los algoritmos recursivos podemos entender que cada llamada recursiva resuelve un problema distinto y el
tamao de cada uno de los sub-problemas debe ser menor que el tamao del problema original. Por analoga,
los algoritmos iterativos van transformando un problema en otro, tambin de tamao ms pequeo, hasta que
se encuentra la solucin (caso base).
Dado un conjunto de problemas y un algoritmo para resolverlos, el tiempo que tardar el algoritmo para resolver
un problema dado P, depender del tamao del mismo. El tiempo que tarda el algoritmo en funcin del tamao
del problema que resuelve lo representaremos por la funcin: T(n).
Varias instancias de un mismo problema con el mismo tamao pueden tardar tiempos diferentes. Dentro de los
problemas con un mismo tamao llamaremos caso peor a aquel problema que tarde ms tiempo en resolverse
y lo representaremos por pp , y por Tp(n) el tiempo que tarda en funcin del tamao. Igualmente el problema
que tarde menos tiempo en resolverse, de entre los que tienen el mismo tamao, lo llamaremos caso mejor y
lo representaremos por pm , y por Tm(n) el tiempo que tarda en funcin del tamao. No debe confundirse caso
mejor con que su tamao sea pequeo, ya que se trata de conceptos diferentes. Por ltimo, si los problemas con
tamao n tienen una distribucin de probabilidad f(p), entonces llamaremos caso medio y lo representaremos
por Tmd(n) a la media de los tiempos que tarda cada uno de esos problemas, es decir,
() =

()()

| ()=

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

2.5.2 Complejidad de los algoritmos iterativos.


Un algoritmo iterativo se compone de secuencia de bloques. Cada bloque es un bloque bsico, un boque if o
un bloque while. Un bloque bsico es una secuencia de instrucciones sin ningn salto de control (es decir sin
if, ni while).
Veamos la forma de determinar los tiempos Tp, Tm, Tmd para los bloques bsicos, la estructura if y la estructura
while. Hablaremos en general de T para referirnos indistintamente al caso peor, mejor o medio y usaremos
superndices cuando queramos hacerlo explcito.
Para un bloque bsico la forma de estimar el tiempo de ejecucin es sumando el tiempo que tarda cada una de
las instrucciones elementales que lo componen. Si hacemos la hiptesis simplificadora que cada instruccin
elemental tarda el mismo tiempo entonces el tiempo que tarda un bloque bsico en ejecutarse es proporcional
al nmero de operaciones elementales. Es decir, para un bloque bsico:
Tp(n) = Tm(n) = Tmd(n) = c k
Donde k es el nmero de operaciones elementales del bloque bsico y c el tiempo de ejecucin de una sentencia
elemental. Por tanto, el tiempo de ejecucin de un bloque bsico no depende del tamao del problema.

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.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

2.5.3 Mtodo de la instruccin crtica para algoritmos iterativos.


Este mtodo simplifica en algunas ocasiones el clculo de la complejidad de los algoritmos iterativos. Para
aplicarlo se trata de buscar la instruccin crtica, que es aquella que se ejecuta el mximo nmero de veces.

Este mtodo dice:


La complejidad de un algoritmo iterativo es igual a la complejidad
del nmero de veces que se ejecute la instruccin crtica.
(T(n)) = (N(n))
donde N(n) es el nmero de veces que se ejecuta la instruccin
crtica en funcin del tamao del problema n.

Este enunciado es fcil de comprobar para algoritmos de la forma:


r;
while(g){
s;
}

En efecto la instruccin crtica es la guarda.


El cardinal del conjunto es el nmero de veces que
se ejecuta el cuerpo: 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 () ()

Donde Ni(n) es el nmero de veces que se ejecuta el bucle anidado i.


En el caso general para calcular N(n) para una instruccin concreta aparecen sumatorios cuyo orden de
complejidad habr que calcular con la tcnicas citadas antes.

63

2.5.4 Complejidad de los algoritmos recursivos sin memoria.


Los problemas que se resuelven con tcnicas recursivas sin uso de memoria dan lugar a un tipo de recurrencias
estudiadas anteriormente. Como ya hemos visto, un problema que se resuelve un algoritmo recursivo del tipo
Divide y Vencers, adopta la forma:
() = {

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

Si el tamao de los sub-problemas es diferente, aparecen recurrencias del tipo

() = () + ( ) ,

> 0

=1

Que se resuelven por la generalizacin del llamado Master Theorem.

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

Estas hay que resolverlas numricamente.

64

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

2.5.5 Complejidad de los algoritmos recursivos con memoria.


Si se usa memoria, el clculo del tiempo es diferente al caso de no usar memoria. Sea, como antes, n el tamao
de x, y sea g(n) el tiempo necesario para calcular los sub-problemas ms el tiempo para combinar las soluciones
en un problema de tamao n, cada problema dentro de un conjunto de problemas, puede ser identificado de
forma nica, por sus propiedades individuales. Sean los problemas x, x1, x2, , xk y sea ni = t(xi) el tamao del
problema i. Para resolver un problema dado debemos resolver un subconjunto de problemas. Sea Ix el conjunto
formado por los problemas que hay que resolver para calcular la solucin de x, entonces el tiempo de ejecucin
verifica:
() = (())

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

2.6 Determinacin del umbral para algoritmos recursivos.


Una cuestin importante en la tcnica de Divide y Vencers es la definicin del tamao que separa los casos base
de los casos recursivos. Llamaremos umbral a ese tamao. Un problema se considera simple (caso base) cuando
su tamao es menor o igual que el umbral elegido. Veamos cmo calcularlo. El tiempo de ejecucin del algoritmo
depender del umbral seleccionado, aunque como hemos visto no cambiar su cota asinttica. Sea un algoritmo
del tipo Divide y Vencers donde el coste de la solucin directa (caso base) sea h(n), entonces el tiempo de
ejecucin verifica:
() = {

()

( ) + ()

1 0
> 0

Dependiendo del umbral que se escoja, las


constantes multiplicativas resultantes en la funcin
T(n) variarn. El umbral ptimo ser aquel para el
que T(n) sea menor, y se encontrar en aquel valor
de n para el que sea indiferente realizar otra llamada
recursiva o usar el caso base, por lo que podremos
tomar que los sub-problemas siguientes se
resuelven mediante el caso base.

Sustituyendo T(n) = h(n) en ( ) + () e igualando a h(n) la ecuacin para el umbral es:


(

0
) + (0 ) = (0 )

El clculo del umbral ptimo puede hacerse de dos formas:


Empricamente: Se ejecuta el algoritmo con distintos umbrales hasta determinar el ptimo.
Tericamente: Se trabaja sobre la ecuacin de recurrencia para determinar su valor.

Ejemplo. Sea el algoritmo con la ecuacin de recurrencia dada por:


2

() = {
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

Es decir n0 = 64, ser el umbral ptimo.


66

160 =

1 2
,
4 0

0 = 64

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

2.7 Ejercicios de rdenes de Complejidad.


2.7.1 Algoritmos iterativos.

Sean los siguientes ejemplos donde consideramos las variables previamente declaradas de tipo entero,
determine la complejidad de los siguientes algoritmos:

Problema 1:

for (int i = 1; i <= n; i++) {


r += a * i;
}

Describimos cada operacin realizada:


-

La inicializacin de la variable i, es una operacin.


Por cada iteracin (desde 1 hasta n veces) del bucle se ejecutan las siguientes operaciones:
- Comprobar si se ha alcanzado el final del bucle, una operacin.
- Incrementar el valor de la variable i en una unidad y asignar el nuevo valor a la variable i,
dos operaciones.
ltima comprobacin de la guarda que, al no verificarse, finaliza la iteracin, una operacin.

Por tanto tenemos:

() 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;
}

Este problema puede describirse desde estos dos casos semejantes:

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;
}

Para ambos casos observamos que:


i

Caso 1: {1, 1+r, 1+2r, 1+3r, ,n}


es lo mismo !
Caso 2: {n, n-r, n-2r, n-3r, , 1}

luego,

()

0 0 0+1 (log )0 = 0
=1+

donde k=0 y p=0, por tanto: () ()


O bien,

()

0 0
=1+

() ()

Tambin podemos plantearlo mediante una ecuacin recursiva considerando que el tamao es m = n - i.
() = ( ) + 0

donde a=1, b=r, c=1, d=0, por tanto, si r 1, entonces: () ()


El problema original tiene la complejidad asociada al primer valor del tamao () ( ) = ().

68

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Problema 3:

i = 1;
while (i <= n) {
s = s + i;
i = i / r;
}

Este problema puede describirse desde estos dos casos semejantes:

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;
}

Para ambos casos observamos que:


i

Caso 1: {1, 1r1 , 1r2 , 1r3 , ,n}


es lo mismo !
Caso 2: {n, 1r-1 , 1r-2 , 1r-3 , , 1}

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

donde a=1, b=r, d=0, p=0 por tanto, si r 2 entonces: () ( )


El problema original tiene la complejidad asociada al primer valor del tamao () ( ).

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;
}

El ndice vara desde 1 hasta , luego:


=

()
=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

() ()

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Problema 8:
Suponiendo que los valores de a[i] pertenecen a 0 k (k<n) y todos son igualmente probables calcular
la complejidad del caso medio para valores grandes de n y k.
r = 0;
for (int i = 0; i < n - k; i++) {
if (a[i] == k) {
for (j = 1; j <= a[i]; j++) {
r += a[i + j];
}
}
}

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

Qu ocurre al variar k en los siguientes casos: k = 1, k = n/2 y k = n 1 ?


(, 1)

(, )
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

() ( )

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

2.7.2 Algoritmos recursivos.

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

Donde: a=2, b=3, d=0, p=0

() = 2 ( ) + 0
3

Luego: () ( ) ( )
73

Problema 14:

int Func (int n) {


int x, j, i;
if (n < 10) {
i = n;
} else {
i = 1;
j = 0;
while ((i * i) <= n) {
j = j + A(i);
i = i + 1;
}
x = n;
while (x > 1) {
j = j + x;
x = x / 4;
for (int ii = 1; ii <= n; ii++) {
j = j * B(ii, n);
}
}
i = 2 * Func(n / 2) + j;
}
return i;
}

Asumiendo los resultados de los problemas 7 y 8 anteriores tenemos: () = (2 ) + (2 log )


Donde hemos tenido en cuenta que (n) + (2 log ) (2 log )
donde: a=1, b=2, d=2, p=1

Luego: () ( )

Problema 15:

double Func (int n, double a)


double r;
if (n == 1) {
r = a;
} else {
r = Func(n / 2,
for (int i = 1;
r += a *
}
}
return r;
}

a + 1) - Func(n / 2, a - 1);
i <= n; i++) {
i;

Observamos que la operacin crtica es el bucle:


for (int i = 1; i <= n; i++) {
r += a * i;
}

Segn hemos visto en el Problema 3, tenemos:

() = (log ) () ( )
=

donde a=2, b=2, d=1, p=0


74

Luego: () ( ) = ( )

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Problema 16:
int Func (int a, int b) {
int r;
if (a == 0 || b == 0) {
r = 1;
} else {
r = 2 * Func(a - 1, b - 1);
}
return r;
}

Sea n = min(a, b). Tengamos en cuenta que min(a-1, b-1) = n-1.


Luego: () ()

Por tanto () = ( 1) + 0 , donde: a=1, b=1, d=0


Problema 17:

int Func (int n) {


int i, j, z, r;
if (n < 1) {
r = 1;
} else {
z = 0;
for (i = 1; i < n; i++) {
for (j = 1; j < i * i; j++) {
z++;
}
}
r = z * ((Func(n - 2)) ^ 2);
}
return r;
}

Tomando el resultado del Problema 5, tenemos:


() = ( 2) +
1

() =
=1

2 1

2 1

0 = 0

1 0 0 2

=1

=1

=1

=1

=1

1 3
+ (3 )
3

() = ( 2) + = ( 2) + 3

donde: a=1, b=2, c=1, d=3

aplicamos a = cb Luego: (+1 ) = (4 ) y por tanto, () ( )

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) + ( )

donde: a=1, b=1, d=1,

Luego: ( ) ( 2 ).

La llamada inicial, con npr = p = n , tiene un tiempo ( ) ( )

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

2.7.3 Funciones Recurrentes.

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

2.7.4 Cmo obtener el Orden de Complejidad de una definicin recursiva.

Para obtener el Orden de Complejidad de una definicin recursiva debemos seguir los siguientes pasos:

Paso 1: Obtener una Definicin Recursiva a travs de operaciones elementales.

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)

Esto se debe a que estamos calculando el conjunto los tiempos de


ejecucin y no operaciones aritmticas entre llamadas recursivas.

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

y aplicamos el siguiente modelo:


0 () + 1 ( 1) + + ( ) = 1 1 () + 2 2 () + + ()

donde deberemos identificar cada variable, estas son:


a0 = 1, a1 = -1, a2 = -1, k = 2 (valor mximo de T(n k)) .
pero si hubiramos tenido algo como:
() ( 1) ( 2) = 2 + 22 + 3

distinguimos dos polinomios diferentes, que trataramos de distinta manera, estos son:
(22 + 2) + (3 )

identificamos cada variable:


Primer polinomio (22 + 2) : coeficiente (b1) = 1

grado (d1) = 2

Segundo polinomio (3 ) :

grado (d2) = 0

coeficiente (b2) = 3

sustituyendo en:
1 1 () + 2 2 () + + ()

obtendramos:
1 1 () + 3 2 ()

lo que hubiera dado lugar a la expresin:


() ( 1) ( 2) = 1 () + 3 ()

78

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


sigamos, suponiendo que la funcin recursiva que estudiabamos al principio (Serie de Fibonacci) es ahora esta
otra que cuenta con los dos polinomios adicionales descritos.

Paso 4: Obtenemos el Polinomio Caracterstico, usando la siguiente ecuacin caracterstica:


(0 + 1 1 + + ) ( 1 )1 +1 ( 2 )2+1 ( ) +1 = 0

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

Y nos quedamos con la raz de mayor valor, r = 3, y anotamos su multiplicidad, m = 1.

Paso 6: Obtenemos el Orden de Complejidad (), sustituyendo los valores definidos, en la siguiente expresin:
(()) = (1 )

Tendremos por tanto,


(()) = (11 3 ) = (3 ) = ( )

Concluimos por tanto que el Orden de Complejidad de la funcin recursiva propuesta es:
() ( ).

79

2.7.4.1

Ejercicio propuesto n 1

Obtener el Orden de Complejidad de la siguiente Definicin Recursiva:


1
+2
() = { 4( 1) ( 2)
+2
3

=0
=1
>1

Paso 1: Ya tenemos la Definicin Recursiva, nos la ha proporcionado el propio enunciado.


Paso 2: Obtenemos la Ecuacin de Recurrenciaicin, T(n) de los componentes recursivos de la Definicin
Recursiva anterior:
() =

4
1
( 1) + ( 2) + 2
3
3

Paso 3: Obtenemos la Funcin de Recurrencia a partir de la Ecuacin de Recurrencia:


4
1
() ( 1) ( 2) = 2
3
3

y aplicamos el siguiente modelo:


0 () + 1 ( 1) + 2 ( 2) = 1 1 ()

identificamos cada variable, estas son:


4

a0 = 1, a1 = - 3, a2 = - 3, b1 = 1, d1 = 0 k = 2, [d1 = 0, el polinomio es una cte., un 2]


4

Establecemos, por simplificacin: a1 = - 1 33 1 , a2 = 0 33 1

Paso 4: Obtenemos el Polinomio Caracterstico, sustituyendo esas variables, en la ecuacin caracterstica:


(0 + 1 1 + + ) ( 1 )1+1 = 0
(1 2 + (1) 21 + (1) 22 ) ( 1)0+1 = 0
( 2 1) ( 1) = 0

Paso 5: Calculamos las races del Polinomio Caracterstico y sus multiplicidades:


( 1)

1 = 1

( 2 1)

=1

1 + 5
16
2
1 5
3 =
06
2
{
2 =

Nos quedamos con la raz de mayor valor, r =

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 )

Tendremos por tanto,

1 + 5
1 + 5
(()) = (11 (
) ) = ((
) ) = ( )
2
2
.

Concluimos por tanto que el Orden de Complejidad de la definicin recursiva propuesta es: () ( ).
80

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


2.7.4.2

Ejercicio propuesto n 2

Obtener el Orden de Complejidad de la siguiente Ecuacin de Recurrencia:


() = ( 1) + + 22 + 3

Paso 1:y Paso2: Ya tenemos la Ecuacin de Recurrencia, nos la ha proporcionado el propio enunciado.

Paso 3: Obtenemos la Funcin de Recurrencia a partir de la Ecuacin de Recurrencia:


() ( 1) = 22 + 4

y aplicamos el siguiente modelo:


0 () + 1 ( 1) = 1 1 ()

identificamos cada variable, estas son:


a0 = 1, a1 = -1, b1 = 1, d1 = 2, k = 1
Paso 4: Obtenemos el Polinomio Caracterstico, sustituyendo esas variables, en la ecuacin caracterstica:
(0 + 1 1 + + ) ( 1 )1+1 = 0
(1 1 + (1) 11 ) ( 1)2+1 = 0
( 1) ( 1)3 = 0

Paso 5: Calculamos las races del Polinomio Caracterstico y sus multiplicidades:


( 1) ( 1)3

1 = 1

=4

Nos quedamos con la raz de mayor valor (nica), r = 1, y con su multiplicidad, m = 4.

Paso 6: Obtenemos el Orden de Complejidad (), sustituyendo los valores definidos, en la siguiente
expresin:
(()) = (1 )

Tendremos por tanto,


(()) = (41 1 ) = (3 )

Concluimos por tanto que el Orden de Complejidad de la definicin recursiva propuesta es: () ( ).

81

2.7.4.3

Ejercicio propuesto n 3

Obtener el Orden de Complejidad de la siguiente Ecuacin de Recurrencia:


() = 4( 2) + 5

Paso 1:y Paso2: Ya tenemos la Ecuacin de Recurrencia, nos la ha proporcionado el propio enunciado.

Paso 3: Obtenemos la Funcin de Recurrencia a partir de la Ecuacin de Recurrencia:


() 4( 2) = 5

y aplicamos el siguiente modelo:


0 () + 1 ( 2) = 1 1 ()

identificamos cada variable, estas son:


a0 = 1, a1 = -4, b1 = 1, d1 = 1, k = 2
Paso 4: Obtenemos el Polinomio Caracterstico, sustituyendo esas variables, en la ecuacin caracterstica:
(0 + 1 1 + + ) ( 1 )1+1 = 0
(1 2 + (4) 21 ) ( 1)1+1 = 0
( 2 4) ( 1)2 = 0

Paso 5: Calculamos las races del Polinomio Caracterstico y sus multiplicidades:


( 1)2
( 2 4)

1 = +1
2 = +2
{
3 = 2

=2
=1
=1

Nos quedamos con la raz de mayor valor, r = +2, y con su multiplicidad, m = 1.

Paso 6: Obtenemos el Orden de Complejidad (), sustituyendo los valores definidos, en la siguiente
expresin:
(()) = (1 )

Tendremos por tanto,


(()) = (11 2 ) = (2 ) = ( )

Concluimos por tanto que el Orden de Complejidad de la definicin recursiva propuesta es () ( ).

82

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


2.7.4.4

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:
-

Identificamos las dimensiones del vector v como: 1


Sea el tamao del vector = j i

Tomemos como pivote la mitad del vector, esto es: = 2

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]
{ (, , )

Donde, adems, se define: =

1 ( )
2 ( )
3 ( )
4 ( )

Tamao del problema: tamao del vector, esto es, j i.


Caso mejor: los casos base.
Caso peor: los casos recursivos.

83

2.8 Ejercicios de exmenes.


2.8.1 Ordenacin de una array de enteros.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Aparatado 3)
El algoritmo podra ser reescrito con el bucle ms externo mediante un while para facilitar la visualizacin
de las caractersticas que podra tener el algoritmo recursivo final del que procede este algoritmo
iterativo.
void InsertionSort(int a[], int
int j, p = 1;
int temp;
while (p < n) {
temp = a[p];
for (j = p; j > 0
a[j] = a[j
a[j - 1] =
}
p++;
}
}

n) {

&& a[j - 1] > temp; j--) {


- 1];
temp;

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 )

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

2.8.2 Imprimir al revs.


Dada una palabra que tiene n caracteres (n > 0).
1. Realizar una funcin recursiva final imprimeAlReves en el lenguaje Java que imprima dicha palabra al
revs. Indicar cul debera ser la llamada inicial a dicha funcin.
2. Especificar cules son los casos mejor y peor para dicha funcin, y calcular la complejidad de cada uno
de ellos.
3. Explicar cmo se podra realizar la transformacin de la funcin recursiva final a una iterativa, para
obtener una funcin imprimeAlReves iterativa.
4. Modificar la funcin anterior para que imprima al revs solamente los k (k>0) primeros caracteres de la
palabra, siendo k mucho menor que n. Indicar cul debera ser ahora la llamada inicial a dicha funcin.
Especificar ahora cules son los casos mejor y peor para dicha funcin, y calcular la complejidad de cada
uno de ellos.

Nota: Algunos mtodos y su descripcin de la clase String:


MTODO
int length()
int indexOf(int ch)
int lastIndexOf(int ch)
char charAt(int index)
String substring(String n1,String n2)
String toUpperCase()
String toLowerCase()
String replace(char oldChar, char newChar)

boolean equalsIgnoreCase(String cad)

int compareTo(String OtroString)

int compareToIgnoreCase(String Otro)


String valueOf(N)
char[] toCharArray()

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);
}
}

La llamada inicial a dicha funcin sera:

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));
}

La llamada inicial a dicha funcin sera:

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);
}
}

La llamada inicial a dicha funcin sera:

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

2.8.3 Divisores de un nmero.


Dado un nmero n, se desea determinar el nmero de divisores de dicho nmero distintos de 1 y del propio n
(1 y n siempre van a ser divisores de n, y por lo tanto, no se van a tener en cuenta).
La solucin a dicho problema se puede definir recursivamente de la siguiente forma:
0
(, ) = { (, + 1)
1 + (, + 1)

=
( < ) (% 0)
( < ) (% = 0)

siendo la llamada inicial: divisores(n, 2).


Se pide:
1. Implemente en lenguaje C el mtodo int divisores(int n, int k) acorde a la definicin recursiva
proporcionada.
2. Indique cul es el tamao y el orden de complejidad del mtodo implementado en el apartado anterior.
Justifique su respuesta.
3. Para dicho problema:
a. Proporcione una definicin recursiva final.
b. Indique cul sera la llamada inicial para dicha definicin recursiva final.
c. Implemente en lenguaje C el mtodo int divisoresFinal(int n, int k, int ac) acorde a dicha
definicin recursiva final.
4. Implemente en lenguaje C el mtodo int divisoresIterativo(int n, int k) que resuelva el problema
de forma iterativa.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

2.8.4 Clculo de la interal.


La siguiente expresin es una descripcin recursiva para la integral de una funcin f entre dos puntos a y b.

() = () + ()

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)

() = 2 (2 ) + entonces: () = 1 + 2 para el caso mejor y caso peor.


91

2.8.5 Determinar la existencia de un elemento en un vector.


Dada la siguiente funcin:
#define FALSE 0
#define TRUE 1
#define TAM 100
int vv[TAM];
int m(int v[], int a, int b, int c) {
int x = FALSE;
if (a <= b) {
if (v[a] == c) {
x = TRUE;
} else {
x = m(v, a + 1, b, c);
}
}
return x;
}

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


SOLUCIN:

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)

El caso peor se produce cuando el elemento c no se encuentra en el array ( V[i] i ).


El T(n) para el caso peor ser:
Sabiendo que: a=1, b=1, d=0, entonces T(n) = (nd+1) luego, T(n) = T(n -1) + k (n)

93

2.8.6 Suma de los dgitos de un nmero.


Dado un nmero de tipo long int se desea implementar en el lenguaje C la funcin que suma todos sus dgitos.
Por ejemplo el nmero 7104546031 devolvera como resultado 31. El prototipo de la funcin podra ser:
int sumaDigitos(long int n)

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

2.8.7 Clculo de un sumatorio.


Se desea disear un algoritmo recursivo no final denominado calculo de acuerdo con el esquema propuesto en
clase (con las funciones esCasoBase, solucionBase, siguiente y combina) para calcular el sumatorio desde un
valor de = i hasta n de la expresin (i!/!)(2A[]B[]). Siendo A y B dos vectores de n enteros cada uno e i un
valor comprendido entre 1 y n.

!
(, , , ) = ( ) (2[] [])
!
=

Tenga en cuenta que esa expresin anterior es equivalente a esta otra:

(! + 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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

2.8.8 Obtener el mayor valor de un vector.


Dada la siguiente definicin recursiva:
[0]
(, ) = { []
(, 1)

=0
[] > (, 1)

Donde V ser un vector de enteros de tamao TAM. Se pide:


a) Implementar en C una funcin recursiva lineal no final.
b) Implementar en C una funcin recursiva lineal no final equivalente a la funcin del apartado a)
utilizando solo una llamada recursiva.
c) A partir del apartado b) implementar una funcin recursiva lineal final en C.
d) A partir del apartado c) implementar una funcin iterativa en C.
e) Defina los tamaos y calcule los T(n) para las funciones implementadas en los apartados a) y c)
considerando nicamente los casos peores.
NOTA: Para todos los apartados la llamada inicial ser: deco(&V, TAM-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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

2.8.9 Anlisis.
Sean g y f dos funciones definidas como sigue:

int * f(int * v, int i, int j) {


(
int h, k;
for (h = i; h < j; h++) {
for (k = i; k < j; k++) {
v[h] = v[h] * v[k];
}
}
return v;
}

[]
=
, , ) = { (, , + ) [] < 1
(, + 2, ) [] 1
= (, , )

=
3

Teniendo en cuenta que el tamao del problema viene especificado por n = j - i, se pide:

1. Analice, justificadamente, la complejidad de la funcin f.


2. Analice, justificadamente, la complejidad de la funcin g. (Tenga en cuenta que, para ello, deber haber
analizado la funcin f previamente).
3. Indique, justificadamente, cul es el tipo de recursividad de la funcin g (mltiple, final, no final, simple).
En caso de ser no final, generalice la funcin g para obtener una nueva definicin recursiva que sea final.
4. Escriba, en C, el cdigo asociado a la definicin recursiva g.
5. Indique, justificadamente, si convendra utilizar o no memoria. Cul sera la complejidad del algoritmo
si se utilizara memoria? (no olvide justificar su respuesta).
6. Aplique el esquema de transformacin recursivo-iterativo para dar una implementacin iterativa, en C,
de la funcin g. Y s, justifique el cdigo creado haciendo hincapi en el esquema de transformacin.

99

SOLUCIN:

Aparatado 1)

(, ) = = () =
= =

=0 =0

=0 =0

1 2
(2 )
2

Aparatado 2) Tamao n = ( j i ) de los siguientes problemas:


1. (( + ) ) = =

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

2.8.10 Invierte nmero.


Se desea implementar un algoritmo recursivo en lenguaje C denominado long invierteNumero(long numero)
que devuelva dicho nmero invertido.
Ejemplo: Si el nmero es 1234567, el mtodo invierteNumero(1234567) devolver 7654321.
SE PIDE:
1) Recursividad no final:
a. Indique la definicin recursiva no final para invertir un determinado nmero de forma recursiva.
b. Implemente el algoritmo recursivo no final en lenguaje C de acuerdo a la definicin recursiva del
apartado 1.a).
2) Recursividad final:
a. Indique las transformaciones necesarias para transformar la definicin recursiva no final del
apartado 1.a) a una definicin recursiva final. Indique la definicin recursiva final resultante.
b. Implemente el algoritmo recursivo final en lenguaje C a partir de la definicin del apartado 2.a).
3) Implemente el algoritmo iterativo a partir del algoritmo recursivo final del apartado 2 en lenguaje C.
4) Complejidad:
a. Determine el tamao del problema.
b. Describa el caso mejor y el caso peor del algoritmo recursivo no final del apartado 1 y el
algoritmo recursivo final del apartado 2, respectivamente.
c. Calcule el T(n) y determine la complejidad del algoritmo recursivo no final del apartado 1 y el
algoritmo recursivo final del apartado 2, respectivamente, para el caso mejor y el caso peor.
NOTA:
No use arrays ya que ser penalizado. Utilice operaciones aritmticas.
Generalice slo si lo considera necesario.
Dispone del mtodo ya implementado int numeroDigitos(long numero) para saber el nmero de dgitos de
un determinado nmero. La complejidad de dicho mtodo es de orden constante.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Apartado 2.b )
long invierteNumeroFinal(long numero) {
long r = 0;
if (numero < 10) {
r = numero;
} else {
r = invierteNumeroFinalG(numero, 0);
}
return r;
}
// 1 Solucin usando potencia:
long invierteNumeroFinalG(long numero, long u) {
long r = 0;
long y = 0;
if (numero < 10) {
r = numero + u;
} else {
y = numero / 10;
u = u + (numero % 10) * potencia(10, numeroDigitos(numero) - 1);
r = invierteNumeroFinalG(y, u);
}
return r;
}
// 2 Solucin sin usar potencia:
long invierteNumeroFinalG(long numero, long u) {
long r = 0;
long y = 0;
if (numero < 10) {
r = numero + u * 10;
} else {
y = numero / 10;
u = u * 10 + (numero % 10);
r = invierteNumeroFinalG(y, u);
}
return r;
}

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.

Algoritmo recursivo final:


1 solucin usando potencia:
() (()) igual que algoritmo no final.

2 solucin sin usar potencia:


La recurrencia quedara de la siguiente forma:
() = ( 1) + 0
= 1, = 1, = 0, = 1 , = 1, = 1 (+1 )
(()) = ()

104

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

2.8.11 Funcin.
Dada la siguiente funcin recursiva:

int func(int *v, int i, int j, int c) {


int mitad, terc, s;
if (i >= j) {
s = v[i];
} else {
mitad = (i + j) / 2;
terc = (j - i) / 3;
if (v[mitad] == c) {
s = v[mitad] + func(v, i, i + 2 * terc, c) + func(v, j - 2 * terc, j, c);
} else {
s = func(v, j - terc, j, c);
}
}
return s;
}

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)

El tamao se definir como N = j - i + 1 = TAM

Apartado b)
Caso mejor (a[i] != c para todo i) donde:

() = ( ) +
3

Aplicando las ecuaciones de recurrencia, tenemos que, a = 1, b = 3, p = 0, d = 0.


Por tanto estamosen el caso B.1.2, luego
() = (log())

Caso peor (a[i] == c para todo i) donde:


() = 2 (

2
)+
3

Aplicando las ecuaciones de recurrencia, tenemos que, a = 2, b = 3/2, p = 0, d = 0.


Por tanto estamosen el caso B.1.1, luego
() = (

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)

El tamao se definir como N = i

Apartado b)

Caso mejor, peor y medio son los mismo.

() = ( ) +
2

Aplicando las ecuaciones de recurrencia, tenemos que, a = 1, b = 2, p = 0, d = 0.


Por tanto estamosen el caso B.1.2, luego
() = (log())

106

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

3 Recursividad: Resolucin de problemas con listas.


3.1 Introduccin.
Objetivos del tema:
o
o
o

Aprender las operaciones bsicas para trabajar con listas de elementos.


Aprender a resolver problemas complejos utilizando listas.
Discutir la relacin entre los tratamientos secuenciales y la recursividad lineal.

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.

3.2 Tratamientos secuenciales.


En este captulo estudiaremos la relacin de los tratamientos secuenciales y la recursividad lineal por una parte
y por otra implementar los algoritmos en Java y en C.
Los tratamientos secuenciales buscan transformar, filtrar y obtener valores acumulados de una lista de
elementos.
Desde un punto de vista abstracto llamamos:
Secuencias de n elementos: Secuencias que representaremos por [e0, e1, e2, , en-1].
Variables listas: l, l1, l2,
Variables funcin: f, f1, fl, fs,
Variables predicado: p, pl, p1, p2,
Representaremos las listas de varias maneras:
Lista explcita, la modelaremos por la tupla l = (dt, n). Dnde dt son los datos y n el nmero de los mismos.
Los datos sern de cualquier tipo de datos indexable como Array, List, etc. En estas listas, y de ah su nombre,
los datos son explcitos y estn disponibles para cada ndice de la lista en la memoria.
Listas implcitas. Una primera forma de estas listas la representaremos en la forma [fl, n] Dnde n es el
nmero de elementos y fl(i) una funcin que cada i [0, n-1] nos devolver el valor de la correspondiente
casilla. Una segunda forma de listas implcitas las modelaremos como [fs, v0, pld]. Donde fs es una funcin
que a partir del valor v de una posicin calcula el valor fs(v) de la posicin siguiente, v0 el valor de la posicin
inicial (la de ndice 0), y pld(v) un predicado que especifica un dominio dentro del cual deben estar los valores
de la lista. El ltimo elemento ser aquel cuyo siguiente elemento est fuera del dominio. Alternativamente
podemos describir la lista dando una propiedad que tiene el ltimo elemento y solamente l. Lo haremos en
la forma [fs, v0, pld].
Por las definiciones dadas podemos ver, y luego daremos los detalles de implementacin, que cada lista (en
cualquiera de las dos formas descritas) tiene asociado un algoritmo iterativo que puede ir generando
sucesivamente todos sus elementos.
Una primera idea a resaltar es que el conjunto de sub-problemas que son necesarios para resolver un problema
dado, en un algoritmo recursivo lineal, forman una lista definida implcitamente. El valor v0 es el problema
original, la funcin fl(v) la que calcula el sub-problema y el predicado pl(v) est definido por el conjunto de
problemas alcanzables desde v0.
107

Sobre las listas definimos varios operadores:


|l|

Nmero de elementos de la lista.

l[i]

Valor del elemento en la posicin i si i [0,|l|-1] o (no existe el valor) si i [0,|l|-1].

l[i j]

Sublista formada por las posiciones en el intervalo [i j] si [i j] [0,|l|-1] o si no se


cumple lo anterior.

l+Lv

Adicin de un elemento v a una lista por la derecha (ltimo elemento de l).


Es decir: [e0, e1, e2, , en-1] + Lv = [e0, e1, e2, , en-1, v].

v+Ll

Adicin de un elemento v a una lista por la izquierda (primer elemento de l).


Es decir: v + L[e0, e1, e2, , en-1] = [v, e0, e1, e2, , en-1].

l1 + Ll2

Concatenacin de listas. Es decir, concatena l1 con l2 tal que


[a0, a1, a2, , an-1] + L[b0, b1, b2, , bm-1] = [a0, a1, a2, , an-1, b0, b1, b2, , bm-1].

+L[l0, l1, l2, ]

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.

Veamos sus signaturas:

108

next(it) o next ((l, i, v))

Toma un iterador y devuelve otro (con su posicin avanzada en una


unidad) y el valor actual del primero (l, i+1, l[i]) o (it1, v).

hasNext(it) o hasNext((l, i, v))

Devuelve verdadero si la posicin actual (valor de i) est en el


intervalo [0, |l|-1]. Falso en otro caso.

it = iterator (l)

Crea un iterador formado por la lista de entrada y sita la posicin en


0 (o en el valor de la primera posicin).

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Operaciones sobre listas usando iteradores:
filter

Filtra l conservando los elementos que cumplan pt (implementacin: predicate)

transform

Convierte los elementos de l aplicndoles ft (implementacin: function)

Las funciones filter y transform construyen listas a partir de otras listas.


Sus signaturas son:
filter(l, pt) = f1(iterator(l), pt, [])
transform(l ,ft) = f2(iterator(l),ft, [])

donde f1(it, pt, r) = { 1 ( , , + )


1 ( , , )

donde f2(it, ft, r) = {

()
() ()
() ()

()
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

accumulate((, true, false), l)

all

Iterables

accumulate((, false, true), l)

size

Iterables

accumulate((+1, 0), l)

max

Ordering

accumulate((max, ), l)

min

Ordering

accumulate((min, ), l)

isOrdered

Ordering

accumulate((<0, true, false, l)

isStrictOrdered

Ordering

accumulate((0, true, false, l)

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)

Construye un iterable a partir de un array.

Iterable<T> fromDomain(Function <T, T> fl, T v0, Predicate <T> pl)

Construye un iterable a parir de una


funcin, un valor inicial y un predicado que
indica el dominio.

Iterable<T> fromToLast(Function <T, T> fl, T v0, Predicate <T> pl)

Construye un iterable a parir de una


funcin, un valor inicial y un predicado que
si es el ltimo elemento.

Iterable<T> fromIndex(Function <Integer, T> fl, Integer n)

Construye un iterable a parir de una


funcin y un entero.

110

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Las tuplas podemos implementarlas como clases genricas con mtodos para obtener cada una de las
componentes y mtodos estticos para crear nuevos valores.
Tupla2<T1, T2>
T1 getP1();
T2 getP2();
Tupla2<T1, T2> create(T1 p1, T2 p2);
Tupla2<T1, T2> create(Tupla2<T1, T2> t);
Tupla2<T, T> create(List<T> t);
Tupla2<T, T> create(T[] t);
Tupla3<T1, T2, T3>
T1 getP1();
T2 getP2();
T3 getP3()
Tupla2<T1, T2, T3> create(T1 p1, T2 p2, T3 p3);
Tupla2<T1, T2, T3> create(Tupla2<T1, T2,T3> t);
Tupla2<T, T, T> create(List<T> t);
Tupla2<T, T, T> create(T[] t);

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);

Donde el tipo BinaryFunction<A, B, C> tiene el mtodo:


C apply(A a, B b);

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Veamos la forma de implementar en C el acumulador siguiente como una instancia del esquema anterior.
accumulate((+L,[]), transform(filter([dt, n], x > 2), x2))

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
}
}
}

Veamos ahora cmo quedara:


accumulate((b (v%3 == 0), false, true), [3*v+1, n1, v < n2])

int haymultiplode3(int n1, int n2) {


*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

3.4 Adaptacin de problemas.


Vamos inicialmente a estudiar un par de problemas (Potencia entera y Fibonacci). Posteriormente, los problemas
que siguen, vamos a verlos de una manera ms general teniendo en cuenta los siguientes elementos: problema
original, problema generalizado, propiedades (compartidas e individuales del mismo) y tipo de la solucin. Todo
ello los resumiremos en una ficha que nos proporcione una idea general del algoritmo con suficientes detalles
como para poder implementarlo en C o en Java.

3.4.1 Potencia entera.


La potencia entera es un problema que aparece en contextos muy diferentes. Vamos a estudiar varias versiones
recursivas e iterativas para resolver este problema.
El problema de la potencia entera es el clculo de an donde n es un entero no negativo. En esta definicin general
entran muchos problemas diferentes:

an donde a es de tipo entero o real.

An donde A es una matriz cuadrada.

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

Aplicando la ecuacin de recurrencia: T(n) = aT(n-b) + cng(n) con a = 1, c = 1 y grado de g(n) = 0


(constante). El grado de complejidad resultante es: T(n) (n).

114

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Versin No final ( log(n) ) :
La segunda posibilidad, ms compleja pero que dar lugar a algoritmos ms eficientes, se basa en la
propiedades: a0 = 1, an = cd(n, a)(an/2)2 . Donde la funcin cd(n, a) devuelve a si n es impar y 1 si es par.

La segunda propiedad es debido a las igualdades = 2 si n es par y = 1 + 2 si n es impar. Estas


2
2
propiedades permiten hacer la definicin recursiva:
1
2

( (, ))
2

(, ) =

( (, ))
2
{

=0
> 0 %2 = 0

> 0 %2 0

que puede ser compactado como:


1
(, ) = {

(, ) = {

(, ) ( (, ))
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.

Versin Final ( log(n) ) :


Vamos buscar un nuevo operador que nos permita reordenar los clculos y obtener una versin recursiva
final. Para ello veamos, en primer lugar, un algoritmo para obtener la representacin en binario de un
entero. Una primera versin recursiva lineal no final es:
[]

() = (2) + [0]

( ) + [1]
{
2

1
> 1 %2 = 0
> 1 %2 0

que puede ser compactado como:


() = {

() = {

[]

( ) + ()
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 )

Veamos cmo disear un acumulador para la expresin anterior. Observemos que 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

Aadimos un acumulador u tal que en cada momento = ( 2 ) (2 ) y tengamos en cuenta que si di = 0,


el ltimo trmino es la unidad, por lo que no es necesario acumularlo y si di = 1, s. Combinemos todo en un
problema generalizado de la forma (a, n, r, u) y veamos cul es el problema siguiente y cul es el ltimo elemento:

(, , , ) = (, , 2 , (, ))
2
(, , , ) = = 0
1 %2 = 0
(, ) = {
%2 = 1

De donde obtenemos la solucin recursiva final:


(, ) = (, , , 1)
(, , , ) = {

(, , 2 , (, ))
2

=0
>0

La secuencia de llamadas para n = 9 es:


potencia(a, 9) = pt(a, 9, a, 1) = pt(a, 4, a2, a) =
= pt(a, 2, a4, a) = pt(a, 1, a8, a) = pt(a, 0, a16, a9) = a9

La definicin propuesta es recursiva final. Ahora podemos obtener una


solucin iterativa cuyo algoritmo es el siguiente:
116

long potencia_i(int a, int n) {


long r, u;
r = a;
u = 1;
while (n > 0) {
if (n % 2 == 1) {
u = u * r;
}
r = r * r;
n = n / 2;
}
return u;
}

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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
) = ( )

Donde M es denominada la Matriz de Fibonacci.


Es decir, el problema de Fibonacci puede representarse como un problema de potencia entera con base una
matriz. En efecto tengamos en cuenta:

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:

(a, b)2 = (a2 + 2ab, a2 + b2)


I = (0, 1)
base = (1, 0)
(a, b) * (c, d) = (bc + ac + ad, ac, bd)

Teniendo en cuenta las propiedades


anteriores podemos modelar las Matrices de
Fibonacci como un tipo con las operaciones
m2, m1 * m2 y las constantes que representan
base, I. Otra forma es representar cada
Matriz de Fibonacci por el par (a, b) con las
operaciones anteriores. Siguiendo el
segundo camino declararemos las variables
(au, bu), (ar, br) y la variable temporal (at, bt)
Con esa notacin, adaptando el algoritmo
iterativo de la potencia entera y teniendo en
cuenta las ideas de asignacin paralela,
obtenemos el siguiente algoritmo:
Para valores grandes de n el nmero de
Fibonacci no cabe en un Long. Una
implementacin mejor debe usar BigInteger.

static Long fibonacci (Integer n) {


Long ar, br;
Long au, bu;
Long at, bt;
ar = 1L;
br = 0L;
au = 0L;
bu = 1L;
while (n > 0) {
if (n % 2 == 1) {
at = bu * ar + au * ar + au * br;
bt = au * ar + bu * br;
au = at;
bu = bt;
}
at = ar * ar + 2 * ar * br;
bt = ar * ar + br * br;
ar = at;
br = bt;
n = n / 2;
}
return au;
}

117

3.4.3 Mximo y Mnimo.


El problema consiste en, dada una lista, encontrar los elementos mximo y mnimo con respecto a un orden
dado. El problema se generaliza para encontrar el mximo y mnimo entre las posiciones i, j de la lista.
El conjunto de problemas tiene dos propiedades compartidas: d (de tipo List<E>) que contiene la lista de
elementos, ord (de tipo Comparator<E>) que contiene un orden con respeto al cual buscamos el mximo y
mnimo. Las propiedades individuales son: i, entero en [0, d.size()) consultable, y J, entero en (I, d.size()]
consultable.
La solucin al problema tiene dos propiedades: M (de tipo E) consultable y m (de tipo E) consultable. Una
solucin la representaremos por (M, m). Usaremos las funciones max y min que calculan el mximo o mnimo
de dos elementos dado un orden como primer parmetro.
En general los valores de un tipo dado los representaremos identificadores con subndices.

MaxMin
Tcnica:

Divide y Vencers sin Memoria

Tamao:

N=ji

Propiedades Compartidas:

d, List<E>
ord, Comparator<E>

Propiedades Individuales

i, entero en [0, d.size())


j, entero en (i, d.size()]

Solucin:

(M, n)

Instanciacin:
(, ) = {

|| = 0

(0, . ()) || 0

Problema generalizado:
(, )
(, )
(, ) = {
((, ), (, ))
=

=1
=2
>2

= = []
= max(, [], []) , = min(, [], [])

+
2

((1 , 1 ), (2 , 2 )) = (max(, 1 , 2 ) , min(, 1 , 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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

3.4.4 Bsqueda binaria.


El problema consiste en, dada una lista ordenada con respecto a un orden, encontrar si existe, la posicin de un
elemento dado o devolver -1, si no lo encuentra. El problema se generaliza aadiendo dos propiedades: i, entero
en [0, d.size()] y j, entero en [i, d.size()]. El problema generalizado busca el elemento en el segmento de lista
definido por el intervalo [i, j).
El conjunto de problemas tiene tres propiedades comunes: d (de tipo List<E>) que contiene la lista ordenada de
elementos, ord (de tipo Comparator<E>) que contiene un orden con respeto al cual est ordenada la lista y elto
(de tipo E) el elemento a buscar. Adems cada problema tiene las dos propiedades individuales i, entero en [0,
d.size()] y j, entero en [i, d.size()].
La solucin al problema es un entero en [-1, d.size()). Si es -1, entonces el elemento elto no est en d.

Bsqueda Binaria
Tcnica:

Divide y Vencers sin Memoria

Tamao:

N=ji

Propiedades Compartidas:

d, List<E>
ord, Comparator<E>
elto, E

Propiedades Individuales

i, entero en [0, d.size())


j, entero en (i, d.size()]

Solucin:

Entero en [-1, d.size())

Instanciacin:
(, , ) = (0, . ())

Problema generalizado:
1

(, ) =
(, )
{ ( + 1, )
=

= []
< []
> []

+
2

Recurrencia:

() = ( ) +
2

Complejidad:

(log )

119

El algoritmo en Java es como sigue:

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, int i, int j, E key, Ordering<? super E> ord) {


i);

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

3.4.5 Bandera Holandesa.


El problema consiste en, dada una lista y un elemento de la misma, que llamaremos pivote, reordenarla, de
menor a mayor, para que resulten tres bloques: los menores que el pivote, los iguales al pivote y los mayores
que el pivote. El algoritmo devuelve dos enteros que son las posiciones de las casillas que separan los tres
bloques formados.

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);

El esquema del algoritmo:


void reordena3(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) {
transiciones;
}
*r1 = a;
*r2 = b;
}

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:

[]
(, , )[] =
[]
{ []

[, ) [, )
=
=

El prototipo completo de esta funcin en C es:


void intercambia(double * dt, int a, int b, int i, int j);

El prototipo completo de esta funcin en Java es:


void intercambia(List<T> dt, int a, int b, int i, int j);

122

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Llamando sp1 a partir de un problema, nos da el siguiente:
1 ((, , ), , , , + 1, + 1, )
1 (, , , , , , ) = { 1 (, , , , , + 1, )
1 ((, , 1), , , , , , 1)

[] <
[] =
[] >

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

La complejidad es por tanto,


() = ( 1) + = ()

() ()

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;
}

Veamos ahora un problema similar al anterior La Bandera Portuguesa :


Dado una lista y un elemento del mismo, que llamaremos pivote, reordenar un segmento del mismo para que
queden dos bloques: los menores que el pivote, y los iguales o mayores al pivote. El algoritmo debe devolver un
entero que separa los dos bloques formados.
Generalizamos el problema original ahora con dos parmetros (dt, n, i, j, a, b, p). El invariante es ahora:
(, , , , , , , ) ( [] < ) ( [] )
[0,)

[,)

y el tamao del problema: t(dt, n, i, j, a, b, p) = b - a.


123

Escogemos la casilla a para definir el problema siguiente.


1 (, , ) = {

1 (, + 1, )
1 ((, , 1), , 1)

[] <
[]

El algoritmo completo queda:


2 (, , , , ) = 2 (, , , , , , )

=0
2 (, , , , , , ) = {
1 (2 (, , , , , , )) > 0

Con todas esas ideas el algoritmo queda en C :


int reordena2(int * dt, int i, int j, int p) {
int a, b;
a = i;
b = j;
while (b - a > 0) {
if (dt[a] < p) {
a++;
} else {
intercambia(dt, a, b - 1);
b--;
}
}
return a;
}

Bandera Holandesa
Tcnica:

Divide y Vencers sin Memoria

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:

()

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

3.4.6 Ordenacin de una lista: QuickSort.


El problema consiste en, dada una lista, ordenarla con respecto a un orden dado. La solucin que buscamos es
la misma lista de entrada pero ordenada. El problema se generaliza como en problemas anteriores aadiendo
dos propiedades: i, j. El problema generalizado ordena el trozo de lista definido por el intervalo [i, j).

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

Ordenacin de una Lista: QuickSort


Tcnica:

Divide y Vencers sin Memoria

Tamao:

N=ji

Propiedades Compartidas:

d, List<E>
ord, Comparator<E>

Propiedades Individuales

i, entero en [0, d.size())


j, entero en (i, d.size()]

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


El clculo del umbral debemos hacerlo por aproximaciones. El cdigo escrito en Java es:
public static <E extends Comparable<? super E>> void sort(List<E> lista) {
Ordering<? super E> ord = Ordering.natural();
quickSort(lista, 0, lista.size(), ord);
}
public static <E> void sort(List<E> lista, Comparator<? super E> cmp) {
Ordering<? super E> ord = Ordering.from(cmp);
quickSort(lista, 0, lista.size(), ord);
}
private static <E> void quickSort(List<E> lista, int i, int j, Ordering<? super E> ord) {
Preconditions.checkArgument(j >= i);
Tupla<Integer, Integer> p;
if (j - i <= 4) {
Ordenes.ordenaBase(lista, i, j, ord);
} else {
E pivote = escogePivote(lista, i, j);
p = Lists2.reordenaMedianteBanderaHolandesa(lista, pivote, i, j, ord);
quickSort(lista, i, p.getP1(), ord);
quickSort(lista, p.getP2(), j, ord);
}
}

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

void quickSort(int arr[], int left, int right)


{
int index = partition(arr, left, right);
if (left < index - 1)
quickSort(arr, left, index - 1);
if (index < right)
quickSort(arr, index, right);
}

void quickSort(int arr[], int left, int right)


{
int i = left, j = right;
int tmp;
int pivot = arr[(left + right) / 2];
/* partition */
while (i <= j) {
while (arr[i] < pivot)
i++;
while (arr[j] > pivot)
j--;
if (i <= j) {
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
i++;
j--;
}
};

int partition(int arr[], int left, int right) {


int i = left, j = right;
int tmp;
int pivot = arr[(left + right) / 2];
while (i <= j) {
while (arr[i] < pivot)
i++;
while (arr[j] > pivot)
j--;
if (i <= j) {
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
i++;
j--;
}
};

/* 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

Comenzamos con la lista completa. El elemento pivote ser el 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

3 es menor que 4: avanzamos por la izquierda.


2 es menor que 4: nos mantenemos ah.
7 es mayor que 4 y 2 es menor: intercambiamos.

1 - 3 - 2 - 6 - 7 - 5 - 4
iyj

Avanzamos por la izquierda y la derecha.

1 - 3 - 7 - 6 - 2 - 5 - 4
i

5 es mayor que 4 y 1 es menor. Intercambiamos.

1 - 3 - 7 - 6 - 2 - 5 - 4
i

Comparamos con el 5 por la izquierda y el 1 por la derecha.

Avanzamos por ambos lados.

1 - 3 - 2 - 4 - 7 - 5 - 6
p
1 - 3 - 2

En este momento termina el ciclo principal, porque los ndices se


cruzaron. Ahora intercambiamos lista[i] con lista[sup].
Aplicamos recursivamente a la sublista de la izquierda (ndices 0 - 2).
1 es menor que 2: avanzamos por la izquierda.

1 - 2 - 3

3 es mayor: avanzamos por la derecha.


Como se intercambiaron los ndices termina el ciclo.
Se intercambia lista[i] con lista[sup].

1 - 2 - 3 - 4 - 5 - 6 - 7

128

El mismo procedimiento se aplicar a la otra sublista. Al finalizar y unir


todas las sublistas queda la lista inicial ordenada en forma ascendente.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

3.4.7 Ordenacin de una lista: MergeSort.


Un algoritmo diferente para resolver el problema es el MergeSort. Este algoritmo parte la lista en dos mitades
iguales, ordena cada sublista y posteriormente mezcla ordenadamente los resultados. Necesitamos un algoritmo
auxiliar que produzca una lista ordenada a partir de dos sublistas ya ordenadas. Es el algoritmo ms(d, i, k, j, dt)
que ordena la sublista (d, i, j) asumiendo ordenadas las sublistas (d, i, k) y (d, k, j) usando la lista dt.Para ello usa
la funcin m0(d1, i1, j1, d2, i2, j2, d3, i3, n3) que combina las sublistas ordenadas (d1, i1, j1) y (d2, i2, j2) en la sublista
ordenada (d3, i3, j3) y la funcin cp0(d1, i1, j1, d2, ,i2, j2) que copia la sublista (d2, i2, j2) en la (d1, i1, j1) a partir de la
posicin i1. Ambas funciones pueden ser diseadas iterativamente. Para ello ampliamos cada sublista con un
ndice ki para irlas recorriendo.
Los problemas generalizados son, respectivamente,
m0g(d1, i1, j1, k1, d2, i2, j2, k2, d3, i3, j3, k3)
cp0g(d1, i1, j1, k1, d2, i2, j2, k2)
En cada caso se mantiene un invariante. En el primero la sublista (d3, i3, j3) es una mezcla ordenada de las sublistas
(d1, i1, j1) y (d2, i2, j2). En el segundo la sublista(d1, i1, j1) es una copia de (d2, i2, j2). En el primer caso el tamao del
problema es j3 - k3. En el segundo j2 - k2. A partir de esas ideas es fcil disear el esquema iterativo.

Ordenacin de una Lista: MergeSort


Tcnica:

Divide y Vencers sin Memoria

Tamao:

N=ji

Propiedades Compartidas:

d, List<E>
ord, Comparator<E>

Propiedades Individuales

i, entero en [0, d.size())


j, entero en [i, d.size()]

Solucin:

List<E>

Instanciacin:
(, ) = (, 0, . (), )

Problema generalizado:
(, , , ) = {
=

(, )
(2 , , , , 2 )

< 0
> 0

+
2

(1 , 1 ) = (, , , )
(2 , 2 ) = (1 , , , )

Recurrencia:

() = 2 ( ) + ()
2

Complejidad:

( log )

129

El cdigo asociado es:

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

3.4.8 Bsqueda del elemento k-simo.


El problema consiste en, dada una lista no ordenada encontrar el elemento que ocupara la posicin k-sima con
respecto a un orden dado. El problema se generaliza aadiendo dos propiedades: i, entero en [0, d.size()] y j,
entero en [i+1, d.size()]. El problema generalizado supone buscar el k-simo elemento del segmento de lista
[i, j). El conjunto de problemas tiene dos propiedades comunes: d (de tipo List<E>) que contiene la lista, ord (de
tipo Comparator<E>) que contiene el orden de referencia y k (de tipo entero en [0, d.size()).
Para plantear el algoritmo necesitamos varios algoritmos previos algunos de los cuales son similares a los usados
en el algoritmo de ordenacin visto anteriormente:
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, a, b) re1(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 en [i, a), los iguales en [a, b) y los
mayores en [b, j). El mtodo devuelve la sublista reordenada y posicin del pivote. Este algoritmo puede
ser implementado iterativamente con complejidad (n) segn vimos anteriormente.

Bsqueda del elemento k-simo


Tcnica:

Divide y Vencers sin Memoria

Tamao:

N=ji

Propiedades Compartidas:

d, List<E>
ord, Comparator<E>
k, entero en [0, d.size())

Propiedades Individuales

i, entero en [0, d.size())


j, entero en [i+1, d.size()]

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

3.4.9 Multiplicacin de enteros muy grandes.


El problema consiste en disear un algoritmo para multiplicar enteros muy grandes. Suponemos que un entero
est representado en notacin decimal por una cadena de caracteres posiblemente muy larga. Sea el tamao
del problema la longitud de esta cadena. Los algoritmos clsicos de multiplicacin de enteros tienen una
complejidad de (n2). La suma de enteros tiene complejidad (n).
Para plantear el problema modelemos en primer lugar un entero grande. Vamos a dotarlo de tres propiedades:
Sup, Inf, Add. Si representamos un entero grande por una cadena de caracteres, que representa en el entero en
base 10, entonces las propiedades anteriores nos dan respectivamente: el entero representado por la mitad
superior de la cadena, el representado por mitad inferior y la suma de ambos. Representaremos por e, e1, e2,
diversos enteros grandes y por es, ei las partes superior e inferior respectivamente del entero e y ea = es + ei la
suma de ambas partes del entero. Tambin, suponiendo que el tamao de e es n y de ei es k entonces podemos
escribir e = 10kes+ei. Podemos comprobarlo en el ejemplo 123 = 12(10)1+3. A partir de esta idea podemos
multiplicar dos enteros cuyas partes inferiores tengan el mismo tamao para obtener:
1:

= 1 2 = (10 1 + 1 )(10 2 + 2 ) = 102 1 1 + 10 (1 1 + 1 2 ) + 1 2

A su vez, vemos que:


= 1 2 ,

= 1 2 ,

= (1 + 1 )(2 + 2 ),

= 1 2 + 1 2

Por lo que podemos reescribir la expresin para un entero e como:


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

Donde el sub-problema 2 en este caso es de la forma (1 + 1 )(2 + 2 ) = 1 2 . Vemos que en el caso 2


el clculo de los sub-problemas ms el tiempo de combinacin de las soluciones es (n). La recurrencia
para el tiempo de ejecucin del algoritmo para el caso 2 es:

() = 3 ( ) + (n),
2

T(n) = (log2 3 ) = (158 )

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

Multiplicacin de Enteros Grandes (Algoritmo de Karatsuba)


Tcnica:

Divide y Vencers sin Memoria

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);
}

El algoritmo diseado ser la implementacin del mtodo getM.


El mtodo setK fija el tamao de la parte inferior.
El mtodo getA (suma de dos enteros grandes), que puede hacerse en tiempo lineal.
El resto de los mtodos representan las propiedades comentadas.
import java.math.BigInteger;
import java.util.Random;

public static void main(String[] args) {


long start, stop, elapsed;
Random random = new Random();
int N = Integer.parseInt(args[0]);
BigInteger a = new BigInteger(N, random);
BigInteger b = new BigInteger(N, random);

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)));
}
}

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

3.4.10 Subsecuencia de suma mxima.


Supongamos ahora que tenemos una secuencia de enteros, positivos y negativos. El problema que se trata de
resolver es encontrar la sub-secuencia cuya suma sea mxima. Por ejemplo, en la secuencia 1, -2, 11, -4, 13, -5,
2, 3, entonces la sub-secuencia de suma mxima es la sombreada, en negrita, que suma 20.
La solucin la modelamos mediante el tipo SubSecuenciaMax con tres propiedades: i, j, v de tipo entero. Las dos
primeras indican los lmites de la sub-secuencia y la tercera el valor de la suma de los elementos en la subsecuencia. Una solucin la representaremos por s i, j, k.
El problema tiene una propiedad compartida: d (de tipo List<Integer>) que contiene la secuencia de elementos.
El problema se generaliza aadiendo dos propiedades individuales: i, entero en [0, d.size()) y j, entero en [i+1,
d.size()], que definen la sublista en el intervalo [i, j).
Para el diseo concreto necesitamos el algoritmo que llamaremos Secuencia Centrada. La llamada al algoritmo
es de la forma s a, b, v = sc(i, j, s) y devuelve la sub-secuencia de suma mxima de entre todas las que se extienden
en posiciones en [i, j) y contienen los elementos de las posiciones s-1 y s que estn contenidas en [i, j). La
complejidad del algoritmo es (n).
Las ideas las resumimos en la ficha siguiente:

Subsecuencia de Suma Mxima


Tcnica:

Divide y Vencers sin Memoria

Tamao:

N=ji

Propiedades Compartidas:

d, List<E>

Propiedades Individuales

i, entero en [0, d.size())


j, entero en [i+1, d.size()]
v, entero

Solucin:

(a, b, v) con relacin de orden definida por v

Instanciacin:
() = (, 0, . ())

Problema generalizado:
(, , ) = {
=

(, , [])
((, , ), (, , ), (, , , ))

=1
>1

+
2

(1 , 2 , 3 ) = (1 , 2 , 3 )

Recurrencia:
() = 2 ( ) + ()
2

Complejidad:

( log )

135

La implementacin en Java es de la forma:


public static SubSecuencia getSubSecuenciaMaxima
(List<Double> lista)
{
Ordering<SubSecuencia> ord = Ordering.natural();
return getSubSecuenciaMaxima(lista, 0, lista.size(), ord);
}
private static SubSecuencia getSubSecuenciaMaxima
(List<Double> lista, int i, int j, Ordering<SubSecuencia> ord)
{
SubSecuencia r = null;
if (j - i <= 1) {
r = new SubSecuencia(lista, i, j);
} else {
int k = (i + j) / 2;
SubSecuencia s1 = getSubSecuenciaMaxima(lista, i, k, ord);
SubSecuencia s2 = getSubSecuenciaMaxima(lista, k, j, ord);
SubSecuencia s3 = getSubSecuenciaMaximaCentrada(lista, i, j, k);
r = ord.max(s1, s2, s3);
}
return r;
}

El clculo de la subsecuencia centrada puede hacerse mediante:


private static SubSecuencia getSubSecuenciaMaximaCentrada
(List<Double> lista, int a, int b, int k)
{
Double sumaMaxima = Double.MIN_VALUE;
Double suma = 0.;
int i1 = k;
int j1 = k;
int from = i1;
int to = j1;
for (i1 = k - 1; i1 >= a; i1--) {
suma = suma + lista.get(i1);
if (suma > sumaMaxima) {
sumaMaxima = suma;
from = i1;
}
}
suma = sumaMaxima;
for (j1 = k; j1 < b; j1++) {
suma = suma + lista.get(j1);
if (suma > sumaMaxima) {
sumaMaxima = suma;
to = j1 + 1;
}
}
SubSecuencia sm = new SubSecuencia(lista, from, to);
return sm;
}

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

3.4.11 Clculo del par ms cercano.


Dado un conjunto de n puntos disear mediante la tcnica de Divide y Vencers un algoritmo que devuelva los
dos puntos ms cercanos. Por tanto partimos de que todos los puntos son distintos.
La solucin la modelamos con el par (p1, p2) que tiene tres propiedades consultables: P1, P2 de tipo Punto y
Distancia de tipo Double. Asumimos modelado el tipo Punto.
El problema tiene dos propiedades compartidas: ps, de tipo Set<Punto>, es el conjunto de
puntos original y px que es la lista anterior ordenada segn la X. Adems tiene las
propiedades individuales py, la misma lista ordenada segn la Y, donde las variables i, j, que
especifican un subintervalo de puntos [i, j) en la lista px.

Clculo del Par ms Cercano


Tcnica:

Divide y Vencers sin Memoria

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.

El cdigo para implementar esta funcionalidad en Java:


private static ParDePuntos masCercanoCentral (
,
,
)
ParDePuntos r
= null;
Double
d
= s.getDistancia();
Punto
pIzq = null;
Punto
pDer = null;

ParDePuntos s
List<Punto> yCentral
Ordering<ParDePuntos> ordNatural
{

for (int i = 0; i < yCentral.size(); i++) {


pIzq = yCentral.get(i);
for (int j = i + 1; j < yCentral.size(); j++) {
pDer = yCentral.get(j);
r = ParDePuntos.create(pIzq, pDer);
if (r.getDistancia() > d) {
break;
}
s = ordNatural.min(s, r);
d = s.getDistancia();
}
for (int j = i - 1; j >= 0; j--) {
pDer = yCentral.get(j);
r = ParDePuntos.create(pIzq, pDer);
if (r.getDistancia() > d) {
break;
}
s = ordNatural.min(s, r);
d = s.getDistancia();
}
}
return s;
}

138

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

3.4.12 Clculo del Conjunto de Puntos Maximales.


Se dice que un punto P del plano domina a otro Q cuando ambas coordenadas de
P son mayores o iguales que las de Q, siendo P Q. Si P domina a Q decimos que
Q es dominado por P. Se dice que un punto P es maximal en una lista de puntos L
si no existe ningn punto en L que domine a P.
Utilizando la tcnica de Divide y Vencers, obtener un algoritmo que encuentre
todos los puntos maximales de una lista de puntos. En el ejemplo de la figura, los
puntos en negrita son los maximales del conjunto de todos los puntos dibujados.
La solucin la modelamos con el tipo: Set<Punto>.
El problema tiene una propiedad compartida, ps de tipo List<Punto>, y tiene dos propiedades individuales i, j
ambas de tipo Integer.

Clculo de Maximales
Tcnica:

Divide y Vencers sin Memoria

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.

Una implementacin en Java de la funcin de combinacin es:

private static Set<Punto> puntosMaximalesCombina(Set<Punto> si, Set<Punto> sd) {


Double maxYD = Double.MIN_VALUE;
Set<Punto> r = Sets.newHashSet(sd);
for (Punto p : sd) {
if (p.getY() > maxYD) {
maxYD = p.getY();
}
}
for (Punto p : si) {
if (p.getY() > maxYD) {
r.add(p);
}
}
return r;
}

140

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

3.5 Ejercicios propuestos.


Ejercicio 1.
Dado un vector V de palabras ordenadas alfabticamente de tamao n, se desea encontrar una palabra
dada P, usando un algoritmo de Divide y Vencers que divide el vector en tres partes, comprueba si la
palabra es la buscada en los dos puntos intermedios de la particin y hace la llamada correspondiente
para el tramo donde posiblemente se encuentre. Si la palabra no est entonces devolver -1, y si est
devolver la posicin donde se encuentra.
Suponiendo que la funcin de comparacin de palabras tiene complejidad (n), hallar la ecuacin de
recurrencia del tiempo de ejecucin del algoritmo de divide y vencers anterior y resuelva dicha ecuacin
de recurrencia para obtener la cota superior en el caso peor.

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.

Ejemplo de figura rotada 90 en el sentido de las agujas del reloj.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

3.6 Ejercicios de exmenes.


3.6.1 Ordenacin de listas mediante QuickSort.

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:

static E ep(List<E> datos ,int i, int j)

static int rs(List<E> datos, E e, int i, int j, Comparator<T> comp)

static void sb(List<E> datos, int i, int j, Comparator<T> comp) :

: Elige aleatoriamente un elemento de la sub-lista datos


definida por las posiciones [i, j). A dicho elemento se le denomina pivote. Este mtodo se ejecuta en
tiempo (1).
: Reestructura la sub-lista
datos comprendida entre las posiciones [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 posicin del pivote. Este algoritmo est implementado iterativamente con complejidad (n).
Ordena por algn mtodo
iterativo conocido la sub-lista datos definida por los ndices [i, j). Estos algoritmos (como el de ordenacin
por insercin) tienen una complejidad de (n2).

La clase ProblemaQuickSortImpl define un constructor con la siguiente cabecera:


public ProblemaQuickSortImpl(
,
,
,
,
)

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.

private List<E> dYV(ProblemaQuickSort p);


boolean esCasoBase();
List<E> getSolucionCasoBase();
int getNumSubProblemas();
ProblemaDyV<List<E>> getSubProblema(int ind);
List<E> combinaSoluciones(List<E>... soluciones);

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

Ordenacin de una Lista: Quicksort


Tcnica:

Divide y Vencers sin Memoria

Tamao:

N=ji

Propiedades Compartidas:
Propiedades Individuales
Solucin:
Instanciacin:

(, ) = (0, . ())

Problema generalizado:
= (, )
= (, , )
(, ) =
(, )
{ [( + 1, ) ]


>

Recurrencia:

1
() = 1 + (( 1) + ( ))

=0

Complejidad:

( log )

Ordenacin de una Lista: Quicksort


Tcnica:

Divide y Vencers sin Memoria

Tamao:

N=ji

Propiedades Compartidas:

Datos, List<E>
Orden, Comparator<E>
Umbral, Integer

Propiedades Individuales

i, entero en [0, Datos.size())


j, entero en (i, Datos.size()]

Solucin:
Instanciacin:

List<E>
(, ) = (0, . ())

Problema generalizado:
(, )
= (, )
= (, , )
(, ) =
(, )
{ [( + 1, ) ]
1

Recurrencia:

144

>

1
() = 1 + (( 1) + ( ))

=0

Complejidad:

( log )

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Apartado a)
private List<E> dYV(ProblemaDyV<List<E>> p) {
List<E> s;
if (p.esCasoBase()) {
s = p.getSolucionCasoBase();
} else {
int numeroDeSubProblemas = p.getNumeroSubProblemas();
S[] soluciones = Utiles.creaArray(numeroDeSubProblemas);
for (int i = 0; i < numeroDeSubProblemas; i++) {
ProblemaDyV<List<E>> pr = p.getSubProblema(i);
s = dYV(pr);
soluciones[i] = s;
}
s = p.combinaSoluciones(soluciones);
}
return s;
}

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

Puede utilizar los siguientes mtodos:


Integer elegirPivote(List<Integer> numeros, Integer i, Integer j)

: Mtodo que elige un valor como

pivote para una lista de nmeros enteros entre los ndices i y j.


List<Integer> bh(List<Integer> numeros, Integer p, Integer i, Integer j): Mtodo que realiza el algoritmo

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());

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.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

Contador de ocurrencias nicas en una lista


Tcnica:

Divide y Vencers sin Memoria

Tamao:

N=ji

Propiedades Compartidas:

numeros, List<E>
umbral, Integer

Propiedades Individuales

i, Integer en [0, numeros.size())


j, Integer en [0, numeros.size()]

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

3.6.3 Bsqueda de puntos.

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)

Se dispone de una funcin ya implementada y denominada detector:


boolean detector (int x, int y, int size, 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:

Divide y Vencers sin Memoria

Tamao:

N = size x size (matriz de puntos)

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

Si slo 1 pixel cumple la propiedad: () = ( ) +


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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

3.6.4 Cuestioinario tipo Test.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

3.6.5 Sucesin de Jacobsthal-Lucas.

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.

Resuelve la tcnica Divide y Vencers.


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. public BigInteger combinaSoluciones (BigInteger[] soluciones). Combina cada solucin de la lista
de soluciones de manera que juntas resuelvan el problema completo. Puede utilizar las
operaciones de BigInteger: add(BigInteger a):BigInteger y multiply(BigInteger a):BigInteger.
private BigInteger dYV(ProblemaDyV<BigInteger> p).
public boolean esCasoBase().

Ayuda: Algunas operaciones de BigInteger son:


BigInteger add(BigInteger val), BigInteger multiply(BigInteger val) y BigInteger div(BigInteger val).
153

Aparatado a)
Sucesin de Jacobsthal-Lucas
Tcnica:

Divide y Vencers con Memoria.


Al dividir el problema en subproblemas ((n-1) y (n-2)), aparecen
repetidos los problemas ms de una vez. Guardando en memoria las
operaciones ya realizadas disminuye el tiempo computacional y el
nmero de operaciones a realizar.

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

() ()

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


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() <= 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

3.6.6 Sucesin de Padovan.

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.

public BigInteger combinaSoluciones (BigInteger[] soluciones). Combina cada solucin de

la lista de soluciones de manera que juntas resuelvan el problema completo.

156

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Solucin:
Aparatado a)
Sucesin de Padovan
Tcnica:

Divide y Vencers con Memoria.


Al dividir el problema en subproblemas ((n-2) y (n-3)), aparecen
repetidos los problemas ms de una vez. Guardando en memoria las
operaciones ya realizadas disminuye el tiempo computacional y el
nmero de operaciones a realizar.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

3.6.7 Funciones con comportamiento similar.

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

995 > 990

f2.getValueY(205) es 800
f2.getValueY(210) es 775

800 > 775

Y por tanto sera true para este intervalo.


Sin embargo, en la figura (b), el mtodo compSimilar devolvera false, ya que entre el intervalo 2 y 4 la funcin f1
es estrictamente creciente y la funcin f2 es estrictamente decreciente.
Para comprobar si son decrecientes 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 1015

995 < 1015

f2.getValueY(205) es 800
f2.getValueY(210) es 775

800 > 775

Y por tanto sera false para este intervalo.

Se pide:
1. Rellene la siguiente ficha.
2. Implemente el mtodo propuesto mediante la tcnica DyV basndose en la ficha.
159

Funciones con comportamiento similar


Tcnica:

Divide y Vencers sin Memoria.

Tamao:

(x2 x1 ) / epsilon

Propiedades Compartidas:

f1, Funcion
f2, Funcion

Propiedades Individuales

X1, Double [f1.getInicioX(), f1.getFinX()]


X2, Double [f2.getInicioX(), f2.getFinX()]
Boolean

Solucin:
Instanciacin:

(1, 2, ) = (1. (), 1. ())

Problema generalizado:
(1, 2) = {

(1, 2)
(1, ) && (, 2)

(2 1)
(2 1) >

1 + 2
2

Recurrencia:

() = 2 ( ) +
2

Complejidad:

() ()

Boolean compSimDyV(Double x1, Double x2) {


Boolean res;
if (x2 - x1 <= epsilon) {
res = signoIncY(x1, x2);
} else {
Double k = (x1 + x2) / 2;
Boolean r1 = compSimDyV(x1, k);
Boolean r2 = compSimDyV(k, x2);
res = r1 && r2;
}
return res;
}
// Otra opcin tambin vlida sera:
Boolean compSimDyV(Double x1, Double x2) {
Boolean res;
if (x2 - x1 <= epsilon) {
res = signoIncY(x1, x2);
} else {
Double k = (x1 + x2) / 2;
res = compSimDyV(x1, k) && compSimDyV(k, x2);
}
return res;
}

Boolean signoIncY(Double x1, Double x2){


return
( (f1.getValue(x1) > f1.getValue(x2)) && (f2.getvalue(x1) > f2.getValue(x2)) )
||
( (f1.getValue(x1) < f1.getValue(x2)) && (f2.getvalue(x1) < f2.getValue(x2)) )
}

160

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

3.6.8 Sistema de votacin.

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());

// si por ejemplo p fuera 2

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:

Divide y Vencers sin Memoria.

Tamao:

j-i

Propiedades Compartidas:

d, List<Integer>
n, d.size()

Propiedades Individuales

i, entero en [0, d.size()]


j, entero en [i+1, d.size()]
Integer

Solucin:
Instanciacin:

() = ((, 0, . ()) < 4 ? (, 0, ()) 0)

Problema generalizado:
0
1[]
(, , ) =

(1, , )
(1, , )
{ 0

> , >
2
2

> , , >
2
2
2

> , , >
2
2
2

= (, , )
(1, , ) = (, , , )

Recurrencia:

1
() = + ()

=0

Complejidad:

() ()

Integer BuscaPropuestaMayoritaria (List<Integer> votos) {


Integer r;
r = vMayoR(d, 0, d.size(), d.size());
return (r < 4 ? r : 0);
}
Integer vMayoR (List<Integer> d, Integer i, Integer j, Integer tam) {
Integer pivote, r, a, b;
if (j - i <= tam / 2)
r = 0;
else {
pivote = elegirPivote(d, i, j);
List<Integer> solbh = bh(d, i, j, pivote);
a = solbh.get(0);
b = solbh.get(1);
if (b - a > tam / 2) {
r = pivote;
} else {
if (a - i > tam / 2)
r = vMayoR(d, i, a, tam);
else if (j - b > tam / 2)
r = vMayoR(d, b, j, tam);
else
r = 0;
}
}
return (r);
}

162

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

3.6.9 Notas de alumnos.


Despus de presentarse a un concurso de mbito internacional, se han publicado on-line la lista con la
puntuacin obtenida por cada candidato. Dicha publicacin consiste en una serie de pginas en PDF ordenadas
por el NIF del alumno. Cada PDF contiene una lista de 30 alumnos ordenados, tambin, por el NIF. Dado que hay
muchsimos alumnos (y por lo tanto, ficheros PDF), se quiere implementar un algoritmo que sea capaz de buscar,
en la lista de PDFs, la nota de un alumno dado por su NIF.
Para ello dispone de los siguientes mtodos ya implementados:
a)

double getNota(PDF pagina, int NIF). Dado

el NIF de un alumno y la pgina a mirar, devuelve la nota de


dicho alumno en dicha pgina o -1 si el alumno no est en dicha pgina. Este mtodo es de complejidad
constante.
b) int getPrimerNIF(PDF pagina). Devuelve el NIF menor (el primero) que existe en la pgina pagina. Este
mtodo es de complejidad constante.
c) int getUltimoNIF(PDF pagina). Anlogo al anterior.

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>

Caso mejor: Est en la primera posicin. Complejidad constante.


Caso peor: Est en la ltima posicin. Complejidad lineal.

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

Caso mejor: Se encuentra en medio. Complejidad constante.


Caso peor: Se encuentre en los extremos. T(n)=T(n/2) Complejidad logartmica.

164

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

3.6.10 Periodo de mximo riesgo.


Un inversor est sopesando invertir en un activo financiero. Para poder estimar el riesgo que asume el inversor,
nos piden que calculemos el periodo de mximo riesgo del activo. Se define el periodo de mximo riesgo como
el periodo de tiempo donde el activo ha producido la prdida mxima (medida en tanto por ciento). Para realizar
estos clculos nos proporcionan un array con los porcentajes de variaciones de rendimientos del activo en las
ltimas N sesiones. Las variaciones sern positivas si el valor del activo sube y negativas en caso contrario. Para
el siguiente array:

-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.

NOTAS sobre el diagrama de clases:

Los metodos getI(), getJ() y getVar() de la clase PeriodoDeMaximoRiesgo devuelven respectivamente


la sesin inicial y final del periodo, y el porcentaje de variacin de rendimiento del activo en dicho
periodo.
El mtodo double getVarSession(int pos) de la clase ProblemaInversionActivoFinanciero devuelve la
variacin de rendimiento producida por el activo en la sesin pos. Y los mtodos getI() y getJ()
devuelven la primera y la ltima sesin del problema generalizado.
Dispone del mtodo private PeriodoDeMaximoRiesgo getVarLocal(int s) que calcula el periodo de
mximo riesgo considerando que la sesin s siempre estar incluida en dicho periodo.

165

private PeriodoDeMaximoRiesgo getVarLocal(int s) {


double sm = getVarSesion(s);
int si = s, sj = s;
double vs = sm;
for (int k = s - 1; getI() <= k; k--) {
vs = vs + getVarSesion(k);
if (sm > vs) {
sm = vs;
si = k;
}
}
vs = sm;
for (int k = s + 1; k <= getJ(); k++) {
vs = vs + getVarSesion(k);
if (sm > vs) {
sm = vs;
sj = k;
}
}
return new PeriodoDeMaximoRiesgoImpl(si, sj, sm);
}

166

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


SOLUCIN :

Apartado 1)

DYV sin memoria.

Apartado 2)

Esquema DYV sin memoria:

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)

Mtodos de la clase ProblemaInversionActivoFinancieroDyV:

public boolean esCasoBase() {


return (getJ() - getI() + 1 == 1 || getJ() < getI());
}
public PeriodoDeMaximoRiesgo getSolucionCasoBase() {
PeriodoDeMaximoRiesgo rm = new PeriodoDeMaximoRiesgoImpl();
if (getJ() - getI() + 1 == 1)
rm = new PeriodoDeMaximoRiesgoImpl(getI(), getJ(), getVarSesion(getI()));
if (getJ() < getI())
rm = new PeriodoDeMaximoRiesgoImpl(getI(), getJ(), Integer.MAX_VALUE);
return rm;
}
public int getNumeroSubProblemas() {
return 2;
}
public ProblemaDyV<PeriodoDeMaximoRiesgo> getSubProblema(int i) {
int m = (getI() + getJ()) / 2;
ProblemaDyV<PeriodoDeMaximoRiesgo> sp = null;
if (i == 0)
sp = new ProblemaInversionActivoFinancieroDyV(getI(), m - 1, getVarSesiones());
if (i == 1)
sp = new ProblemaInversionActivoFinancieroDyV(m + 1, getJ(), getVarSesiones());
return sp;
}
public PeriodoDeMaximoRiesgo combinaSoluciones(PeriodoDeMaximoRiesgo... soluciones) {
int s = (getI() + getJ()) / 2;
PeriodoDeMaximoRiesgo rm = getVarLocal(s);
PeriodoDeMaximoRiesgo rmIzq = soluciones[0];
PeriodoDeMaximoRiesgo rmDer = soluciones[1];
if (rm.getVar() > rmIzq.getVar()) {
rm = rmIzq;
}
if (rm.getVar() > rmDer.getVar()) {
rm = rmDer;
}
return rm;
}

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

3.6.11 Secuencia de ADN.


Una secuencia de ADN se puede representar como una cadena que contiene las letras que representan cada una
de las bases nitrogenadas posibles: A (Adenina), G (Guanina), C (Citosina) y T (Timina).
Ejemplo:

GTCGGGATGCACCTGAGAAA

La informacin de entrada es un array donde viene representada la secuencia de ADN.


Adems, para facilitar la resolucin del problema se identificar cada base nitrogenada
con un identificador y el problema consistir en encontrar el nmero de repeticiones
del identificador que ms se repite en la secuencia. Se trata de disear un algoritmo
mediante la tcnica de divide y vencers que encuentre la base nitrogenada
(representada por su identificador) que ms se repite.

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 buscaMaxCBase(List<Integer> adn):

Integer elegirPivote(List<Integer> adn, Integer i, Integer j): Mtodo que elige un valor como pivote

Mtodo que busca el mximo de una lista de identificadores.


Este mtodo es de O(n) y solo se usar cuando el tamao del problema es menor que un umbral.
para una lista de enteros entre los ndices i y j.

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());

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.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


SOLUCIN:

Aparatado 1)

Secuencia de ADN
Tcnica:

Divide y Vencers sin Memoria.

Tamao:

j-i

Propiedades Compartidas:

adn
umbral

List<Integer>
Integer

Propiedades Individuales

i
j
Integer

Integer [0, adn.size())


Integer [i+1, adn.size())

Solucin:
Instanciacin:

() = (, 0, . ())

Problema generalizado:
(, , )
= (, , );
(1 , 2 = (, , , );
= max(
(, , ) =
(, , 1 ),
2 1 ,
(, 2 , )
)
{
]

>

Aparatado 2)

Integer buscaMax(List<Integer> adn, Integer i, Integer j) {


if (j - i <= umbral) {
buscaMaxCBase(adn, i, j);
} else {
Integer p = elegirPirvote(adn, i, j);
List<Integer> solBH = new ArrayList<Integer>();
Integer r = Math.max(buscaMax(adn, i, solBH[0]), solBH[1] - solBH[0]);
r = Math.max(r, buscaMax(and, solBH[1], j));
return r;
}
}

169

3.6.12 Nube de puntos tridimensional.


En un espacio tridimensional, tenemos ubicados una nube de
puntos definidos por tres coordenadas (x, y, z) respecto a un
punto origen (0, 0, 0) dentro de este espacio.
Se desean almacenar estos puntos en un array a partir de un
orden establecido. Este orden debe estar basado en la suma de
sus coordenadas de mayor a menor valor de manera que en las
primeras posiciones se encontrarn los puntos ms alejados del
origen y en las ltimas los ms cercanos.
Por tanto y, a partir de este modelado y siguiendo el esquema
de la tcnica de Divide y Vencers, se pide resolver las siguientes
cuestiones.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


SOLUCIN:

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

3.6.13 Tendencias del Ibex35.


Dada dos empresas del ndice burstil Ibex35 E1 y E2, se desea
implementar mediante la tcnica de Divide y Vencers el mtodo
inversionTendencias que indique si ambas empresas siguen la
misma tendencia de inversin, es decir, siguen una tendencia
inversora o no inversora para cualquier intervalo de tiempo
considerado.
Una empresa tiene una tendencia inversora cuando la diferencia del valor de la empresa en cada extremo del
intervalo de tiempo es positiva. Supngase siempre discretizados de los valores de las Empresa en intervalos del
tamao indicado por la variable umbral. Es decir, suponiendo un umbral de 1 ao y si estuviramos en el caso
base donde el intervalo es [1998-1999] tendramos: E1.getValorInversion(1998) es 9.885, y
E1.getValueInversion (1999) es 11.230, entonces tenemos que: 11.230 - 9.885 > 0, por lo tanto la empresa E1
tiene una tendencia inversora en dicho intervalo. En el caso de que la tendencia fuera no inversora esta diferencia
sera negativa.
La funcin inversionTendencias debe devolver cierto cuando en todos los intervalos de tiempo establecido las
empresas en estudio; E1 y E2, tienen la misma tendencia, y falso en caso contrario. En las imgenes siguientes
podemos ver dos empresas con las mismas tendencias de inversin y otras con diferentes tendencias de
inversin.

Se pide:

1. Rellenar la ficha adjunta. Se debe entregar la hoja de la ficha rellena.


2. Implemente un algoritmo basado en Divide y Vencers basndose en la definicin anteriormente
hecha y siguiendo el prototipo a continuacin:
public Boolean inversionTendenciasDyV(Empresa e1, Empresa e2, int umbral, int i, int j) {}

172

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


SOLUCIN:

Aparatado 1)

Tendencias del Ibex35


Tcnica:

Divide y Vencers.

Tamao:

(x2 x1) / umbral

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

4 Ampliacin de Colecciones de Datos (Grafos).


4.1 Grafos en el mundo real.
Existen mltiples y diversos ejemplos de grafos en el mundo
real. El problema de los puentes de Knigsberg, tambin
llamado ms especficamente problema de los siete
puentes de Knigsberg, que se muestra en la figura, es un
clebre problema matemtico resuelto por Leonhard Euler
en 1736 y cuya resolucin dio origen a la teora de grafos.
Su nombre se debe a Knigsberg, el antiguo nombre que
reciba la ciudad rusa de Kaliningrado. Esta ciudad es
atravesada por el ro Pregolya, el cual se bifurca para rodear con sus brazos a la isla Kneiphof, dividiendo el
terreno en cuatro regiones distintas, las que entonces estaban unidas mediante siete puentes. El problema fue
formulado en el siglo XVIII y consista en encontrar un recorrido para cruzar a pie toda la ciudad, pasando slo
una vez por cada uno de los puentes, y regresando al mismo punto de inicio.

El fsico Kirchhoff, en 1847, con el fin de estudiar el clculo de la intensidad y la diferencia


de potencial de cada elemento de una red elctrica, entre los cuales se encuentran:
resistencias, bobinas, condensadores, fuente de tensin, etc., estudi los grafos conexos
con el objetivo de desarrollar un mtodo efectivo para el anlisis de estas redes elctricas.

Los grafos tambin se usan en el mundo real para modelar el comportamiento. Se


denomina mquina de estados a un modelo de comportamiento de un sistema con
entradas y salidas, en donde las salidas dependen no slo de las seales de entradas
actuales sino tambin de las anteriores. Las mquinas de estados se definen como un
conjunto.

Otra utilizacin de los grafos es definir la


dependencia entre diferentes entidades. Por
ejemplo, los diagramas UML son un lenguaje
grfico para visualizar, especificar, construir y
documentar un sistema. La mayora de ellos son
grafos.

175

Las tcnicas de solucin de problemas en Inteligencia Artificial, en


general, incorporan el uso de grafos. Por ejemplo todo proceso de
bsqueda puede ser visualizado como el recorrido por un grafo
en el que cada nodo representa un estado y cada rama representa
las relaciones entre los estados cuyos nodos conecta.

El N-puzle es un problema que se puede modelar


con grafos para resolverlo. Como se observa en la
Fig. 4 se trata de un puzle que consiste en un marco
que contiene piezas cuadradas y numeradas en
orden aleatorio, con una ficha que falta. El objetivo
del puzle es colocar los cuadros en orden (ver la
figura), haciendo movimientos deslizantes que
utilizan el espacio vaco. El N-puzle es un problema
clsico para el modelado de algoritmos heursticos.

176

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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.

4.2.1 Trminos de un grafo.


Veamos, en primer lugar, un conjunto de trminos sobre grafos:
-

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.

Bucle: Arista que une vrtice consigo mismo.

Ciclo: Camino cerrado donde no hay vrtices repetidos salvo el primero que coincide con el ltimo.

Grafos Dirigidos: Si todas sus aristas son dirigidas.

177

Grafos No Dirigidos: Si todas sus aristas son no dirigidas.

Grafos Mixtos: Puede haber aristas dirigidas y no dirigidas.

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 Acclico: Grafo que no tiene ciclos.

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.

rbol: Es un grafo simple conexo y sin ciclos.

Bosque: Es un grafo sin ciclos. Es, tambin, un conjunto de rboles libres.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

4.2.2 Tipo Graph.


Como para otros tipos de agregados de datos hablaremos de grafos explcitos y de grafos implcitos o virtuales.
Los agregados explcitos se construyen agregando y/o eliminando elementos del mismo. En los agregados
virtuales solo se describen predicados que indican si un elemento pertenece al agregado o no.
Para modelar un grafo explcito (Graph) necesitamos conocer su conjunto de vrtices y su conjunto de aristas.
Asumimos que cada vrtice es de tipo V y cada arista de tipo E. El grafo entonces es de tipo Graph <V,E>. Usaremos
la interfaz y las herramientas proporcionadas por jGrapht que pueden ser conseguidas en: http://jgrapht.org/
Una interfaz que abarca los diferentes tipos de grafos es: Graph

<V,E>.

Los invariantes para todos los tipos de grafos son:


Los extremos de una arista deben estar incluidos en el conjunto de vrtices.
Todos los vrtices del grafo deben ser distintos entre s en el sentido definido por equals para el tipo V.
Todas las aristas del grafo deben ser distintas entre s en el sentido definido por equals para el tipo E.

Graph <V,E>

Tipo retorno

addEdge
(V sourceVertex, V targetVertex)

addEdge (V sv, V tv, E e)

boolean

addVertex (V v)

boolean

containsEdge (E e)

boolean

containsEdge (V sv, V tv)

boolean

containsVertex (V v)

boolean

edgeSet ()

Set<E>

edgesOf (V v)

Set<E>

getAllEdges (V sv, V tv)

Set<E>

getEdge (V sv, V tv)

getEdgeFactory ()

EdgeFactory<V,E>

getEdgeSource (E e)

getEdgeTarget (E e)

gedEdgeWeight (E e)

double

removeAllEdges
(Collection<? extends E> edges)

boolean

removeAllEdges (V sv, V tv)

boolean

removeAllEdges (V sv, V tv)

Set<E>

removeEdge (E e)

boolean

removeAllVertices
(Collection<? extends V>
vertices)

boolean

removeAllEdges (V sv, V tv)

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:

4.2.3 Tipo DirectedGraph.


Los grafos dirigidos son aquellos en los que todas las aristas estn dirigidas. Hereda todos los mtodos de la
interfaz Graph y ofrece cuatro nuevos mtodos.
En la siguiente tabla se describen los mtodos que aade esta interfaz.
DirectedGraph <V, E>
extends Graph <V, E>
incomingEdgesOf (V v)

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.

4.2.4 Tipo UndirectedGraph.


El tipo UndirectedGraph es un subtipo de Graph que slo contiene aristas no dirigidas. Aade un nico mtodo a los
que hereda de Graph.
En la siguiente tabla se describe el mtodo que aade esta interfaz.
UndirectedGraph <V, E>
extends Graph <V, E>
degreeOf (V v)

Tipo retorno

Descripcin
Devuelve el grado del vrtice v. Es decir el nmero de aristas
que lo tocan.

int

4.2.5 Tipo WeightedGraph.


El tipo WeightedGraph es el grafo que representa el grafo ponderado. Se caracteriza por tener un peso asociado a
cada una de las aristas del grafo.
Esta interfaz aade un atributo y un mtodo a los que ya hereda de Graph con la siguiente semntica:
WeightedGraph <V, E>
extends Graph <V, E>

Tipo retorno

DEFAULT_EDGE_WEIGHT

double

setEdgeWeight (E e, double peso)

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.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

4.2.6 Subgrafos, Unin de Grafos y Cambio de tipos.


Un grafo, como hemos visto antes, es un cojunto de vrtices y aristas que describiremos como g = (v, e). Un grafo
g1 = (v1, e1) es un subgrafo de otro g2 = (v2, e2) si v1 est incluido en v2 y e1 en e2. Dado un grafo podemos obtener
un subgrafo del mismo tipo escogiendo un subconjunto de vrtices y aristas. Asumimos que el tipo G cumple G
extends Graph<V,E>.
Tipo
retorno

Subgraph <V,E>
extends Graph <V,E>
Subgraph (G base, Set<V> vs)

Graph

Subgraph (G base, Set<V> vs, Set<E> es)

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

Devuelve una vista no dirigida del grafo.

Graph

Devuelve una vista con pesos del grafo.

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.

4.2.7 Factoras de Vrtices, Aristas y Grafos.


Es adecuado crear vrtices, aristas y grafos mediante factoras. Para ello dispondremos de los siguientes tipos:
VertexFactory <V>
createVertex ()

Tipo retorno
V

EdgeFactory <V,E>

Tipo retorno

createEdge (V sv, V tv)

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.

4.2.8 Tipo GraphPath.


Un camino en el grafo es representado por el tipo GraphPath.
GraphPath <V,E>
extends Graph <V,E>
getEdgeList ()
getEndVertex ()
getGraph ()
getStartVertex ()
getWeight ()

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

4.3 Formatos de ficheros para representar grafos y visualizacin de los mismos.


Hay diferentes tipos de formatos para representar grafos. Aqu veremos un par de estos formatos ms conocidos:
el formato gdf y el formato dot. Ambos formatos tienen propsitos diferentes. El primero tiene como objetivo
representar la informacin asociada a los datos del grafo, es decir, las propiedades de los vrtices y las aristas, el
conjunto de vrtices el conjunto de aristas y sus conexiones. El segundo est ms orientado a representar la
informacin que se necesita para representar grficamente un grafo, es decir, posibles colores, figuras
geomtricas asociadas a los vrtices, estilo de las aristas, etc. Veamos los detalles de cada uno de ellos.
Los archivos gdf tienen dos secciones claramente diferenciadas: la seccin de vrtices encabezada por la lnea
que comienza con nodedef> y la seccin de aristas que comienza en edgedef>. Ambas secciones se componen de
un conjunto de lneas cada una de las cuales describe las propiedades (separadas por comas) de un vrtice o de
una arista. Los nombres de las propiedades vienen en la cabecera de la seccin. Aunque se puede explicitar un
tipo para cada una de las propiedades usaremos el tipo por defecto (hilera de caracteres) para todas las
propiedades.
Para cada vrtice es completamente necesario indicar un identificador que debe ser nico para cada vrtice (es
la primera propiedad) y puede tener tantas propiedades adicionales como consideremos. En este caso hemos
aadido la propiedad Label.
Para cada arista hay que especificar los identificadores del vrtice origen y el destino (son las dos primeras
propiedades). En este caso hemos aadido la propiedad edgeweight. Si es necesario, podemos aadir
propiedades que identifiquen de forma nica a cada arista, si hubiera varias entre un mismo par de vrtices.

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

createVertex (String[] s);

getVertex (String[] s);

Crea un vrtice a partir de s. Si existe otro vrtice igual


a l devuelve el ya construido.
Vrtice final.

StringEdgeFactory <V,E>
extends EdgeFactory <V,E>

Tipo retorno

Descripcin

createEdge (V sv, V tv, String[] s)

getEdge (V sv, V tv, String[] s)

182

Crea una arista entre los vrtices sv y tv con la


informacin adicional representada en s.
Obtiene la una arista entre los vrtices sv y tv.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


En ambos casos el parmetro s (de tipo String[]) representa el conjunto de propiedades representadas en una
lnea. Las factoras podran ser ampliadas para recuperar vrtices o aristas ya creados segn determinados
criterios. Con los tipos anteriores diseamos una extensin para la factora de grafos proporcionada por jGrapht.
Es la factora Graphs2 cuyos mtodos son:
Graph <V,E>
extends Graph <V,E>
static Graph<V,E> newGraph <V,E> (
String file,
GraphType t,
StringVertexFactory<V>, vf
StringEdgeFactory<V,E> ef
)
static WeightedGraph <V,E>
WeightedGraph <V,E> (
Graph<V, E> g,
Function<E, Double> f
)

Tipo retorno

Descripcin

Graph<V,E>

Crea un grafo a partir de un fichero con la informacin


de vrtices y aristas.

WeightedGraph<V,E>

Devuelve una vista con pesos donde los pesos de las


aristas se calculan mediante una funcin.

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

Genera identificadores de los vrtices.


Genera etiquetas de los vrtices.
Genera etiquetas de las aristas.
Genera otros atributos para los vrtices.
Genera otros atributos para las aristas.

El grfico anterior es el resultado de procesar el fichero anterior con la herramienta comentada.

184

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

4.4 Grafos virtuales.


Aadimos la etiqueta virtual a un agregado de datos cuando no podemos o no queremos tener los elementos
del agregado completamente en memoria y solamente disponemos de una descripcin de algunas propiedades
de los mismos, por lo que podremos tener secuencias numricas virtuales, conjuntos y listas virtuales, etc.

Cundo son tiles?


Con problemas que evolucionan por mltiples estados y sera muy costoso modelarlos todos a priori.
Cuando un algoritmo de grafos solventara nuestro problema.
Ejemplos: Laberintos, Torres de Hanoi, N-puzzle, Aeropuertos

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

abstract class VirtualGraph<V,E>


implements Graph<V,E>
addEdge
(V sourceVertex, V targetVertex)
addEdge (V sv, V tv, E e)
addVertex (V v)

Tipo retorno
abstract boolean
-

containsEdge (E e)

abstract boolean

containsEdge (V sv, V tv)

abstract boolean

containsVertex (V v)

abstract boolean

edgeSet ()

edgesOf (V v)

abstract Set<E>

getAllEdges (V sv, V tv)

abstract Set<E>

getEdge (V sv, V tv)

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.

Un ejemplo de grafo virtual nos lo da el problema de resolver un N-puzle.


El juego del 8-Puzzle usa tablero con 9 casillas, las cuales van enumeradas del 1 al 8 ms una
casilla vaca. Los movimientos posibles del puzle consisten en intercambiar la casilla vaca
con alguno de sus vecinos mediante movimientos horizontales, verticales, hacia la izquierda
o hacia la derecha. El problema consiste en, dada una configuracin inicial, llegar a una
configuracin final (meta) mediante los movimientos permitidos.
El problema puede ser modelado mediante un grafo virtual. Los vrtices del grafo representan cada una de las
posibles distribuciones de los 8 nmeros y una casilla vaca en un tablero de 3 filas y tres columnas. Un vrtice
en particular viene representando en el grfico:

186

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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 containsEdge(V sv, V tv)

boolean containsVertex(V v)

Set<E> edgesOf(V v)

Set<V> getAllEdges(V sv, V tv)

E getEdge(V sv, V tv)

V getEdgeSource(E e)

V getEdgeTarget(E e)

double gedEdgeWeight(E e)

EdgeFactory<V,E> getEdgeFactory().

// Asumimos que cada arista tiene peso 1.

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

4.5 Recubrimiento y componentes conexas.

4.5.1 Recubrimiento mnimo.


Un rbol de recubrimiento mnimo de un grafo es un subgrafo que incluye todos los vrtices del grafo y minimice
la suma total de los pesos de las aristas escogidas.
El algoritmo de Kruskal busca un rbol de recubrimiento mnimo de un grafo.
Su complejidad es (E log E). Donde E es el nmero de aristas.
El algoritmo devuelve el conjunto de aristas que definen el rbol.

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.

4.5.2 Recubrimiento de vrtices.


El problema del recubrimiento mnimo anterior consista en escoger un subconjunto de aristas cuyos extremos
incluyeran todos los vrtices y cuya suma de pesos fuera mnima. Podramos llamarlo recubrimiento mnimo de
aristas. De forma similar, el problema llamado recubrimiento de vrtices, consiste en escoger el mnimo nmero
de vrtices que tocan todas las aristas del grafo.
Orden de complejidad: NP (mayor que polinomial).
La clase VertexCovers implementa un algoritmo para resolver el problema.
VertexCovers ()

Tipo retorno

find2ApproximationCover (Graph<V,E> g)

static <V,E> Set<V>

findGreedyCover (UndirectedGraph<V,E> g)

static <V,E> Set<V>

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

4.5.3 Componentes conexas.


Un grafo no dirigido es conexo si, entre cada dos vrtices, existe un camino del grafo. Una componente conexa
de un grafo no dirigido es un subgrafo maximal conexo. Un grafo conexo tiene una sola componente conexa.
Un grafo dirigido es dbilmente conexo (weakly connected) si, al reemplazar cada arista dirigida por una no
dirigida, resulta un grafo conexo. Un grafo dirigido es fuertemente conexo si, para cada par de vrtices u, v existe
un camino dirigido de u v o de u v. 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.
La clase ConnectivityInspector calcula las componentes conexas de un grafo no dirigido y las componentes
dbilmente conexas de un grafo dirigido. El tipo es, tambin, una implementacin del tipo GraphListener.
ConnectivityInspector (DirectedGraph <V,E> g)
ConnectivityInspector (UndirectedGraph <V,E> g)
connectedSetOf (V v)

Set <V>

connectedSets ()

List <Set<V>>

isGraphConnected ()

boolean

pathExists (V sourceVertex, V targetVertex)

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.

Componentes conexas de un grafo:

El tipo StrongConnectivityInspector calcula las componentes fuertemente conexas de un grafo dirigido.


StrongConnectivityInspector
(DirectedGraph <V,E> directedGraph)

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.

Componentes fuertemente conexas:

189

4.6 Recorridos sobre grafos.


Los grafos son agregados de datos y, como todos los agregados, pueden ofrecer diferentes maneras de recorrer
los elementos del mismo. En concreto, diferentes maneras de recorrer los vrtices de un grafo. En Java, cada
forma concreta de recorrer los elementos de un agregado, se consigue implementado un objeto de tipo Iterable.
Cada recorrido puede considerarse como un problema de bsqueda de un vrtice con propiedades especficas
entre los vrtices de un grafo. Por eso hablaremos indistintamente de recorridos o de bsquedas. De entre ellos
los ms conocidos son: recorrido en anchura, recorrido en profundidad, orden topolgico y siguiente vrtice ms
cercano. Veamos cada uno de ellos y la forma de obtenerlos.
Aunque los recorridos pueden ser extendidos para recorrer todas las componentes conexas de un grafo en los
que sigue, consideraremos slo el recorrido de una componente conexa. Cada recorrido comienza en un nodo
inicial (o en un conjunto de vrtices iniciales) y es exhaustivo en el sentido que visita todos los vrtices de la
componente conexa dada (o de todas las componentes conexas del grafo si el recorrido se ha extendido para
ello).
Cada recorrido define un rbol de recubrimiento (o un bosque si el recorrido se ha extendido a todas las
componentes conexas del grafo). Las aristas del rbol de recubrimiento definen caminos, con propiedades
especficas para cada recorrido, desde cada vrtice al vrtice inicial (vrtices iniciales).
Cada recorrido implementa el tipo Iterator<V> y puede informar a listeners sobre los siguientes eventos:

Ha comenzado el recorrido de una nueva componente conexa.


Ha terminado el recorrido de una componente conexa.
Se ha atravesado una arista dada.
Se ha visitado un vrtice dado.
Se ha cerrado un vrtice dado. Cundo se cierra un vrtice es algo especfico de cada recorrido.

Los listeners deben implementar TraversalListener<V,E>.

4.6.1 Recorrido en anchura.


La bsqueda en anchura visita cada nodo, posteriormente sus hijos, luego los hijos de estos, etc. Es por tanto,
un recorrido por niveles o, si se quiere, por distancia al nodo origen (asociando peso 1 a cada arista): primero los
que estn a distancia 0, luego los que estn a distancia 1, etc.
Este recorrido se puede aplicar tanto a grafos dirigidos como a los no dirigidos.
El recorrido en anchura define un rbol de recubrimiento donde las aristas escogidas definen caminos mnimos
desde cada vrtice al inicial (asociando peso 1 a cada arista). La distancia al vrtice inicial es el nivel de cada
vrtice.
Cuando desde un vrtice se alcanza otro por primera vez, a travs de una arista, sta se incluye en el rbol de
recubrimiento.

La clase BreadthFirstIterator<V,

E>

implementa un iterador que hace el recorrido en anchura.

Sus constructores son:


BreadthFirstIterator <V,E>
BreadthFirstIterator (Graph<V,E> g)
BreadthFirstIterator (Graph<V,E> g, V startVertex)

190

Descripcin
El vrtice inicial es arbitrario.
El vrtice inicial es el vrtice indicado startVertex.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

4.6.2 Recorrido segn el siguiente ms cercano.


Es un tipo de recorrido que generaliza la bsqueda en anchura. Ahora el siguiente vrtice es el ms cercano al
inicial segn la suma de los pesos de las aristas del camino hasta l.
Este recorrido se puede aplicar a grafos dirigidos o no dirigidos.
La clase ClosestFirstIterator
cercano. Constructores:

<V,E>

implementa un iterador que hace el recorrido en segn el siguiente ms

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

ClosestFirstIterator <V, E>


getShortestPathLength (V vertex)

double

getSpanningTreeEdge (V vertex):

Descripcin
Longitud del camino mnimo hasta el vrtice inicial.
Arista que define el camino mnimo hasta el vrtice inicial.

Junto a los anteriores dispondremos de una variante de ClosestFirstIteratorG<V,E> con un funcionamiento y


mtodos similares a ClosestFirstIteratorG<V,E> pero donde la longitud del camino se define como en los
algoritmos A* que veremos ms adelante.

4.6.3 Bsqueda en profundidad.


La bsqueda en profundidad tiene varias variantes segn se visite un
vrtice antes, despus o entre sus hijos. Las diferentes variantes son:
Preorden: Cada vrtice se vista antes que cada uno de sus hijos.
Postorden: Cada vrtice se visita despus que todos sus hijos.
Inorden: Si el nmero de hijos de cada vrtice en el rbol de
recubrimiento es dos como mximo entonces cada vrtice se visita despus
de sus hijos izquierdos y antes de sus hijos derechos.
Pre: 1-2-5-9-10-6-3-4-7-11-12-8

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

4.6.4 Orden topolgico.


Es un tipo de recorrido que se aplica a grafos dirigidos. En este recorrido cada vrtice va despus que los vrtices
que le anteceden en el grafo dirigido.
Con las restricciones anteriores
hay varios recorridos posibles.
Algunos son:
-

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

implementa un iterador que hace el recorrido en orden topolgico.

Sus constructores son:


TopologicalOrderIterator <V,E>

Descripcin

TopologicalOrderIterator (DirectedGraph<V,E> dg)


TopologicalOrderIterator (DirectedGraph<V,E> dg, Queue<V> queue):

192

La cola, y su posible implementacin,


permiten elegir uno de los posibles
recorridos.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

4.7 Camino Mnimo.


El problema de camino mnimo se puede enunciar del siguiente modo:
Dado un grafo y dos vrtices, encontrar el camino (secuencia de aristas), con longitud mnima entre ambos
vrtices.

4.7.1 Algoritmo de Dijkstra.


El algoritmo de Dijkstra encuentra el camino ms corto entre dos vrtices de un grafo. Los pesos deben ser no
negativos. La implementacin que se ofrece tiene una complejidad de (V2).
Si ei es el peso de una arista dada entonces, para el uso de este algoritmo, definimos la longitud del camino C
formado por una secuencia de aristas como:

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.

4.7.2 Algoritmo de Bellman-Ford.


El algoritmo de Bellman-Ford encuentra el camino mnimo (entendido, como antes, como suma de los pesos de
las aristas del camino) entre dos vrtices. El algoritmo acepta pesos negativos en las aristas. La complejidad del
algoritmo es (VE). Donde V es el nmero de vrtices y E el nmero de aristas.
La clase BellmanFordShortestPath<V,E> implementa el algoritmo. Sus mtodos ms importantes son:
BellmanFordShortestPath( Graph<V,E> graph
, V startVertex)
getPathEdgeList (V endVertex)

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

4.7.3 Algoritmo de FloydWarshall.


El algoritmo de FloydWarshall encuentra los caminos ms cortos (definidos como anteriormente) entre cada
dos vrtices de un grafo. El algoritmo acepta pesos negativos en las aristas. La complejidad es (V3).
La clase FloydWarshallShortestPaths<V,E> implementa el algoritmo. Sus mtodos ms importantes son:
FloydWarshallShortestPaths
(Graph<V,E> graph)
getShortestPath(V a, V b)

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

Camino mnimo entre a y b.


Caminos mnimos desde v al resto de vrtices.
Longitud del camino mnimo de a hasta b.
Nmero de caminos mnimos.
Longitud del camino mnimo ms largo.
Grafo de partida.

4.7.4 Generalizacin del Algoritmo de Dijkstra: Algoritmos A*


Los Algoritmos A* son una generalizacin del algoritmo de Dijkstra donde la definicin de longitud del camino
es ms general. Ahora podemos asignar pesos no slo a las aristas, sino tambin a los vrtices. Ejemplos seran
la resolucin de laberintos: slo w(v) peso en los vrtices; resolucin de puzzles: slo w(e), peso en las aristas;
resolucin de problemas tipo aeropuertos: w(e): duracin del vuelo, w(vi, ee, es), peso en el vrtice i dependiendo
de las aristas de entrada y salida (tiempo de escala).
Sea como antes: ei el peso de una arista, vi el peso asociado a un vrtice, hij el costo estimado del camino del
vrtice i al j y wijk el peso asociado al vrtice i asumiendo que se ha llegado a l mediante la arista j y se sale de
l mediante la arista k. La longitud del camino C es entonces:
|| = + +

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Los algoritmos A* funcionan como el de Dijkstra pero en cada momento mantienen, en el rbol de recubrimiento
generado, la ruta mnima que va del vrtice inicial al final a travs del vrtice actual.
La clase DijkstraShortestPathG<V,E> implementa el algoritmo A*. Sus constructores y mtodos relevantes son:
DijkstraShortestPathG
( WeightedVertexGraph<V,E> graph
, V startVertex
, V endVertex
)
getPath ()

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).

4.7.5 Problema del Viajante.


Se denomina Problema del Viajante el encontrar un camino cerrado en
un grafo que pase por todos los vrtices del grafo una sola vez y tenga
la longitud mnima. El algoritmo HamiltonianCycle() permite calcular una
aproximacin de la solucin del problema.
La implementacin concreta requiere que el grafo sea completo y se
verifique la desigualdad triangular, es decir, para tres vrtices x, y, z
entonces: d(x, y) + d(y, z) > d(x, z).

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

4.8 Redes de Flujo.


Una red de flujo es un grafo dirigido donde cada arista tiene una capacidad (que debe ser no negativa) y recibe
un flujo que debe ser menor o igual a esa capacidad. Un flujo sobre una red de flujo debe satisfacer la siguiente
restriccin (Ley de Conservacin): la cantidad de flujo que llega a un vrtice debe ser igual al que sale del mismo
excepto cuando es un vrtice fuente o un vrtice sumidero. Los de primer tipo producen flujo y los del segundo
consumen flujo.

4.8.1 Algoritmo de Edmonds-Karp (Flujo Mximo).


El problema del Flujo Mximo calcula el mximo flujo que puede fluir por cada arista desde un vrtice fuente
hasta un vrtice sumidero con las restricciones en las capacidades mximas en las aristas. Los datos de partida
son los pesos de las aristas del grafo dirigido que representan la capacidad mxima de las mismas. El algoritmo
de EdmondsKarp resuelve el problema. La complejidad de este algoritmo tiene la cota superior VE2.
EdmondsKarpMaximumFlow
(DirectedGraph<V,E> network)
calculateMaximumFlow (V source, V sink)

void

getCurrentSink()

getCurrentSource()

getMaximumFlow()

Map<E, Double>

getMaximumFlowValue()

double

Tipo retorno

Descripcin

Calcula el flujo mximo desde source a sink.


Vrtice sumidero actual.
Vrtice fuente actual.
Flujo mximo por cada una de las aristas.
Flujo mximo.

Por ejemplo, supongamos que se tenemos distintas mquinas conectadas por


distintas tecnologas de redes con distintos anchos de banda. Este algoritmo
permite calcular cul sera el mximo ancho de banda posible entre dos
equipos, teniendo en cuenta el ancho de banda de todos los posibles nodos
intermedios.

4.8.2 Corte mnimo.


Un corte en una red de flujo es una
particin de sus vrtices en dos
conjuntos disjuntos tal que una de ellas
contenga el vrtice fuente y la otra el
vrtice sumidero. El peso del corte es la
suma de los pesos de las asistas que van
del primer conjunto al segundo.
La solucin es encontrar el corte con el peso mnimo en una red de flujo.
La clase StoerWagnerMinimumCut<V,E> es una implementacin del algoritmo que busca el corte mnimo. Esta
implementacin tiene complejidad |V||E|log|E|.
EdmondsKarpMaximumFlow (DirectedGraph<V,E>
network)
minCut()

Set<V>

minCutWeight()

double

vertexWeight(V v)

double

Tipo retorno

Descripcin

Conjunto de vrtices de un lado del corte mnimo.


Peso del corte mnimo.
Suma de los pesos de las aristas que entran en v.
197

4.8.3 Problema de Red de Flujo.


El problema de red de flujo (tambin llamado problema de circulacin) es una
generalizacin del problema de flujo mximo visto anteriormente. En este
problema junto al flujo mximo permitido en cada arista se puede establecer
un flujo mnimo en cada una de ellas. Adems se fija un coste unitario para
transportar el flujo por cada arista. Se generaliza el concepto de fuente y
sumidero. Se introducen restricciones adicionales en algunos vrtices. Esto lo
indicamos con el mtodo isVertexRestricted(v). Los vrtices con restricciones
adicionales pueden producir flujo (+) y/o consumirlo (-). Sobre el flujo
producido o consumido se permite un flujo mximo, mnimo y un coste
unitario (o beneficio unitario si es un vrtice consumidor). Los vrtices sern,
entonces, de dos tipos: con restricciones y sin ellas.
Las restricciones del problema son una generalizacin de las Leyes de Kirchoff
descritas anteriormente. A cada arista se asocia un flujo, tambin a los vrtices
con restricciones. Asumimos para cada vrtice que, la suma de los flujos
salientes menos los entrantes es igual al flujo producido en el vrtice (con
signo positivo) o al consumido en l (con signo negativo).
Carretera con tramos:
- Moverse por un tramo supone un
coste (/gasolina).
- Algunos tramos tienen
incorporaciones (+) y/o salidas (-).
- El flujo mnimo puede ser 0.

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

Flow <V, E>


Flow (FlowGraph<V,E > network).

Flow (FlowGraph<V,E> network, boolean min)

calculateFlow(V source, V sink)

void

getEdgeFlow()

Map<E,Double>

getSourceFlow()

Map<E,Double>

getSinkFlow()

Map<E,Double>

getCostValue()

double

198

Descripcin

Parmetro min a True.


Parmetro min a True para minimizar.
Calcula el flujo desde source a sink.
Flujo por cada una de las aristas.
Flujo en los vrtices fuente.
Flujo en los vrtices sumidero.
Valor de la funcin objetivo.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

4.8.4 Tipos de Problemas.


El problema del flujo mximo es un problema particular que pretende maximizar el flujo minimizando el coste
desde un vrtice a un conjunto de destinos. El problema se modela dando a cada arista un flujo mnimo de cero,
mximo el dado por la arista y coste cero. El vrtice origen es un vrtice productor sin cota superior (o una cota
muy grande) y coste 0. Cada vrtice destino lo modelamos como un vrtice consumidor sin cota superior (o una
cota muy grande) y coste 1. Al ser los vrtices destinos consumidores tratamos de obtener el mximo flujo
posible (el que maximiza el beneficio).
El problema de los caminos disjuntos en aristas trata de encontrar el nmero de caminos que no comparten
ninguna arista que va de un origen a un destino. Se modela como un problema de flujo mximo con un peso en
cada arista de 1. El nmero mnimo de caminos viene dado por el flujo mximo. Cada camino por el conjunto de
aristas que tienen flujo 1.
El problema de los caminos disjuntos en vrtices trata de encontrar el nmero de caminos que no comparten
ningn vrtice que van de un origen a un destino. Se modela como un problema de flujo mximo con un peso
en cada arista de 1. El nmero mnimo de caminos viene dado por el flujo mximo. El problema se modela como
un problema de flujo mximo pero donde aadimos a cada nodo intermedio (distinto al origen y destino) una
restriccin. Cota superior 1, cota inferior 0 y coste 0. Cada camino, como antes, viene dado por el conjunto de
aristas que tienen flujo 1.
El problema de la conectividad de redes trata de buscar el mnimo nmero de aristas tales que eliminadas
desconectan un vrtice de un destino.
El problema de transporte es otro caso particular. Se trata de enviar un flujo desde un conjunto de vrtices
productores a otro de vrtices consumidores. En este problema los costes y beneficios unitarios de los vrtices
son 0. Se puede generalizar considerando vrtices intermedios que no producen ni consumen flujo.
El problema de la asignacin trata de asignar n personas a n tareas. Se supone que el coste de realizar la tarea j
por la persona i es c(i, j). Se trata de decidir qu persona hace cada tarea para minimizar la suma de los costes
respectivos. El problema se modela mediante una red de flujo cuyos vrtices son las personas y las tareas. Una
arista dirigida desde cada persona a cada tarea con coste, el coste de la asignacin correspondiente. Mximo y
mnimo flujo igual a 1. Las personas las modelamos como vrtices productores con mximo y mnimo flujo igual
a 1 y coste 0. Las tareas las modelamos como vrtices consumidores con mximo y mnimo flujo igual a 1 y coste
0. La solucin viene dada por las aristas con flujo 1. Una variante de este problema es cuando hay ms personas
que tareas. El problema se modela igual pero ahora cada persona tiene, como antes el mximo flujo igual a 1, el
coste 0, pero el coste mnimo igual a 0.
El problema de camino mnimo (un caso particular del problema del camino mnimo que se resuelve por el
algoritmo de Dijkstra visto antes) trata de busca el camino de longitud mnimo desde un vrtice origen a un
vrtice destino en ese tipo de grafos. Puede ser modelado mediante una red de flujo. Se trata de asignar a cada
arista un cota superior infinito, inferior 0 y un coste igual a la longitud de la arista. El vrtice origen lo modelamos
como un vrtice productor con coste 0 y capacidad de produccin mnima y mxima 0. El vrtice destino lo
modelamos como un vrtice consumidor con coste 0 y capacidad de consumo mnima y mxima 0. La solucin
del problema (el camino mnimo viene dado por la secuencia de aristas que tengan flujo 1). Este problema puede
ser generalizado a otro con un vrtice origen y varios destinos y buscar el camino mnimo de cada vrtice a
alguno de los destinos. El problema se modela como antes. Cada nodo destino vrtice consumidor con coste 0,
capacidad de consumo mxima y mnima de 1.
Otra variante es encontrar el camino mnimo desde un vrtice origen a cada uno de n destinos. El problema se
modela como antes. Cada nodo destino vrtice consumidor con coste 0, capacidad de consumo mxima y
mnima de 1. El nodo origen productor con coste 0 y capacidad de produccin mnima y mxima igual a n.
199

4.9 Grafos AND-OR.


Los grafos AND-OR son un tipo de grafos dirigidos con peso en las aristas, dnde hay dos tipos de vrtices:
vrtices OR (problemas) que representamos por un rectngulo y vrtices AND. Si un vrtice no tiene aristas
salientes es un vrtice hoja y, si no tiene entrantes, es el vrtice raz. Asumimos que slo hay un vrtice raz.
Entre otras cosas se usan para representar tareas de Scheduling. En este caso, las aristas representan tareas y los
vrtices eventos de finalizacin. El peso de cada arista indica la duracin de la tarea asociada. El evento asociado
a un vrtice OR puede ocurrir si ha terminado alguna de las tareas asociadas a sus aristas salientes. El evento
asociado a un vrtice AND slo puede ocurrir si han terminado todas las tareas asociadas a cada una de sus
aristas salientes.
Una solucin de un grafo AND-OR es un sub-grafo que incluye la raz y por cada vrtice OR una de sus aristas
salientes y el vrtice en el extremo y por cada vrtice AND cada una de las aristas salientes y sus respectivos
extremos.
Dada una solucin, tal como hemos definido antes, podemos asignar un tiempo a cada vrtice. El problema trata
de buscar la solucin cuyo tiempo asociado al vrtice raz sea ms bajo. Es decir, el conjunto de tareas de la
solucin encontrada, con sus restricciones de precedencia, termina lo ms pronto posible.

Tipo retorno

AndOrAlgorithm (AndOr<V,E> graph)


getVertexSet()

Set<V>

getEdgeSet()

Set<E>

getTimeOfVertex(V v)

double

Descripcin

Conjunto de vrtices de la solucin.


Conjunto de aristas de la solucin.
Tiempo asociado al vrtice v si pertenece a la solucin.

Un ejemplo de este tipo de grafos es:

Para representar los grafos AND-OR definimos el tipo AndOrGraph.


public interface AndOrGraph<V, E> extends DirectedGraph<V, E> {
public enum VertexType {
AND, OR
};
VertexType getVertexType(V v);
}

200

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

4.10 Coloreado de Grafos.


Dado un grafo, se define su nmero cromtico, como el mnimo nmero de colores necesario para dar un color
a cada vrtice de tal forma que, dos vrtices vecinos, tengan colores distintos.
La clase CromaticNumber hace una implementacin de este algoritmo:
CromaticNumber()

Tipo retorno

findGreedyChromaticNumber
(UndirectedGraph<V,E> g)

static <V,E> int

findGreedyColoredGroups
(UndirectedGraph<V,E> g)

static <V,E> Map<Integer,Set<V>>

Descripcin

Encuentra el nmero de colores


requeridos para colorear el grafo g.
Encuentra los grupos de coloracin del
grafo g.

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,

Resolucin de ejercicios tipo:


Dada una ciudad compuesta por barrios, situar el mnimo nmero de
retenes de bomberos de modo que atiendan las incidencias de su propio
barrio o del colindante.

201

202
0

Capacidad
arista

Caminos disjuntos en vrtices

Conectividad de redes
(n aristas que si se eliminan
desconectan origen y destino)

Transporte

Asignacin

Camino mnimo

Caminos disjuntos en aristas

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

Suma (flujos aristas *


coste unitario)

Suma (flujos aristas *


coste unitario)

Suma (flujos aristas *


coste unitario)

Varios orgenes,
destinos e intermedios
Flujo total = suma flujos
Productores = suma
flujos consumidores
Slo orgenes (M) y
destinos (N)
Flujo productores =
nmero tareas

Flujo total sumideros =


nmero aristas corte

Flujo mximo

Flujo total en sumideros

Flujo total en sumideros

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:

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

4.11 Ejercicios resueltos.


1. Desarrolle un mtodo que, dado un grafo explcito no dirigido devuelva una lista de conjuntos de vrtices
donde cada elemento de la lista sea el conjunto de todos los vrtices de una componente conexa distinta
(una componente conexa es un conjunto maximal de vrtices alcanzables), entre todos los conjuntos de
la lista deben estar todos los vrtices del grafo. Por ejemplo, para el grafo:

La lista podra ser (el orden es indiferente):

SOLUCIN:

public <V, E> List<Set<V>> conjuntos(UndirectedGraph<V, E> g) {


ConnectivityInspector<V, E> ci = new ConnectivityInspector<V, E>(g);
return ci.connectedSets();
}

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


public Set<Planeta> planetsConsumig(String r) {
class FilterByConsumedResource implements Predicate<Planeta> {
String r;
public FilterByConsumedResource(String r) {
this.r = r;
}
@Override
public boolean apply(Planeta arg0) {
return arg0.getRecursosConsumidos().contains(r);
}
}
return Sets.filter(wg.vertexSet(), new FilterByConsumedResource(r));
}

class Set2Multimap implements Function<Set<Planeta>, Multimap<String, Planeta>> {


@Override
public Multimap<String, Planeta> apply(Set<Planeta> arg0) {
Multimap<String, Planeta> mm = HashMultimap.create();
for (Planeta p : arg0) {
for (String r : p.getRecursosConsumidos()) {
mm.put(r, p);
}
}
return mm;
}
}

public Double minimumCostToConsumingPlanet(SimpleWeightedGraph<Planeta, DefaultWeightedEdge> wg,


Planeta p) {
Set<Planeta> pConsuming = this.planetsConsumig(p.getRecursoProducido());
assert pConsuming.size() > 0;
Double min = Double.MAX_VALUE;
DijkstraShortestPath<Planeta, DefaultWeightedEdge> aDisjktra;
Double l;
for (Planeta pDes : pConsuming) {
aDisjktra = new DijkstraShortestPath<Planeta, DefaultWeightedEdge>(wg, p, pDes);
l = aDisjktra.getPathLength();
assert l < Double.MAX_VALUE;
if (l < min)
min = l;
}
return min;
}

public Boolean decreaseCost(SimpleWeightedGraph<Planeta, DefaultWeightedEdge> wg, Planeta pOrg,


Planeta pA, Planeta pB, double cost) {
Double cOrg = this.minimngCostToconsumingPlanet(wg, pOrg);
assert cOrg != -1;
assert cOrg < Double.MAX_VALUE;
wg.setEdgeWeight(wg.addEdge(pA, pB), cost);
Double cNext = this.minimngCostToconsumingPlanet(wg, pOrg);
wg.removeEdge(pA, pB);
return cNext < cOrg;
}

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


4. Implemente una funcin que convierta un grafo sin peso en un grafo exactamente igual pero en el que
todas las aristas tengan un peso de 1.0.
A partir de la transformacin anterior, implemente un mtodo que, dado un grafo, un vrtice origen, un
vrtice destino y un nmero de aristas, indique si existe o no un camino que vaya desde el origen al destino
pasando como mximo por el nmero de aristas indicadas.

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 class G2WG<V> implements Function<Graph<V, DefaultEdge>


, WeightedGraph<V, DefaultWeightedEdge>>
{
@Override
public WeightedGraph<V, DefaultWeightedEdge> apply(Graph<V, DefaultEdge> arg0) {
WeightedGraph<V, DefaultWeightedEdge> wg =
new SimpleWeightedGraph<V, DefaultWeightedEdge>(DefaultWeightedEdge.class);
Graphs.addAllVertices(wg, arg0.vertexSet());
for (DefaultEdge de : arg0.edgeSet()) {
wg.setEdgeWeight(wg.addEdge(arg0.getEdgeSource(de), arg0.getEdgeTarget(de)), 1.0);
}
return wg;
}
}

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

4.12 Ejercicios propuestos.


1. Una red social imaginaria y pequea, se representa con un grafo en el que los vrtices modelan a los
miembros de las redes sociales y las aristas (sin etiqueta ni peso) modelan la amistad entre dos miembros.
Desarrolle:
a) Un mtodo que devuelva un conjunto con aquellos miembros que no tengan ningn amigo.
b) Un mtodo que, dados dos miembros, devuelva la lista ms corta de amigos que hay desde un
miembro a otro miembro.
public Set<Miembro> solitarios (SimpleGraph<Miembro, Amistad> redSocial) {
return Sets.newHashSet(Iterables.filter(redSocial.vertexSet(),
new Predicate<Miembro>() {
@Override
public boolean apply(Miembro arg0) {
return (redSocial.degreeOf(arg0) == 0);
}
}));
}

public List<Miembro> listaMasCorta ( SimpleGraph<Miembro, Amistad> redSocial


,Miembro mOrg, Miembro mDes) {
List<Miembro> amigos = Lists.newArrayList();
List<Amistad> lstA
= DijkstraShortestPath.findPathBetween(redSocial, mOrg, mDes);
for (Amistad a : lstA) {
amigos.add(grafo.getEdgeSource(a));
}
amigos.remove(mOrg);
return amigos;
}

2. En el fichero topologa.txt se almacena la topologa de una red de comunicaciones. Los vrtices


(etiquetados con objetos de clase String), representan mquinas que se pueden comunicar, y las aristas
(etiquetas con valores double), representan el ancho de banda disponible entre dos mquinas. Desarrolle
un mtodo que, dado dos mquinas A y B, encuentre el camino por el que se puede obtener el mayor ancho
de banda.
public Set<Camino> mejorAnchoBanda (SimpleWeightedGraph<Maquina, Camino> redComunicacion) {
for (Camino c : redComunicacion.edgeSet()) {
redComunicacion.setEdgeWeight(c, redComunicacion.getEdgeWeight(c) * - 1 );
}
KruskalMinimumSpanningTree<Maquina, Camino> k =
new KruskalMinimumSpanningTree<Maquina, Camino>(redComunicacion);
for (Camino c : redComunicacion.edgeSet()) {
redComunicacion.setEdgeWeight(c, redComunicacion.getEdgeWeight(c) * - 1 );
}
return k.getEdgeSet();
}

208

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


3. Existe una clase CV con los siguientes atributos: {a: String, b: Integer, c: Boolean} y existe una segunda clase
CE con los siguientes atributos: {s: String, i: Integer}. Tambin existe un fichero G.txt que contiene la
informacin necesaria para representar un grafo en el que las etiquetas de los vrtices sean objetos CV y las
etiquetas de las aristas sean objetos CE.
Implemente un mtodo, y todos los elementos auxiliares necesarios, que permita cargar dicho grafo desde
el fichero.
public SimpleGraph<CV, CE> leerGrafoDesdeFichero(String nombreFichero) {
SimpleGraph<CV, CE> grafo = new SimpleGraph<CV, CE>(CE.class);
File archivo = null;
FileReader fr = null;
BufferedReader br = null;
String linea = null;
String[] datos = null;
try {
// Lectura del archivo de vrtices
archivo = new File(nombreFichero + ".cv");
fr = new FileReader(archivo);
br = new BufferedReader(fr);
datos = null;
CV vertice = null;
while ((linea = br.readLine()) != null) {
datos = linea.split(";");
vertice = new CV(datos[0]);
vertice.setA(datos[1]);
vertice.setB(Integer.parseInt(datos[2]));
vertice.setC(Boolean.parseBoolean(datos[3]));
grafo.addVertex(vertice);
}
fr.close();
// Lectura del archivo de aristas
archivo = new File(nombreFichero + ".ce");
fr = new FileReader(archivo);
br = new BufferedReader(fr);
datos = null;
CE arista = null;
CV vOrg = null;
CV vDes = null;
while ((linea = br.readLine()) != null) {
datos = linea.split(";");
arista = new CE();
vOrg = new CV(datos[0]);
vDes = new CV(datos[1]);
arista.setS(datos[2]);
arista.setI(Integer.parseInt(datos[3]));
grafo.addEdge(vOrg, vDes);
}
fr.close();
// Devolvemos el grafo
return grafo;
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fr != null) {
fr.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}

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.

public Set<DefaultWeightedEdge> movReina


(SimpleWeightedGraph<Casilla, DefaultWeightedEdge> tablero)
{
List<Casilla> visita = Lists.newArraList();
visita = HamiltonianCycle.getApproximateOptimalForCompleteGraph(tablero);
return visita;
}

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.

public List<GraphPath<Ciudad, Carretera>> rutasPosibles


(SimpleWeightedGraph<Ciudad, Carretera> mapa, Ciudad cOrg)
{
FloydWarshallShortestPaths<Ciudad, Carretera> fw =
new FloydWarshallShortestPaths<Ciudad, Carretera>(mapa);
return fw.getShortestPaths(cOrg);
}
public Ciudad andenMasProximo (SimpleWeightedGraph<Ciudad, Carretera> mapa, Ciudad cDes)
{
DijkstraShortestPath<Ciudad, Carretera> dsp;
double distMax = Double.MAX_VALUE;
double distancia;
Ciudad cAux = null;
for (Ciudad cOrg : mapa.vertexSet()) {
if (cOrg.tieneAnden()) {
dsp = new DijkstraShortestPath<Ciudad, Carretera>(mapa, cOrg, cDes);
distancia = dsp.getPathLength();
if (distancia < distMax) {
distMax = distancia;
cAux = cOrg;
}
}
}
return cAux;
}

210

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


6. Un juego consiste en establecer una red vas entre distintas ciudades de un mapa. Los jugadores compran
vas y las colocas en el mapa, para conectar distintas ciudades (toda nueva va que un jugador ponga tiene
que estar conectada a la red de vas de dicho jugador).
Adems, el juego tambin permite vender vas obsoletas. Suponiendo que la red de vas de un jugador est
expresada como un grafo (las vas seran las aristas del grafo), utilice el algoritmo de Kruskal para determinar
qu vas (aristas del grafo) se podran vender de tal manera que todas las ciudades que ya estuvieran
conectadas por vas siguieran estando conectadas.

public Set<Via> viasDelJugador (SimpleWeightedGraph<Ciudad, Via> grafo, String jugador) {


return Sets.newHashSet(Iterables.filter(grafo.edgeSet(), new Predicate<Via>() {
public boolean apply(Via arg0) {
return arg0.getPropietario().equals(jugador);
}
}));
}
public boolean esViaValida (SimpleWeightedGraph<Ciudad, Via> grafo, Via v) {
return Iterables.any(viasDelJugador(grafo, v.getPropietario()), new Predicate<Via>()
{
public boolean apply(Via arg0) {
return ( grafo.getEdgeSource(v).equals(grafo.getEdgeSource(arg0))
|| grafo.getEdgeTarget(v).equals(grafo.getEdgeSource(arg0))
);
}
});
}
public void aadirVia (SimpleWeightedGraph<Ciudad, Via> grafo, Via v) {
if (esViaValida(grafo, v))
grafo.addEdge(grafo.getEdgeSource(v), grafo.getEdgeTarget(v));
else
throw new IllegalArgumentException("Error al aadir: " + v.toString());
}

public Set<Via> viasObsoletas (SimpleWeightedGraph<Ciudad, Via> grafo, String jugador) {


SimpleWeightedGraph<Ciudad, Via> grafoJugador =
new SimpleWeightedGraph<Ciudad, Via>(Via.class);
Graphs.addAllVertices(grafoJugador, grafo.vertexSet());
for (Via c : viasDelJugador(grafo, jugador)) {
grafoJugador.addEdge(grafo.getEdgeSource(c), grafo.getEdgeTarget(c));
}
KruskalMinimumSpanningTree<Ciudad, Via> k =
new KruskalMinimumSpanningTree<Ciudad, Via>(grafoJugador);
Set<Via> todas = grafoJugador.edgeSet();
todas.removeAll(k.getEdgeSet());
return todas;
}

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

No hay diferencia horaria entre los aeropuertos.


No se deben tener en cuenta situaciones imprevisibles como retardos en vuelos.
Todos los das el horario de vuelos en cada aeropuerto es el mismo.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


10. Tenemos un laberinto formado por casillas con las siguientes caractersticas:
a. Existen casillas infranqueables (las modelaremos con un entero de valor -1).
b. Las dems casillas pueden atravesarse pero la dificultad para hacerlo no es siempre la misma. Para
modelarlo usaremos un entero que variar entre 0 y 10. Las ms sencillas de atravesar tienen coste
0 y las ms costosas tendrn valor 10.
Dado un fichero con la informacin de cada casilla se crear el laberinto que lo modela y se resolver
utilizando un grafo virtual. Partimos siempre de la casilla superior izquierda y finalizamos por la casilla
inferior derecha. Los movimientos permitidos son en vertical y horizontal, nunca en diagonal.
Un ejemplo de dicho fichero es el siguiente:
0, -1, -1, 0, 0, 0, 0, 1
0, 1, 0, 2, 5, -1, -1, 0
0, -1, -1, 0, 0, 0, 0, 0
-1, -1, 0, 0, -1, 0, -1, -1
0, 2, -1, 1, 2, 1, 0, -1
4, 0, 5, -1, -1, 0, 0, 0
Se desea solucionar el problema utilizando la ruta con menor dificultad (puede pasar por ms casillas
pero la suma de los pesos de las casillas atravesadas ser mnima).

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


15. Cree un recorrido en profundidad, otro en anchura y otro segn el vrtice ms cercano para un grafo del
siguiente tipo UndirectedGraph<String, DefaultEdge>.

public List<String> recorridoEnProfundidad


(UndirectedGraph<String, DefaultEdge> grafo, String vOrg)
{
DepthFirstIterator<String, DefaultEdge> dfi =
new DepthFirstIterator<String, DefaultEdge> (grafo, vOrg);
return Lists.newArrayList(dfi);
}

public List<String> recorridoEnAnchura


(UndirectedGraph<String, DefaultEdge> grafo, String vOrg)
{
BreadthFirstIterator<String, DefaultEdge> bfi =
new BreadthFirstIterator<String, DefaultEdge> (grafo, vOrg);
return Lists.newArrayList(bfi);
}

public List<String> recorridoMasCercano


(UndirectedGraph<String, DefaultEdge> grafo, String vOrg)
{
ClosestFirstIterator<String, DefaultEdge> cfi =
new ClosestFirstIterator<String, DefaultEdge>(grafo, vOrg);
return Lists.newArrayList(cfi);
}

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Aristas:
import org.jgrapht.graph.DefaultWeightedEdge;
public class Calle extends DefaultWeightedEdge {
private static final long serialVersionUID = 1L;
public String toString() {
return "{" + super.getSource().toString() + "->" +
super.getTarget().toString() +
"<" + super.getWeight() + ">" +
"}";
}
}

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

4.13 Ejercicios de exmenes.


4.13.1 Departamento de juguetes de Pap Noel.
El departamento de juguetes de Pap Noel quiere automatizar la planificacin de las
rutas para el reparto de material por las distintas fbricas de juguetes distribuidas por
todo el mundo. Una vez que se ha distribuido el material a todas las fbricas,
posteriormente, Pap Noel se encargar de repartir los regalos que fabrican a las
localidades que tiene asignadas dicha fbrica.
El departamento dispone de un grafo con todas las fbricas de juguetes, as como los trayectos directos que
conectan las fbricas entre ellas. Cada fbrica y cada trayecto estn definidos por un identificador y el nombre
correspondiente. Adems, cada trayecto tiene una determinada longitud y direccin, y cada fbrica tiene
informacin sobre el pas en el que se ubica, as como el nmero de juguetes que fabrica para Pap Noel y los
reparta posteriormente.
Se pide:
a) 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 (de qu tipo seran los vrtices y las aristas), teniendo en cuenta
que debe estar implcito en el propio grafo la distancia que existe entre cada trayecto.
b) Implemente un mtodo que permita calcular el nmero de kolmetros totales que hay en el mapa de
trayectos (hay que tener en cuenta todos los trayectos existentes).
c) Implemente un mtodo que dado un nmero entero n pasado por parmetro, devuelva el conjunto de
fbricas que tienen como mnimo n fbricas vecinas a las que puede ir. (Haga uso de la interfaz Predicate
e Iterables).
d) Implemente un mtodo que permita calcular el mnimo nmero de kilmetros que voy a necesitar para
poder cubrir de material a todas las fbricas del grafo.
e) Implemente un mtodo que permita calcular el conjunto de trayectos que necesito para cubrir de
material todas las fbricas del grafo.
f)

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.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


SOLUCIN:
Apartado a)
Sera necesario un grafo dirigido y ponderado. Tambin podra un multigrafo.
Los vrtices seran de tipo Fbrica y las aristas de tipo Trayecto
Fbrica:
private
private
private
private

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

4.13.2 Proyecto de futuro metro.


Con la puesta en marcha del futuro metro de Sevilla se pretende ofrecer a los ciudadanos una nueva red de
transporte pblico que interconecte el mayor nmero de zonas para facilitar una mayor movilidad por la ciudad.
Las lneas y tramos de cada lnea planificadas para el futuro metro son las que se muestran en la siguiente figura.
Tenga en cuenta que cada tramo slo puede pertenecer a una lnea.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


SOLUCIN:
Aparatado 1)
Sera necesario grafo dirigido y ponderado.
Los vrtices seran de tipo Estacion y las aristas de tipo Linea.
Estacion:
private
private
private
private

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

4.13.3 Fichas de domin.


El domin es un juego de mesa en el que se emplean unas fichas rectangulares, divididas en dos cuadrados, cada
uno de los cuales lleva marcados de cero a seis puntos. Dadas una serie de fichas de domin, se desean
encadenar de modo que para cada ficha, su segundo cuadrado tenga el mismo nmero de puntos que el primer
cuadrado de la siguiente ([1|2] encadena correctamente con [2|4]).
Aunque puedan encadenarse por los dos extremos de la secuencia (derecha o izquierda), lo simplificaremos
obligando a incluir las piezas por la derecha (eso no quita que una pieza pueda rotarse para poder encadenarla).
Para modelar el problema hemos decidido utilizar un grafo virtual. En l definimos un vrtice como la
configuracin actual de las piezas de domin con las siguientes propiedades accesibles:
public List<Pieza> getUsadas();
// Lista de piezas usadas hasta el momento
public List<Pieza> getRestantes(); // Lista de piezas no usadas todava.

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:

public static class EsVerticeValido implements Predicate<Vertice>

(la clase completa)

Un vrtice es vlido si hay una nica ficha usada o si habiendo ms de una, la penltima y la ltima
encadenan correctamente.

public static Set<Arista> aristasAdyacentes(Vertice from)

Teniendo en cuenta lo siguiente:


Los movimientos posibles se corresponder con la la inclusin de las posibles piezas restantes en la
secuencia.
Tenemos modelada la clase pieza y las dems clases tal y como puede verse en el diagrama UML.

222

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

223

SOLUCIN:

Apartado 1)

public static class EsVerticeValido implements Predicate<Vertice> {


public EsVerticeValido() {
super();
}
@Override
public boolean apply(Vertice c) {
int tam = c.getUsadas().size();
return (tam <= 1 || c.getUsadas().get(tam - 2).getSegundoCuadrado().equals(
c.getUsadas().get(tam - 1).getPrimerCuadrado())
);
}
}

Apartado 2)

public static Set<Arista> aristasAdyacentes(Vertice from) {


Set<Arista> r = Sets.newHashSet();
Function<Pieza, Vertice> cs = new VerticeSiguiente(from);
Vertice to;
for (Pieza mov : from.getRestantes()) {
to = cs.apply(mov);
if (EsVerticeValido.apply(to)) {
r.add(Aristas.create(from, to));
}
}
return r;
}

224

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

4.13.4 La cabalgata de los Reyes Magos.

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.

Atributos de la clase Cruce (dispone de mtodos getter y setter):


int id;

// identificador nico del cruce.

Atributos de la clase Tramo (dispone de mtodos getter y setter):


int
String
int
int
boolean
int

id;
nombre_calle;
n_inicio;
n_fin;
hay_cabalgata;
tiempo_estimado;

//
//
//
//
//
//

identificador nico del tramo de calle.


nombre de la calle.
el nmero menor que contiene esta calle (incluido).
el nmero mayor que contiene esta calle (incluido).
indica si, actualmente, la cabalgata est en el tramo de calle.
indica el tiempo que tarda la cabalgata en recorrer el tramo.

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

Implemente los siguientes mtodos:


5)

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

pasa como parmetro. (Utilice la librera Guava).


7)

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)

private Set<Tramo> getTramosAdyacentes(Tramo t):

Recupera del grafo todos los tramos que son

adyacente al pasado como parmetro.


9)

void sensorEntraEnTramo(int idIn): Actualiza el atributo hayCabalgata del tramo indicado y la posicin

actual de la cabalgata. La funcin elevar la excepcin IllegalArgumentException cuando el tramo. pasado


como parmetro, no sea el que corresponde en el recorrido, cuando en el tramo indicado ya hubiera
cabalgata o cuando el tramo indicado no sea un extremo de la cabalgata.
10) void sensorSaleDeTramo(int idIn): Actualiza el atributo hayCabalgata del tramo indicado. La funcin
elevar la excepcin IllegalArgumentException cuando en el tramo indicado no hubiera cabalgata o
cuando el tramo indicado no sea un extremo de la cabalgata.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


SOLUCIN:
Apartado 1)
boolean pasaPorCasa(final String nombreCalle, final int numero) {
return Iterables.any(mapa.edgeSet(), new Predicate<Tramo>() {
public boolean apply(Tramo c) {
return c.nombre.equals(nombreCalle) && c.n_inicio <= numero && c.n_fin >= numero && recorrido.contains(c.id_tramo);
}
});
}

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

4.13.5 Planificacin de rutas de paquetes.


Un servicio de mensajera quiere automatizar la planificacin de las rutas para el
reparto de paquetes por las distintas centrales que tienen alojadas por toda la
cuidad. Una vez que el servicio reparte los paquetes a todas las centrales,
posteriormente cada una se encarga de repartir todos los paquetes que recibe a una
serie de localidades que tiene asignadas.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


SOLUCIN:

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;

El tipo Trayecto tiene las propiedades:


private Integer identificador;
private Double distancia;
private String nombre;

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Apartado 4)

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

4.13.6 Transporte de objetos incompatibles, con capacidad.


Desde el departamento de soluciones innovadoras se nos ha pedido desarrollar un programa para resolver
problemas de incompatibilidades en traslados o cambios de estado.
Un ejemplo de dicho tipo de problema es el problema del barquero en el que
un barquero transporta un lobo, una oveja y una lechuga en una barca en la
que nicamente hay sitio para uno de estos tres elementos. En ningn
momento, el lobo puede quedarse con la oveja sin el barquero, ya que,
entonces, se la comera, y lo mismo ocurrira si se dejase a la oveja sola con la
lechuga. Una posible resolucin sera:

Otro ejemplo que se podra modelar sera el traslado de una


crcel a otra. Habra que llevar al alcaide, a los cocineros, las
armas de los guardias y a los presos. Dada las incompatibilidades
que hay, no puede realizarse el traslado de uno en uno, por lo
que hace falta, al menos, un transporte con capacidad para dos.
Estos problemas pueden modelarse con una tabla de incompatibilidades (de tipo Table<ObjetoTransporte,
ObjetoTransporte, Boolean>) y una capacidad del transporte (de tipo Integer). Estas propiedades compartidas
sern atributos estticos de la clase Transportes.
Cada estado del problema tendr dos conjuntos de objetos
(zona1 y zona2 de tipo Set<ObjetoTransporte>) y la posicin del
transporte (de tipo enumerado ZonaTransporte, Zona1: indica
que est en la zona1, Zona2: indica que est en la zona2). El
objetivo ser pasar los objetos de la zona1 a la zona2, teniendo
en cuenta que no puede darse ninguna incompatibilidad donde
no est el transporte. Estas propiedades del estado sern los
atributos de la clase ProblemaTransporte.
Por ejemplo, para el problema del barquero, la
tablaDeIncompatibilidades ser la tabla mostrada y la capacidad
ser 1. En el estado inicial, la zona1 tendr tres objetos de tipo
ObjetoTransporte (el lobo, la oveja y la lechuga) y zona2 sera un
conjunto vaco; la posicin del transporte ser Zona1. En el
estado final zona1 estar vaca, zona2 contendr los citados tres
elementos y la posicin ser Zona2.
Dado el gran nmero de opciones de resolver estos problemas, se har uso de Grafos Virtuales para modelar la
resolucin. Se utilizar el algoritmo de Dijsktra para obtener la forma ms rpida de resolver el problema (menor
nmero de pasos). Los vrtices sern los estados que tiene el problema (clase ProblemaTransporte) que tiene
las propiedades zona1, zona2 y posicin.
La tablaDeIncompatibilidades y la capacidad se encuentran dentro de la clase Transportes que, adems, contiene
mtodos estticos para resolver estos problemas.
232

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Dentro de la clase Transportes, implemente:
1. public static void creaDatosLoboOvejaLechuga(). Inicializa dos atributos estticos de la clase
(estadoInicial y estadoFinal de tipo ProblemaTransporte que tiene un constructor que recibe un String
con el nombre del objeto). Adems inicializa los otros dos atributos estticos de la clase
tablaDeIncompatibilidades (suponga un constructor de Table sin parametros) y capacidad.

2. La clase interna EsEstadoVlido, que ha de ser un Predicate sobre objetos ProblemaTransporte.


Comprueba que no haya incompatibilidades dentro del estado.
class EsEstadoValido implements Predicate<ProblemaTransporte> {
// TODO
}

Use el mtodo esttico boolean esCompatible (Set<ObjetoTransporte> s, Table<ObjetoTransporte,


ObjetoTransporte, Boolean> i) , que comprueba si un estado s es compatible segn i.

3) La clase interna EstadoSiguiente, que es una Function que, dado un ProblemaTransporte en su


constructor, lo transforme en el siguiente estado a partir de un Set<ObjectoTransporte> que indican los
objetos que cambian de zona. Considere que el conjunto es vlido (es decir, todos los objetos estn en
la misma zona en la que est el transporte y, adems, su tamao es menor o igual que la capacidad). No
ha de comprobar que el ProblemaTransporte resultante sea vlido.
class EstadoSiguiente implements Function<Set<ObjetoTransporte>, ProblemaTransporte> {
// TODO
public EstadoSiguiente(ProblemaTransporte from) {
// TODO
}
// TODO
}

4) public static Set<Arista> aristasAdyacentes(ProblemaTransporte from): A partir del objeto from


devuelve un conjunto de aristas (cree las aristas utilizando la fbrica Aristas y su mtodo esttico create
que recibe dos vrtices, es decir, dos ProblemaTransporte), que representan todos los posibles
movimientos vlidos que puede hacer el transporte.
Para el estado inicial del barquero, existiran tres posibles movimientos, sin embargo, slo uno de ellos
sera vlido.
Para un problema con capacidad superior a 1, el nmero de posibilidades aumenta, ya que el transporte
podr transportar cualquier combinacin de los elementos que haya en su zona. Para esta problemtica
haga uso del mtodo esttico de la clase Transportes:
Set<Set<Object>> getMovimientos(Set<Object> elementos, int capacidad)

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Apartado 3)
public EstadoSiguiente(ProblemaTransporte from) {
super();
this.from = from;
}
public ProblemaTransporte apply(Set<ObjetoTraslado> change) {
ProblemaTransporte to = new ProblemaTransporte();
Set<ObjetoTraslado> add, remove;
to.lugar1 = new HashSet<ObjetoTraslado>(from.lugar1);
to.lugar2 = new HashSet<ObjetoTraslado>(from.lugar2);
if (from.posicion == PosicionGuardian.LADO1) {
to.posicion = PosicionGuardian.LADO2;
add = to.lugar2;
remove = to.lugar1;
} else {
to.posicion = PosicionGuardian.LADO1;
add = to.lugar1;
remove = to.lugar2;
}
add.addAll(change);
remove.removeAll(change);
return to;
}

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

4.13.7 Gestin de usuarios de la ETSII.

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).

Implemente los siguientes mtodos:

static Set<Usuario> grupoPrincipal (SimpleWeightedGraph <Usuario, DefaultWeightedEdge> grafo);

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.

static Set<Usuario> usuariosMasPopulares (SimpleWeightedGraph<Usuario, DefaultWeightedEdge>


grafo);

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.

static int distanciaGrupoPpal (SimpleWeightedGraph<Usuario, DefaultWeightedEdge> grafo);

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


SOLUCIN:

static Set<User> grupoPrincipal (SimpleWeightedGraph<User, DefaultWeightedEdge> grafo)


{
ConnectivityInspector<User, DefaultWeightedEdge> componentes =
new ConnectivityInspector<User, DefaultWeightedEdge>(grafo);
List<Set<User>> grupos = componentes.connectedSets();
Comparator<Set<User>> comp = new Comparator<Set<User>>() {
@Override
public int compare(Set<User> s1, Set<User> s2) {
return ((Integer) s1.size()).compareTo(((Integer) s2.size()));
}
};
return Ordering.from(comp).max(grupos);
}

public Set<User> usuariosMasPopulares (SimpleWeightedGraph<User, DefaultWeightedEdge> grafo)


{
Set<User> grupo = grupoPrincipal(grafo);
Comparator<User> comp = new Comparator<User>() {
public int compare(User arg0, User arg1) {
Integer grado0 = grafo.degreeOf(arg0);
Integer grado1 = grafo.degreeOf(arg1);
return grado0.compareTo(grado1);
}
};
return Ordering.from(comp).max(grupo);
}

static double distanciaGrupoPpal (SimpleWeightedGraph<User, DefaultWeightedEdge> grafo)


{
Set<User> grupo = grupoPrincipal(grafo);
FloydWarshallShortestPaths<User, DefaultWeightedEdge> floyd =
new FloydWarshallShortestPaths<User, DefaultWeightedEdge>(grafo);
double acum = 0;
for (User u1 : grupo) {
for (User u2 : grupo) {
if (u1 != u2) {
acum += floyd.shortestDistance(u1, u2);
}
}
}
return acum / 2;
}

static double distanciaGrupoPpal_Mejor (SimpleWeightedGraph<User, DefaultWeightedEdge> grafo)


{
Set<User> grupo = grupoPrincipal(grafo);
FloydWarshallShortestPaths<User, DefaultWeightedEdge> floyd =
new FloydWarshallShortestPaths<User, DefaultWeightedEdge>(grafo);
double acum = 0;
Set<User> vistos = Sets.newHashSet();
for (User u1 : grupo) {
vistos.add(u1);
for (User u2 : grupo) {
if (u1 != u2 && !vistos.contains(u2)) {
acum += floyd.shortestDistance(u1, u2);
}
}
}
}

237

4.13.8 Red social.

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:

En base al sistema ideado, se pide responder a las siguientes cuestiones:


a) Sabiendo que las propiedades de los usuarios son el identificador de usuario (el cual es nico para cada
uno de ellos), el nombre y la direccin, y que la nica propiedad de la arista es el nmero de aficiones
que comparten los usuarios a los que une, indique el nmero mnimo de archivos necesarios para
representar la informacin del grafo. Adems, indique de manera general, cul sera el contenido de
cada uno de dichos archivos.
b) Implementar un mtodo llamado getAficionesComunes que, dados dos objetos de tipo Usuario y el
grafo anterior, identifique el nmero de aficiones que tienen en comn dichos usuarios. Tenga en cuenta
que el grafo anterior se modelar mediante un grafo ponderado no dirigido. Si los usuarios no son
amigos, el resultado ser 0.
c) Implementar un mtodo llamado getMejorAfinidad, que recibiendo por parmetro un conjunto de
usuarios y el grafo anterior, determine la afinidad mxima entre los usuarios presentes en el conjunto.
Tenga en cuenta que el nmero de usuarios presentes en el conjunto puede ser menor que el nmero
de usuarios presentes en el grafo.
d) Implementar un mtodo denominado getUsuarioByName que, dado un nombre de usuario, busque
entre todos los usuarios del grafo aquellos que cumplan con el requisito de que su nombre sea igual al
pasado por parmetro.
e) Por ltimo, se desea obtener un rbol de expansin mximo que permita establecer un canal de
comunicacin entre cada par de usuarios, de forma que la afinidad entre el par de usuarios sea mxima
Qu algoritmo debera aplicar para este fin? Describa en lenguaje natural, si fuera necesario, las
modificaciones que habra que realizar sobre el algoritmo original.

238

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


SOLUCIN:
Apartado a)
Bastara con emplear dos archivos, dado que las aristas no tienen ninguna propiedad salvo el peso. Los
archivos necesarios seran el fichero de vrtices y el de adyacencias.
El primero de ellos debera contener el identificador de vrtice, en este caso el id de usuario y, a
continuacin, separados por comas las diferentes propiedades del usuario:
idUsuario1;idUsuario1,nombreUsuario,direccionUsuario.

En cuanto al segundo archivo, el de adyacencias, la informacin que debera contener sera el


identificador del vrtice origen y destino, adems del peso de la arista que los une. Es decir:
idUsuario1;idUsuario2;8

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:

public Integer getAficionesComunes(Usuario usr1, Usuario usr2, SimpleWeightedGraph grafo) {


Integer ret = 0;
Aficion aficion = grafo.getEdge(usr1, usr2);
if (aficion != null)
ret = grafo.getEdgeWeight(aficion);
return ret;
}

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

4.13.9 Construccin de un avin.

La construccin de un avin es un proceso complejo que incluye, entre otras


actividades, la construccin por separado de cada una de las piezas que
componen el avin. Estas piezas, se construyen en fbricas localizadas en
diferentes ciudades y para el ensamblaje final, es necesario transportar cada
una de las diferentes piezas hasta una nica fbrica comn en donde
finalmente se ensamblan.
Las fbricas se localizan slo en algunas ciudades, y entre todas las ciudades, existe algn tipo de transporte
hacia alguna otra ciudad.
Cada ciudad, por tanto, posee un identificador y adems, una lista con las fbricas que existen en la ciudad. Si
no existiera ninguna fbrica, esta lista estara vaca. El transporte siempre se realiza entre fbricas de diferentes
ciudades.
Para el transporte de las piezas entre ciudades, solo existe un tipo de transporte que puede ser: terrestre,
ferroviario, areo o martimo. Adems, tendremos un identificador, una distancia y un coste de desplazamiento
asociado a cada transporte.
Se pide:
1. Indique cmo modelar el problema indicando de qu tipo seran los vrtices y las aristas, los atributos
que tendran ambos tipos y adems, el tipo de grafo que necesita. Respecto a la ponderacin del grafo,
sta podra referirse a alguna de las propiedades que posee el transporte. Por tanto, para este ejercicio
indique todas las propiedades como atributos del transporte, sin tener en cuenta cul usara para su
ponderacin.
2. Implemente un mtodo que, dada una ciudad origen (con al menos una fbrica en la que se ha fabricado
una de las piezas) y una ciudad de destino (con una fbrica que ensamblar todas las piezas), calcule el
camino de menor coste desde la ciudad origen a la ciudad destino. Diga qu propiedad establecera en
el grafo para la ponderacin de las aristas.
3. Para mejorar la red de comunicaciones entre las diferentes ciudades que poseen fbricas se quiere
instalar un cableado de fibra ptica que las conecte. Para ello, tendr que descartar del grafo las aristas
del tipo de transporte areo y martimo. Por tanto, el nuevo cableado slo podr ser construido
aprovechando la red ferroviaria o terrestre actual. Implemente un mtodo que permita calcular el
mnimo nmero de lneas de cableado que tendramos que construir para que todas y cada una de las
ciudades con fbrica estn conectadas a la red de comunicaciones y, adems, 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 dado el grafo devuelva el conjunto de ciudades que poseen al menos una
fbrica. (Haga uso de la interfaz Predicate e Iterables).

240

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


SOLUCIN:
Aparatado 1)
Sera necesario un grafo dirigido y ponderado o un grafo simple ponderado (ambos valen).
Los vrtices seran de tipo Ciudad y las aristas de tipo Transporte.
Tipo Ciudad:
private String
identificador;
private List<Fabrica> fabricas;

Tipo Transporte:
public
private
private
private
private

enum TipoTransporte { TERRESTRE, MARITIMO, FERROVIARIO, AEREO }


String
identificador;
TipoTransporte tipo;
Double
coste;
Double
distancia;

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


SOLUCIN:
Apartado 1)
Se utilizar un grafo simple dirigido (SimpleDirectedGraph<Node, AControl >) con tipos Node y
AControl como vrtices y aristas. Se aceptar que se utilizara el tipo DefaultEdge!para las aristas.
Las clases vrtices y aristas se describen a continuacin:
String
id
clase Node { TipoNodo tipo
int
duracion

clase AControl { String id

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

4.13.11

Plan de vuelo VANT.


Debemos implementar un sistema que construya el plan de vuelo de un
vehculo areo no tripulado (VANT). El sistema recibir un mapa que ser
modelado mediante un grafo virtual formado por casillas y aristas. El avin
saldr de un punto del mapa con destino a otro punto del mapa. A la hora de
crear el plan de vuelo el sistema se puede encontrar con obstculos que debe
salvar y con zonas con distintos niveles de riesgo que debe minimizar.
INI
0
0
0
0
0
0

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

Se pide implementar los siguientes mtodos de las clases Casillas y VANTCaminoMinimo:

public class Casillas {


private static int nx; // Tamao mximo del mapa (eje x)
private static int ny; // Tamao mximo del mapa (eje y)
public static enum Orientacion {
Norte, Este, Sur, Oeste
}
public static Predicate<Casilla> esCasillaValida = new Predicate<Casilla>() {
public boolean apply(Casilla c) {
// TODO
}
};
// Lista de movimientos del avin.
private static List<Pair<Integer, Integer>> listaDeMovimientos = Lists.newArrayList();
public static void inicializaMovimientos() { // Inicializa la lista de movimientos del avin
// TODO
}
public static Casilla create(int x, int y) {
//UTILIZAR NO IMPLEMENTAR
}
public static Set<Arista> getAristasACasillasVecinas(Casilla from){
// TODO
}
}
public class
VANTCaminoMinimo
extends
VANTGraph
implements WeightedVertexGraph<Casilla, Arista>
{
public double getVertexWeight(Casilla vertex) {
// TODO
}
public double getVertexWeight(Casilla vertex, Arista edgeIn, Arista edgeOut) {
// TODO
}
public double getWeightToEnd(Casilla startVertex, Casilla endVertex) {
// TODO
}
}

Suponga que dispone de las siguientes clases y mtodos:

246

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


SOLUCIN:

public class Casillas {


private static int nx;
private static int ny;
public static enum Orientacion {
Norte, Este, Sur, Oeste
}
public static Predicate<Casilla> esCasillaValida = new Predicate<Casilla>() {
public boolean apply(Casilla c) {
return c.getX() >= 0 && c.getX() < nx && c.getY() >= 0 && c.getY() < ny &&
c.getInfo() > 0;
}
};
private static List<Pair<Integer, Integer>> listaDeMovimientos = Lists.newArrayList();
public static void inicializaMovimientos() {
listaDeMovimientos.add(Pair.create(1, 0));
listaDeMovimientos.add(Pair.create(-1, 0));
listaDeMovimientos.add(Pair.create(0, 1));
listaDeMovimientos.add(Pair.create(0, -1));
}
public static Casilla create(int x, int y) {
// UTILIZAR NO IMPLEMENTAR
}
public static Set<Arista> getAristasACasillasVecinas(Casilla from) {
Set<Arista> r = Sets.newHashSet();
int x = from.getX();
int y = from.getY();
Casilla to;
for (Pair<Integer, Integer> p : listaDeMovimientos) {
to = Casillas.create(x + p.p1(), y + p.p2());
if (esCasillaValida.apply(to)) {
r.add(Arista.create(from, to));
}
}
return r;
}
}

247

public class VANTCaminoMinimo extends VANTGraph implements WeightedVertexGraph<Casilla, Arista> {


public double getVertexWeight(Casilla vertex) {
return vertex.getInfo();
}
public double getVertexWeight(Casilla vertex, Arista edgeIn, Arista edgeOut) {
if (edgeIn == null)
return 0;
if (edgeOut == null)
return 0;
Casilla v1 = edgeIn.getFrom();
Casilla v2 = vertex;
Casilla v3 = edgeOut.getTo();
int ax = v2.getX() - v1.getX();
int ay = v2.getY() - v1.getY();
int bx = v3.getX() - v2.getX();
int by = v3.getY() - v2.getY();
int inc = 0;
if (ax != bx)
inc = inc + 3;
if (ay != by)
inc = inc + 3;
return inc;
}
public double getWeightToEnd(Casilla startVertex, Casilla endVertex) {
int x = Math.abs(endVertex.getX() - startVertex.getX()) + Math.abs(endVertex.getY() startVertex.getY());
return x;
}
}

248

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

4.13.12

Punto de Inters Turstico (PIT).


La delegacin turstica de una comarca desea realizar una aplicacin
mvil de asistencia a los cicloturistas que la visitan. El funcionamiento
de la aplicacin permitir al usuario seleccionar los Puntos de Inters
Turstico (PIT) que desee para planificar su recorrido de entre los
existentes en la base de datos. La base de datos contiene, los nombres
por los que se identifican los PITs, los tiempos de trayecto entre los
PIT as como el tiempo recomendado de visita de cada PIT. Todos los
tiempos estn medidos en minutos.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Solucin:

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

4.14 Ejercicios de LIGA.


4.14.1 Hermandad de nazarenos.
Una hermandad de nazarenos quiere realizar una salida extraordinaria por su
barrio debido a la efemride de su 50 aniversario fundacional. Sin embargo la
hermandad tiene como requisito indispensable en su recorrido el visitar todas las
iglesias del barrio, siendo el grafo del barrio el que se muestra a continuacin. En
dicho grafo, la iglesia X est representada por el punto IX. Dicho grafo incluye las
calles que unen cada par de iglesias junto con su longitud.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

4.14.2 Cartas MagicWorld.


La empresa de cartas MagicWorld se encarga de la distribucin de mazos de
cartas para juegos de rol. ltimamente se ha detectado que los mazos de
cartas nuevas se encuentran desordenadas. Normalmente los mazos a
estrenar deben estar ordenados segn las categoras: Equipamiento, Magia
y Accin. Cada categora posee una serie de propiedades denominadas
habilidades que representaremos con un array de enteros, el conjunto de
habilidades de forma que sumadas dan un valor a la carta.

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

4.14.3 Empresa de transporte.


Una empresa de transportes internacional quiere automatizar la planificacin de
rutas en el reparto de mercancas por distintas ciudades de toda Europa.
La empresa dispone de informacin de las ciudades donde existen clientes a los
que hay que atender, as como los trayectos directos que conectan las ciudades.
Cada ciudad y cada trayecto estn al menos definidos por un identificador y un
nombre identificativo.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

4.14.4 Fbrica de vehculos.


Para adaptar el sistema a las nuevas normas de seguridad laboral, una fbrica de vehculos necesita establecer
los diferentes recorridos que llevan a las salidas de emergencia del edificio. Sin embargo, no todos los pasillos
de la factora tienen las mismas condiciones y algunas zonas del edificio poseen ms afluencia que otras. En el
siguiente grafo puede apreciar los diferentes caminos existentes, as como el nmero mnimo/mximo de
personas que pueden circular por l. Tenga en cuenta que adems de los caminos, las intersecciones (salas de
distribucin) poseen tambin una capacidad mxima de personas. Esto debe ser tenido en cuenta la hora de
establecer el plan de evacuacin. Suponga que no existe ningn coste por recorrer los pasillos ni las salas de
distribucin.

Partiendo del grafo anterior, se pide:


1. Qu diferencias encuentra entre los tipos FlowGraph y DirectedGraph? Para qu resultarn tiles los
mtodos getUnitEdgeWeight y getMaxVertexWeight?
2. Analice la clase us.lsi.graphs.Flow del proyecto que puede encontrar adjunto a este documento. Cmo es
posible indicar al algoritmo el flujo mximo y mnimo de los diferentes vrtices y aristas? De qu se encarga
el mtodo getConstraints()?
3. Definir las restricciones necesarias para el caso anterior, teniendo en cuenta que el vrtice indicado por una
flecha horizontal es la fuente y el vrtice con una flecha vertical es el sumidero. Deber definir las ecuaciones
de maya necesarias.
4. Definir el fichero .txt con el formato adecuado para poder representar el grafo.
5. Aplicar el algoritmo de flujo mximo para determinar el nmero mximo de personas que pueden circular
por cada uno de los pasillos y salas de distribucin del recinto. Para ello puede usar la clase FlowExample
dentro del paquete us.lsi.graph.flow y completarla.
6. Obtenga el flujo de los vrtices fuente y sumidero.
7. Comprobar mediante el mtodo getRestricciones que las ecuaciones de malla calculadas en el apartado 3
son correctas
8. Es posible que todas las personas puedan ser evacuadas sin problemas?

255

4.14.5 Ordenacin optimizada de un vector.


Se pretende ordenar un determinado vector haciendo uso de algn algoritmo de ordenacin optimizado. El
objetivo es que la complejidad de dicha ordenacin no supere el orden (n log n). En l se parte de un vector
sin ordenar y, aplicando la tcnica de Divide y Vencers, se ordenar de manera recursiva dividiendo el vector
original en dos partes del mismo tamao. Una vez ordenadas ambas partes del vector, se mezclan empleado un
algoritmo iterativo que recorre ambos subvectores ordenados y obtiene un vector resultante ordenado en su
conjunto.
Tenga en cuenta que para que el algoritmo sea eficiente, deber reducir al mximo el coste del sistema iterativo
de ordenacin. Para ello tenga presente que se partir de dos vectores ordenados, resultantes de la llamada
recursiva al algoritmo sobre las dos partes en las que se dividi el problema inicial.

Una vez comprendido el funcionamiento del algoritmo, se pide:


1. Dar una definicin recursiva para el algoritmo. Se trata de una definicin final o no final?
2. Implementar un algoritmo iterativo que, partiendo de un vector ordenado en dos mitades delimitadas por
el elemento central, modifique el vector que se pasa por parmetro de manera que quede ordenado en su
conjunto. La definicin del mtodo ser void ordena (int* vector, int inicio, int fin, int mitad) para
C o int[] ordena (int[] vector, int inicio, int fin, int mitad) para Java. Tenga en cuenta que se
valorar todas las optimizaciones realizadas para reducir su complejidad.
ordena({3, 0, 8, 2, 4, 6, 3, 5, 7}, 3, 8) {3, 0, 8, 2, 3, 4, 5, 6, 7}
3. Partiendo del algoritmo anterior, se pide implementar un algoritmo recursivo que permita ordenar un vector
usando la tcnica propuesta.
4. A partir de la definicin dada en el ejercicio 1, calcule el T(n) y el orden de complejidad del algoritmo.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

4.14.6 Valor predominante en un vector.


Dado un vector de tamao n cuyos valores son enteros positivos tales que 0 < ni < k denominaremos valor
predominante a aqul que se repite en al menos la mitad de las posiciones del vector, siempre que no haya
ningn otro que cumpla la misma condicin.
Supongamos que los elementos del vector no pueden ser ordenados y slo podemos comparar su igualdad.
Defina un algoritmo con el mtodo de divide y vencers que devuelva el valor predominante del vector o indique
que no existe. Se tomar el valor -1 para indicar que no existe predominante.
El cdigo divide el vector en dos partes y llama recursivamente. Cuando el vector tiene tamao 1 devuelve su
valor como predominante.
La devolucin de ambas partes puede proporcionar:
-

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.

La parte izquierda tiene predominante, la derecha no. Igual que el anterior.

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

4.14.7 Planificacin hdrica.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

4.14.8 Tasa de natalidad.


Dada la tasa de natalidad entre los aos 1975 y 2012 queremos calcular cual ha sido periodo temporal donde la
tasa ha tenido su mximo crecimiento.

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

4.14.9 Sistema descentralizado de clculo de rutas dinmicas.


El clculo del camino ms corto entre dos puntos de un mapa no supone ningn problema para ningn tipo de
navegador convencional y es til en un alto nmero de situaciones. No obstante, en lugares sujetos a cambios
frecuentes (por ejemplo, aquellos que sufren las inclemencias meteorolgicas, guerras, o estn en periodos de
desarrollo), estos navegadores tradicionales suelen presentar deficiencias ya que los caminos y carreteras de
estos lugares cambian localmente con ms frecuencia que los propios ciclos de actualizacin de los mapas
globales sobre los que trabajan.
Se desea implementar un sistema de informacin que permita conocer una ruta entre dos localidades (ya sea un
poblado, una aldea, una ciudad, etc.) pero que tenga en cuenta la informacin en tiempo real que provea cada
entidad proveedora de informacin (ya sea un poblado concreto, la localidad, el propio pas, etc.). Por ello, no
se dispondr de un mapa global (grafo), sino que ste se tendr que ir creando a partir de los mapas parciales
que provea cada entidad.
Dado que la informacin no est centralizada, se dispone de un mecanismo mediante el cual, conociendo la
localidad, se puede saber qu entidades, suscritas a este sistema, disponen de informacin sobre las vas de
acceso (ya sean caminos, carreteras, autovas, etc.) cerca de esa localidad. Para cada entidad, se tendr la
posibilidad de conocer su nombre, el mapa de la zona (grafo) con la informacin sobre las localidades (vrtices)
y sus vas de acceso (aristas) junto con la fecha de ltima actualizacin. De esta manera, de todas las entidades
con informacin sobre una localidad, habr que utilizar nicamente la ms reciente.
El sistema considera que una entidad contiene informacin sobre una localidad si,
adems de estar dicha localidad contenida en el grafo de la entidad, contiene todas
las vas adyacentes a ella. Adicionalmente, el grafo contendr las localidades
adyacentes a esta, aunque no se considera que la entidad contenga informacin
sobre estas ltimas localidades. En el ejemplo de la figura, la entidad AlcalaGAyto
ha actualizado su sistema el da 17/05/1985 e informacin sobre Alcal de
Guadaira, Dos Hermanas y Utrera, sin embargo, su grafo, adems de estas tres
localidades, incluye a Sevilla y a Montequinto. (Esto ya se encuentra
implementado).
Se dispone de una API con el modelado del problema (interfaces Entidad, Localidad
y Via) y una clase de utilidades (clase Info). Puede hacer uso de esta clase de
utilidades es.dynamicRoute.Info que permite saber qu entidades contienen
informacin sobre una localidad dada, as como obtener un objeto de tipo
Localidad a partir del nombre de sta.
Deber implementar un sistema que permita buscar la ruta entre dos localidades de manera que se minimice
una funcin objetivo dada (por ejemplo, la ruta ms corta, la ruta ms rpida, etc.), y donde se podrn especificar
algunos requisitos (por ejemplo, evitar caminos, autovas de peaje, o cuya velocidad mxima sea superior a 90,
etc.). El prototipo de la funcin ser:
List<Via> calculaRuta(Localidad locOrigen, Localidad locDestino, Integer funcObjetivo, Integer requisitos)

funcObjetivo es un entero que podr valer 1 o 2, dependiendo de si el objetivo a optimizar es el tiempo de la


ruta (1) o la distancia (2).
requisitos es un entero que podr valer 0, 1, 2 o 3, dependiendo de si no se quieren especificar requisitos (0), se
quiere especificar slo el requisitos 1 (evitar peajes) (1), slo el requisitos 2 (evitar carreteras cuya velocidad
media sea superior a 60) (2), o los dos a la vez (3).

260

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Se pide:
1. Indique qu tipo de grafo utilizara para este problema.
2. Implemente las siguientes utilidades en una clase que se llame Utilidades:
a. Dos mtodos estticos que sirvan como funciones objetivo: Una que sirva para minimizar el
tiempo de la ruta y otra que sirva para minimizar la distancia recorrida en la ruta. Las funciones
tan slo recibirn un objeto de tipo Via y devolvern un Double.
i. double funcionTiempo(Via v)
ii.

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)

3. Implemente el grafo correspondiente llamado NavegadorGraph. Ha de contener un constructor que


permita indicarle cul es la funcin objetivo a minimizar y los criterios a tener en cuenta. Por ejemplo,
new NavegadorGraph(1,3); crear un grafo que considere, como funcin objetivo: minimizar el tiempo
de la ruta; y como criterios a tener en cuenta: evitar peajes y carreteras que superen los 60 km/h de
velocidad media.
4. Implemente el mtodo calculaRuta. El mtodo devuelve null cuando no existe una ruta entre dos
localidades dadas. Para simplificar este mtodo, no considere el uso de heursticas en el clculo de la
ruta.

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

Clase NavegadorGraph parcialmente implementada:

public class NavegadorGraph {


private Integer criterios;
private Integer funcionObjetivo;
public NavegadorGraph(Integer criterios, Integer functionObjetivo) {
this.criterios = criterios;
this.funcionObjetivo = functionObjetivo;
}
private boolean compruebaCriterios(Via v) {
boolean res = true;
switch (criterios) {
case 0:
res = true;
break;
case 1:
res = Utilidades.criterioNoPeajes(v);
break;
case 2:
res = Utilidades.criterioVelocidadMenor60(v);
break;
case 3:
res = Utilidades.criterioNoPeajes(v) && Utilidades.criterioVelocidadMenor60(v);
}
return res;
}
@Override
public boolean containsEdge(Via arg0) {
boolean res;
// TODO: Puede preguntar si continene una via que une dos localidades. Puede darse el caso de
que se tenga entidadaes con info sobre una localidad y no la otra.
return res;
}
@Override
public boolean containsVertex(Localidad arg0) {
// TODO: Solo devolvera cierto si la localidad es la principal de alguna entidad, sino, es como
si no la contuviera
}
@Override
public Via getEdge(Localidad arg0, Localidad arg1) {
Via res;
Entidad e = Utils.entidadMasActualizada(arg0);
if (e == null) {
e = Utils.entidadMasActualizada(arg1);
}
if (e == null) {
res = null;
} else {
Set<Via> todas = e.getInfo().getAllEdges(arg0, arg1);
for (Via v : todas) {
if (compruebaCriterios(v)) {
res = v;
break;
}
}
return res;
}
}
@Override
public EdgeFactory<Localidad, Via> getEdgeFactory() {
return null;
}
}

262

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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.

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 monumentos).
4. Cree una clase MapaMonumentos que modele el grafo (creacin de monumentos y calles, modificacin,
eliminacin,)
5. Cree una clase llamada MisionAdornosNavidad donde se realicen las diferentes operaciones sobre el mapa
de monumentos:
a. Mtodo que disee el recorrido que le permita a un turista visitar todos los monumentos adornados
pasando por cada uno una nica vez (no pasa nada si pasa por calles que no estn adornadas).
b. Mtodo que le indique el nmero total de metros de calles que hay en el mapa.
c. Mtodo que le indique el nmero total de metros de adornos necesarios para decorar las calles
seleccionadas.
d. Mtodo que le indique el conjunto de monumentos que tienen fecha anterior a una fecha
determinada.
e. Invente 2 nuevos mtodos que considere importantes para ayudar al ayuntamiento a adornar la
ciudad.
6. Implemente una clase TestMisionAdornosNavidad que compruebe todos los mtodos implementados y
muestre por pantalla el grafo y el resultado de los diferentes mtodos.

Nota: Puede aadir toda la funcionalidad que estime oportuna.

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.

Nota: Puede aadir toda la funcionalidad que estime oportuna.

264

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

4.14.12

Planificacin de regalos de los tres Reyes Magos.


Sus Majestades los Tres Reyes Magos de Oriente estn planificando el
reparto de regalos de la noche de Reyes. El principal problema es que tienen
que visitar todas las ciudades para repartir los regalos a todos los nios del
mundo. Nunca han tenido problemas de tiempo porque se han sabido
organizar perfectamente, sin embargo, ahora quieren automatizar dicha
planificacin.

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.

Nota: Puede aadir toda la funcionalidad que estime oportuna.

265

4.14.13

Misin Zombie.

Ha aterrizado en una ciudad del planeta un grupo de zombies que pretenden


conquistar el planeta, para ello debern matar al rey de los vivos que se encuentra
en una determinada ciudad. Los zombies deben trazar una estrategia para llegar
al rey sin ser avistados por los vivos. Cuantos ms zombies lleguen al rey, mayor
ser la probabilidad de matarlo. Para ello, los zombies tienen un mapa con todas
las ciudades del planeta. Para ir de una ciudad a otra se puede ir por distintas
carreteras. Para cada carretera se conoce el mximo nmero de zombies que
pueden transitar por ella por la noche (durante el da moriran por la luz solar) sin que sean avistados y destruidos
por los vivos (algunas carreteras estn ms vigiladas por los vivos que otras).
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 MapaCarreteras que modele el grafo (creacin de ciudades y carreteras, modificacin,
eliminacin,)
5. Cree una clase llamada MisionZombie donde se realicen las diferentes operaciones sobre el mapa de
carreteras que los zombies consultarn para matar al rey de los vivos:
a. Los zombies quieren saber el nmero mximo de ellos que pueden ir desde la ciudad origen hasta
la ciudad del rey por la noche sin ser avistados. Hay que tener en cuenta que los zombies slo pueden
viajar de noche y no se pueden instalar ms de un da en una ciudad si no quieren ser descubiertos.
b. Mtodo que le indique cmo llegar al rey con el menor nmero de zombies posibles.
c. Mtodo que le indique en el camino del apartado b) con cuntos zombies llegaran.
d. Mtodo que devuelva la carretera en el que puede haber un mayor nmero de zombies.
e. (Haga uso de la interfaz Predicate e Iterables).
f.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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.

5.2 Algoritmo de la Programacin Dinmica.


Veamos el esquema general de un problema de Programacin Dinmica. Como vimos anteriormente, un
problema que se resuelve por la tcnica de Divide y Vencers, adopta la forma:

() = {
(, (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

Siendo = (1 ), i [1, k] las soluciones de los subproblemas la operacin = (, 1 , , ) construye


la solucin del problema asumiendo que se ha seleccionado la alternativa a. La operacin =
construye la solucin del problema original X a partir de las soluciones obtenidas para cada alternativa. A la
operacin CombS la denominaremos Combina Soluciones Parciales y a la operacin SelecA la denominaremos
Selecciona Alternativa. A partir de X y de a se pueden obtener los subproblemas: 1 , 2 , , .
Las operaciones CombS y SelecA tienen distintos comportamientos con respecto a los subproblemas que no
tienen solucin. El operador CombS devuelve el valor cuando alguno de los subproblemas no tiene solucin,
esto es: = ( , 1 , , , , ). La operacin SelecA devuelve cuando no existe solucin para ninguna
alternativa. El operador SelecA verifica lo siguiente:
= , (, )

= (, )

= = (, )

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.

5.3 Programacin Dinmica con memoria y Solucin de un conjunto de problemas.


La Programacin Dinmica puede usar memoria como en la tcnica de Divide y Vencers con Memoria. En
general, si no decimos lo contrario, asumimos que la Programacin Dinmica es con uso de memoria. El esquema
que hace explcito este uso de la memoria es:

+ (, )
(, ) = {
+ (, )
+ (, )

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 =

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


La solucin de un problema concreto Xi del conjunto de problemas podemos obtenerla buscando en el conjunto
de pares (problema, solucin) devuelto, es decir:
( ) = (, )( )

Si slo tenemos un problema de partida entonces su solucin es directamente:


() = (, )()

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.

5.4 Problemas y Grafos.


Veamos las relaciones que existen entre la tcnica de la Programacin Dinmica y los grafos.
Al resolver un problema aparecen un conjunto de problemas donde el que queremos, o los que queremos
resolver, estn incluidos. Cada problema en ese conjunto tiene diversas propiedades: unas comunes a todos los
problemas del conjunto y otras individuales a cada uno de ellos. As cada problema, dentro del contexto del
conjunto de problemas considerado, puede identificarse por un conjunto de valores para las propiedades
individuales consideradas. Asociado a cada problema X aparece un conjunto de alternativas, Ax, que depende
del problema. Tal como vimos el esquema de la Programacin Dinmica es de la forma:
() = {

(, , (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

El tipo de grafo que se forma es un grafo AND-OR,


donde los vrtices OR tienen asociado un problema X y
los vrtices AND un par problema-alternativa (X, a). Si
el contexto lo permite, a los vrtices AND slo le
asociamos la alternativa, puesto que el vrtice
antecesor ya indicar el problema original.
En estos grafos, las hojas son casos base o problemas
cuyo conjunto de alternativas es vaco.
Adems, cada vrtice tiene asociado un operador que es capaz de calcular la solucin de un problema a partir
de las de los subproblemas. Los vrtices OR (problemas) tienen asociado el operador SelecA(). Este operador
calcula la solucin del problema entre las diferentes soluciones calculadas para cada una de las alternativas. Los
vrtices AND (por problema-alternativa(X, a)), tienen asociado el operador CombS() que obtiene la solucin de
un problema, tras tomar una alternativa, combinando las soluciones de los subproblemas.

5.5 Caracterizacin de las soluciones de un problema de Programacin Dinmica.


Los problemas de un conjunto de problemas que se quieren resolver mediante Programacin Dinmica se
organizan en un grafo dirigido y bipartito. Los problemas constituyen los vrtices OR y cada vrtice AND tiene
asociado un problema ms una de las alternativas que podemos tomar para resolverlo. Los vrtices AND los
representaremos como crculos y los vrtices OR como rectngulos. De cada vrtice OR (cada problema del
conjunto de problemas) parten un conjunto de aristas dirigidas que conducen a los correspondientes vrtices
AND. De cada vrtice AND parten aristas que conducen a sus subproblemas (estos son, de nuevo, vrtices OR).
Las soluciones, si las hay a cada problema del conjunto de problemas representado en el grafo, pueden ser
caracterizadas por un subgrafo del grafo anterior. Este subgrafo tiene como vrtice maximal al problema cuya
solucin queremos caracterizar. Todas las hojas son casos base, de cada vrtice OR parte una y slo una de las
aristas existentes en grafo original y de cada vrtice AND parte cada una de las aristas existentes en el grafo
original. Si no es posible obtener un subgrafo con las caractersticas anteriores, el problema no tiene solucin.
Dado un subgrafo que representa una solucin es posible calcularla recorriendo el grafo de abajo hacia arriba.
Partimos de las soluciones de los casos base incluidos en la solucin y vamos aplicando los operadores de
combinacin CombS() a las soluciones de los subproblemas para ir obteniendo las soluciones de los problemas.
Un problema puede tener muchas soluciones, cada una de las cuales, vendr representada por un subgrafo. Si
el problema es de Optimizacin entonces se trata de escoger la mejor de todas ellas. Este trabajo lo va haciendo
el operador SelecA() al seleccionar, para cada problema, la alternativa que proporciona la mejor solucin.
En cada caso, la solucin buscada ser de un tipo determinado que habr que especificar. Si el problema es de
Optimizacin, para caracterizarlo, habr que indicar adems, una propiedad de la solucin a optimizar. En
cualquier caso, para identificar de forma nica la solucin, es suficiente con indicar cul es la alternativa elegida
para cada problema. Por lo que, conocidas las alternativas elegidas para cada problema, conocemos el subgrafo
que define la solucin y por tanto, el valor de la misma. Por cuestiones de eficiencia, en general, es preferible
primero buscar el subgrafo que define la solucin recordando (en la memoria del algoritmo) la alternativa
escogida y el valor de la propiedad de la solucin a optimizar. Posteriormente, reconstruir el valor de la solucin
pedida. Al par formado por la alternativa elegida, valor de la propiedad relevante de la solucin lo llamaremos
solucin parcial. Dadas las soluciones parciales para un problema y los subproblemas involucrados es posible
reconstruir la solucin. Para cada problema debemos indicar la forma de reconstruir la solucin a partir de la
solucin parcial.
270

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

5.6 Casos particulares y Variantes de la Programacin Dinmica.


5.6.1 Caso de Reduccin.
El caso particular de Reduccin, en el contexto de la Programacin Dinmica, se da cuando el nmero de
subproblemas para cada alternativa es igual a uno (k = 1). El esquema para este caso particular es:
() = {


((, , (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

5.6.2 Programacin Dinmica con Filtro.


Usando mecanismos para afinar el clculo del conjunto podemos obtener un caso particular de la
Programacin Dinmica que denominaremos Programacin Dinmica con Filtro. Esta variante de la tcnica es
adecuada para resolver problemas de Optimizacin en el caso de Reduccin. Puede usarse para resolver un
problema o un conjunto de problemas. La idea consiste en tener disponible un buen valor de la propiedad a
optimizar y eliminar del conjunto aquellas alternativas de las que podemos asegurar que tomndolas, no
alcanzaremos una solucin con un valor mejor. Y la forma de llevarlo a cabo es generalizar el problema con dos
nuevas propiedades. La primera, valAcu es el valor acumulado (segn las alternativas ya escogidas) para la
propiedad que queremos optimizar. La segunda, valMejor es el mejor valor obtenido hasta el momento de la
propiedad a optimizar. Es una variable compartida por todos los problemas. Disponemos adems, de una funcin
cota(X, a) que, para cada problema y alternativa seleccionada, es capaz de calcular una cota superior para el
valor de la propiedad a optimizar de la solucin de ese problema si tomramos esa alternativa. Suponemos que
el problema de Optimizacin es de Maximizacin. Si fuera de Minimizacin habra hacer pequeos ajustes.
Como hemos explicado antes, si estamos en un caso de Reduccin, partiendo de un problema dado X0 y siguiendo
la secuencia de alternativas {a1, a2, , ar} alcanzaremos un problema que representaremos por Xr. El problema
generalizado (Xr, valAcu, valMejor) representa al problema Xr que ha sido alcanzado desde X0 siguiendo la
secuencia de alternativas {a1, a2, , ar}. El valor acumulado de la propiedad a optimizar despus de escoger las
alternativas {a1, a2, , ar} es valAcu, y valMejor es el mejor valor de esa propiedad, a optimizar, encontrada.
Para hacer un tratamiento ms homogneo suponemos que la funcin de cota cota(X, a) puede aplicarse si X es
un caso base. En ese caso asumimos que hay una nica alternativa y que la funcin de cota nos proporciona
directamente el valor de la propiedad a optimizar. Por otra parte podemos ver que valAcu + cota(X, a) es una
cota superior para el valor de la propiedad a optimizar del problema original X0. Luego si siguiendo esa alternativa
no se cumple que valAcu + cota(X, a) > valMejor entonces puede ser descartada. Igualmente, si estando en un
problema X escogemos la alternativa a entonces se produce un incremento del valor acumulado dado por
incValAcu(X, a). El esquema de Programacin Dinmica con Filtro es:
:
(, ) = {
(, , ) = {

((, , (, , )))

(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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


anterior). El problema original f(X) es una instancia del problema generalizado dando valores iniciales adecuados
a las variables valAcu, valMejor.
() = (, 0 )
= 0

5.6.3 Programacin Dinmica Aleatoria.


En la tcnica de Divide y Vencers vimos la posibilidad de incluir en el algoritmo una variable aleatoria para
decidir el tamao de los subproblemas. Esta idea puede ser usada tambin en la Programacin Dinmica. Aqu
tenemos la posibilidad adicional de escoger aleatoriamente una de las alternativas disponibles en todos o en un
subconjunto de los problemas. La idea es escoger aleatoriamente una de las alternativas posibles un nmero
especificado de veces y, posteriormente, continuar con el algoritmo de Programacin Dinmica. Por ejemplo,
escoger aleatoriamente slo una de las alternativas para los problemas de tamao mayor o igual a N y escoger
todas las alternativas posibles para tamaos menores a N. Enfocado de esta manera, el algoritmo de
Programacin Dinmica Aleatoria puede encontrar la solucin con una probabilidad p, que habr que estimar y
que fallar en el resto de los casos. Esa probabilidad p depender evidentemente del valor de N escogido. Se
trata de repetir el algoritmo hasta encontrar una solucin.

(, , (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

5.6.4 Bsqueda de slo una solucin.


Cuando el problema puede tener muchas soluciones (porque no sea un problema de Optimizacin o por otras
razones) puede ser interesante, en algunos casos, slo una de ellas o varias de las posibles, aunque no todas.
En el caso de Reduccin, la Programacin Dinmica puede ser ajustada fcilmente para obtener una de las
soluciones de un problema. En este caso sabemos que hay una solucin cuando alcanzamos un caso base desde
el problema original. En ese momento podemos reconstruir la solucin y hacer que el algoritmo termine. Esta
idea se puede extender para encontrar un nmero dado de soluciones, si existen.

5.6.5 Bsqueda de todas las soluciones.


Cuando el problema puede tener muchas soluciones (porque no sea un problema de Optimizacin o por otras
razones) puede ser interesante, en algunos casos, encontrarlas todas.
La Programacin Dinmica puede ser ajustada para obtener todas las soluciones de un problema dado. La idea
es considerar que la solucin del problema f(X) es de tipo Set<S> donde S es el tipo de la solucin. De igual
manera hay que disear nuevos operadores CombST, SelecAT, STb en funcin de los antiguos CombS, SelecA y Sb
respectivamente, para tener en cuenta el tipo de las soluciones. El esquema de la Programacin Dinmica con
memoria y para calcular todas las soluciones es:
() = {

((, , (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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

5.7 Correccin y Complejidad de los algoritmos de Programacin Dinmica.

5.7.1 Correccin de un algoritmo de Programacin Dinmica.


Para demostrar la correccin deberemos:
Demostrar que las soluciones de los casos base son correctas.
Suponiendo que son correctas las soluciones de los subproblemas, demostrar que la combinacin de sus
soluciones mediante el operador CombS(), produce la solucin correcta.
Suponiendo que son correctas las soluciones para cada una de las alternativas, demostrar que la
combinacin de sus soluciones mediante el operador SelecA(), produce la solucin correcta para del
problema completo.
Demostrar que el tamao va decreciendo estrictamente cuando pasamos de un problema a sus
subproblemas y que esa secuencia decrece alcanzando alguno de los casos base.
Dado un problema demostrar que las alternativas elegidas cubren todas las posibilidades.

5.7.2 Complejidad de los problemas de Programacin Dinmica.


Al ser la Programacin Dinmica una extensin de la tcnica de Divide y Vencers con memoria podemos usar
la misma metodologa para el clculo de la complejidad. Para resolverlo debemos resolver un subconjunto de
problemas. Para un problema x sea g(x) el tiempo necesario para calcular las alternativas, los subproblemas, el
tiempo para combinar las soluciones de los subproblemas con el operador CombS() y posteriormente con el
SelecA(). Sea I el conjunto formado por los problemas que hay que resolver para poder obtener la solucin de
X de tamao n. Entonces, tiempo de ejecucin podemos expresarlo como:
() = ()

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 = || = (||)

El problema, para el clculo de la complejidad, es calcular (||).

275

5.8 Versin iterativa de los algoritmos de Programacin Dinmica.


En muchos casos es posible encontrar un esquema iterativo a partir de otro recursivo de Programacin Dinmica.
Para ello, debemos pensar como para todos los algoritmos iterativos, en un Estado, el valor inicial de este estado
y el cambio de ese estado en cada iteracin. No hay una forma general de hacer esto pero, como vimos en las
transformaciones recursivo-iterativas, podemos considerar dos enfoques: uno hacia abajo (bastante sistemtico
si el problema es recursivo lineal final, y otro de abajo hacia arriba que parte de los casos base y, a partir de ellos,
se van construyendo las soluciones.
Veamos algn ejemplo del segundo enfoque. En todos los algoritmos iterativos tenemos que disear un Estado.
En los casos en que la solucin de un problema de tamao k depende solamente de subproblemas de tamao
k-1 podemos elegir el estado para que guarde las soluciones de todos los problemas de un tamao dado n y sus
soluciones. El Estado inicial contendr las soluciones a los problemas de tamao pequeo asociados a los casos
base. La transicin del Estado que contiene los problemas de tamao n y sus soluciones a los de tamao n+1
puede obtenerse del planteamiento recursivo del problema.
En efecto, con el planteamiento anterior, el problema X sera de tamao n y los problemas 1 , 2 , , de tamao
n-1. El Estado podemos disearlo para que contenga las soluciones a los problemas de tamao como hemos
comentado. El Estado podra ser diseado, en general, como una funcin que, teniendo como dominio los
problemas de tamao n, nos proporcione la solucin para cada uno de ellos (o posiblemente la no existencia de
solucin). Si los problemas de tamao n pueden ser identificados por 2 propiedades de tipo entero, entonces la
funcin puede ser implementada por una tabla.
La transicin de un Estado al siguiente consistir en encontrar la solucin de cada problema de tamao n+1
usando la frmula de la Programacin Dinmica anterior. El esquema de actualizacin del estado sera:
{
() = ((, , (1 ), (2 ), , ( )))
}

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

5.9 Programacin Dinmica y el Algoritmo A*.


Ideas a tener en cuenta:

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*.

El problema de Programacin Dinmica tanto de Reduccin como de Optimizacin podemos esquematizarlo


como:

277

5.10 Detalles de Implementacin.


El esquema de Programacin Dinmica puede ser programado de forma genrica. Para ello hace falta disear
una interfaz que debe implementar los problemas que se quieran resolver por esta tcnica, y ser del tipo
ProblemaPD<S,A,T> tal como se muestra en la siguiente pgina.
El tipo ser genrico y sus parmetros, los siguientes:
S:

Tipo de la solucin del problema.


Tipo de la alternativa.
T: Tipo de la propiedad a optimizar (objetivo), si es un problema de optimizacin, o alguna propiedad
de la solucin.
A:

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

int size(): Calcula el tamao del problema.


boolean esCasoBase(): Decide si el problema es un caso base.
Sp<A,T> getSolucionCasoBase(): Obtiene la solucin del problema asumiendo que es un caso base.
Sp<A,T> seleccionaAlternativa(List<Sp<A,T>> ls): Selecciona

la mejor solucin parcial obtenida para las


diferentes alternativas. Asumimos que si el nmero de alternativas es cero el problema no tiene solucin. La
no existencia de solucin lo podemos representar de muchas formas. Una posibilidad, que seguiremos aqu,
es representar la no solucin por el valor null. Este mtodo debe tener en cuenta, adems, que tiene que
combinar las soluciones obtenidas para cada una de las alternativas cuando estas existan y descartar aquellas
alternativas que no alcancen soluciones (en ese caso representadas, en principio, por el valor null).
ProblemaPD<S,A,T> getSubProblema(A a, int i): Obtiene el subproblema i-simo despus de tomar la
alternativa a. Los problemas se numeran desde cero.
Sp<A,T> combinaSolucionesParciales(A a,List<Sp<A,T>> ls): Combina las soluciones de los subproblemas.
Si no hay solucin para un subproblema (solucin null) entonces no hay solucin para el problema.
Iterable<A> getAlternativas(): Devuelve un iterable con las alternativas posibles para ese problema.
int getNumeroSubProblemas(A a): Nmero de subproblemas dada una alternativa.
S getSolucion(A a, List<S> ls): Mtodo proporcionado para reconstruir la solucin a partir de las
soluciones parciales de los subproblemas.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


/**
* Interface a implementar un problema para ser resuelto por la tcnica de Programacin Dinmica
* @param <S> El tipo de la solucin del problema
* @param <A> El tipo de las alternativas
* @param <T> El tipo del objetivo
* @see AlgoritmoPD.Sp
*/
public interface ProblemaPD<S,A,T extends Comparable<? super T>> {
/**
* @constraint El tamao de un problema debe ser mayor que el de cada uno de sus subproblemas
* @return El tamao de un problema
*/
int size();
/**
* @return Si el problema es un caso base
*/
boolean esCasoBase();
/**
* @return La solucin del caso base
*/
Sp<A, T> getSolucionCasoBase();
/**
* @constraint Si todos los valores en ls son null la solucin devuelta es null
* @pre ls!=null
* @param ls - Soluciones de los subproblemas alcanzados tras tomar las distintas alternativas
* @return La solucin del problema
*/
Sp<A, T> combinaAlternativas(List<Sp<A, T>> ls);
/**
* @pre 0 <= i < getNumeroSubProblemas(a)
* @param a - Alternativa escogida
* @param i - Nmero del subproblema
* @return Subproblema
*/
ProblemaPD<S,A,T> getSubProblema(A a, int i);
/**
* @pre ls.size() == getNumeroSubProblemas(a) y a est incluida en getAlternativas()
* @constraint Si un elemento de ls es null la solucin es tambin null
* @param a - Alternativa escogida
* @param ls - Soluciones de los subproblemas
* @return La solucin para un problema si las soluciones para los subproblemas
estn en ls y se escoge la alternativa a
*/
Sp<A, T> combinaSoluciones(A a , List<Sp<A, T>> ls);
/**
* @constraint Si un problema no tiene solucin el conjunto de alternativas es vaco
* @return Las alternativas disponibles para el problema
*/
Iterable<A> getAlternativas();
/**
* @param a - Alternativa escogida
* @return Nmero de subproblemas para una alternativa dada
*/
int getNumeroSubProblemas(A a);
/**
* @constraint Si el problema es un caso base ls.size()==0 y
* la solucin devuelta es la solucin del caso base si se escoge la alternativa a.
* @pre ls !=null y a una de las alternativas del problema
* @param a - Alternativa escogida
* @param ls - Soluciones de los subproblemas
* @return La solucin para un problema si las soluciones para los subproblemas estn en ls
y se escoge la alternativa a. Si un problema no tiene solucin se devuelve null.
Un problema que tiene algn subproblema sin solucin no tiene solucin tampoco
*/
S getSolucion(A a, List<S> ls);
}

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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

Map<ProblemaPD<S, A, T>, Sp<A, T>> solucionesParciales;


ProblemaPD<S, A, T>
problema;
Iterable<ProblemaPD<S, A, T>>
problemas;
T
mejorValor;

AlgoritmoPD(Iterable<ProblemaPD<S, A, T>> ps) {


problema = Iterables.get(ps, 0);
problemas = ps;
mejorValor = null;
}
public void ejecuta() {
do {
solucionesParciales = Maps.newHashMap();
for (ProblemaPD<S, A, T> p : problemas) {
pD(p);
}
} while (isRandomize && solucionesParciales.get(problema) == null);
}

282

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


private Iterable<A> filtraRandomize(ProblemaPD<S, A, T> p, Iterable<A> alternativas) {
Iterable<A> alt;
if (isRandomize && p.size() > sizeRef) {
List<A> ls = Lists.newArrayList(alternativas);
List<A> r = Lists.newArrayList();
if (!ls.isEmpty()) {
int e = Math2.getEnteroAleatorio(0, ls.size());
r.add(ls.get(e));
}
alt = r;
} else {
alt = alternativas;
}
return alt;
}
private boolean pasaFiltro(ProblemaPDF<S, A, T> pdf, A a) {
boolean r = true;
if (conFiltro) {
r = mejorValor == null || AlgoritmoPD.tipo == Tipo.Max
&& pdf.getObjetivoEstimado(a).compareTo(mejorValor) > 0
|| AlgoritmoPD.tipo == Tipo.Min
&& pdf.getObjetivoEstimado(a).compareTo(mejorValor) < 0;
}
return r;
}
private void actualizaMejorValor(ProblemaPDF<S, A, T> pdf) {
T objetivo = pdf.getObjetivo();
if (conFiltro
&& (mejorValor == null || AlgoritmoPD.tipo == Tipo.Max
&& objetivo.compareTo(mejorValor) > 0 || AlgoritmoPD.tipo == Tipo.Min
&& objetivo.compareTo(mejorValor) < 0))
{
mejorValor = pdf.getObjetivo();
}
}
/**
* @param pd
* @return Si pd es un subproblema encontrado al resolver el problema inicial
*/
public boolean isSubproblema(ProblemaPD<S, A, T> pd) {
return this.solucionesParciales.containsKey(pd);
}
/**
* @return Nmero de subproblemas encontrado al resolver el problema inicial
*/
public int getNumeroDeSubproblemas() {
return this.solucionesParciales.keySet().size();
}
/**
* @param pd - Problema del que se quiere obtener la solucin
* @return Solucin del problema o null si no tiene solucin o no ha sido
* encontrado por el algoritmo. Esta es la Reconstruccin de la solucin
*/
public S getSolucion(ProblemaPD<S, A, T> pd) {
S s = null;
if (solucionesParciales.containsKey(pd)) {
Sp<A, T> e = solucionesParciales.get(pd);
if (e != null) {
List<S> soluciones = Lists.<S> newArrayList();
for (int i = 0; i < pd.getNumeroSubProblemas(e.alternativa); i++) {
soluciones.add(getSolucion(pd.getSubProblema(e.alternativa,i)));
}
s = pd.getSolucion(e.alternativa, soluciones);
}
}
return s;
}

283

private Sp<A, T> pD(ProblemaPD<S, A, T> p) {


Sp<A, T> e = null;
ProblemaPDF<S, A, T> pdf = null;
if (conFiltro)
pdf = (ProblemaPDF<S, A, T>) p;
if (solucionesParciales.containsKey(p)) {
e = solucionesParciales.get(p);
} else if (p.esCasoBase()) {
e = p.getSolucionCasoBase();
solucionesParciales.put(p, e);
if (conFiltro)
actualizaMejorValor(pdf);
} else {
List<Sp<A, T>> solucionesDeAlternativas = Lists.newArrayList();
List<A> alternativasElegidas = Lists.newArrayList();
for (A a : filtraRandomize(p, p.getAlternativas())) {
if (!pasaFiltro(pdf, a)) {
continue;
}
alternativasElegidas.add(a);
int numeroDeSubProblemas = p.getNumeroSubProblemas(a);
List<Sp<A, T>> solucionesDeSubProblemas = Lists.newArrayList();
for (int i = 0; i < numeroDeSubProblemas; i++) {
ProblemaPD<S, A, T> pr = p.getSubProblema(a, i);
Sp<A, T> sp = pD(pr);
if (sp == null) {
solucionesDeSubProblemas = null;
break;
}
solucionesDeSubProblemas.add(sp);
}
Sp<A, T> sa = null;
if (solucionesDeSubProblemas != null) {
sa = p.combinaSoluciones(a, solucionesDeSubProblemas);
}
if (sa != null) {
solucionesDeAlternativas.add(sa);
}
}
if (solucionesDeAlternativas != null
&& !solucionesDeAlternativas.isEmpty()) {
e = p.combinaAlternativas(solucionesDeAlternativas);
}
if (e != null) {
e.alternativas = alternativasElegidas;
}
solucionesParciales.put(p, e);
}
return e;
}

284

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

/**
* @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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

5.11 Representacin del grafo de problemas y alternativas.


En muchos casos es interesante mostrar grficamente el conjunto de problemas, las alternativas y los
subproblemas a los que conducen.
Hemos decidido representar los problemas mediante un rectngulo con una etiqueta que ser la devuelta por el
toString del problema. Los problemas que no tengan solucin los representamos dentro de un rombo.
Las alternativas las representamos mediante un crculo, con una etiqueta, que es su valor.
Para conseguir ese objetivo generamos un fichero intermedio que puede ser procesado por las herramientas
que vimos en el captulo de grafos.

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>]

El grafo resultante es:

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>]

El grafo resultante es:

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 grafo resultante es:

Siendo el fichero generado para este ejemplo:


digraph Mochila {
size="100,100";
"(35,2)" [shape=box];
"(35,2),2" [label=2];
"(35,2)" -> "(35,2),2"[style=bold,arrowhead=dot];
"(35,2),2" -> "(5,1)"[style=bold,arrowhead=dot];
"(20,1)" [shape=diamond];
"(35,2),1" [label=1];
"(35,2)" -> "(35,2),1";
"(35,2),1" -> "(20,1)";
"(5,1)" [shape=box];
"(5,1),0" [label=0];
"(5,1)" -> "(5,1),0"[style=bold,arrowhead=dot];
"(5,1),0" -> "(5,0)"[style=bold,arrowhead=dot];
"(5,0)" [shape=box];
}

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

showAllGraph(String nombre, String titulo, ProblemaPD<S,A,T> pd)

private void

marcarEnSolucion(ProblemaPD<S,A,T> pd)

private String problema(ProblemaPD<S,A,T> p, Sp<A, T> e)


private String alternativa(ProblemaPD<S,A,T> p, A alternativa)
private String aristaProblemaToAlternativa(ProblemaPD<S,A,T> p, A alternativa, Sp<A, T> e)
private String aristaAlternativaToProblema(ProblemaPD<S,A,T> p, A alternativa, ProblemaPD<S,A,T> ps, Sp<A,T> e)
private void

288

showAll(ProblemaPD<S, A, T> p)

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

5.12 Problemas Tipo.


5.12.1 Mochila.
Tenemos un conjunto de objetos con un
determinado peso y valor que nos
gustara guardar en una mochila. La
mochila tiene capacidad para llevar un
peso mximo y, para cada tipo de
objeto, hay especificado un lmite
mximo de unidades dentro de la
mochila. Se pide encontrar la configuracin de objetos que, sin sobrepasar el peso de la mochila ni el nmero de
unidades especificadas para cada uno de ellos, maximiza la suma de los valores de los objetos almacenados.
Para definir los problemas del conjunto de problemas trataremos cada problema como un objeto
(independientemente de su implementacin final) y disearemos un tipo cuyas instancias sern los problemas.
Igual que en el diseo de tipos tendremos, para los problemas, propiedades bsicas y derivadas, compartidas e
individuales, etc.
Partimos de un conjunto de objetos disponibles para almacenar en la mochila que representamos como una
propiedad compartida del conjunto de problemas. Es la propiedad LstObjDisp, List<ObjetoMochila> ordenada
y sin repeticin. Cada objeto disponible tendr las propiedades (valor, peso, maxUd, q). Estas propiedades
representan, respectivamente el valor unitario, el peso unitario, el nmero mximo de unidades permitido y el
valor por unidad de peso. La propiedad derivada: valor por unidad por unidad de peso se calcula como: =

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, =

El tipo SolucionMochila es un subtipo de MultiSet<ObjetosEnMochila> con las propiedades adicionales


ValorTotal (entero) de los objetos almacenados en la mochila y PesoTotal (entero) de los objetos en la mochila.
Una solucin la representamos por lo tanto por (ms, valorTotal, pesoTotal). Donde ms es el multiconjunto de
objetos en la mochila. Como solucin parcial escogemos el par formado por la alternativa y el valor total de la
solucin.
Como en la mayora de los casos de problemas resueltos mediante Programacin Dinmica, generalizamos el
problema. Una forma de generalizarlo es resolver el problema para una capacidad de la mochila (que
representaremos por Cap ) usando solamente un subconjunto de los objetos disponibles. El subconjunto de
objetos los representamos por un ndice entero J que representa el subconjunto de objetos disponibles cuyos
ndices son menores o iguales a J dentro de la lista LstObjDisp.
El problema generalizado tiene como propiedades individuales: Cap (entero 0), J (entero en [0, r)). Donde r es
el nmero de objetos disponibles distintos. En este contexto, dos problemas son iguales si tienen iguales sus
propiedades individuales: Cap y J.

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.
((, )) = ((, ))

El mximo valor es:

Valor de valorTotal.

Los casos base sern problemas del tipo:

(Cap, 0, ).

La solucin directa del caso base es:

+ ((0), )

La solucin parcial es:

( , 0 )

Donde = min ( , 0 ) y valor0, peso0, maxUd0 representan respectivamente, el valor unitario, el


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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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
[

[entero] valor unitario.


[entero] peso unitario.
[entero] nmero mximo de unidades permitidas.
[real] (prop. derivada) valor por unidad de peso, q =

Propiedades
Individuales:

Cap [entero] 0
J
[entero] [0, LstObjDisp.size() )

Solucin:

Sol = (ms, valorTotal, pesoTotal), de tipo SolucionMochila


ms
SolucionMochila [ valorTotal
pesoTotal

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 ( )

((, )) = 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

La lnea en verde indica la reconstruccin.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Veamos los detalles de implementacin del Problema de la Mochila. Como la mayora de las decisiones se repiten
en muchos problemas, por cada tipo implementaremos la factora correspondiente.
Los objetos que estn disponibles para introducir en la mochila tienen las propiedades: cdigo, valor unitario,
peso unitario, nmero mximo de unidades en la mochila y ratio valor peso. Diseamos la clase ObjetoMochila
con esas propiedades y dos mtodos de factora: uno que toma los valores de las propiedades bsicas y otro que
toma una cadena de caracteres con la informacin para las propiedades bsicas, separada por comas. Hacemos
que los objetos del tipo tengan un orden natural dado por la propiedad ratio valor peso y, si esta es igual, por el
cdigo. Lo diseamos como un tipo inmutable.
La solucin del Problema de la Mochila es un multiconjunto de objetos con las propiedades adicionales valor
total y peso total de la mochila. Para ello implementamos la clase SolucionMochila. Su orden natural ser segn
el valor total de la mochila. Dotamos a los objetos de este tipo de 3 propiedades: ValorTotal, PesoTotal y Objetos.
La tercera, de tipo Multiset<ObjetoMochila>, es la nica propiedad bsica, las otras dos son derivadas. La clase
tendr dos constructores, uno de los cuales construye una solucin vaca. Tambin tendr un mtodo para aadir
varias unidades de un objeto a la solucin. La diseamos como un tipo inmutable.
La propiedad a optimizar es de tipo entero, las alternativas tambin son de tipo entero.
El Problema de la Mochila es un problema de Optimizacin donde se da una capacidad para la mochila y una
lista de objetos disponibles. Estas propiedades del problema original estarn disponibles para todos los
problemas. Vamos a resolver el problema mediante la tcnica Con Filtro.
Diseamos la clase ProblemaMochila como una factora para crear problemas generalizados, para hacer algunos
trabajos previos como leer los objetos disponibles de un fichero y ordenarlos. Estos objetos disponibles ya
ordenados es una propiedad de esta clase que compartirn todos los problemas generalizados.
La clase ProblemaMochilaPDF es el tipo de los problemas generalizados que se van a resolver por la tcnica de la
Programacin Dinmica con Filtro.
Este problema tiene como propiedades individuales: capacidad, index y valor acumulado. Como propiedades
compartidas tiene: objetosDisponibles y mejorValorEncontrado. Otra propiedad compartida sera el orden que
se establece sobre las soluciones parciales (acerca del tipo Sp<Integer, Integer>).
Dos problemas son iguales si tienen iguales: capacidad e index.

293

El Problema de la Mochila se resuelve con la clase ProblemaMochila, tal como se indica:

public class ProblemaMochila {


// Propiedades
private static
private static
public static

compartidas...
List<ObjetoMochila>
objetosDisponibles;
Comparator<ObjetoMochila> ordenObjetos;
Integer
capacidadInicial;

public static void leeObjetosDisponibles(String fichero) {


ordenObjetos = Ordering.natural();
Iterable<String> objetos = Iterables2.fromFile(fichero);
objetosDisponibles = Lists.newArrayList();
for (String s : objetos) {
objetosDisponibles.add(ObjetoMochila.create(s));
}
Collections.sort(getObjetosDisponibles(), ordenObjetos);
}
public static List<ObjetoMochila> getObjetosDisponibles() {
return objetosDisponibles;
}
public static Comparator<ObjetoMochila> getOrdenObjetos() {
return ordenObjetos;
}
public static ProblemaMochila create(Integer capacidad, Integer index) {
return new ProblemaMochila(capacidad, index);
}
public static ProblemaMochila create() {
return new ProblemaMochila(ProblemaMochila.capacidadInicial,
ProblemaMochila.getObjetosDisponibles().size() - 1);
}
// Propiedades individuales...
private Integer capacidad;
private Integer index;
private ProblemaMochila(Integer capacidad, Integer index) {
super();
this.capacidad = capacidad;
this.index = index;
}
public Integer getCapacidad() {
return capacidad;
}
public Integer getIndex() {
return index;
}
public static ObjetoMochila getObjeto(int index) {
return ProblemaMochila.getObjetosDisponibles().get(index);
}
public static Integer getValorObjeto(int index) {
return ProblemaMochila.getObjetosDisponibles().get(index).getValor();
}

294

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

public static Integer getPesoObjeto(int index) {


return ProblemaMochila.getObjetosDisponibles().get(index).getPeso();
}
public static Integer getNumMaxDeUnidades(int index) {
return ProblemaMochila.getObjetosDisponibles().get(index).getNumMaxDeUnidades();
}
public Integer getNumMaxDeUnidades() {
return ProblemaMochila.getNumMaxDeUnidades(this.index);
}
public static Integer numeroEnteroMaximoDeUnidades(Integer index, Integer capacidad) {
return Math.min(capacidad / ProblemaMochila.getPesoObjeto(index),
ProblemaMochila.getNumMaxDeUnidades(index));
}
public static Double numeroRealMaximoDeUnidades(Integer index, Double capacidad) {
return Math.min(capacidad / ProblemaMochila.getPesoObjeto(index),
ProblemaMochila.getNumMaxDeUnidades(index));
}
public boolean isFinal() {
return this.index < 0;
}
public Integer getCotaSuperiorValorEstimado() {
Double r = 0.;
Double c = (double) getCapacidad();
Double nu;
int index = getIndex();
while (true) {
if (index < 0 || c <= 0.) break;
nu = ProblemaMochila.numeroRealMaximoDeUnidades(index, c);
r = r + nu * ProblemaMochila.getValorObjeto(index);
c = c - nu * ProblemaMochila.getPesoObjeto(index);
index--;
}
return (int) Math.ceil(r);
}
public Integer getCotaSuperiorValorEstimado(Integer a) {
Double r = 0.;
Double c = (double) getCapacidad();
Double nu = (double) a;
int index = getIndex();
while (true) {
r = r + nu * ProblemaMochila.getValorObjeto(index);
c = c - nu * ProblemaMochila.getPesoObjeto(index);
index--;
if (index < 0 || c <= 0.) break;
nu = ProblemaMochila.numeroRealMaximoDeUnidades(index, c);
}
return (int) Math.ceil(r);
}

295

public Integer getCotaInferiorValorEstimado() {


Integer r = 0;
Integer c = getCapacidad();
Integer nu;
;
int index = getIndex();
while (true) {
if (index < 0 || c <= 0.) break;
nu = ProblemaMochila.numeroEnteroMaximoDeUnidades(index, c);
r = r + nu * ProblemaMochila.getValorObjeto(index);
c = c - nu * ProblemaMochila.getPesoObjeto(index);
index--;
}
return (int) Math.ceil(r);
}
public Integer getCotaInferiorValorEstimado(Integer a) {
Integer r = 0;
Integer c = getCapacidad();
Integer nu = a;
int index = getIndex();
while (true) {
r = r + nu * ProblemaMochila.getValorObjeto(index);
c = c - nu * ProblemaMochila.getPesoObjeto(index);
index--;
if (index < 0 || c <= 0.) break;
nu = ProblemaMochila.numeroEnteroMaximoDeUnidades(index, c);
}
return (int) Math.ceil(r);
}
@Override
public String toString() {
return "(" + capacidad + "," + index + ")";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((capacidad == null) ? 0 : capacidad.hashCode());
result = prime * result + ((index == null) ? 0 : index.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof ProblemaMochila))
return false;
ProblemaMochila other = (ProblemaMochila) obj;
if (capacidad == null) {
if (other.capacidad != null)
return false;
} else if (!capacidad.equals(other.capacidad)) return false;
if (index == null) {
if (other.index != null)
return false;
} else if (!index.equals(other.index))
return false;
return true;
}
public boolean isSolucion(SolucionMochila s) {
boolean r = s.getPeso() <= this.getCapacidad();
for (ObjetoMochila e : s.elements()) {
if (!r) break;
r = s.count(e) <= e.getNumMaxDeUnidades();
}
return r;
}
}

296

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


La clase que resuelve el Problema de la Mochila con Filtro sera:

public class ProblemaMochilaPDF implements ProblemaPDF<SolucionMochila, Integer, Integer> {


private ProblemaMochila problema;
private Integer valorAcumulado;
public static ProblemaMochilaPDF create(ProblemaMochila problema, Integer valorAcumulado) {
return new ProblemaMochilaPDF(problema, valorAcumulado);
}
public static ProblemaMochilaPDF create() {
AlgoritmoPD.tipo = AlgoritmoPD.Tipo.Max;
return new ProblemaMochilaPDF(ProblemaMochila.create(), 0);
}
private ProblemaMochilaPDF(ProblemaMochila problema, Integer valorAcumulado) {
super();
this.problema = problema;
this.valorAcumulado = valorAcumulado;
}
@Override
public int size() {
return problema.getIndex();
}
@Override
public boolean esCasoBase() {
return problema.getIndex() == 0 || problema.getCapacidad() == 0;
}
@Override
public Sp<Integer, Integer> getSolucionCasoBase() {
Preconditions.checkState(this.esCasoBase(), "El problema " + toString() + " no es un caso base");
Integer numeroDeUnidades = ProblemaMochila.numeroEnteroMaximoDeUnidades(problema.getIndex(),
problema.getCapacidad());
return Sp.<Integer, Integer> create(numeroDeUnidades,
numeroDeUnidades * ProblemaMochila.getValorObjeto(problema.getIndex()));
}
@Override
public Sp<Integer, Integer> combinaAlternativas(List<Sp<Integer, Integer>> ls) {
Sp<Integer, Integer> e = null;
if (!ls.isEmpty()) {
e = ls.get(0);
}
return e;
}
@Override
public ProblemaPD<SolucionMochila, Integer, Integer> getSubProblema(Integer a, int i) {
Preconditions.checkArgument(i == 0, "Slo hay un problema y se ha recibido i = " + i);
int index = problema.getIndex();
ProblemaMochila p = ProblemaMochila.create(
problema.getCapacidad() - a * ProblemaMochila.getPesoObjeto(index), index - 1);
Integer acumulado = this.valorAcumulado + a * ProblemaMochila.getValorObjeto(index);
ProblemaMochilaPDF pr = create(p, acumulado);
return pr;
}
@Override
public Sp<Integer, Integer> combinaSoluciones(Integer a, List<Sp<Integer, Integer>> ls) {
Preconditions.checkArgument(ls.size() == 1, "Slo hay un problema y se ha recibido = " + ls.size());
Sp<Integer, Integer> e = null;
int index = problema.getIndex();
if (ls != null)
e = ls.get(0);
if (e != null && e.alternativa != null)
e = Sp.<Integer, Integer> create(a, ls.get(0).propiedad + a * ProblemaMochila.getValorObjeto(index));
return e;
}

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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.

El problema tiene como propiedades derivadas:


-

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:
(, , , , )

El problema original es una instancia del conjunto de problemas generalizados anteriores:


_() = (, 0, , , )

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, ) | , , + }

Las alternativas son por tanto, nmeros enteros.


El objetivo del problema es encontrar una solucin que tenga un nmero de reinas r igual a N y ninguna reina
amenace a otra. Esto es equivalente a decir que los cardinales de filOcu, dgPpOcu, dgScOcu (propiedades
derivadas de la solucin) sean iguales a N.

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.

Modelado del problema:


Reina:
El problema consiste en disear un algoritmo para realizar la asignacin de N reinas en un tablero de ajedrez de
tamao N x N. Para plantear el problema modelamos en primer lugar una Reina.

Vamos a dotarlo de dos propiedades:

fila: Entero, consultable. Fila en la que se sita la reina.


columna: Entero, consultable. Columna en la que se sita la reina.

A continuacin detallaremos algunas de sus caractersticas:


Igualdad: Dos reinas son iguales si estn la misma fila y columna.
Orden: La implementacin proporcionar un orden a partir de la fila y la
columna de la reina.
-

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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.

El grfico generado para un problema de 4 reinas es el siguiente:

302

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

N Reinas
Tcnica:

Programacin Dinmica Aleatorio

Tamao:

N (nmeros de reinas y dimensin del tablero)

Propiedades
Compartidas:

N [entero] [4, ] nmeros de reinas y dimensin del tablero.

Propiedades
Individuales:

col

[entero] [0, N )

Columna asignada/ocupada a esa reina.

filOcu

[Lista<entero>]

Lista de filas ocupadas.

dgPpOcu

[Lista<entero>]

Lista de diagonales principales ocupadas.

dgScOcu

[Lista<entero>]

Lista de diagonales secundarias ocupadas.

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:

Posiciones de cada reina.

|col| = |filOcu| = |dgPpOcu| = |dgScOcu| = N y ninguna amenace a otra.


Alternativas:

,,,
= { [0, ) | , , + }

Solucin parcial:

(a, null) donde a = Alternativa

Instanciacin:

_() = (0, {}, {}, {})

Problema generalizado:

= {}

( , ) {} = 1

() = {

((, , ()))

> 1

= (, , , )
= ( + 1, + , + 1 , + 2 ),

1 =
2 = +

(, , ( , )) = (, )
( ):

Funcin de reconstruccin:

(, ) = {

()

(1
{

() =

( + , + 1 , + 2 )

() =

, ) + ( + , + 1 , + 2 )

() >

1 =
2 = +

303

public class ProblemaReinaPD implements ProblemaPD<SolucionReina, Integer, Integer> {


public static ProblemaReinaPD create(ProblemaReina problema) {
return new ProblemaReinaPD(problema);
}
public static ProblemaReinaPD create() {
AlgoritmoPD.tipo = AlgoritmoPD.Tipo.Min;
return new ProblemaReinaPD(ProblemaReina.create());
}
private ProblemaReina problema;
private ProblemaReinaPD(ProblemaReina problema) {
super();
this.problema = problema;
}
public int size() {
return ProblemaReina.getNumeroDeReinas() - problema.getColumna();
}
/**
* Es caso base si la columna en la que vamos a situar la reina es igual al
* nmero de reinas - 1. Hay que tener en cuenta que se empieza a colocar en
* la columna 0
*/
public boolean esCasoBase() {
return problema.getColumna().equals(ProblemaReina.getNumeroDeReinas() - 1);
}
/**
* En el caso base, ponemos la reina en la posicin que quede disponible
* dentro de las posibles alternativas. O lo que es lo mismo, en la fila
* disponible siempre y cuando queden filas disponibles. El valor a
* optimizar no es necesario poner elemento a optimizar, se puede dejar a
* null.
*/
public Sp<Integer, Integer> getSolucionCasoBase() {
Sp<Integer, Integer> res = null;
if (Iterables.size(getAlternativas()) > 0) {
Integer a = Iterables2.elementRandom(getAlternativas());
res = Sp.create(a, null);
}
return res;
}
/**
* Sern alternativas aquellas filas que no estn ocupadas o que no sean
* parte de una diagonal principal o secundaria donde puedan sea amenazada
* por otras reinas
*/
public Iterable<Integer> getAlternativas() {
Iterable<Integer> iterAlt =
Iterables2.fromArithmeticSequence(0, ProblemaReina.getNumeroDeReinas(), 1);
return Iterables.filter(iterAlt, new PredicateAlternativas(problema));
}
private static class PredicateAlternativas implements Predicate<Integer> {
ProblemaReina p;
public PredicateAlternativas(ProblemaReina p) {
this.p = p;
}
public boolean apply(Integer fila) {
return (!p.getFilasOcupadas().contains(fila)) &&
(!p.getDiagonalesPrincipalesOcupadas().contains(fila - p.getColumna())) &&
(!p.getDiagonalesSecundariasOcupadas().contains(fila + p.getColumna()));
}
}

304

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


/**
* Solo tenemos un subproblema, pero es necesario preguntar si esCasoBase
* para la reconstruccin de la solucin
*/
public int getNumeroSubProblemas(Integer a) {
return esCasoBase() ? 0 : 1;
}
/**
* Para crear un nuevo subproblema tenemos que crear nuevas listas y
* conjuntos de filas ocupadas, diagonales principales ocupadas y diagonales
* secundarias ocupadas incluyendo la alternativa (fila) a
*/
public ProblemaPD<SolucionReina, Integer, Integer> getSubProblema(Integer a, int i) {
List<Integer> fo = Lists.newArrayList(problema.getFilasOcupadas());
Set<Integer> dpo = Sets.newHashSet(problema.getDiagonalesPrincipalesOcupadas());
Set<Integer> dso = Sets.newHashSet(problema.getDiagonalesSecundariasOcupadas());
Integer r = problema.getColumna();
Integer f = a;
Integer d1 = f - r;
Integer d2 = f + r;
fo.add(f);
dpo.add(d1);
dso.add(d2);
ProblemaReina p = ProblemaReina.create(r + 1, fo, dpo, dso);
return ProblemaReinaPD.create(p);
}
/**
* Si la solucin parcial que nos llega es distino de null, entonces creamos
* una solucin parcial con la alternativa que habamos cogido. Al igual que
* en el caso base, el valor a optimizar se puede dejar a null
*/
public Sp<Integer, Integer> combinaSolucionesParciales(Integer a, List<Sp<Integer, Integer>> ls) {
Sp<Integer, Integer> res = null;
if (!ls.isEmpty() && ls.get(0) != null) {
res = Sp.create(a, null);
}
return res;
}
/**
* Como no hay que optimizar nada, del conjunto de soluciones parciales que
* nos llegan, primero eliminamos las nulas, y si no est vaca, entonces
* nos quedamos con cualquiera de ellas de manera aleatoria
*/
public Sp<Integer, Integer> seleccionaAlternativa(List<Sp<Integer, Integer>> ls) {
Sp<Integer, Integer> res = null;
if (!ls.isEmpty()) {
Iterable<Sp<Integer, Integer>> aux = Iterables.filter(ls, Predicates.notNull());
if (Iterables.size(aux) != 0) {
res = Iterables2.elementRandom(aux);
}
}
return res;
}
public SolucionReina getSolucion(Integer a, List<SolucionReina> ls) {
SolucionReina r;
if (ls.size() == 0 || ls.get(0) == null) {
r = new SolucionReina();
} else {
r = new SolucionReina(ls.get(0));
}
r.addReina(new Reina(a, problema.getColumna()));
return r;
}
public int hashCode() { }
public boolean equals(Object obj) { }
public String toString() {
return problema.toString();
}
}

305

5.12.3 Camino mnimo.


Dado un conjunto de ciudades conectadas por carreteras, de las
que conocemos su longitud, encontrar el camino ms corto
posible entre dos ciudades dadas.
Sea un grafo G, donde los vrtices son ciudades y las aristas son
caminos entre ellas. Cada arista est etiquetada por un entero
que es la longitud entre las ciudades que conecta.
Cada vrtice del grafo lo indexamos mediante un entero.
Asumimos que el ndice est comprendido en [0, n-1] donde n
es el nmero de ciudades.
Queremos encontrar el Camino Mnimo entre dos ciudades dadas que representaremos por Corigen y Cdestino.
Este problema se puede resolver de muchas otras formas pero vamos a resolverlo generalizndolo a este otro:
encontrar el Camino Mnimo de org a des usando como camino intermedio aquellas ciudades cuyos ndices estn
en el conjunto [0, k]. Con este planteamiento, cada problema lo podemos representar por (Cdesde, Chasta, Cinter)
donde Cdesde y Chasta valores en [0, n-1] y Cinter en [-1, n-1]. El valor de Cinter-1 indica que el camino intermedio no
contiene ninguna ciudad.
Un camino en el grafo lo vamos a representar por una secuencia de ciudades conectadas cada una a la siguiente
mediante una arista. Cada camino tiene una longitud que es la suma del valor de sus aristas. La no existencia de
camino lo representaremos por .
Como soluciones parciales escogemos, de forma similar a otros casos, el par formado por la alternativa y la
longitud del camino.
Representamos por g el grafo de partida y por (Cdesde, Chasta) la arista, si la hay, entre las ciudades Cdesde y Chasta y
por |(Cdesde, Chasta)| su longitud. La primera decisin es escoger el tipo de alternativas. Para cada problema
tenemos dos alternativas: {Y, N}. La primera alternativa representa S pasar por la ciudad Cinter y la segunda
alternativa NO pasar por la ciudad Cinter.
Dotamos a dos caminos Camino1, Camino2 de la operacin de concatenacin Camino1 + Camino2 y la de
inicializacin Camino = (Cdesde, Chasta) que convierte una arista en un camino de un slo tramo.
Las ideas anteriores pueden ser adaptadas para encontrar la versin iterativa del algoritmo recursivo de FloydWarshall. Cada problema se representaba por la tripleta (Cdesde, Chasta, Cinter) siendo Cinter +1 el tamao del
problema. Por tanto, los problemas de tamao Cinter pueden representar por el par de enteros (Cdesde, Chasta) ya
que le grafo es una propiedad compartida. As, para cada estado, nos proporciona la solucin de un problema
que puede implementarse por una tabla. Esta tabla tk nos dar el camino del vrtice Cdesde al Chasta usando vrtices
en el conjunto [0, Cinter] o si no hay camino. Designamos entonces por tk(Cdesde, Chasta) el camino y por
|tk(Cdesde, Chasta)| su longitud.
Como hemos visto antes, la solucin de un problema de Programacin Dinmica con memoria puede
generalizarse fcilmente para buscar la solucin de conjuntos de problemas relacionados. Se trata de indicar el
conjunto de problemas a resolver y aprovechar los clculos guardados en la memoria de unos problemas para
otros. En este caso, y con estas ideas, podramos buscar los Caminos Mnimos entre dos ciudades cualesquiera
de un grafo. Para ello proporcionaramos un Iterable que contenga todos los pares de ciudades del grafo. Al
resolver cada problema se utilizaran las soluciones a los subproblemas que se comparten con el resto de
problemas del Iterable. Este esquema, para la bsqueda de los Caminos Mnimos entre cada dos ciudades, es
conocido como el Algoritmo de Floyd-Warshall. El algoritmo descrito aqu es su versin recursiva.
306

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Ejemplo:
Hallar el camino mnimo en el grafo con las siguientes distancias.
El caso base es para Cinter = -1. Es decir, caminos de Cdesde a Chasta sin
usar ningn vrtice intermedio. El estado inicial viene
representado en la tabla de distancias, t-1, siguiente:

= [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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

Camino Mnimo
Tcnica:

Programacin Dinmica

Tamao:

numCiu

Nmeros de ciudades o vrtices del grafo.

Propiedades
Compartidas:

G(Ciudad, Camino)

Grafo dirigido ponderado en aristas.

Corigen

[entero]

Ciudad origen.

Cdestino

[entero]

Ciudad destino.

numCiu [entero]
Propiedades
Individuales:

(Prop. Derivada) Nmero de ciudades (vrtices).

Cdesde

[entero] [0, numCiu-1]

Ciudad origen parcial.

Chasta

[entero] [0, numCiu -1]

Ciudad destino parcial.

Cinter

[entero] [-1, numCiu -1]

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)

Lista de ciudades desde Corigen hasta Cdestino.

( , ) + ( , )
( , )

donde: a = Alternativa,

, , = (, )

long = Longitud del camino mnimo recorrido.

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

5.12.4 Multiplicacin de matrices encadenadas.


2 5
{[
4 3

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


El tipo SolucionMatriz tendr las propiedades: ExpresionConParentesis [cadena], que representa la forma
concreta de operar las matrices que estn en las posiciones indicadas y, NumeroDeOperaciones [entero], resultante
tras dicha parentizacin.
Como solucin parcial escogemos el par formado por la alternativa y el valor total de la solucin (nmero de
operaciones).
El objetivo del problema es encontrar una solucin para la cual el nmero de multiplicaciones sea mnimo.
Todas las ideas anteriores estn resumidas en la ficha siguiente:
Multiplicacin de Matrices Encadenadas
Tcnica:
Programacin Dinmica
Tamao:
N=J-I
Propiedades
M
[Lista<Matriz>]
Compartidas:
Propiedades
I
[entero] [0, M.size() )
Individuales:
J
[entero] [I+1, M.size() ).
Solucin:
s = re,n de tipo SolucionMatriz
Objetivo:
Encontrar re,n tal que n tenga el menor valor posible.
Alternativas:
, = { | > , }
Solucin Parcial: (a, n) siendo n el nmero de multiplicaciones.
() = (0, . ())
Instanciacin:
Problema generalizado:
(, 0) = 1
( + 1, +1 +1 ) = 2

(, ) =

{ , ( (, , (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

Modelado del problema:


Matriz:
El problema consiste en disear un algoritmo para realizar una multiplicacin de matrices encadenadas. Para
plantear el problema modelamos en primer lugar una Matriz.
Vamos a dotarlo de dos propiedades:
numeroFilas : [entero] Nmero de filas de la matriz.
numeroColumnas: [entero] Nmero de columnas de la matriz.
En notacin abreviada fi y ci respectivamente para la matriz que ocupa la
posicin i en la lista.

Dos matrices son iguales si tienen el mismo nmero de filas y de columnas.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


SolucinMatriz:
La clase SolucionMatriz que contendr el nmero de operaciones o de
multiplicaciones necesarias y la expresin matricial asociada a la misma.

problema: Objeto de tipo ProblemaMatriz.

Implementacin:

ProblemaMatrizPD implements ProblemaPD<SolucionMatriz, Integer, Integer>

boolean esCasoBase(): Se trata de un caso base si el ndice j-simo (propiedad j) menos el ndice i-simo

(propiedad i) de la propiedad problema es igual a 1 o a 2 segn indica la ficha.


Sp<Integer, Integer> getSolucionCasoBase(): Devuelve una solucin parcial de tipo Sp segn el caso
base correspondiente teniendo en cuenta la ficha.
Iterable<Integer> getAlternativas(): Devuelve un iterable de enteros con las posibles alternativas
para la multiplicacin de matrices encadenadas. Las alternativas sern las posiciones donde se puede
colocar un parntesis. Hay que tener en cuenta que una posicin S debe cumplir que I < S < J, por lo que
se deber hacer uso de fromArithmeticSequence de la clase Iterables2 el cual recibe 3 parmetros: inicio
(incluido), fin (no incluido) e incremento.
int getNumeroSubProblemas(): Este mtodo devuelve 2, ya que el problema se divide en 2 subproblemas.
ProblemaPD<SolucionMatriz, Integer, Integer> getSubProblemas (Integer a, int i): Devuelve un
nuevo objeto de tipo ProblemaMatrizPD teniendo en cuenta el subproblema indicado mediante el
parmetro i. Para ello, previamente ha de crear el subproblema de tipo ProblemaMatriz correspondiente:
- Si se trata del subproblema 0, crea un nuevo problema de tipo ProblemaMatriz para resolver la
multiplicacin de matrices encadenadas desde la matriz i-sima (propiedad i) hasta la matriz
cuya posicin es la alternativa.
- Si se trata del subproblema 1, crea un nuevo problema de tipo ProblemaMatriz para resolver la
multiplicacin de matrices encadenadas desde la matriz cuya posicin es la alternativa hasta la
matriz j-sima (propiedad j).
Sp<Integer, Integer> combinaSolucionesParciales(Integer a, List<Sp<Integer, Integer>> ls):
- Obtiene las dos primeras soluciones de la lista de soluciones parciales en caso de que existan.
- Obtiene el nmero de filas de la matriz i-sima, el nmero de filas de la matriz a-sima y el
nmero de columnas de la matriz que est en la posicin j-1.
- Devuelve una nueva solucin parcial a partir de las 2 soluciones parciales obtenidas. El nmero
de operaciones se calcula a partir de las operaciones de las anteriores soluciones, ms las
necesarias para multiplicar los dos subconjuntos de matrices representados en dichas
soluciones.
Sp<Integer, Integer> seleccionaAlternativa(List<Sp<Integer, Integer>> ls): Si la lista de
soluciones parciales no es vaca, se obtiene el menor valor usando el orden natural de la clase Sp.
SolucionMatriz getSolucion(Integer a, List<SolucionMatriz> ls): Permite la reconstruccin de la
solucin final.

313

public class ProblemaMatrizPD


implements ProblemaPD<SolucionMatriz, Integer, Integer>
{
public static ProblemaMatrizPD create(ProblemaMatriz problema) {
return new ProblemaMatrizPD(problema);
}
public static ProblemaMatrizPD create() {
AlgoritmoPD.tipo = AlgoritmoPD.Tipo.Min;
return new ProblemaMatrizPD(ProblemaMatriz.create());
}
private ProblemaMatriz problema;
private ProblemaMatrizPD(ProblemaMatriz problema) {
super();
this.problema = problema;
}
@Override
public int size() {
return problema.getJ() - problema.getI();
}
@Override
public boolean esCasoBase() {
return size() < 3;
}
@Override
public Sp<Integer, Integer> getSolucionCasoBase() {
if (size() == 1)
return Sp.create(null, 0);
else {
int i = problema.getI();
return Sp.create(i + 1,
ProblemaMatriz.getFila(i) * ProblemaMatriz.getColumna(i) + ProblemaMatriz.getColumna(i + 1));
}
}
@Override
public Iterable<Integer> getAlternativas() {
return Iterables2.fromArithmeticSequence(problema.getI() + 1, problema.getJ(), 1);
}
@Override
public int getNumeroSubProblemas(Integer a) {
return esCasoBase() ? 0 : 2;
}
@Override
public ProblemaPD<SolucionMatriz, Integer, Integer> getSubProblema(Integer a, int i) {
// i = Nmero del subproblema que estemos resolviendo
if (i == 1)
return ProblemaMatrizPD.create(ProblemaMatriz.create(problema.getI(), a));
else
return ProblemaMatrizPD.create(ProblemaMatriz.create(a, problema.getJ()));
}
@Override
public Sp<Integer, Integer> combinaSolucionesParciales(Integer a, List<Sp<Integer, Integer>> ls) {
Sp<Integer, Integer> sp1 = ls.get(0);
Sp<Integer, Integer> sp2 = ls.get(1);
int i = problema.getI();
int j = problema.getJ();
Matriz m1 = ProblemaMatriz.getMatriz(i);
Matriz m2 = ProblemaMatriz.getMatriz(a - 1);
Matriz m3 = ProblemaMatriz.getMatriz(j - 1);
int x = m1.getNumeroFilas() * m2.getNumeroColumnas() * m3.getNumeroColumnas();
return Sp.create(a, sp1.propiedad + sp2.propiedad + x);
}

314

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

@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

5.12.5 Cambio de monedas.


Dada una cantidad en sistema monetario se pide devolver dicha cantidad con el menor
nmero de monedas posibles. Un sistema monetario est definido por un conjunto M
de monedas diferentes. Cada moneda la representamos por monedai, i [0, r-1] y el
valor de cada una de ellas por valori. La solucin buscada es un multiconjunto de las
monedas disponibles. Cada moneda tiene la propiedad valor [entero]. Como notacin
para representar la moneda que ocupa la posicin i en M usaremos
. Para el valor unitario de la

moneda en la posicin i usaremos: valori .


El tipo SolucionMoneda es un subtipo de Multiset<Moneda> con las propiedades adicionales de Valor (entero) de
las monedas incluidas en la solucin, NumeroDeMonedas [entero] incluidas en la solucin. Podemos disear una
solucin parcial del problema con una lista de enteros que representan las opciones tomadas y dos propiedades
adicionales: Valor y NumeroDeMonedas.
Las propiedades compartidas del conjunto de problemas son: Monedas, List<Moneda> ordenada de menor a
mayor valor y sin repeticin, que seran la monedas disponibles.
Vamos a generalizar el problema. Hay varias formas posibles de hacerlo, una forma es resolver el problema
planteado dada una cantidad N con un subconjunto de las monedas del sistema monetario. Las monedas
disponibles, aunque es un conjunto, las hemos representado por su forma normal, una lista ordenada sin
repeticiones. Una moneda concreta la podemos representar por su ndice en Monedas. Un subconjunto de
monedas por otro ndice J que representa el subconjunto de monedas cuyos ndices son menores o iguales a J.
El problema generalizado cuenta con las propiedades individuales: Cant [entero] que sera la cantidad de
monedas, y J (entero [0, Monedas.size()). En este contexto, dos problemas son iguales si lo son sus
propiedades individuales: Cant y J.
Las alternativas disponibles podemos representarlas con nmeros enteros. El nmero de unidades que podemos
usar de la moneda monedaj si la cantidad de dinero a cambiar es Cant. Las alternativas sern A = {0, 1, 2, , r}

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Todas las ideas anteriores estn resumidas en la ficha siguiente:

Cambio de Monedas
Tcnica:

Programacin Dinmica

Tamao:

N=J

Propiedades
Compartidas:

Monedas [Lista <Moneda>] ordenada y sin repeticin.

Propiedades
Individuales:

Cant [entero] 0. Cantidad total a devolver para el problema individual.

Solucin:

SolucinMoneda, Multiset<Moneda>

Objetivo:

Encontrar S monedas,valor,n tal que valor = Cant y que n tenga el menor valor posible.

Monedas disponibles en el sistema monetario. La lista se mantiene ordenada de menor a


mayor valor aunque sera posible un orden alternativo.
J [entero] [0, Monedas.size() ). Representa al subconjunto de monedas del sistema
monetario cuyos ndices son menores o iguales a J, nicas que pueden usarse para
encontrar la solucin del problema individual.

Generalizacin: Encontrar el menor nmero de monedas para devolver la cantidad Cant de


un subconjunto de monedas del sistema, dado por las monedas de ndices 0..J
Alternativas:

Las alternativas posibles podemos representarlas con nmeros enteros: nmero de


unidades que podemos usar de la moneda si la cantidad de dinero a cambiar es Cant.
, = {. .0},

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

public ProblemaPD<SolucionMoneda, Integer, Integer> getSubProblema(Integer a, int i) {


Integer vj = this.getMonedaJ().getValor();
Integer cambio = cantidad - a * vj;
return ProblemaMoneda.createPD(cambio, j - 1);
}
public Sp<Integer, Integer> combinaSoluciones(Integer a, List<Sp<Integer, Integer>> ls) {
Sp<Integer, Integer> res = ls.get(0);
if (res != null) {
Integer vj = getMonedaJ().getValor();
res = new Sp<Integer, Integer>(a, ls.get(0).solucionParcial + a * vj);
}
return res;
}
public Iterable<Integer> getAlternativas() {
List<Integer> res = Lists.newArrayList();
List<Moneda> monedas = ProblemaMoneda.getMonedasDisponibles();
for (Moneda m : monedas) {
Integer r = cantidad / m.getValor();
if (cantidad == r * m.getValor()) {
Integer n = Math.min(r, m.getNumeroDeUnidades());
res.add(n);
}
}
return res;
}
public int getNumeroSubProblemas(Integer a) {
return this.esCasoBase() ? 0 : 1;
}
public SolucionMoneda getSolucion(Integer e) {
return new SolucionMoneda();
}
@Override
public SolucionMoneda getSolucion(Integer a, List<SolucionMoneda> ls) {
return ls.isEmpty() ? null : ls.get(0);
}
public Integer getCantidad() {
return cantidad;
}
public Integer getJ() {
return j;
}
public Moneda getMonedaJ() {
return monedaJ;
}
@Override
public String toString() {
return "\n ProblemaMonedaPD [cantidad=" + cantidad + ", j=" + j + ", monedaJ=" + monedaJ + "]";
}
}

319

5.12.6 Subsecuencia comn ms larga.


El problema se enuncia as:

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>]

Las propiedades individuales de cada problema son:

I, de tipo [entero] I [0, X.size() )


J, de tipo [entero] J [0, Y.size() )

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Todas las ideas anteriores estn resumidas en la ficha siguiente:
Subsecuencia comn ms larga
Tcnica:

Programacin Dinmica

Tamao:

N=I+J

Propiedades
Compartidas:

[Lista<T>]

[Lista<T>]

Propiedades
Individuales:

[entero] [0, X.size() )

[entero] [0, Y.size() )

Solucin:

[Lista<T>]

Objetivo:

Encontrar Z tal que |Z| tenga el mayor valor posible.

Alternativas:

Instanciacin:

{}

{, }

, = {

(, ) = (|| 1, || 1)

Problema generalizado:

(, ) =

{}

< 0 || < 0

, ((, ( 1, 1)))

, (

(, ( 1, ))

(, (, 1))

(, ): .
(, ): .
(, ): + .
, ( ): ||.

321

5.13 Problemas de exmenes.


5.13.1 Transformacin de cadenas de caracteres.

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

[E] Eliminar o en la posicin 4.


[C] Cambiar r por a en la posicin 3.
[C] Cambiar r por s en la posicin 2.

Ejemplo 2: Transformar la cadena abbac en la cadena abcbc:

abbac abac
abac ababc
ababc abcbc

[E] Eliminar b en la posicin 3.


[A] Aadir b en la posicin 4.
[C] Cambiar a por c en la posicin 3.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Se pide:
Implementar los siguientes mtodos de la clase ProblemaTransCadPDImpl a partir de la ficha que se proporciona
a continuacin.
a)
b)
c)
d)
e)
f)

public boolean esCasoBase().


public SolucionTransCad getSolucionCasoBase().
public int getNumeroDeAlternativas().
public int getNumeroSubProblemasAnd(String a).
public ProblemaPD<SolucionTransCad, String> getSubProblemaAnd(String a, int i).
public SolucionTransCad combinaSolucionesAnd(String a, SolucionTransCad... ls).

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:

(, ) = (. (), . ())

[entero] [0, u.size() )


[entero] [0, v.size() ).

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Apartado e)
public ProblemaPD<SolucionTransCad, String> getSubProblemaAnd(String a, int i) {
ProblemaPD<SolucionTransCad, String> p = null;
if (a.equals("N") || a.equals("C")) {
p = new ProblemaTransCadPDImpl(getI() - 1, getJ() - 1);
} else if (a.equals("E")) {
p = new ProblemaTransCadPDImpl(getI() - 1, getJ());
} else if (a.equals("A")) {
p = new ProblemaTransCadPDImpl(getI(), getJ() - 1);
} else {
throw new SituacionIlegal("La alternativa no es vlida.");
}
return p;
}

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

public class ProblemasTransfoCadenas {


private static String cadenaInicial, cadenaFinal;
// ...
public static Iterable<ProblemaPD<SolucionTransfoCadenas, String, SolucionParcialTransfoCadenas>>
createPD() {
List<ProblemaPD<SolucionTransfoCadenas, String, SolucionParcialTransfoCadenas>>
lista = Lists.newArrayList(createPD(cadenaInicial.length(), cadenaFinal.length()));
return lista;
}
}

public class SolucionesTransfoCadenas {


// ...
public static SolucionParcialTransfoCadenas createSolucionParcial(String cambio, Integer num){
return new SolucionParcialTransfoCadenas(cambio, num);
}
public static SolucionParcialTransfoCadenas createSolucionParcial
(SolucionParcialTransfoCadenas sp, String cambio) {
return new SolucionParcialTransfoCadenas(sp, cambio);
}
}

public interface SolucionTransfoCadenas extends Comparable<SolucionTransfoCadenas> {


List<String> getTransiciones(); // secuencia de cadenas, desde cadenaInicial a cadenaFinal
Integer getNumCambios();
}

public class SolucionParcialTransfoCadenas implements Comparable<SolucionParcialTransfoCadenas> {


private List<String> cambios; // lista de N, E, A, C
private Integer numCambios;
// nmero de cambios efectivos (sin contar N)
// ...
public List<String> getCambios() {
return cambios;
}
public Integer getNumCambios() {
return numCambios;
}
// ...
}

public class ProblemaTransfoCadenasPD implements


ProblemaPD<SolucionTransfoCadenas, String, SolucionParcialTransfoCadenas> {
private Integer i;
private Integer j;
private String tipoCambio[] = { "N", "E", "A", "C" };
private static String cadenaInicial = ProblemasTransfoCadenas.getCadenaInicial();
private static String cadenaFinal = ProblemasTransfoCadenas.getCadenaFinal();
private class CodigoCambio implements Function<Integer, String> {
public String apply(Integer a) {
return tipoCambio[a];
}
}
private CodigoCambio codigoCambio = new CodigoCambio();
private static Ordering<SolucionParcialTransfoCadenas> ordSolParcial = Ordering.natural();
// ...
public boolean esCasoBase() {
return (getI() == 0 || getJ() == 0);
}

326

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


public SolucionParcialTransfoCadenas getSolucionCasoBase() {
SolucionParcialTransfoCadenas s = null;
if (getI() == 0 && getJ() == 0) {
s = SolucionesTransfoCadenas.createSolucionParcial();
} else if (getI() == 0) {
s = SolucionesTransfoCadenas.createSolucionParcial("A", getJ());
} else if (getJ() == 0) {
s = SolucionesTransfoCadenas.createSolucionParcial("E", getI());
}
return s;
}
public int getNumeroSubProblemas(String a) {
return 1;
}
public ProblemaPD<SolucionTransfoCadenas, String, SolucionParcialTransfoCadenas>
getSubProblema(String a, int i)
{
ProblemaPD<SolucionTransfoCadenas, String, SolucionParcialTransfoCadenas> p = null;
if (a.equals("N") || a.equals("C")) {
p = ProblemasTransfoCadenas.createPD(getI() - 1, getJ() - 1);
} else if (a.equals("E")) {
p = ProblemasTransfoCadenas.createPD(getI() - 1, getJ());
} else if (a.equals("A")) {
p = ProblemasTransfoCadenas.createPD(getI(), getJ() - 1);
}
return p;
}
public SolucionParcialTransfoCadenas combinaSolucionesAnd
(String a, List<SolucionParcialTransfoCadenas> ls)
{
return SolucionesTransfoCadenas.createSolucionParcial(ls.get(0), a);
}
public SolucionParcialTransfoCadenas combinaSolucionesOr (List<SolucionParcialTransfoCadenas> ls) {
SolucionParcialTransfoCadenas s = null;
Iterable<SolucionParcialTransfoCadenas> listaFiltrada =
Iterables.filter(ls, Predicates.notNull());
if (Iterables.size(listaFiltrada) > 0) {
s = ordSolucionParcial.min(listaFiltrada);
}
return s;
}
public SolucionTransfoCadenas getSolucion(SolucionParcialTransfoCadenas e) {
String cadena = cadenaInicial;
Integer x = 0;
Integer y = 0;
List<String> transiciones = Lists.newArrayList(cadenaInicial);
for (String cambio : e.getCambios()) {
if (cambio.equals("N")) {
x++;
y++;
} else if (cambio.equals("E")) {
cadena = cadena.substring(0, x).concat(cadena.substring(x + 1));
} else if (cambio.equals("A")) {
cadena = cadena.substring(0, x).concat(cadenaFinal.subSequence(y, y + 1)
.toString().concat(cadena.substring(x)));
x++;
y++;
} else if (cambio.equals("C")) {
cadena = cadena.substring(0, x).concat(cadenaFinal.subSequence(y, y + 1)
.toString().concat(cadena.substring(x + 1)));
x++;
y++;
}
if (!cambio.equals("N")) {
transiciones.add(cadena);
}
}
return SolucionesTransfoCadenas.createSolucion(transiciones);
}
}

327

5.13.2 Contratacin de personal.


Dado un presupuesto PT de un proyecto para la contratacin de personal y un
sistema de categoras profesionales Categorias, se pide invertir dicho presupuesto
contratando el menor nmero de trabajadores posibles, ya que cuanto menor sea
el nmero de trabajadores, ms fcil ser coordinar las tareas del proyecto. El
sistema de categoras profesionales est definido por un conjunto c de categoras
diferentes. Cada categora se representar por ci , donde i [0, c.size ), y si como
el sueldo que obtienen los trabajadores que pertenecen a dicha categora. De esta manera, podemos representar
a una categora profesional de manera compacta como ci s,t , representando ti el nmero de trabajadores de la
categora i cuyo sueldo es si. Representaremos una solucin del problema de contratacin de personal como los
trabajadores necesarios para cubrir el presupuesto total PT. Una manera compacta de definirla es como sigue:
Sol c, s, t es la solucin con el multiconjunto de categoras utilizadas c cuyo presupuesto final (o lo que es lo mismo,
la suma de los sueldos de todos los trabajadores que van a ser contratados) es s y el nmero total de trabajadores
utilizados es t. Donde se corresponde a la solucin con el multiconjunto vaco y que no hay solucin. Las
alternativas para la contratacin de personal son el nmero de trabajadores que podemos usar de la categora c
si el presupuesto del que se dispone es p.
A = {0, 1, 2,, k} con k = p/si
El objetivo del problema es encontrar una solucin cuyo presupuesto final sea el presupuesto total inicial PT y
tenga el menor nmero de trabajadores posibles. Los detalles se muestran en la ficha siguiente:
Contratacin de Personal
Tcnica:
Tamao:
Propiedades
Compartidas:

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

{ , ( ( , ( , 1))) > 0, > 0


( , ):
, (,,
):

Complejidad:
328

( pCategorias.size() )

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Se pide:
Implemente de la clase ProblemaCategoriaPDImpl los mtodos:
a)
b)
c)
d)
e)
f)

public boolean esCasoBase().


public SolucionCategoria getSolucionCasoBase().
public int getNumeroDeAlternativas().
public int getNumeroSubProblemasAnd(Integer a).
public ProblemaPD<SolucionCategoria, Integer> getSubProblemaAnd(Integer a, int i).
public SolucionCategoria combinaSolucionesAnd(Integer a, SolucionCategoria... ls).

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

5.13.3 Inversiones en acciones.


Se dispone de un fondo de inversin que dispone de una cantidad de dinero M
para invertir en acciones. Al invertir la cantidad X en una accin el fondo espera
recibir un cierto beneficio o inters. Se desea disear un algoritmo que intente
repartir la cantidad M entre N acciones distintas de forma que se obtenga el
mximo beneficio esperado posible. Para poder resolver el problema se dispone
de una funcin beneficio(i, x) que devuelve el beneficio o inters que obtiene el
fondo al invertir la cantidad x en la accin i. Para cualquier accin i se cumple que
beneficio(i, 0) = 0 y que si x > y, entonces beneficio(i, x) >= beneficio(i, y).
Se dispone de los siguientes tipos (en notacin compacta):
Accion

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

Lacciones ListaAccion Lista de inversiones.


[
ben
entero
Beneficio de la solucin.

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)

Cantidad de dinero a invertir.

[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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


SOLUCIN:

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.

Utilice el diagrama UML mostrado para realizar los distintos apartados.

334

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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

[entero] [0, Embarcaderos.size() )


[entero] [0, Embarcaderos.size() )

Solucin:

[entero]

Objetivo:

Encontrar S tal que el coste sea mnimo.

Alternativas:

, = { | > , } k representa un embarcadero intermedio entre i y j.

Instanciacin:

(, ) = (, )

Coste mnimo.

Problema generalizado:
(, ) = {

(+1,) ((, (, 1)))

//

(, ): (, ) +

//

( ):

//

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

5.13.5 Servicio de bicicletas.


ETSIIbici es un servicio de uso de bicicletas que cuenta con una serie de
estaciones repartidas por una ciudad. La ventaja de este sistema es que se puede
disfrutar de un nmero ilimitado de trayectos en bicicleta. Sin embargo, para
realizar el recorrido slo se pueden utilizar los trayectos habilitados que conectan
unas estaciones con otras. Adems, no todas las estaciones estn conectadas
entre s mediante trayectos.
La idea es hacer un recorrido desde una estacin origen hasta una estacin destino tardando el menor tiempo
posible. Para ello, se desea obtener mediante un algoritmo de Programacin Dinmica la secuencia ptima de
estaciones por las que debemos pasar para llegar desde la estacin origen hasta la estacin destino.

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

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 se ha visto en clase).
De la clase ProblemaEstacionBicicletasPD:
b) public boolean esCasoBase()
c) public SolucionParcialEstacionBicicletas getSolucionCasoBase()
d) public Iterable<Integer> getAlternativas()
e) public
ProblemaPD<SolucionEstacionBicicletas,Integer,SolucionParcialEstacionBicicletas>
getSubProblema(Integer a, int s)

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.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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:

Encontrar una solucin S L, t tal que el tiempo t sea mnimo.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Apartado 2)
Punto a)
private E pD(ProblemaPD<S, A, E> p) {
E s = null;
if (map.containsKey(p)) {
s = map.get(p);
} else if (p.esCasoBase()) {
s = p.getSolucionCasoBase();
map.put(p, s);
} 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);
map.put(p, s);
}
return s;
}

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

5.13.6 Compaa discogrfica.


Una compaa discogrfica quiere sacar un recopilatorio con los grandes
xitos de uno de sus artistas principales. Para ello, dispone de una lista de
canciones a repartir entre dos CDs diferentes. Cada cancin tiene
asociada un ttulo y el tiempo en segundos. Por otro lado, se conocen
como capCD1 y capCD2 a las capacidades de tiempo, medidas en
segundos, de cada uno de los discos de la recopilacin.
La discografa requiere nuestra ayuda para encontrar la composicin de
canciones en los disco de tal forma que se maximice el tiempo total de
canciones entre ambos CDs.
En cada paso, se va a ir analizando cada cancin, y decidiendo si se va a meter en el CD 1 o CD 2. Se analizar la
alternativa 1 (CD 1) si el tiempo de la cancin que se est analizando es menor que el tiempo restante de la cara
1, e igual con la alternativa 2 (CD 2). Adems, se dar la opcin de no poner la cancin en ninguno de los CDs, en
cuyo caso sera alternativa 0. Una vez decidido, se actualizar el tiempo restante del CD en el que se ha decidido
meter la cancin, para continuar con el anlisis de la siguiente cancin. La bsqueda termina cuando no hay ms
canciones que analizar o cuando no hay ms tiempo en ninguno de los CDs.
Por tanto, dada la lista de canciones LC = { 0, 1, , 1} se desea obtener mediante un algoritmo de
Programacin Dinmica la composicin ptima de canciones entre los dos discos para conseguir que el tiempo
total de canciones sea mximo.
En todo momento vamos a tener acceso a la lista de canciones, LC (List<Cancion>) que contiene cada una de las
canciones disponibles. La lista est ordenada de mayor a menor.
Adems, tambin se conocen en todo momento las capacidades de tiempo, medidas en segundos, de cada uno
de los CDs, llamadas capCD1 y capCD2, respectivamente.
Cada cancin se identifica por una cadena que contiene el ttulo de la cancin y un entero que refleja el tiempo,
medido en segundos, que dura la cancin.
El tiempo de la cancin que se encuentra en la posicin j de LC se representar por tj donde j [0, LC.size()).
Por otro lado, cada problema individual deber tener informacin sobre la cancin que se est analizando
mediante un ndice J [0, LC.size()) y el tiempo restante en ambos CDs, tRestCD1 y tRestCD2 respectivamente.

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.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Se pide:

1) Completar la ficha adjunta del problema a resolver.


Los apartados a completar, son los sealados mediante

//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;
}

3) Implementar los siguientes mtodos de la clase ProblemaDiscografiaPD teniendo en cuenta la ficha


adjunta:
a) public boolean esCasoBase()
b) public Sp<Integer, Integer> getSolucionCasoBase()
c) public Iterable<Integer> getAlternativas()
d) public ProblemaPD<SolucionDiscografia,Integer,Integer> getSubProblema(Integer a,int i)
e) public Sp<Integer, Integer> combinaSoluciones(Integer a, List<Sp<Integer, Integer>> ls)

Nota:
-

Utilice el diagrama UML proporcionado para realizar los distintos apartados.


Suponga implementadas todas las clases proporcionadas en el diagrama UML, salvo los mtodos de la
clase ProblemaDiscografiaPD que se piden para su resolucin.

343

344

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Apartado 1)

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

[entero] [0, LC.size() )

// ndice sobre la lista de canciones

Propiedades
Individuales

Solucin:

tiempoRestCD1 [entero]

// Tiempo restante en segundos en CD 1

tiempoRestCD2 [entero]

// Tiempo restante en segundos en CD 2

SolucionDiscografica, s = (lcCD1, lcCD2)


lcCD1 : Lista de canciones del CD 1.
lcCD2 : Lista de canciones del CD 2.

Objetivo:

Encontrar una solucin S tal que el tiempo total de canciones entre los dos CDs sea mximo.

Solucin Parcial:

(a, tiempoTotal), siendo el tiempo total el referente al de las canciones a optimizar.

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)

private Sp<A, E> pD(ProblemaPD<S, A, E> p) {


Sp<A, E> e = null;
ProblemaPDF<S, A, E> pdf = null;
metricas.addLLamadaRecursiva();
if (conFiltro)
pdf = (ProblemaPDF<S, A, E>) p;
if (solucionesParciales.containsKey(p)) {
e = solucionesParciales.get(p);
metricas.addUsoDeLaMemoria();
} else if (p.esCasoBase()) {
e = p.getSolucionCasoBase();
solucionesParciales.put(p, e);
metricas.addCasoBase();
} else {
List<Sp<A, E>> solucionesDeAlternativas = Lists.newArrayList();
List<A> alternativasElegidas = Lists.newArrayList();
for (A a : filtraRandomize(p, p.getAlternativas())) {
if (conFiltro && !pdf.pasaFiltro(a)) {
continue;
}
alternativasElegidas.add(a);
int numeroDeSubProblemas = p.getNumeroSubProblemas(a);
List<Sp<A, E>> solucionesDeSubProblemas = Lists.newArrayList();
for (int i = 0; i < numeroDeSubProblemas; i++) {
ProblemaPD<S, A, E> pr = p.getSubProblema(a, i);
Sp<A, E> sp = pD(pr);
solucionesDeSubProblemas.add(sp);
}
Sp<A, E> sa = p.combinaSoluciones(a, solucionesDeSubProblemas);
solucionesDeAlternativas.add(sa);
}
e = p.combinaAlternativas(solucionesDeAlternativas);
if (e != null)
e.alternativas = alternativasElegidas;
solucionesParciales.put(p, e);
}
if (conFiltro && e != null)
pdf.actualizaMejorValor(e.solucionParcial);
return e;
}

346

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Apartado 3)

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

5.13.7 Mezcla de pcimas encadenadas.


Un mago dispone de una secuencia de N pcimas dispuestas en una fila
P = {p0, p1, , pn-1}. Cada pcima tiene asociado un color y cada color
viene identificado por un nmero comprendido entre 0 y 99 (ambos
inclusive). Por tanto, una pcima tendr asociado uno de los 100 colores
posibles.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


El color de la pcima resultante tras mezclar todas las pcimas desde la posicin i hasta la posicin j de la lista
de pcimas se representar por pij donde i, j [0, pocimas.size()].
Dispone del mtodo getColorGenerado(Integer i, Integer j) en la clase ProblemaPcimasPD que calcula el color
de la pcima resultante tras mezclar todas las pcimas comprendidas en el intervalo [i, j), es decir, desde la
pcima i-sima hasta la pcima j-sima (no inclusive). Por tanto, no ser necesario almacenar el color resultante
tras la combinacin de las distintas parejas de pcimas.
Por otro lado, cada problema individual deber tener informacin sobre la pcima inicial, I [0, pocimas.size) y
la pcima final, J [0, pocimas.size).
Las alternativas (representadas mediante un entero) son el conjunto de las posiciones hasta la cual se puede
realizar la mezcla desde la pcima I hasta la pcima J (no inclusive).
El objetivo del problema es encontrar la agrupacin ptima de pcimas desde la pcima I hasta la pcima J (no
inclusive), de forma que el humo generado sea el mnimo posible.
La solucin ser de la forma se,h , donde e es la expresin que indica la agrupacin de pcimas obtenidas por el
algoritmo y h, indica la cantidad de humo generado teniendo en cuenta dicha agrupacin.
Existe una funcin de reconstruccin que construye la solucin ptima conteniendo la expresin e y h.

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;
}

3) Implementar los siguientes mtodos de la clase ProblemaPocimasPD teniendo en cuenta la ficha


adjunta:
a. public boolean esCasoBase()
b. public Sp<Integer, Integer> getSolucionCasoBase()
c. public Iterable<Integer> getAlternativas()
d. public ProblemaPD<SolucionPocimas, Integer, Integer> getSubProblema(Integer a, int i)
e. public Sp<Integer, Integer> combinaSoluciones(Integer a, List<Sp<Integer, Integer>> ls)
Nota:
-

350

Utilice el diagrama UML proporcionado para realizar los distintos apartados.


Suponga implementadas todas las clases proporcionadas en el diagrama UML, salvo los mtodos de la
clase ProblemaPocimasPD que se piden para su resolucin.
No es necesario almacenar una nueva lista con las nuevas pcimas generadas ya que es posible calcular
los colores resultantes a partir de un rango.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

351

Apartado 1)

Mezcla de pcimas encadenadas


Tcnica:

Programacin Dinmica

Tamao:

N=J-I

Propiedades
Compartidas:

[Lista<entero>]

Propiedades
Individuales

I
J

[entero] [0, P.size() )


[entero] [I+1, P.size() ]

Solucin:

SolucionPocimas, S = (e, n)
e : Expresin con agrupacin de pcimas.
n : Cantidad total de humo generado.

Objetivo:

Encontrar una solucin S tal que n sea mnimo.

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

(, )
(, )

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Apartado 2)

private Sp<A, E> pD(ProblemaPD<S, A, E> p) {


Sp<A, E> e = null;
ProblemaPDF<S, A, E> pdf = null;
metricas.addLLamadaRecursiva();
if (conFiltro)
pdf = (ProblemaPDF<S, A, E>) p;
if (solucionesParciales.containsKey(p)) {
e = solucionesParciales.get(p);
metricas.addUsoDeLaMemoria();
} else if (p.esCasoBase()) {
e = p.getSolucionCasoBase();
solucionesParciales.put(p, e);
metricas.addCasoBase();
} else {
List<Sp<A, E>> solucionesDeAlternativas = Lists.newArrayList();
List<A> alternativasElegidas = Lists.newArrayList();
for (A a : filtraRandomize(p, p.getAlternativas())) {
if (conFiltro && !pdf.pasaFiltro(a)) {
continue;
}
alternativasElegidas.add(a);
int numeroDeSubProblemas = p.getNumeroSubProblemas(a);
List<Sp<A, E>> solucionesDeSubProblemas = Lists.newArrayList();
for (int i = 0; i < numeroDeSubProblemas; i++) {
ProblemaPD<S, A, E> pr = p.getSubProblema(a, i);
Sp<A, E> sp = pD(pr);
solucionesDeSubProblemas.add(sp);
}
Sp<A, E> sa = p.combinaSoluciones(a, solucionesDeSubProblemas);
solucionesDeAlternativas.add(sa);
}
e = p.combinaAlternativas(solucionesDeAlternativas);
if (e != null)
e.alternativas = alternativasElegidas;
solucionesParciales.put(p, e);
}
if (conFiltro && e != null)
pdf.actualizaMejorValor(e.solucionParcial);
return e;
}

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

5.13.8 Ubicacin de invitados.


Se desea hacer uso de la tcnica de Programacin Dinmica para ubicar un
nmero N de invitados (identificados por un entero entre 0 y N-1) en un nmero
M de mesas (identificadas por un entero entre 0 y M-1) teniendo en cuenta que:
El nmero mximo de invitados por mesa es 10.
Se minimice el nmero de conflictos entre invitados que se sienten en la
misma mesa.

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.

El problema tendr las siguientes propiedades compartidas:

N: Entero que representa el nmero de invitados.


M: Entero que representa el nmero de mesas.
Conflictos:
Estructura
de
elementos
booleanos
(modelada
mediante
el
tipo
Table<Integer,Integer,Boolean>) que almacena los conflictos existentes entre los distintos invitados.
De esta forma, las claves (de tipo entero) inv1 e inv2 tienen asociado true en caso de que exista un
conflicto entre los invitados inv1 e inv2, y false en caso contrario.

El problema tendr las siguientes propiedades individuales:

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Se pide:
1. Complete la ficha (las alternativas y el combina soluciones).
2. Implementar los siguientes mtodos conforme a la ficha proporcionada:
De la clase AlgoritmoProgramacionDinamica:
a) El algoritmo de PD visto en clase, es decir, el mtodo private E pD(ProblemaPD<S, A, E> p).
Indique claramente si debe usarse el esquema con o sin memoria.
De la clase ProblemaInvitadosPD:
b) public boolean esCasoBase()
c) public int getNumeroSubProblemas(Integer a)
d) public SolucionInvitados combinaSolucionesOr(List<SolucionInvitados> ls)
e) public Iterable<Integer> getAlternativas(). Este mtodo puede devolver directamente un
objeto de tipo Set<Integer>, ya que es un iterable sobre elementos de tipo Integer.

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:

Ainv,mesaInv,nConflictos,nInv = { a, [0M-1], nInv[a] < 10 }

Instanciacin:

(, , ) = (0, {},0, {0,0,0, ,0} )

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

5.13.9 Traducciones de textos.


En el departamento de una empresa de traducciones se desea hacer traducciones
de textos entre varios idiomas. Se dispone de algunos diccionarios. Cada diccionario
permite la traduccin (bidireccional) entre dos idiomas con una dificultad asociada.
En el caso ms general, no se dispone de diccionarios para cada par de idiomas por
lo que es preciso realizar varias traducciones. Dados N idiomas y M diccionarios,
determine si es posible realizar la traduccin entre dos idiomas dados y, en caso de
ser posible, determine la secuencia de traducciones de menor dificultad para poder
realizar la traduccin entre dos idiomas dados.
Disee un algoritmo basado en Programacin Dinmica que resuelva el problema para que la dificultad de las
traducciones necesarias entre dos idiomas dados sea la mnima posible.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Apartado 1 >
Traduccin
Tcnica:

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:

,, = {0, 1} 0 No pasar por el idioma intermedio K, 1 Pasar por el idioma intermedio K.

Instanciacin:

pt(grafo, idiomas,idiomaOrigen, IdiomaDestino) = ptg(idiomaOrigen, idiomaDestino, idiomas.size-1)

Problema generalizado:
(0, 0)
(, , ) =

=
, = 1, ,

(0, )

, = 1, ,

(, 0, (, , 1))
,, (
)
(, 1, (, , 1), (, , 1))
{

. . .

//

(, , )
(, 0, ( , )) = (0, )
(, 1, ( , 1), ( , 2)) = (1, 1 + 2)

//

,, : ((0, 1), (1, 2)) = min((0, 1), (1, 2))

361

Apartado 2>

private Sp<A, T> pD(ProblemaPD<S, A, T> p) {


Sp<A, T> e = null;
ProblemaPDF<S, A, T> pdf = null;
if (conFiltro)
pdf = (ProblemaPDF<S, A, T>) p;
if (solucionesParciales.containsKey(p)) {
e = solucionesParciales.get(p);
} else if ( p.esCasoBase() ) {
e = p.getSolucionCasoBase() ;
solucionesParciales.put(p, e);
actualizaMejorValor(pdf);
} else {
List<Sp<A, E>> solucionesDeAlternativas = Lists.newArrayList();
List<A> alternativasElegidas = Lists.newArrayList();
for (A a : filtraRandomize(p, p.getAlternativas() )) {
if (pasaFiltro(pdf, a)) {
continue;
}
alternativasElegidas.add(a);
int numeroDeSubProblemas = p.getNumeroSubProblemas(a) ;
List<Sp<A, T>> solucionesDeSubProblemas = Lists.newArrayList();
for (int i = 0; i < numeroDeSubProblemas; i++) {
ProblemaPD<S, A, T> pr = p.getSubProblema(a, i) ;
Sp<A, T> sp = pD(pr) ;
if (sp == null) {
solucionesDeSubProblemas = null;
break;
}
solucionesDeSubProblemas.add(sp);
}
Sp<A, T> sa = null;
if (solucionesDeSubProblemas != null) {
sa = p.combinaSoluciones(a, solucionesDeSubProblemas) ;
}
if (sa != null) {
solucionesDeAlternativas.add(sa);
}
}
if (solucionesDeAlternativas != null
&& !solucionesDeAlternativas.isEmpty()) {
e = p.seleccionaAlternativas(solucionesDeAlternativas) ;
}
if (e != null)
e.alternativas = alternativasElegidas;
solucionesParciales.put(p, e);
}
return e;
}

362

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Apartado 3>

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

Antivirus para ETSII.


Dentro de las necesidades del centro de clculo de la ETSII se encuentra la de
instalar un antivirus que proteja a todos los equipos frente a amenazas externas.
Esto implica la necesidad de instalar extensiones del antivirus para prevenir
posibles ataques. El problema con el que se encuentra el administrador del centro
de clculo es que el espacio de disco reservado para instalar las extensiones del
antivirus es limitado.

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

2) De la clase ProblemaAntivirusPD, implemente los siguientes mtodos:


public boolean esCasoBase () {
public Sp<Integer, Integer> getSolucionCasoBase ()
public Iterable<Integer> getAlternativas () {
public ProblemaPD<SolucionAntivirus, Integer, Integer>
getSubProblema (Integer alternativa, int i)

364

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


SOLUCIN

Apartado 1 >

Antivirus para ETSII


Tcnica:

Programacin Dinmica

Tamao

Nmero de extensiones

Propiedades
Compartidas:

Extensiones
EspacioTotal

[List<Extension>]
[entero] 0

Propiedades
Individuales

I
EspacioRestante

[entero] [0, N )
[entero] 0

Solucin:

s = {extensioni | i [0, N), amenazas, espacio}

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>

public boolean esCasoBase () {


return ((problema.getI() == 0) || (problema.getEspacioRestante() = 0.0));
}

public Sp<Integer, Integer> getSolucionCasoBase () {


Sp<Integer, Integer> res = null;
Extension ext = ProblemaAntivirus.getExtensionesDisponibles().get(problema.getI());
if (ext.getTamao() <= problema.getEspacioRestante()) {
res = Sp.create(1, ext.getAmenazas());
} else {
res = Sp.create(0, 0.0);
}
return res;
}

public Iterable<Integer> getAlternativas () {


List<Integer> res = Lists.newArrayList();
res.add(new Integer(0));
Extension ext = ProblemaAntivirus.getExtensionesDisponibles().get(problema.getI());
if (problema.getEspacioRestante() >= ext.getTamao()) {
res.add(new Integer(1));
}
return res;
}

public ProblemaPD<SolucionAntivirus, Integer, Integer>


getSubProblema (Integer alternativa, int i)
{
Extension ext = ProblemaAntivirus.getExtensionesDisponibles().get(problema.getI());
ProblemaAntivirus p = null;
if (alternativa == 0) {
p = ProblemaAntivirus.create(problema.getI() - 1,
problema.getEspacioRestante());
} else {
p = ProblemaAntivirus.create(problema.getI() - 1,
problema.getEspacioRestante() - ext.getTamao());
}
return create(p);
}

366

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

5.14 Problemas propuestos.

5.14.1 Secuenca de ADN.

Para el alineamiento global de secuencias de ADN y de protenas (emparejamiento de smbolos de las


secuencias) se usan algoritmos, basados en Programacin Dinmica, que producen el alineamiento
ptimo de dos secuencias. Supngase que en la bsqueda de dicho alineamiento se utiliza una funcin
que pondera positivamente la coincidencia de dos elementos semejantes en las secuencias y
negativamente la aparicin de huecos y la discordancia de smbolos en el emparejamiento. Por
ejemplo, el siguiente alineamiento para las secuencias ASILO y SELLOS puntuara positivamente
los emparejamientos que aparecen encuadrados y negativamente los dems:

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:
-

La coincidencia de dos elementos se pondera con +1 y la discordancia como -1.


Un salto en la secuencia se pondera con -1 (dos ltimos casos de Si,j).

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

5.14.2 Camino de mxima anchura.


La circulacin viaria de una ciudad viene representada por un grafo dirigido
en el que los vrtices se corresponden con intersecciones de calles y las
aristas con las propias vas de trfico, de manera que se dispone de la
anchura, en nmero de carriles, de cada va. As, Ai,j representa el nmero
de carriles de la va que une la interseccin vi con la interseccin vj, en el
sentido desde vi a vj (0 si no hay una va que una directamente a las dos
intersecciones en el sentido indicado). Se define la anchura de un camino
entre dos intersecciones a la correspondiente al tramo de menor anchura.
Obtener, mediante Programacin Dinmica:
a)
b)

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:

Encontrar S c,a tal que a tenga el mayor valor posible.

Alternativas:

,, = {, }

Instanciacin:

(, ) = (, , . () 1)

Propiedades
Individuales

Problema generalizado:
{},

(, , ) =

=
, = 1, (, )
, = 1, (, )
(, (, , 1))

,, (

(, , 1) )
(, {
})
(, , 1)

(, ): .
, ,
(, 1 , 2 ): 1 2 ,
.
, ,
,, ( , ):

368

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


public class TestAnchuraCalles {
public static void main(String[] args) {
ProblemasAnchuraCalles.cargaDatos("grafo-bras.txt");
AlgoritmoConVariosProblemas<SolucionAnchuraCalles> a =
Algoritmos.create(ProblemasAnchuraCalles.createPD(), TipoDeProblema.MEJOR_SOLUCION);
a.ejecuta();
Set<Problema<SolucionAnchuraCalles>> conSol = a.conSolucion();
Set<Problema<SolucionAnchuraCalles>> sinSol = a.sinSolucion();
System.out.println("Problemas con solucin:");
for (Problema<SolucionAnchuraCalles> p : conSol) {
System.out.println("Problema: " + p + " Solucion: " + a.getSolucion(p));
}
System.out.println("Problemas sin solucin:");
for (Problema<SolucionAnchuraCalles> p : sinSol) {
System.out.println("Problema: " + p);
}
}
}

public class ProblemasAnchuraCalles {


private static List<Interseccion> intersecciones;
private static List<Calle> calles;
private static WeightedGraph<Interseccion, Calle> mapa;
private static GraphFactory<Interseccion, Calle> factMapa;
private static StringVertexFactoryInterseccion vertexInterseccion;
private static StringEdgeFactoryCalle edgeCalle;
public static void cargaDatos(String fichero) {
factMapa = new GraphFactory<Interseccion, Calle>();
vertexInterseccion = new StringVertexFactoryInterseccion();
edgeCalle = new StringEdgeFactoryCalle();
mapa = factMapa.createDirectedWeightedGraph(fichero, vertexInterseccion, edgeCalle);
intersecciones = new ArrayList<Interseccion>(mapa.vertexSet());
calles = new ArrayList<Calle>(mapa.edgeSet());
}
// ...
public static ProblemaPD<SolucionAnchuraCalles, Integer, SolucionParcialAnchuraCalles>
createPD(Integer origen, Integer destino, Integer usando) {
return new ProblemaAnchuraCallesPD(origen, destino, usando);
}
public static Iterable<ProblemaPD<SolucionAnchuraCalles, Integer, SolucionParcialAnchuraCalles>>
createPD() {
Iterable<Pair<Integer, Integer>> it0 =
Iterables2.from(0, intersecciones.size(), 0, intersecciones.size());
Iterable<Pair<Integer, Integer>> it1 =
Iterables.filter(it0, Predicates.not(new ParEnterosIguales()));
Iterable<ProblemaPD<SolucionAnchuraCalles, Integer, SolucionParcialAnchuraCalles>> it2 =
Iterables.transform(it1, new Par2ProblemaCaminoPD());
return it2;
}
private static class ParEnterosIguales implements Predicate<Pair<Integer, Integer>> {
public boolean apply(Pair<Integer, Integer> arg0) {
return arg0.p1().equals(arg0.p2());
}
}
private static class Par2ProblemaCaminoPD implements
Function<Pair<Integer, Integer>,
ProblemaPD<SolucionAnchuraCalles, Integer, SolucionParcialAnchuraCalles>> {
public ProblemaPD<SolucionAnchuraCalles, Integer, SolucionParcialAnchuraCalles>
apply(Pair<Integer, Integer> arg0) {
return createPD(arg0.p1(), arg0.p2(), intersecciones.size() - 1);
}
}
}

369

public class SolucionesAnchuraCalles {


// ...
public static SolucionParcialAnchuraCalles createSolucionParcial
(SolucionParcialAnchuraCalles solPar, SolucionParcialAnchuraCalles solPar2)
{
SolucionParcialAnchuraCalles s = null;
if (solPar != null && solPar2 != null) {
s = new SolucionParcialAnchuraCalles(solPar, solPar2);
}
return s;
}
}

public interface SolucionAnchuraCalles extends Comparable<SolucionAnchuraCalles> {


List<Interseccion> getCaminoIntersecciones();
List<Calle> getCaminoCalles();
Double getAnchura();
}

public class SolucionParcialAnchuraCalles implements Comparable<SolucionParcialAnchuraCalles> {


private List<Interseccion> caminoIntersecciones;
private Double anchura;
// ...
public SolucionParcialAnchuraCalles ( SolucionParcialAnchuraCalles solPar
, SolucionParcialAnchuraCalles solPar2)
{
List<Interseccion> lista2 = solPar2.getCaminoIntersecciones();
caminoIntersecciones = Lists.newArrayList();
for (Interseccion c : solPar.getCaminoIntersecciones()) {
caminoIntersecciones.add(c);
}
for (int i = 1; i < lista2.size(); i++) {
caminoIntersecciones.add(lista2.get(i));
}
anchura = Math.min(solPar.getAnchura(), solPar2.getAnchura());
}
}

370

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


public class ProblemaAnchuraCallesPD implements
ProblemaPD <SolucionAnchuraCalles, Integer, SolucionParcialAnchuraCalles>
{
private Integer origen;
private Integer destino;
private Integer usando;
private static Ordering<SolucionParcialAnchuraCalles> ordSolParcial = Ordering.natural();
public ProblemaAnchuraCallesPD(Integer origen, Integer destino, Integer usando) {
super();
this.origen = origen;
this.destino = destino;
this.usando = usando;
}
// ...
public boolean esCasoBase() {
return (origen == destino || usando == -1);
}
public SolucionParcialAnchuraCalles getSolucionCasoBase() {
SolucionParcialAnchuraCalles s = null;
if (origen == destino) {
s = SolucionesAnchuraCalles.createSolucionParcial(origen);
} else // usando== -1
if (ProblemasAnchuraCalles.getMapa().containsEdge(
ProblemasAnchuraCalles.getIntersecciones().get(origen),
problemasAnchuraCalles.getIntersecciones().get(destino))) {
s = SolucionesAnchuraCalles.createSolucionParcial(origen, destino);
}
return s;
}
public Iterable<Integer> getAlternativas() {
return Iterables2.from(0, 2, 1);
}
public int getNumeroSubProblemas(Integer a) {
return a + 1;
}
public ProblemaPD<SolucionAnchuraCalles, Integer, SolucionParcialAnchuraCalles> getSubProblema
(Integer a, int i) {
ProblemaPD<SolucionAnchuraCalles, Integer, SolucionParcialAnchuraCalles> p = null;
if (a == 0) {
p = ProblemasAnchuraCalles.createPD(origen, destino, usando - 1);
} else { // a==1
if (i == 0) {
p = ProblemasAnchuraCalles.createPD(origen, usando, usando - 1);
} else { // i==1
p = ProblemasAnchuraCalles.createPD(usando, destino, usando - 1);
}
}
return p;
}
// combinaSolucionesParciales
public SolucionParcialAnchuraCalles combinaSolucionesAnd(Integer a, List<SolucionParcialAnchuraCalles> ls) {
SolucionParcialAnchuraCalles s = null;
if (a == 0) {
s = ls.get(0);
} else {
if (ls.get(0) != null && ls.get(1) != null) {
s = SolucionesAnchuraCalles.createSolucionParcial(ls.get(0), ls.get(1));
}
}
return s;
}
// seleccionaAlternativa
public SolucionParcialAnchuraCalles combinaSolucionesOr(List<SolucionParcialAnchuraCalles> ls) {
SolucionParcialAnchuraCalles s = null;
Iterable<SolucionParcialAnchuraCalles> listaFiltrada = Iterables.filter(ls, Predicates.notNull());
if (Iterables.size(listaFiltrada) > 0) {
s = ordSolucionParcial.max(listaFiltrada);
}
return s;
}
}

371

5.14.3 Problemas varios.

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

Apoyndose en la cantidad g(n, d) nmero de ordenaciones posibles de n elementos, siendo d el nmero de


elementos distintos, obtenga un algoritmo de Programacin Dinmica que calcule el nmero de ordenaciones
posibles de n elementos. Justifique la respuesta mediante las relaciones correspondientes.
372

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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:
-

No hay diferencia horaria entre los aeropuertos.


Los retardos en los transbordos ya estn incluidos en los datos.
No se deben tener en cuenta situaciones imprevisibles como retardos en vuelos.
Todos los das el horario de vuelos en cada aeropuerto es el mismo.

373

5.15 Problemas de LIGA.


Para todos los problemas se pide:
1. Realice en primer lugar la ficha para determinar el tamao del problema, las propiedades individuales y
compartidas del problema, casos bases, alternativas, nmero de subproblemas, para cada alternativa
qu subproblema/s, funcionalidad del mtodo combinaSolucionesParciales, y seleccionaAlternativa, etc.
2. Realice un algoritmo que resuelva el problema especificado mediante la tcnica de Programacin
Dinmica. Para ello, implemente en el siguiente orden las clases con sus correspondientes interfaces
(Utilice el modelado de clases visto en prcticas/teora):
a. Implemente la clase Modelo, que contengan las propiedades elementales.
b. Para implementar el ProblemaPD necesitar definir el tipo de:
i.
Solucin (S).
ii.
Alternativa (A).
iii.
Tipo de la propiedad a optimizar (T).
c. Implemente la clase ProblemaXXX en la cual se definen las propiedades individuales y compartidas
del problema as como los constructores y mtodos para crear problemas. Adems, en esta clase
podr crear todos los mtodos que estime oportuno para reducir el cdigo de la clase ProblemaXXXPD
que debe implementar a continuacin.
d. Implemente la clase ProblemaXXXPD que implemente ProblemaPD. En dicha clase se implementarn
todos los mtodos definidos en la interfaz ProblemaPD, as como los mtodos para crear problemas
de Programacin Dinmica.
e. Implemente la clase SolucionXXX.
f. Ejecute el test de prueba para comprobar el correcto funcionamiento de la solucin dada.
g. Genere el archivo grafo.gv que contiene el grafo AND-OR con las alternativas y subproblemas
resultantes y convirtalo a un archivo .png.
Sea XXX el Modelo a tratar segn el ejercicio.

374

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

5.15.1 Compaa de ferrocarriles.


Una compaa de ferrocarriles posee n estaciones S1, ,Sn, entre las cuales
existen varias conexiones mediante los trenes disponibles. Tenga en
cuenta que entre un par de estaciones tan slo puede existir como mximo
un tren que las une, el cual podr realizar recorridos de ida y vuelta.
Esta compaa est tratando de mejorar su servicio al cliente mediante
terminales de informacin. Para ello, desea que dada una estacin origen
S0 y una estacin destino Sd, un terminal debe ofrecer la informacin sobre
la duracin del trayecto de los trenes que hacen la conexin entre S0 y Sd y
que minimizan el tiempo de trayecto total.
Necesitamos implementar un algoritmo que realice esta tarea a partir de la lista con las estaciones disponibles,
el grafo con las conexiones entre dichas estaciones y los trenes que las unen. Suponga que no hay tiempos de
espera entre trenes y que, naturalmente, no todas las estaciones estn conectadas entre s por lneas directas;
as, en muchos casos hay que hacer transbordos aunque se supone que tardan tiempo cero en efectuarse.
Resuelva este problema mediante la tcnica de Programacin Dinmica (sin usar algoritmos implementados
previamente sobre grafos).
Para modelar el problema, tenga en cuenta que ser necesario almacenar la duracin del recorrido de cada uno
de los trenes que componen la red de transporte. Al igual que es importante tener en cuenta qu par de
estaciones comunica dicho tren. Para simplificar el problema, tenga en cuenta que un tren slo puede conectar
un nico par de estaciones. Adems, el tren deber tener un identificador almacenado en una cadena para
reconocer cada uno de los trenes.
Por su parte, las estaciones quedarn identificadas tan slo por un identificador de tipo cadena que representar
de manera unvoca a cada una de ellas.
Pista: Resuelva el problema como un problema de caminos mnimos.

375

5.15.2 Ruta de autobuses.


Una empresa privada de autobuses se plantea ofrecer un nuevo servicio de recogida y
entrega de pasajeros desde un punto origen a un punto destino. Para ello, disponen de
un mapa en el que tienen localizadas las rutas a las que quieren dar servicio junto con la
duracin y el nmero de pasajeros estimados de cada viaje.
Estas rutas las obtienen cada da a partir de una aplicacin web en la que las personas demandan estos servicios.
Por ejemplo, si una asociacin de vecinos desea realizar un viaje a la playa, realiza esta peticin a travs de la
aplicacin web indicando el origen, el destino y el nmero de pasajeros estimados.
En base a esto, el gerente de la empresa necesita conformar las rutas para este servicio en un horario de x horas
al da teniendo en cuenta que si el nmero de pasajeros es mayor, el precio del billete es menor, y si la distancia
del viaje es mayor, el precio del billete es mayor.
Para ello, dispone de una frmula de beneficios econmicos que representa la cantidad de dinero que se obtiene
al trasladar a un nmero de pasajeros en una ruta concreta (el beneficio econmico se obtiene del nmero de
pasajeros y la distancia del viaje).
beneficio = precioBillete x numeroPasajeros x costeHora
donde el precio del billete se obtiene a partir de la siguiente tabla donde se indica el descuento a aplicar segn
el nmero de pasajeros:
N de Pasajeros
Descuento

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.

5.15.3 Juego de monedas.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

5.15.4 PDDL - Planning Domain Definition Language.


PDDL (Planning Domain Definition Language) es un lenguaje para la definicin de problemas de planificacin. En
estos problemas se quiere encontrar el plan (secuencia de acciones) que permita, dado un estado concreto
(estado inicial), conseguir un objetivo (ciertas condiciones que tiene que cumplir el estado). Entre otras cosas,
este lenguaje permite especificar:

1) El estado inicial de un problema.


2) Las posibles acciones que pueden ser aplicadas sobre un
estado.
3) La condicin de estado final, es decir, el objetivo.
La solucin ptima sera:
{Abajo, Abajo, Abajo, Derecha, Derecha, Arriba, Derecha,
Derecha, Abajo, Abajo}

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)

Este mtodo recibe los siguientes parmetros:


1) E estado_inicial: Objeto que representa el estado inicial.
2) List<Accion<E>>: Conjunto con todas las posibles acciones aplicables a los estados. Cada accin est
compuesta por dos elementos:
a. Predicate<E> precondicion: Representa la precondicin de la accin, es decir, esta accin slo
puede ser aplicada a los estados que cumplan este Predicate.
b. Function<E, E> efecto: Representa el efecto de la accin, es decir, dado un estado, crea un
estado nuevo siendo ste el resultado de haber aplicado la accin sobre aqul.
3) Predicate<E> objetivo: Representa la condicin que tiene que cumplir un estado para ser considerado
objetivo.
4) int numeroMaximoDePasos: Nmero mximo de acciones a aplicar. Dado que los problemas de
planificacin pueden no tener solucin, el usuario podr especificar cuntas acciones, como mximo,
quiere como resultado.
Este mtodo devolver una lista de acciones List<Accion<E>>. Si estas acciones son aplicadas en ese orden,
transformarn el estado_inicial en otro que cumple la condicin indicada en objetivo.
Recomendaciones:
-

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.

5.15.6 Bateras de coches elctricos.


Segn un estudio realizado por un taller de reparaciones de coches elctricos la avera ms
comn de estos coches est relacionada con fallos de la batera (supuesto estas averas el
23% del total de las reparaciones realizadas por el taller). Para optimizar el tiempo que tardan
los mecnicos en arreglar este tipo de bateras se proponer realizar un programa que busque
entre todas las bateras que dispone el taller cual sera la combinacin de bateras que mejor
se ajustase al modelo de coche.
A diferencia de un coche de gasolina o disel los coches elctricos tienen un contenedor de bateras donde puede
ir ms de una batera. Dicho contenedor puede tener un nmero ilimitado de bateras, siempre y cuando, la
suma del calor que disipa cada batera de forma individual sea menor que la capacidad trmica que admite el
contenedor del coche.
El principal problema, que tienen los mecnicos para llevar a cabo este tipo de reparaciones, no est relacionado
con el tiempo que tardan en montar las bateras, si no con encontrar, entre todas las bateras disponibles en el
taller, la mejor combinacin de bateras que permita arrancar el coche elctrico. Para asegurar el xito de la
operacin, el mecnico deber encontrar la combinacin de bateras cuya suma de amperios sea la mayor
posible y de forma que no incumpla las restricciones que se especificaron sobre el contenedor del coche.

378

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

5.15.7 Contratacin en recursos humanos.


El departamento de recursos humanos de una empresa necesita optimizar la
contratacin de una serie de candidatos y ajustarse a un presupuesto dado.
Adems, necesita contratar a candidatos de diferentes categoras profesionales
y que la cantidad de candidatos totales sea mxima.
La siguiente tabla es un ejemplo de la tabla de categoras de la que se dispone.

5.15.8 Juego de los golpes.


Un gran maestro entrena a los futuros campeones mundiales en el arte del golpe. El
maestro se ha dado cuenta de que algunos de sus alumnos son muy buenos aplicando
varias veces el mismo tortazo, mientras que otros tienen ms efectividad aplicando
golpes distintos a sus rivales.
Vamos a desarrollar una aplicacin del algoritmo de Programacin Dinmica sin filtro
para disear el plan de combate de un luchador, es decir, la secuencia y nmero de
repeticin de golpes que un luchador necesita para batir a un enemigo.
Un luchador tiene una secuencia de golpes finita y, por cada golpe tenemos el nmero mximo de repeticiones
de dicho golpe que el luchador puede dar antes de caer exhausto, el dao que causa dicha torta, la energa que
consume la torta y el multiplicador.
La primera vez que el luchador aplica un golpe, este genera un dao. Las sucesivas veces que el luchador aplica
el mismo golpe el dao lleva un bonus que se suma al dao original que depende el propio golpe y de las
repeticiones y que, incluso, puede ser negativo.
Cada luchador tiene una resistencia y cada torta merma esa resistencia. La resistencia no puede bajar de 0. El
maestro va a calcular la combinacin de golpe ms efectiva que aproveche lo mximo posible la resistencia del
alumno.
Puede utilizar la siguiente informacin para validar que su solucin funciona.
Golpe
1
2
3

Dao
60
74
1

Consumo
24
15
1

Repeticiones
2
2
10

Bonus
1
10
1
379

380

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

6 Algoritmos de Vuelta Atrs (Backtracking).


6.1 Introduccin.
En los algoritmos de Vuelta Atrs (Backtraking) pueden verse implementaciones eficientes de casos particulares
de Programacin Dinmica. Cada variante de la Programacin Dinmica da lugar a una variante de los algoritmos
de Vuelta Atrs.
En el captulo anterior hemos visto la Programacin Dinmica como una tcnica que generaliza a la conocida
como Divide y Vencers (con o sin memoria). Vimos diversas variantes de la Programacin Dinmica: con y sin
memoria, aleatoria, con filtro, bsqueda de todas las soluciones, bsqueda slo de una.
En la Programacin Dinmica a cada problema, dentro del contexto del conjunto de problemas considerado
poda identificarse por un conjunto de valores para las propiedades individuales consideradas. Asociado a cada
problema aparecen un conjunto de alternativas, que depende del problema. Tal como vimos el esquema de la
Programacin Dinmica es de la forma:

() = {
(, , (1 ), (2 ), , ( ))

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.

6.2 El Algoritmo de Vuelta Atrs.


Los algoritmos de Vuelta Atrs (y sus variantes) son algoritmos especialmente diseados para resolver de forma
eficiente problemas de Programacin Dinmica en el caso particular de Reduccin y sin Memoria. Son, por lo
tanto, adecuados para resolver problemas que se pueden concebir en trminos de grafos abstractos cuyos
vrtices representan problemas y cuyas aristas representan relaciones de reduccin de un problema a otro de
tamao ms pequeo cuando escogemos una de las alternativas posibles.
Los algoritmos de Vuelta Atrs hacen una exploracin en profundidad del grafo. Comienzan por un vrtice, que
representa el problema, y van explorando en profundidad el grafo anotando la secuencia de alternativas que se
han tomado. Se llaman de Vuelta Atrs porque, siguiendo el esquema de exploracin en profundidad, cuando
llegan a un vrtice hoja (con solucin o sin ella) vuelven hacia atrs, al vrtice padre, para seguir buscando la
primera u otra solucin. Cada solucin la podemos caracterizar por la secuencia de alternativas desde el vrtice
que representa el problema hasta el problema final.
Los algoritmos de Vuelta Atrs tienen distintas variantes que reproducen las mismas ideas que vimos en la
Programacin Dinmica:

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.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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

6.3 Concepto de Estado.


En la implementacin de los algoritmos de Vuelta Atrs se dispone de un objeto global que vaya guardando el
problema actual, el conjunto de alternativas escogidas y posiblemente otra informacin derivada.
Los algoritmos de Vuelta Atrs, parten de un vrtice, el problema a resolver, van eligiendo alternativas y
alcanzado nuevos vrtices. Llamaremos vrtice inicial al que tiene asociado el problema a resolver y vrtice actual
el alcanzado por el algoritmo en un momento dado. El estado, por tanto, guarda el vrtice actual y la secuencia
de alternativas que tiene como primer elemento la tomada en es el vrtice inicial y como ltimo elemento la
escogida para llegar al vrtice actual. La secuencia de alternativas puede ser vaca si nos encontramos en el
vrtice inicial. Tal como hemos visto antes una secuencia de alternativas define una solucin cuando alcanzamos
el problema final.
Un estado, por lo tanto, 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 necesarios
para aadir (y eliminar en su caso) alternativas. El estado inicial debe tener la informacin sobre el problema
inicial y una secuencia vaca de alternativas escogidas.
Con estas ideas podemos disear un tipo genrico, EstadoBT, que depende de los parmetros 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 el tipo ProblemaBT que contendr las propiedades del problema a
resolver y, a partir de ellas, crear el estado inicial.
public interface ProblemaBT<S, A> {
EstadoBT<S, A> getEstadoInicial();
}
public interface EstadoBT<S, A> {
void
avanza(A a);
void
retrocede(A a);
int
size();
boolean
isFinal();
Iterable<A> getAlternativas();
S
getSolucion();
}

Los mtodos que contiene ProblemaBT<S, A> son:

EstadoBT<S, A> getEstadoInicial():

Devuelve el estado inicial. Es decir un estado vaco pero con la

informacin del problema inicial.


Los mtodos que contiene EstadoBT<S, A> son:

void avanza(A a):

Aade una alternativa al estado y lo actualiza para que pueda informar sobre el

problema actual.

void retrocede(A a):

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.

Elimina la ltima alternativa del estado y lo actualiza. La alternativa a eliminar

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


public interface EstadoBTF<S, A, E> extends EstadoBT<S, A> {
E
getCotaValor(A a);
void
actualizaMejorValor();
boolean pasaFiltro(A a);
boolean esUnValorMejorDeLaPropiedad();
}

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.

6.4 Diseo, correccin y complejidad.


El diseo de los algoritmos de Vuelta Atrs puede partir, en general, de un diseo similar al que hemos usado en
Programacin Dinmica. Si el problema resultante es de Reduccin y sin uso de Memoria entonces tenemos un
candidato adecuado para ser resuelto por estas tcnicas. Alternativamente podemos partir de un grafo implcito
definido por un conjunto de vrtices que representan problemas (estados o situaciones) conectados por aristas
dirigidas que son las alternativas que tenemos para reducir un problema a otro o pasar de un estado a otro.
La siguiente tarea es disear el estado concreto para resolver el problema. Esto implica, fundamentalmente
definir las propiedades adecuadas del estado que permitan que ste cumpla su cometido: pueda indicarnos el
problema actual en cada momento, mantener la secuencia de alternativas escogidas para llegar al problema
actual. Igualmente hay que implementar las transiciones adecuadamente. Es decir los mtodos avanza y
retrocede.
Para ello podemos seguir las pautas que usamos en Programacin Dinmica para pasar de un problema a un
subproblema. En efecto, el mtodo avanza(A a) transforma un estado que representa un problema en otro que
representa el subproblema al que se reduce cuando se toma la alternativa a. El mtodo retrocede(A a) hace
justamente lo contrario.
La correccin de este tipo de algoritmos puede seguir los mismos pasos que en el caso de Programacin
Dinmica: conjunto de problemas, subproblemas y transiciones entre ellos, conjunto de alternativas, etc.
Los algoritmos de Vuelta Atrs suelen usarse sin Memoria. Esto es adecuado cuando no se comparten los
subproblemas (es decir los caminos desde el problema original hasta los casos base no se cortan). En caso que
se compartan los subproblemas se podran disear algoritmos de Vuelta Atrs con memoria.
La complejidad es equivalente a la del algoritmo de Programacin Dinmica equivalente (con o sin Memoria
segn el caso).

385

6.5 Detalles de implementacin.


El algoritmo genrico de Vuelta Atrs (Backtracking) es de la forma:
public class AlgoritmoBT<S, A, T extends Comparable<? super T>>
public static enum
public static Tipo
public S
public Set<S>
public static int
public static boolean
public static Integer
public static boolean
public static boolean
private ProblemaBT<S,A>
private EstadoBT<S,A>
private EstadoBTF<S,A,T>
private boolean
private T

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;

public AlgoritmoBT(ProblemaBT<S, A> p) {


problema = p;
mejorValor = null;
}
public void ejecuta() {
do {
estado = problema.getEstadoInicial();
if (conFiltro)
estadoF = (EstadoBTF<S, A, T>) estado;
bt();
metricas.addIteracion();
} while (isRandomize && soluciones.size() < numeroDeSoluciones);
}
private Iterable<A> filtraRandomize(EstadoBT<S, A> p, Iterable<A> alternativas) {
Iterable<A> alt;
if (isRandomize && p.size() > sizeRef && !Iterables.isEmpty(alternativas)) {
alt = Iterables2.unitaryRandomIterable(alternativas);
} else {
alt = alternativas;
}
return alt;
}
private void actualizaSoluciones() {
if (conFiltro) {
T objetivo = estadoF.getObjetivo();
if ( mejorValor == null
|| AlgoritmoBT.tipo == Tipo.Max && objetivo.compareTo(mejorValor) > 0
|| AlgoritmoBT.tipo == Tipo.Min && objetivo.compareTo(mejorValor) < 0)
{
solucion = estado.getSolucion();
soluciones.add(solucion);
mejorValor = objetivo;
}
} else {
solucion = estado.getSolucion();
soluciones.add(solucion);
}
}
private void bt() {
metricas.addLLamadaRecursiva();
if (estado.isFinal()) {
actualizaSoluciones();
if (soloLaPrimeraSolucion && solucion != null)
exito = true;
if (!soloLaPrimeraSolucion && soluciones.size() >= numeroDeSoluciones)
exito = true;
} else {
List<A> alternativasElegidas = Lists.newArrayList();
for (A a : filtraRandomize(estado, estado.getAlternativas())) {
if (conFiltro && !pasaFiltro(a)) {
continue;
}
alternativasElegidas.add(a);
estado.avanza(a);
bt();
estado.retrocede(a);
if (exito)
break;
}
}
}

386

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Como podemos comprobar, hay un conjunto de variables pblicas adecuadas para configurar el algoritmo:

solucion: La solucin o la mejor solucin, en su caso, encontrada.


soluciones: Conjunto de soluciones encontradas para cada problema (todas o las ms cercanas al mejor

para poder obtener las mejores).


numeroDeSoluciones: Nmero de soluciones que vamos buscando, ya sea en el mtodo aleatorio o
cuando buscamos ms de una.
isRandomize: Si est a cierto, entonces, los primeros sizeref elementos aadidos al estado se escogen
de manera aleatoria y de entre todas las posibles alternativas se escoge solo una, que ser la nica que
se explore.
sizeRef: Establecer el tamao del problema a partir del cual se va a utilizar mtodo aleatorio.
conFiltro: Verdadero si se usa el mtodo con Filtro.
soloLaPrimeraSolucion: Cierto si solo se quiere la primera solucin encontrada o simplemente la
existencia de solucin. Falso si se quieren todas, la mejor o las mejores.

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

6.6 Problemas Tipo.


6.6.1 N-Reinas.
[Vase el enunciado completo del problema en el captulo 5]

N Reinas
Tcnica:

Vuelta Atrs (Backtracking)

Tamao:

N - col (nmeros de reinas y dimensin del tablero, menos las reinas ya colocadas).

Propiedades
Compartidas:

N [entero] [4, ] nmeros de reinas y dimensin del tablero.

Propiedades del col


Estado:
filOcu
dgPpOcu
dgScOcu

[entero] [0, N )
[Lista<entero>]
[Lista<entero>]
[Lista<entero>]

Solucin:

S(col,filOcu,dgPpOcu,dgScOcu) [Lista<Reina>] Posiciones de cada reina

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:

col = 0, filOcu = {}, dgPpOcu = {}, dgScOcu = {}

Estado Final:

col = N

Alternativas:

,,,
= { [0, ) | , , + }

Avanza:

(f) = [col + 1, filOcu + fil, dgPp + d1, dgSc + d2] con d1 = fil-col, d2 = fil+col

Retrocede:

(f) = [col - 1, filOcu - fil, dgPp - d1, dgSc - d2]

388

con d1 = fil-col, d2 = fil+col

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


public class EstadoReinas implements EstadoBT<List<Reina>, Integer> {
private int
columna;
private Set<Integer> filasOcupadas;
private Set<Integer> diagonalesPrincipalesOcupadas;
private Set<Integer> diagonalesSecundariasOcupadas;
private List<Integer> camino;
private EstadoReinas(int columna) {
this.columna
this.filasOcupadas
this.diagonalesPrincipalesOcupadas
this.diagonalesSecundariasOcupadas
this.camino
}

=
=
=
=
=

columna;
Sets.newHashSet();
Sets.newHashSet();
Sets.newHashSet();
Lists.newArrayList();

public static EstadoReinas create(int columna) {


return new EstadoReinas(columna);
}
public Iterable<Integer> getAlternativas() {
Iterable<Integer> pf =
Iterables2.fromArithmeticSequence(0, ProblemaReinas.numeroDeReinas, 1);
Iterable<Integer> pf2 =
Iterables.filter(pf, new Predicate<Integer>() {
public boolean apply(Integer f) {
Reina r = Reina.get(f, getColumna());
return !getFilasOcupadas().contains(f)
&& !getDiagonalesPrincipalesOcupadas().contains(r.getDiagonalPrincipal())
&& !getDiagonalesSecundariasOcupadas().contains(r.getDiagonalSecundaria());
}
});
return pf2;
}
public List<Reina> getSolucion() {
List<Reina> lis = Lists.newArrayList();
int c = 0;
for (Integer f : camino) {
Reina r = Reina.create(f, c);
lis.add(r);
c++;
}
return lis;
}
public boolean isFinal() {
return getColumna() == ProblemaReinas.numeroDeReinas;
}
public void avanza(Integer f) {
Reina r = Reina.get(f, columna);
filasOcupadas.add(r.getFila());
diagonalesPrincipalesOcupadas.add(r.getDiagonalPrincipal());
diagonalesSecundariasOcupadas.add(r.getDiagonalSecundaria());
camino.add(f);
columna = columna + 1;
}
public void retrocede(Integer f) {
columna = columna - 1;
Reina r = Reina.get(f, columna);
filasOcupadas.remove(r.getFila());
diagonalesPrincipalesOcupadas.remove(r.getDiagonalPrincipal());
diagonalesSecundariasOcupadas.remove(r.getDiagonalSecundaria());
camino.remove(f);
}
}

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:

Vuelta Atrs (Backtracking con Filtro Ramifica y Poda)

Tamao:

N=J

Propiedades
Compartidas:

LstObjDisp [Lista<ObjetoMochila>] ordenada de menor a mayor por el valor unitario e


indexada por J, y sin repeticin, Son los objetos disponibles.
valor
peso
ObjetoMochila maxUd
[

[entero] valor unitario


[entero] peso unitario
[entero] nmero mximo de unidades permitidas
[real] (prop. derivada) valor por unidad de peso, q =

CapIni

[entero]

Capacidad inicial de la mochila.

ValMax

[entero]

Valor mximo de la mochila.

valor
peso

Propiedades del CapAct


[entero] 0
Estado:
J
[entero] [0, LstObjDisp.size() )
ValAct
[entero]
Valor actual acumulado.
Solucin:
S = (ms, valorTotal, pesoTotal), de tipo SolucionMochila
Objetivo:

Encontrar una solucin S tal que el peso total pesoTotal CapIni y que el valor total valorTotl
tenga el mayor valor posible.

Estado Inicial:

J = LstObjDisp.size() -1, CapAct = CapIni

Estado Final:

J<0

Alternativas:

,, = {: 0}

= min (

,, = {}

= min (

, ) , > 0
, ) , = 0

Funcin de cota: cota(CapAct, J, alt) = altvalorj + cota(CapAct altpesoj, J - 1)


Avanza:

(alt) = [CapAct - altpesoj ,

Retrocede:

(alt) = [CapAct + altpesoj+1 , J+1, ValAct - altpesoj+1 ]

390

J-1, ValAct + altpesoj ]

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

6.6.3 Cambio de monedas.


[Vase el enunciado completo del problema en el captulo 5]

Cambio de Monedas
Tcnica:

Vuelta Atrs (Backtracking)

Tamao:

N=J

Propiedades
Compartidas:

Monedas
CantidadIni

Propiedades del Cantidad


Estado:
J

[Lista<Moneda>] ordenada de menor a mayor y sin repeticin.


[entero]
Cantidad inicial.
[entero] 0
[entero] [0, Monedas.size() )

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:

J = LstObjDisp.size() -1, Cantidad = CantidadIni

Estado Final:

J<0

Alternativas:

Las alternativas posibles podemos representarlas con nmeros enteros: nmero de


unidades que podemos usar de la moneda J.
, = {: 0}

>0

, = {}

=0

, = {}

=0

, = {}

<0

Avanza:

(a) = [Cantidad - aValorj ,

Retrocede:

(a) = [Cantidad + aValorj+1 , J+1]

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:

Vuelta Atrs (Backtracking con Filtro) Ramifica y Poda

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.

Propiedades del Asig


[BitMap<entero,entero>]
Asignacin persona/tarea.
Estado:
I
[entero]
Siguiente persona.
CosteAcu [entero]
Coste acumulado.
TareasAsig = values(Asig) [propiedad derivada]
Solucin:
SolucionAsignacion
Objetivo:

Encontrar una solucin tal que se obtenga el menor coste posible.

Estado Inicial:

(Asig, i, CosteAcu) ([ ], 0, 0)

Estado Final:

iN

Alternativas:

A Asig, i, CosteAcu = { j : 0N - 1 | j TareaAsig }

Funcin de cota: Cota Asig, i, CosteAcu = (, ) + =+1 min{: 0 1| . (); (, )}


Avanza:

(j) = (Asig, i, CosteAcu) (Asig + Asig[i+1, j], i+1, CosteAcu+Coste(i+1, j))

Retrocede:

(j) = (Asig, i, CosteAcu) (Asig - Asig[i-1, j], i-1 , CosteAcu+Coste(i-1, j))

Si entre las alternativas posibles elegimos una, entonces tenemos un algoritmo Voraz:
Asignacin
Tcnica:

Voraz

Propiedades
Compartidas:

Coste
N

[Tabla<entero,entero,entero>] Coste de una tarea ejecutada por una persona.


[entero]
Nmero de personas y de tareas.

Propiedades del Asig


[BitMap<entero,entero>]
Asignacin persona/tarea.
Estado:
I
[entero]
Siguiente persona.
TareasAsig = values(Asig) [propiedad derivada]
Solucin:
SolucionAsignacion
Objetivo:

Encontrar una solucin tal que se obtenga el menor coste posible.

Estado Inicial:

(Asig, i, CosteAcu) ([ ], 0, 0)

Estado Final:

iN

Alternativas:

A Asig, i, CosteAcu = {j : 0N - 1 | j TareaAsig}

Eleccin:

= ((, )) = min((, ))

Avanza:

(j) = (Asig, i) (Asig+ Asig[i, j], i + 1)

392

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

6.6.5 Tareas y Procesadores.


Se necesita realizar N tareas independientes en una mquina multiprocesador, con
Proc procesadores pudiendo trabajar en paralelo (supngase N > Proc). Sea dur(i) el
tiempo de ejecucin de la i-sima tarea en cualquier procesador. El problema consiste
en determinar en qu procesador hay que ejecutar cada uno de los trabajos, de forma
que el tiempo final de la ejecucin de todos los trabajos (tiempo de ejecucin del
procesador ms cargado) sea mnimo.
Supngase que no hay restricciones acerca de cundo puede comenzar la ejecucin de cada trabajo.
En primer lugar, una propiedad compartida que guarda el mejor tipo obtenido en una solucin anterior:
TiempoMejor. En segundo lugar, el conjunto de alternativas tiene dos filtros adicionales: el primero (que
denominaremos F(a)) elimina una alternativa a2 si ya existe otra a1 que cumpla de TiempoXProc (a1) =
TiempoXProc (a2); el segundo elimina alternativas cuya cota para el valor de la propiedad sea mayor o igual al
mximo valor obtenido previamente. Si escogemos Cota(TareasAsig, TiempoXProc, J) = 0 tentremos la tabla:

Tareas y Procesadores
Tcnica:

Vuelta Atrs (Backtracking con Filtro Ramifica y Poda)

Propiedades
Compartidas:

TareasTiempo [Lista<Tarea>]
Proc
[entero]
TiempoMejor [entero]

Lista de tareas ordenada por duracin.


Nmero de procesadores disponibles.
Tiempo de la mejor solucin.

Propiedades del TareasAsig


[Lista<entero>]
Tareas asignadas.
Estado:
TiempoXProc [Mapa<entero, entero>] Tiempo por procesador.
J
[entero]
Tarea actual.
TareasAsig = values(Asig) [propiedad derivada]
Solucin:
SolucionTareas
Objetivo:

Encontrar una solucin tal que el tiempo de ejecucin sea el mnimo posible.

Estado Inicial:

(TareasAsig, TiempoXProc, J) ({ }, [ ], TareasTiempo.size()-1)

Estado Final:

J<0

Alternativas:

ATareasAsig, TiempoXProc, J = {a: 0N - 1 | F(a) (TiempoXProc (a) + dur(J) < TiempoMejor) }


Ordenadas por el procesador ms cargado.

Avanza:

(a) = (TareasAsig, TiempoXProc, J)


(TareasAsig+(J-1)

Retrocede:

, TiempoXProc+[a, TiempoXProc + dur(J-1)]

, J-1)

(a) = (TareasAsig, TiempoXProc, J)


(TareasAsig-(J+1) , TiempoXProc+[a, TiempoXProc - dur(J+1)] , 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]

Lista de tareas ordenada por duracin.


Nmero de procesadores disponibles.

Propiedades del TareasAsig


[Lista<entero>]
Tareas asignadas.
Estado:
TiempoXProc [Mapa<entero, entero>] Tiempo por procesador.
J
[entero]
Tarea actual.
Solucin:
SolucionTareas
Objetivo:

Encontrar una solucin tal que el tiempo de ejecucin sea el mnimo posible.

Estado Inicial:

(TareasAsig, TiempoXProc, J) ({ }, [ ], TareasTiempo.size()-1)

Estado Final:

J<0

Alternativas:

ATareasAsig, TiempoXProc, J = {a: 0N - 1 | F(a) (TiempoXProc (a) + dur(J) < TiempoMejor) }


Ordenadas por el procesador ms cargado.

Eleccin:

h (TareasTiempo, TiempoXProc, J) = {a: 0N - 1}


tal que TiempoXProc(a) es menor a todas las restantes alternativas.

Avanza:

(J) = (TareasAsig, TiempoXProc, J)


(TareasAsig+(J-1) , TiempoXProc+[a, TiempoXProc + dur(J-1)] , J-1)

394

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

6.6.6 Salto del Caballo de ajedrez.


Consideremos la pieza de un caballo de ajedrez colocado en una posicin, X, Y de un tablero de ajedrez de
dimensiones n n. Se trata de encontrar una sucesin de movimientos vlidos de un caballo de ajedrez de forma
que ste pueda visitar todas y cada una de las casillas del tablero sin repetir ninguna.
El caballo es una pieza del juego del ajedrez distinta a las otras piezas en su forma de moverse. Es la nica pieza
que puede saltar por encima de las dems, describiendo una trayectoria en forma de L. Es decir, se desplaza dos
casillas en direccin horizontal o vertical y una en direccin perpendicular a la anterior.

Disee un algoritmo basado en la tcnica Vuelta Atrs (Backtracking) que resuelva el problema.

395

Caballo de Ajedrez
Tcnica:

Vuelta Atrs (Backtracking)

Tamao:

Tamao tablero(Nmero de casillas) - casillasProcesadas

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:

Encontrar una solucin tal almacene el recorrido del caballo.

Estado Inicial:

casillasProcesadas = 1
posX = posInicioX
posY = posInicioY
tablero[posX, posY] = 1

Estado Final:

casillasProcesadas = 1

Alternativas:

Movimientos vlidos = { m [07] tal que posX + movX [1tamaoTablero]


posY + movY [1tamaoTablero]}
tablero[posX+movX[m], posY+movY[m]] = null }
tablero[posX+movX[m], posY+movY[m]] = null indica que no hemos pasado antes por ah.

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)

public class ProblemaCaballoBT implements ProblemaBT<Table<Integer, Integer, Integer>, Integer> {


public EstadoBT<Table<Integer, Integer, Integer>, Integer> getEstadoInicial() {
return new EstadoCaballo();
}
}

396

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


public class EstadoCaballo implements EstadoBT<Table<Integer, Integer, Integer>, Integer> {
Integer[] movX = null;
Integer[] movY = null;
Table<Integer, Integer, Integer> tablero = null; // prop. del estado
// X,Y,valor para (X,Y)
Integer posX = null; // prop. del estado
Integer posY = null; // prop. del estado
int casillasProcesadas = 0;
int tamaoTablero = 8;
public EstadoCaballo() {
// Inicializar propiedades compartidas
movX = ProblemaCaballo.getMovimientosX();
movY = ProblemaCaballo.getMovimientosY();
tamaoTablero = ProblemaCaballo.getTamaoTablero();
// Inicializar Propiedades del Estado
posX = ProblemaCaballo.getCasillaInicialX();
posY = ProblemaCaballo.getCasillaInicialY();
tablero = HashBasedTable.create();
casillasProcesadas = 1;
tablero.put(posX, posY, casillasProcesadas);
}
public void avanza(Integer movimiento) {
posX = posX + movX[movimiento];
posY = posY + movY[movimiento];
casillasProcesadas++;
tablero.put(posX, posY, casillasProcesadas);
}
public void retrocede(Integer movimiento) {
tablero.remove(posX, posY);
casillasProcesadas--;
posX = posX - movX[movimiento];
posY = posY - movY[movimiento];
}
public Iterable<Integer> getAlternativas() {
Iterable<Integer> movimientos = Iterables2.fromArithmeticSequence(0, movX.length, 1);
movimientos = Iterables.filter(movimientos, new Predicate<Integer>() {
public boolean apply(Integer mov) {
boolean xVal = (posX + movX[mov] >= 1) && (posX + movX[mov] <= tamaoTablero);
boolean yVal = (posY + movY[mov] >= 1) && (posY + movY[mov] <= tamaoTablero);
boolean noVisitada = tablero.get(posX + movX[mov], posY + movY[mov]) == null;
return xVal && yVal && noVisitada;
}
});
return movimientos;
}
public Table<Integer, Integer, Integer> getSolucion() {
return HashBasedTable.create(tablero);
}
public boolean isFinal() {
return tamaoTablero * tamaoTablero == casillasProcesadas;
}
public int size() {
return (tamaoTablero * tamaoTablero) - casillasProcesadas;
}
}

397

public class ProblemaCaballo {


private static Integer tamao;
private static Integer posX;
private static Integer posY;
private static Integer[] movX = { -1, 1, -1, 1, 2, 2, -2, -2 };
private static Integer[] movY = { 2, 2, -2, -2, -1, 1, -1, 1 };
public static Integer getTamaoTablero() {
return tamao;
}
public static Integer getCasillaInicialX() {
return posX;
}
public static Integer getCasillaInicialY() {
return posY;
}
public static Integer[] getMovimientosX() {
return movX;
}
public static Integer[] getMovimientosY() {
return movY;
}
public static void cargaDatos(int posx, int posy, int tamao) {
ProblemaCaballo.posX = posx;
ProblemaCaballo.posY = posy;
ProblemaCaballo.tamao = tamao;
}
public static ProblemaBT<Table<Integer, Integer, Integer>, Integer> create() {
return new ProblemaCaballoBT();
}
public static String toString(Table<Integer, Integer, Integer> sudoku) {
if (sudoku == null)
return "Solucin no encontrada";
String s = "";
for (int f = 1; f <= tamao; f++) {
for (int c = 1; c <= tamao; c++) {
Integer v = sudoku.get(f, c);
if (tamao == 4 && c != 0) {
s = s + "|";
} else {
s = s + " ";
}
if (v != null) {
if (v < 10)
s = s + " " + v;
else
s = s + v;
} else {
s = s + " ";
}
}
s = s + "\n";
}
return s;
}
}

398

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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

Modelado casilla Sudoku:


El problema consiste en disear un algoritmo para resolver un Sudoku. Para plantear el problema modelamos
en primer lugar una CasillaSudoku.
Vamos a dotarlo de cuatro propiedades:

fila: Entero, consultable. Fila en la que se sita la casilla.


columna:

Entero, consultable. Columna en la que se sita


la casilla.
caja: Entero, consultable. Caja en la que se sita la casilla.
valor: Entero, consultable y modificable. cifra asignada a
la casilla.

Modelado Problema y Estado Sudoku:


Para resolver este problema por la tcnica de Vuelta Atrs, necesitamos crear un conjunto de clases relacionadas
con el problema del Sudoku. En primer lugar, tendremos una clase de nuestro problema concreto
ProblemaSudoku, y por otro, los mtodos propios de la tcnica a utilizar para resolver el problema. Como resultado
se obtiene una clase EstadoSudoku donde est todo lo necesario para resolver el problema del sudoku mediante
la tcnica de Vuelta Atrs y la clase ProblemaSudokuBT que tiene la funcin para crear el estado inicial. Las
soluciones para el problema estarn implementados por objetos de tipo Table<Integer,Integer,Integer>, que
representa una matriz donde el primer entero se corresponde con la fila de la casilla, el segundo con la columna
y el tercero con el valor de la casilla.

400

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

Sudoku
Tcnica:

Vuelta Atrs (Backtracking)

Tamao:

casillasVacias.size() - siguienteCasillaVacia

Propiedades
Compartidas:

casillasVacias
casillasOcupadas

[Lista<CasillaSudoku>]
[Lista<CasillaSudoku>]

Propiedades del siguienteCasillaVacia [entero]


Estado:
filas
[Mapa<entero, Set<entero>>]
columnas
[Mapa<entero, Set<entero>>]
cajas
[Mapa<entero, Set<entero>>]
Solucin:
[Tabla<entero, entero, entero>]
t [19]

Objetivo:

Encontrar una solucin tal que: filas[t] = columnas[t] = cajas[t] = [19]

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>

Sus propiedades son:

int siguienteCasillaVacia :

entre [0, nmero de casillas vacas). Representa la casilla estudiada.

Map<Integer, Set<Integer>> filas : Conjunto de valores asignados a una determinada fila.


Map<Integer, Set<Integer>> columnas : Conjunto de valores asignados a una determinada columna.
Map<Integer, Set<Integer>> cajas : Conjunto de valores asignados a una determinada caja.

2. Complete los mtodos de la clase EstadoSudoku tal y como se indica a continuacin.


A. Estudie los constructores de la clase EstadoSudoku.
B. public boolean isFinal():
Considere que se trata del caso en que se han procesado todas las casillas vacas.
C. public Iterable<Integer> getAlternativas():
Devuelve un iterable de enteros con los valores disponibles para la casilla actual. Habr que ir
comprobando que los valores comprendidos entre 1 y 9 no estn en el conjunto de valores previamente
utilizados en la fila, la columna y la caja que ocupada la siguiente casilla.
D. public void avanza(Integer a):
Actualiza los conjuntos filas, columnas y cajas asociadas a la siguiente casilla vaca aadindoles el valor
con que se asignar a dicha casilla. Incrementar en uno el nmero de casillas procesadas
(siguienteCasilla).
E. public void retrocede(Integer a):
Decrementar en uno el nmero de casillas procesadas. Actualiza los conjuntos filas, columnas y cajas
asociadas a la casilla eliminado el valor con que tenga asignada la casilla. Finalmente asignar el valor -1
a la casilla.
F. public int size():
Devuelve el tamao del estado.
G. public Table<Integer, Integer, Integer> getSolucion():
A partir de la lista de casillas ocupadas y de casillas vacas crear la matriz del Sudoku. Esta matriz estar
representada por un objeto de tipo Table.

3. Complete los mtodos de la clase ProblemaSudokuBT tal y como se indica a continuacin.


A. public EstadoBT<Table<Integer, Integer, Integer>, Integer> getEstadoInicial():
Devuelve un objeto de tipo EstadoSudoku.

402

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


public class CasillaSudoku {
private
private
private
private

Integer
Integer
Integer
Integer

caja;
fila;
columna;
valor;

public static CasillaSudoku create(Integer fila,Integer columna,Integer valor,Integer caja) {


return new CasillaSudoku(fila, columna, valor, caja);
}
public static CasillaSudoku create(Integer fila, Integer columna, Integer caja) {
return create(fila, columna, null, caja);
}
private CasillaSudoku(Integer fila, Integer columna, Integer valor, Integer caja) {
super();
this.fila
= fila;
this.columna = columna;
this.caja
= caja;
this.valor
= valor;
}
private CasillaSudoku(Integer fila, Integer columna, Integer caja) {
this(fila, columna, null, caja);
}
public Integer getValor() {
return valor;
}
public void setValor(Integer valor) {
this.valor = valor;
}
public Integer getFila() {
return fila;
}
public Integer getColumna() {
return columna;
}
public Integer getCaja() {
return caja;
}
public String toString() {
return "(" + fila + ", " + columna + "," + valor + ")";
}
public int hashCode() { }
public boolean equals(Object obj) { }
}

403

public class EstadoSudoku implements EstadoBT<Table<Integer, Integer, Integer>, Integer> {


private Map<Integer, Set<Integer>> filas = null;
private Map<Integer, Set<Integer>> columnas = null;
private Map<Integer, Set<Integer>> cajas = null;
private
private
private
private

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


public Iterable<Integer> getAlternativas() {
Iterable<Integer> it = Iterables2.fromArithmeticSequence(1, tamaoSudoku + 1, 1);
// opcin 1
// CasillaSudoku c = casillasVacias.get(siguienteCasillaVacia);
// Iterables.removeAll(it, filas.get(c.getFila()));
// opcin 2
it = Iterables.filter(it, new Predicate<Integer>() {
@Override
public boolean apply(Integer m) {
CasillaSudoku c = casillasVacias.get(siguienteCasillaVacia);
boolean res = !filas.get(c.getFila()).contains(m)
&& !columnas.get(c.getColumna()).contains(m)
&& !cajas.get(c.getCaja()).contains(m);
return res;
}
});
return it;
}
public Table<Integer, Integer, Integer> getSolucion() {
Table<Integer, Integer, Integer> t = HashBasedTable.create();
for (CasillaSudoku c : casillasOcupadas) {
t.put(c.getFila(), c.getColumna(), c.getValor());
}
for (CasillaSudoku c : casillasVacias) {
t.put(c.getFila(), c.getColumna(), c.getValor());
}
return t;
}
public boolean isFinal() {
return siguienteCasillaVacia == casillasVacias.size();
}
public int size() {
return casillasVacias.size() - siguienteCasillaVacia;
}
}

405

public class ProblemaSudoku {


private
private
private
private

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


public static String toString(Table<Integer, Integer, Integer> sudoku) {
if (sudoku == null)
return "Solucin no encontrada";
String s = "";
for (int f = 0; f < tamao; f++) {
for (int c = 0; c < tamao; c++) {
Integer v = sudoku.get(f, c);
if (tamao == 4 && c != 0) {
s = s + "|";
} else {
s = s + " ";
}
if (v != null) {
s = s + v;
} else {
s = s + " ";
}
}
s = s + "\n";
}
return s;
}
}

public class ProblemaSudokuBT implements ProblemaBT<Table<Integer, Integer, Integer>, Integer> {


private ProblemaSudokuBT() {
}
public static ProblemaBT<Table<Integer, Integer, Integer>, Integer> create() {
return new ProblemaSudokuBT();
}
@Override
public EstadoBT<Table<Integer, Integer, Integer>, Integer> getEstadoInicial() {
return new EstadoSudoku();
}
}

407

6.7 Problemas de exmenes.


6.7.1 Orden de envo de mercancas.
Una empresa de transportes dispone de un nico vehculo para el envo de un
determinado tipo de mercanca. Diferentes clientes necesitan dicha mercanca para un
mismo da. Los envos se realizan en un orden, y siempre en cada envo el vehculo debe
volver a la central. El cliente paga en funcin de una oferta fija, y de la hora del da en
que es enviada dicha mercanca. El precio final del pago se calcula como:
Precio final = (oferta * 1000) / horaEnvio + 500.
El objetivo es encontrar, a travs de un algoritmo de Vuelta Atrs, el orden en el que se realizarn las entregas
de la mercanca para maximizar los beneficios de la empresa de transportes, aunque para ello se deje a algunos
clientes sin suministro. Para modelar el problema, se supondr que existen m clientes (c1, c2,..., cm). El tipo Cliente
permite almacenar toda la informacin de un cliente. Los mtodos ms importantes son:
-

Devuelve el tiempo en horas necesario para realizar la entrega de la mercanca, y


retorno a la central despus de la entrega.
Integer getHoraEnvio() : Devuelve la hora de envo prevista para el cliente.
void setHoraEnvio(Integer hora): Establece la hora de envo prevista para el cliente.
Double getPrecioFinal(): Devuelve el precio final que pagar el cliente por la mercanca (se utilizar la hora
de envo almacenada en el cliente para el clculo).
Double getPrecioFinal(Integer hora): Devuelve el precio final que pagar el cliente por la mercanca
utilizando como hora de envo la recibida como parmetro.
Integer getDuracion():

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


La clase EstadoMercanciasImpl permitir almacenar toda la informacin del estado del problema (ver ficha
adjunta). Los atributos sern:
-

SortedSet<Cliente> clientesPendientes:

Clientes para los que an est por decidir si se enviar o no


mercanca. Cada cliente del conjunto contendr en el campo correspondiente a la hora de envo el valor
-1, que indica que an no se ha decidido hora de envo.
List<Cliente> clientesAtendidos: Clientes para los que ya se ha decidido enviar la mercanca.
Cada cliente de la lista contendr actualizado el campo correspondiente a la hora de envo que se ha
decidido.
Integer horaDisponible: Hora a partir de la cual el vehculo para los repartos esta libre.
Double beneficioTotal: Beneficio acumulado de los repartos a los clientes a los que se ha decidido enviar
la mercanca.

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:

Vuelta Atrs (Backtracking)

Tamao:

N = tamao (clientesPendientes)

Propiedades
Compartidas:

conjuntoClientes
horaInicial
horaFinal

Propiedades del clientesAtendidos


Estado:
clientesPendientes
horaDisponible
beneficioTotal
Solucin:
SolucionMercancias

[SortedSet<Cliente>]
[entero]
[entero]
[Lista<Cliente>]
[SortedSet<Cliente>]
[entero]
[real]

Alternativas:

Ae = {ci clientesPendientes | horaDisponible + duracion(ci) horaFinal}

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

duracion(ci) es el tiempo en horas necesarios para el suministro al cliente ci.


precioFinal(ci) precio final que pagara el cliente ci al ser suministrado.
horaEnvio(ci) permite recuperar y establecer la hora en la que se enviar la
mercanca para el cliente ci.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Apartado b)

private void ryp() {


SolucionMercancias solucion = estado.getSolucion();
if (problema.esSolucion(solucion)) {
soluciones.add(solucion);
fin = esFin();
}
if (!fin && problema.isAlternativa(estado)) {
Iterable<Cliente> it = problema.getAlternativas(estado);
for (Cliente alternativa : it) {
estado.add(alternativa);
ryp();
estado.remove();
}
}
}

Apartado c)

public boolean avanza(Cliente c) {


clientesPendientes.remove(c);
clientesAtendidos.add(c);
horaDisponible = horaDisponible + c.getDuracion();
c.setHoraEnvio(horaDisponible);
beneficioTotal = beneficioTotal + c.getPrecioFinal();
return true;
}
public Cliente retrocede() {
Cliente c = clientesAtendidos.remove(clientesAtendidos.size() - 1);
clientesPendientes.add(c);
horaDisponible = horaDisponible - c.getDuracion();
beneficioTotal = beneficioTotal - c.getPrecioFinal();
c.setHoraEnvio(-1);
return 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)

public Iterable<Cliente> getAlternativas(EstadoMercancias e) {


return new IterableBT();
}

412

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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:

Integer getHabitantes(): Devuelve el nmero de habitantes afectados por la actividad.


Integer getCoste(): Devuelve el coste asociado a la actividad.

Tenga en cuenta adems que:


-

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.

El problema se modelar a travs de la clase ProblemaActividadesImpl, que implementa la interfaz


ProblemaRyP<Integer, Boolean, EstadoActividades>. Esta clase almacena el presupuesto inicial (atributo
presupuesto de tipo Integer) que es el tope mximo de gasto total.

413

La clase EstadoActividadesImpl (implementacin de la interfaz EstadoActividades) permitir almacenar toda la


informacin del estado del problema. Los atributos sern:

Integer actividadesProcesadas:

Contador de actividades para las que ya se ha decidido si recibirn o

no subvencin.

List<Actividad>

actividadesAtendidas:

Actividades para las que ya se ha decidido realizar una

subvencin.

Integer presupuestoDisponible: Parte del presupuesto que an no ha sido utilizado.


Integer habitantesTotal: Habitantes cubiertos por las actividades subvencionadas.
List<Actividad>

listaActividades:

Lista que almacena todas las actividades que existen en el

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:

Vuelta Atrs (Backtracking)

Tamao:

N = listaActividades.size() - actividadesProcesadas

Propiedades
Compartidas:

presupuestoTotal
actividadesPrevistas

[entero]
[Lista<Actividad>]

Propiedades del actividadesProcesadas


Estado:
actividadesAtendidas
presupuestoDisponible
habitantesTotal
listaActividades
Solucin:
[entero]

[entero]
[Lista<Actividad>]
[entero]
[entero]
[Lista<Actividad>]

Estado inicial:

=
=
=
=
=

414

actividadesProcesadas
actividadesAtendidas
presupuestoDisponible
habitantesTotal
listaActividades

0
{}
presupuestoTotal
0
actividadesAtendidas

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


private void ryp() {
S solucion = estado.getSolucion();
if (problema.esSolucion(solucion)) {
soluciones.add(solucion);
fin = esFin();
}
if (!fin && problema.isAlternativa(estado)) {
Iterable<A> it = problema.getAlternativas(estado);
for (A alternativa : it) {
estado.add(alternativa);
ryp();
estado.remove();
}
}
}

public boolean avanza(Boolean e) {


if (e) {
Actividad a = listaActividades.get(actividadesProcesadas);
actividadesAtendidas.add(a);
presupuestoDisponible -= a.getCoste();
habitantesTotal += a.getHabitantes();
}
actividadesProcesadas++;
return true;
}

public Boolean retrocede() {


actividadesProcesadas--;
Actividad a = listaActividades.get(actividadesProcesadas);
Boolean ret = actividadesAtendidas.contains(a);
if (ret) {
actividadesAtendidas.remove(a);
presupuestoDisponible += a.getCoste();
habitantesTotal -= a.getHabitantes();
}
return ret;
}

public Iterable<Boolean> getAlternativas(EstadoActividades e) {


List<Boolean> it = Lists.newLinkedList();
it.add(false);
if (e.getPresupuestoDisponible() >=
e.getListaActividades().get(e.getActividadesProcesadas()).getCoste())
it.add(true);
return it;
}

public boolean isAlternativa(EstadoActividades e) {


return e.getActividadesProcesadas() < e.getListaActividades().size()
&& e.getPresupuestoDisponible() > 0;
}

415

6.7.3 Dos mochilas.


Despus de entrenar duro durante ms de un ao, somos capaces de levantar
dos mochilas del suelo. Sin embargo, tenemos un problema a la hora de
intentar rellenar las dos mochilas con los elementos que aporten ms valor.
Cada una de las mochilas tiene una capacidad mxima, que no puede ser
superada por los elementos que se almacenen en su interior. Adems, el
nmero de elementos del mismo tipo entre las dos no puede superar los
elementos de ese tipo disponibles en la tienda. Debe tener en cuenta que
para poder llevar las dos mochilas a la espalda, el peso de ambas debe estar equilibrado. Es decir, la solucin que
obtengamos deber cumplir la condicin de que el peso de ambas mochilas sea exactamente el mismo.
Para resolver el problema disponemos de una lista de elementos de tipo ObjetoMochila (llamada listaObjetos),
que posee las propiedades valorUnitario (valor que aporta un objeto de este tipo a la mochila), pesoUnitario
(peso de un objeto de este tipo), numeroMximo (nmero de unidades disponibles del objeto) y
valorPorUnidadDePeso (cociente entre el valor unitario y el peso unitario, que da una estimacin de del valor
por unidad de peso asociado al objeto). Adems de la lista de objetos, se dispone de dos propiedades que indican
el peso mximo que admite cada mochila (Integer pesoMaximoA, Integer pesoMaximoB).
Las alternativas vendrn representadas por objetos de tipo Pair<Integer, Integer> donde el primer valor indica
el nmero de objetos de un determinado tipo que introduciremos en la mochila A y el segundo valor har
referencia al nmero de objetos a introducir en la mochila B. Para ello, puede hacer uso de la funcin
fromPairSequence(Integer inicio1, Integer fin1, Integer inicio2, Integer fin2), la cual genera un
conjunto de Pair<Integer, Integer> donde el valor del primer elemento del par va desde inicio1 hasta fin1 de
uno en uno y el valor del segundo desde inicio2 hasta fin2. Un ejemplo de esta llamada sera la siguiente:
fromPairSequence(1,5,5,1)

{ Pair(1,5), Pair(2,4), Pair(3,3), Pair(4,2), Pair(5,1) }

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:
-

public void avanza(Pair<Integer, Integer> a)


public void retrocede(Pair<Integer, Integer> a)
public Iterable<Pair<Integer, Integer>> getAlternativas()

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Solucin:
Apartado 1>
Dos mochilas
Tcnica:

Vuelta Atrs (Backtracking)

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>

public void avanza(Pair<Integer, Integer> a) {


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--;
}
public void retrocede(Pair<Integer, Integer> a) {
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;
}
public Iterable<Pair<Integer, Integer>> getAlternativas() {
List<Pair<Integer, Integer>> todas = Lists.newArrayList();
for (int i = 0; i <= listaObjetos[posActual].getNumeroMaximo(); i++) {
todas.addAll(fromPairSequence(0, i, i, 0));
}
return Iterables2.filter(todas, new Predicate() {
public boolean apply(Pair<Integer, Integer> p) {
return
pesoActualA + p.getP1() * listaObjetos[posActual].pesoUnitario
<= capacidadMaximaA
&& pesoActualB + p.getP2() * listaObjetos[posActual].pesoUnitario
<= capacidadMaximaB;
}
});
}

418

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

6.7.4 Organizacin de recursos.


El problema: El saln de belleza AcmeEstilistas funciona a travs del sistema de cita previa:
los clientes han de llamar para reservar los servicios con, al menos, un da de antelacin.
Esto hace que el director del saln sepa los servicios que tiene que atender a lo largo del
da. El saln dispone de muchos empleados y oferta muchos servicios. Cada servicio puede
ser realizado por distintos trabajadores pero, en general, no por todos ya que cada
empleado tiene una formacin diferente.
Los empleados de AcmeEstilistas se quejan de que no existe una organizacin del trabajo. Eso hace que algunos
clientes no puedan ser atendidos cuando llegan al saln porque los empleados que tienen formacin para hacer
ese servicio estn ocupados (atendiendo a otros clientes que, posiblemente, podran haber sido atendidos por
otros empleados). Adems, hay empleados que trabajan muchas horas y otros que trabajan muy pocas. Para
solucionar estos dos problemas, el director del saln ha contratado a la empresa AcmeOrganizadora para que
les organice el trabajo.
La solucin: La empresa AcmeOrganizadora es experta en problemas de organizacin de recursos (por ejemplo,
empleados) para la realizacin de actividades (por ejemplo, servicios de belleza), permitiendo optimizar, adems,
una funcin objetivo cualquiera (por ejemplo, minimizar las diferencias en horas de trabajo de los diferentes
recursos). Para ello utiliza un algoritmo de Vuelta Atrs en dnde, para cada actividad, contempla todas las
alternativas posibles (es decir, recursos que no estn ya ocupados y, adems puedan realizarla).
Implementacin: Se requiere que implemente este algoritmo teniendo en cuenta las siguientes consideraciones:
1. En todo momento se dispondr de la lista de actividades que hay que organizar (plan), as como de la lista
de recursos existentes (recursos).
2. Cada Recurso estar identificado por un id numrico y tendr una lista con las actividades que tiene que
hacer, organizacionRecurso (inicialmente vaca).
3. Cada Actividad tendr asociado un nombre, tiempoDeInicio, duracin y una lista con los recursos que pueden
realizar dicha actividad, posibles.
4. La solucin al problema (SolucionBTOrganizador) se compone de una lista de recursos (organizacion) donde,
para cada recurso, la propiedad organizacionRecurso contiene sus actividades. Adems, desviacion
representar, para dicha solucin, la desviacin del trabajo de cada recurso con respecto a la media (es decir,
es el objetivo a minimizar).
5. Los estados del problema (EstadoBTOrganizador) contiene un entero (i) para indicar que actividad del plan
se est comprobando, adems de la lista de recursos (organizacion) con una asignacin parcial de actividades
(como mucho hasta la actividad i-1).
6. Las alternativas para cada estado, sern los Recursos que, pudiendo ejecutar la actividad i (indicado por el
estado), estn libres durante el tiempo que dura dicha actividad.
Traza: Dado el problema descrito
en la figura, la traza mostrada (en
donde, en cada paso, se muestran
las Propiedades del Estado, es
decir, i y organizacion) dara como
mejor solucin a la que tiene
menor desviacin.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Apartado 1>
Organizacin de Recursos
Tcnica:

Vuelta Atrs (Backtracking)

Propiedades
Compartidas:

plan
recursos

Propiedades del organizacionRecurso


Estado:
i

[Lista<Actividad>]
[Lista<Recursos>]
[Lista<Recurso>]
[entero]

Solucin:

SolucionOrganizacion

Alternativas:

Ae = { ri recursos getLibres( posibles(a), tInicio(a), tInicio(a)+duracion(a) }


Donde a = plan[ i(e) ]

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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:

La solucin tendr una nica propiedad: puntos de tipo List<Integer>.


El problema tendr una nica propiedad compartida: esContraseaValida de tipo EsContraseaValida.
El estado tendr 2 propiedades:
List<Integer> puntos, que almacena los puntos ya visitados.
Integer actual, que es el ltimo punto visitado.
Las alternativas sern de tipo entero, y sern los vecinos de actual que an no han sido visitados, es decir,
vecinos(actual) - puntos.

El diagrama UML relacionado con el problema y la resolucin del mismo mediante backtracking se muestran a
continuacin.

423

424

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


La ficha propuesta es la siguiente:

La contrasea
Tcnica:

Vuelta Atrs (Backtracking)

Propiedades
Compartidas:

esContraseaValida

Propiedades del Puntos


Estado:
actual

[Lista<entero>]

Lgico.

[Lista<entero>]
[entero]

Puntos ya visitados.
ltimo punto visitado.

Solucin:

SolucionContrasea

Objetivo:

S puntos tal que esContraseaValida(puntos-0)

Alternativas:

A puntos, actual = { a vecinos(actual) - puntos }

Estado inicial:

(puntos, actual) (esContraseaValida(puntos-0), 0 )

Estado final:

esContraseaValida(puntos-0)

Avanza:

(puntos, actual) (puntos+a, a)

Retrocede:

anterior = puntos.get(puntos.size()-2)

// penltimo elemento

(puntos, actual) (puntos-a, anterior)

Se pide Implementar los siguientes mtodos conforme a la ficha proporcionada:

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

6.7.6 Suministro de luz.


El problema:
El complejo residencial PocasLuces, con cientos de viviendas,
sufre constantemente cortes en el suministro de luz debido
a la antigedad de las instalaciones.
La comunidad de vecinos ha contratado a la empresa
AcmeRayos para que modernice sus instalaciones y asegure
el suministro de luz en las casas.
Figura: Posible solucin (no ptima) para 8 grupos.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Apartado 1>
Suministro
Tcnica:

Vuelta Atrs (Backtracking con Filtro) Ramifica y Poda

Propiedades
Compartidas:

grColind

[MultiMapa<cadena,cadena>]

Propiedades del grSumi


[MultiSet<cadena>]
Estado:
grSinSumi
[Set<cadena>]
costeTotal
[entero]
estBasic
[Set<cadena>]
estAvanz
[Set<cadena>]
Solucin:
SolucionSuministro
Alternativas:

// 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>

private void bt() {


metricas.addLLamadaRecursiva();
if (estado. isFinal() ) {
actualizaSoluciones();
if (soloLaPrimeraSolucion && solucion != null)
exito = true;
if (!soloLaPrimeraSolucion && soluciones.size() >= numeroDeSoluciones)
exito = true;
} else {
for (A a : filtraRandomize(estado, estado. getAlternativas() )) {
if (conFiltro && !estadoF. pasaFiltro(a) ) {
continue;
}
estado. add(a) ;
bt() ;
estado. remove(a) ;
if (exito)
break;
}
}
}

430

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Apartado 3>
a>
public Iterable<AlternativaSuministros> getAlternativas() {
Set<AlternativaSuministros> ret = Sets.newHashSet();
Set<String> gruposRestantes = Sets.newHashSet(ProblemaSuministros.getGrColind().keySet());
gruposRestantes.removeAll(estAvanz);
gruposRestantes.removeAll(estBasic);
for (String grupo : gruposRestantes) {
ret.add(new AlternativaSuministros(grupo, 0));
ret.add(new AlternativaSuministros(grupo, 1));
}
return ret;
}

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.

Contiene todas las aeronaves que se encuentran en el aeropuerto y deben

ser alojados en un hangar.


List<Hangar> hangares. Almacena todos los hangares disponibles en el aeropuerto.

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.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Tenga en cuenta que la solucin al problema (SolucionBTFOrganizador) se compone de una funcin cuyas claves
son los hangares y los valores la lista de aeronaves que se almacenarn en l.
Las alternativas para cada estado sern los hangares en los que podemos almacenar la aeronave que se est
procesado en el momento actual.
El problema se modelar a travs de la clase EstadoBTFHangaresImpl, que implementa la interfaz
EstadoBTF<Map<Hangar, List<Aeronave>>, Hangar, Double>.

Se pide:
a)
b)
c)
d)
e)
f)

Completar la ficha del problema. Concretamente los apartados Avanza y Retrocede.


public
public
public
public

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:

Vuelta Atrs (Backtracking con Filtro) Ramifica y Poda

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();

Si no quedan plazas libres

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


b>

public boolean isFinal(EstadoBTFHangares e) {


return e.getI() == -1;
}

c>

public Iterable<Hangar> getAlternativas(EstadoBTFHangares e) {


List<Hangar> ret = Lists.newArrayList();
for (Hangar h : hangares) {
if (h.getDimensiones() > aeronaves.get(i).getDimensiones()
&& h.getPlazas() > organizacion.get(h).size())
{
ret.add(h);
}
}
return ret;
}

d>

public void avanza(Hangar h) {


List<Aeronave> aux = organizacion.get(h);
if (aux.isEmpty()) {
costeTotalMantenimiento += h.getCosteMantenimiento();
}
organizacion.put(h, aux.add(aeronaves.get(i)));
i--;
}

e>

public void retrocede(Hangar h) {


i++;
List<Aeronave> aux = organizacion.get(h);
aux.remove(aeronaves.get(i));
if (aux.isEmpty()) {
costeMantenimiento -= h.getCosteMantenimiento();
}
organizacin.put(h, aux);
}

f1>

public Double getCotaValor(Hangar h) {


List<Aeronave> aux = organizacion.get(h);
Double res = costeTotalMantenimiento;
if (aux.isEmpty()) {
res += h.getCosteMantenimiento();
}
return res;
}

f2>

public boolean pasaFiltro(Hangar h) {


return h.getDimensiones() >= aeronaves.get(i).getDimensiones()
&& h.getPlazas() > organizacion.get(h).size();
}

435

6.7.8 Subconjuntos de sumandos.


Dado un conjunto de nmeros enteros E, deseamos obtener todas las combinaciones
posibles que hagan que sumando un subconjunto de dichos nmeros, obtengamos una
cantidad C dada.
Por ejemplo, si tenemos el conjunto de enteros E = {5, 3, 15, 18, 13, 2} y la cantidad a cubrir
C=18, las soluciones al problema seran: S = {13+2+3, 18, 15+3, 13+5}. Las alternativas por
tanto sern sumar o no usar la cantidad actual. Dado esto, se pide:
1. Completar la ficha del problema Suma Parcial.
2. Escribir el cdigo del algoritmo de Ramifica y Poda.
3. Piense una posible funcin de cota al problema actual e implemntela en el mtodo
getCota(AlternativaSumaParcialRyP a, EstadoSumaParcialRyP e).

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

Iterable<AlternativaSumaParcialRyP> getAlternativas(final EstadoSumaParcialRyP e).


void avanza(AlternativaSumaParcialRyP a).

Solucin:
Apartado 1>
Inversiones Acciones
Tcnica:

Vuelta Atrs (Backtracking con Filtro) Ramifica y Poda

Propiedades
Compartidas:

E
C

[Lista<entero>] Elementos disponibles.


[entero]
Cantidad a obtener.

Propiedades del Eusados


[Lista<entero>]
Enteros sumados del conjunto inicial.
Estado:
Srestante
[entero]
Suma de todos los elementos por recorrer del conjunto original.
Cacumulada [entero]
Cantidad acumulada con los elementos usados hasta el momento.
IposicinActual [entero]
Posicin del ltimo elemento recorrido en la lista original.
Solucin:
SolucionSumaParcial
Objetivo:

Encontrar Sol e,o,s,ca,i tal que Cacumulada = C.

Alternativas:

A = {sumar, descartar}

Estado inicial:

Eusados = 0, Srestante = suma de todos los valores de E Eusados, Cacumulada = 0, IposicinActual = 0

Estado final:

Cacumulada = C || I E.size()

Avanza:

Si (a = sumar) entonces Iusados += E(I), Srestante -= E(I), Cacumulada += E(I)

Retrocede:

Si (a = sumar) entonces Iusados -= E(I), Srestante += E(I), Cacumulada -= E(I)

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Apartado 2>
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;
}
}
}

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

6.7.9 Operadoras de Telefona y Datos.


El departamento de informtica recibe un presupuesto para ampliar las conexiones
de voz y de datos de la empresa. El presupuesto se repartir entre las distintas
ofertas recibidas de los operadores de telefona y datos. A parte de conocer el tipo
de servicio (SMS, mvil, Internet, etc), el precio y el operador (Orange, Yoigo, etc)
se han estimado los beneficios que se obtendra si se contratara el servicio. Tenga
en cuenta que un servicio se puede contratar ms de una vez, por ejemplo se puede
contratar ms de una lnea de mvil, siempre y cuando no se supere un cierto
nmero de contratos a partir del cual la empresa no consigue ms beneficios. Tambin puede ser que el operador
exija exclusividad o que si se contrata un servicio de fibra ptica no se pueda contratar otro de ADSL, por tanto
no todos los servicios son compatibles. Para determinar qu servicios contratar y obtener el mayor beneficio
posible ajustado al presupuesto inicial utilice tcnica de Vuelta Atrs (Backtracking con Filtro) Ramifica y Poda.
Se pide que, con ayuda del siguiente diagrama UML, implementar las funciones comentadas con // TODO en la
clase ProblemaPresupuestoRyPImpl:

public class ProblemaPresupuestoRyPImpl implements AlgoritmoConUnaSolucion<SolucionPresupuesto>,


roblemaRyP<SolucionPresupuesto, ServicioConexion, EstadoPresupuesto>, ProblemaPresupuesto {
private List<ServicioConexion> serviciosDisponibles;
private Integer presupuesto;
public
public
public
public

EstadoPresupuesto getEstadoInicial()
{ //
Iterable<ServicioConexion> getAlternativas(EstadoPresupuesto e) { //
boolean isAlternativa(EstadoPresupuesto e)
{ //
boolean esSolucion(SolucionPresupuesto s)
{ //

TODO
TODO
TODO
TODO

}
}
}
}

// Clase disponible para implementar el mtodo getAlternativas()


class IterableRyP implements Iterable<ServicioConexion>, Iterator<ServicioConexion> {
int maxcount;
ServicioConexion servicio;
public IterableRyP(ServicioConexion servicio, int maxcount) {
this.servicio = servicio;
this.maxcount = maxcount;
}
public Iterator<ServicioConexion> iterator() {
return this;
}
public boolean hasNext() {
return maxcount >= 0;
}
public ServicioConexion next() {
servicio.setCantidad(maxcount--);
return servicio;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
// Devuelve verdadero si dos servicios son compatibles, falso en caso contrario.
// NO implementar.
public boolean compatibles(ServicioConexion s1, ServicioConexion s2) { }
}

438

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

439

Solucin:

public class ProblemaPresupuestoRyPImpl implements


AlgoritmoConUnaSolucion<SolucionPresupuesto>,
ProblemaRyP<SolucionPresupuesto, ServicioConexion, EstadoPresupuesto>,
ProblemaPresupuesto
{
List<ServicioConexion> serviciosDisponibles;
Integer presupuesto;
public EstadoPresupuesto getEstadoInicial() {
return new EstadoPresupuestoImpl(serviciosDisponibles, presupuesto);
}
public Iterable<ServicioConexion> getAlternativas(EstadoPresupuesto e) {
List<ServicioConexion> le = e.getServiciosPendientes();
ServicioConexion o = le.get(le.size() - 1);
int mcount = Math.min(e.getPresupuestoRestante() / o.getPrecio(),
o.getMaxCantidad());
for (ServicioConexion s : e.getServiciosProcesados()) {
if (s.getCantidad() > 0 && !compatibles(o, s)) {
mcount = 0;
break;
}
}
return new IterableRyP(o, mcount);
}
public boolean isAlternativa(EstadoPresupuesto e) {
return e.getServiciosPendientes().size() > 0;
}
public boolean esSolucion(SolucionPresupuesto s) {
return (s.getDetalleServicios().size() == getServiciosDisponibles().size()
&& s.getPresupuesto() <= getPresupuesto()
);
}
}

440

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

6.7.10 Transporte de cargamento.


Una flota de N camiones (T1..TN) debe transportar cargamento variado a
otras tantas ciudades (C1..CN). El coste de adjudicar el transporte vara en
funcin de la distancia y de la peligrosidad del trayecto y se resume en la
tabla adjunta. Exponer un algoritmo que calcule de manera ptima a
quin encargarle qu destino de manera que en total el coste sea mnimo.
C1
C2
C3
C4

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:

Vuelta Atrs (Backtracking con Filtro) Ramifica y Poda

Propiedades
Compartidas:

C
N

[Table<Integer, Integer, Integer>]


[Integer]

Propiedades del AS
[Map<Integer, Integer>]
Estado:
I
[Integer]
CA
[Integer]
TA = values(AS)
Alternativas:
A AS, i, CA = { j : 0N - 1 / j TA }

Coste de transportar el cargamento.


Nmero de caminones y de ciudades.
Asignacin / ciudad.
Siguiente camin.
Coste acumulado.
Ciudades asignadas.
// TODO

Funcin de cota: Cota AS, i, J, CA = + [, ] + =+1 min{: 0 1 / ; (, )}


Estado inicial:

( {}, 0 , 0)

// TODO

Estado final:

IN

// TODO

Avanza:

( AS + (I, J), I+1, CA+C[I, J] )

// TODO

Retrocede:

( AS - (I-1, J), I-1, CA-C[I-1, J] )

// 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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


SOLUCIN 2
A>
public Iterable<Integer> getAlternativas() {
Iterable<Integer> pf = Iterables2.fromArithmeticSequence(0, ProblemaAsignacion.N, 1);
Iterable<Integer> pf2 = Iterables.filter(pf, new Predicate<Integer>() {
public boolean apply(Integer c) {
return !asignacionDeCamiones.containsValue(c);
}
});
return pf2;
}

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 Problemas propuestos.

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.

6.8.2 Estaciones de bomberos.


El Gabinete Tcnico de Proteccin contra Incendios del
ayuntamiento de una ciudad decide revisar la localizacin de las
estaciones de bomberos que controla. La ciudad est dividida en
barrios, como se ilustra en el siguiente plano de la misma.
En cada barrio slo puede existir una estacin de bomberos como
mximo. Cada estacin es capaz de atender los incendios que se
produzcan en la zona comprendida por su barrio y los barrios
colindantes. Por motivos econmicos se desea minimizar el
nmero de estaciones de bomberos que existen en la ciudad sin dejar ningn barrio sin atender en caso de
incendio. Por ejemplo, en el plano anterior una de las posibles soluciones sera asignar una estacin de bombero
a los barrios 3, 8 y 9.
Se Pide:
a) Implementar un algoritmo que resuelva el problema utilizando el esquema Vuelta Atrs.
b) Definir una funcin de cota y modifique el apartado anterior para que el Vuelta Atrs pode el rbol de
expansin utilizando dicha funcin.
Nota: Se dispone de la matriz colindantes: Array de [1N, 1N] de Boolean que contiene verdadero en la celda
(i, j) si los barrios i y j son colindantes y falso en caso contrario.

444

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

7 Algoritmos Iterativos de aproximacin.


7.1 Introduccin.
En los captulos anteriores hemos visto distintos tipos de algoritmos recursivos: Divide y Vencers, Programacin
Dinmica y algoritmos de Vuelta Atrs con sus diferentes variantes. Son algoritmos que usan una bsqueda
exhaustiva. Para el caso de problemas de Optimizacin son capaces de encontrar, si existe, la solucin exacta.
Pero en algunos casos, la complejidad un estado son los valores que representarn las posibles soluciones del
problema. La funcin objetivo se evaluar sobre cada una de las instancias del estado diseado.
Los Algoritmos de Aproximacin son iterativos. Usan, por lo tanto, el estado diseado y, para cada instancia del
mismo, definen un conjunto de alternativas. Dado un estado y una de las alternativas posibles, escogida segn
una estrategia, pasamos al estado siguiente. Los algoritmos usan una condicin de parada para decidir cundo
terminan.
Las tres estrategias que estudiaremos son: Voraz, Simulated Annealing y Hill Climbing. En la primera estrategia,
la estrategia Voraz, se escoge, mediante una funcin de eleccin, una de las alternativas posibles y contina,
estado tras estado, hasta que se alcanza la condicin de parada. En las dos ltimas estrategias se escoge
aleatoriamente una alternativa de entre las posibles y se pasa al estado siguiente. Si se acepta se continua y si
no se acepta entonces se vuelve al estado anterior.
Es necesario definir una condicin de aceptacin del estado siguiente en las estrategias Simulated Annealing y
Hill Climbing. En la estrategia Voraz siempre se acepta el estado siguiente.
Los tres tipos de algoritmos son algoritmos de Aproximacin: es decir, slo son capaces de encontrar soluciones
subptimas. Es decir soluciones cercanas a la ptima aunque en algunos algoritmos de este tipo es posible
demostrar que alcanzan la solucin ptima.

445

7.1.1 Algoritmos Voraces


Los algoritmos Voraces que hemos tratado inicialmente en el captulo anterior guardan relacin con los
Algoritmos de Vuelta Atrs y con los de Programacin Dinmica. Partiendo de un Algoritmos de Vuelta Atrs
podemos disear otro Voraz indicando para cada conjunto de alternativas Ax cul de ellas elegir. En definitiva,
definiendo una funcin h(X) que a partir de un problema X elige una de las alternativas del conjunto Ax. La funcin
h(X) tiene como dominio el conjunto de problemas donde Ax no es vaco. Los algoritmos Voraces, parten de un
vrtice X, el problema a resolver, eligen la alternativa h(X) y al tomarla reducen el problema a otro Xa. Desde el
nuevo vrtice comienzan de nuevo eligiendo la alternativa h(Xa) hasta encontrar un problema cuyo conjunto de
alternativas sea vaco. En ese momento comprueban, partir de la secuencia de alternativas escogidas, si han
encontrado una solucin.
Un algoritmo Voraz parte de un conjunto de problemas (representados mediante un conjunto de estados) y
mediante una funcin (que llamaremos next en adelante) va decidiendo sin Vuelta Atrs el siguiente problema.
El algoritmo Voraz parte de un problema inicial (un estado inicial) y va escogiendo de forma irrevocable el
siguiente problema hasta que llega a uno que cumple un criterio especificado.
La funcin next(X, a) parte de un problema X y obtiene otro Y dada una alternativa que se ha escogido
previamente mediante h(X).
En general, los algoritmos Voraces los podemos pensar sin partir de los de Vuelta Atrs. En ese caso partimos de
un problema y sus soluciones posibles (posiblemente cada una de ellas con un valor calculado por las funcin
objetivo). Representamos cada solucin posible mediante una instancia del estado. Igual que antes, para cada
instancia del estado e se definen un conjunto de alternativas Ae. Mediante la funcin h(e) (donde e es una
instancia del estado) se escoge una de las alternativas de Ae . Dado un estado una estrategia Voraz se define
mediante la funcin next(X, a) que, para cada estado y alternativa, define el siguiente.
Por al propiedades que hemos explicado vemos que los algoritmos Voraces pueden implementarse como
algoritmos Iterativos.
Podemos concluir algunas diferencias importantes con los algoritmos de Vuelta Atrs (e implcitamente con los
de Programacin Dinmica de Reduccin):
Los algoritmos Voraces son siempre mucho ms rpidos que los algoritmos de Vuelta Atrs.
Los algoritmos Voraces pueden que no encuentren la solucin aunque exista. Los algoritmos 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 algoritmos 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 de Vuelta Atrs correspondiente. Pero esa demostracin
hay que hacerla especficamente para algoritmos Voraces concretos.

446

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

7.1.2 Algoritmos Simulated Annealing


Los algoritmos Simulated Annealing parten de un problema (como en la estrategia Voraz), sus soluciones
posibles, y para cada una de ellas, un valor calculado por la funcin objetivo. Representamos cada solucin
posible mediante una instancia del estado. Igual que antes, para cada instancia del estado e, se definen un
conjunto de alternativas Ae. Se escoge de forma aleatoria una de las alternativas de Ae. Se calcula el estado
siguiente tras esa alternativa mediante la funcin next(e, a). Si el estado siguiente se acepta se contina y, si no
se acepta, se vuelve al estado anterior.
En este tipo de algoritmos es clave la nocin de aceptacin del nuevo estado. Sean los estados e = next(e, a) y
sean f, f los valores de la funcin objetivo para los estados e, e y = f - f el incremento de la funcin objetivo.
Entonces el nuevo estado se acepta con probabilidad:
() = {

<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()

Si p0 = 099, pf = 001, = 1, n 300 tenemos entonces que T0 = 100, = 098,


o alternativamente,
Si p0 = 099, pf = 001, = 1, n 200 tenemos entonces que T0 = 100, = 097.
Otro parmetro a escoger es m (el nmero de iteraciones a la misma temperatura). Escogidos esos parmetros
el tiempo de ejecucin del algoritmo es proporcional al producto: m n.
El objetivo es encontrar el punto ms alto, sin embargo, existen muchos mximos locales. El algoritmo Simulated
Annealing consigue encontrar el mximo global enfriando la temperatura lentamente.

447

7.1.3 Algoritmos Hill Climbing.


El algoritmo Hill Climbing es un caso particular del algoritmo Simulated Annealing.
Se trata de una tcnica de optimizacin de los algoritmos de bsqueda local. Es un algoritmo iterativo que
comienza con una solucin arbitraria a un problema y luego intenta encontrar una mejor solucin variando
incrementalmente un nico elemento de la solucin. Si el cambio produce una mejor solucin, se realiza otro
cambio incremental sobre la nueva solucin, repitiendo este proceso hasta que no se puedan encontrar mejoras.
Por ejemplo, el algoritmo Hill Climbing puede ser aplicado al problema del viajante. Es fcil encontrar una
solucin inicial que visite todas las ciudades pero sera muy pobre comparada con la solucin ptima. El
algoritmo comienza con dicha solucin y le realiza pequeas mejoras, tales como intercambiar el orden en el
cul dos ciudades son visitadas. Eventualmente, es probable que se obtenga una ruta ms corta.
El algoritmo Hill Climbing es bueno para encontrar un ptimo local (una solucin que no puede ser mejorada
considerando una configuracin de la vecindad) pero no garantiza encontrar la mejor solucin posible (el ptimo
global) de todas las posibles soluciones (el espacio de bsqueda). La caracterstica de que slo el ptimo local
puede ser garantizado puede ser remediada utilizando reinicios (bsqueda local repetida), o esquemas ms
complejos basados en iteraciones, como bsqueda local iterada, en memoria, o modificaciones estocsticas,
como Simulated Annealing.
La relativa simplicidad de este algoritmo lo hace una primera eleccin popular entre los algoritmos de
Optimizacin. Es usado ampliamente en Inteligencia Artificial, para alcanzar un estado final desde un nodo de
inicio. La eleccin del prximo nodo y del nodo de inicio puede ser variada para obtener una lista de algoritmos
de la misma familia. Aunque algoritmos ms avanzados, como Simulated Annealing, pueden devolver mejores
resultados, en algunas situaciones Hill Climbing opera sin diferencias. El algoritmo Hill Climbing, con frecuencia,
puede producir un mejor resultado que otros algoritmos cuando la cantidad de tiempo disponible para realizar
la bsqueda es limitada, por ejemplo en sistemas en tiempo real. El algoritmo puede devolver una solucin vlida
an si es interrumpido en cualquier momento antes de que finalice.

Una superficie convexa.


Los Algoritmos Hill Climbing son apropiados
para optimizar sobre dichas superficies, y van a
converger al mximo global.

Una superficie con dos mximos locales.


(Slo uno de ellos es el mximo global).
Si un Algoritmo Hill Climbing comienza en una
posicin deficiente, converger al mximo
menor.

Hill Climbing aleatorio:


Escoge una alternativa aleatoriamente al igual que Simulated Annealing. Sean los estados e y e tal que e =
next(e, a). Sean f, f' los valores de la funcin objetivo para los estados e, e' y = f'- f el incremento de la funcin
objetivo. Entonces el nuevo estado slo se acepta (asumiendo problemas de minimizacin) cuando < 0.
Hill Climbing sin aleatoriedad:
Dado un estado e, la funcin next(e, a) siempre obtiene un estado e cuyo valor f de la funcin objetivo es mejor
que el valor f de la funcin objetivo del estado e. Por tanto, = f- f, siempre ser negativo (asumiendo problemas
de minimizacin). Debemos tener en cuenta que los estados pueden quedar atrapados en mximos locales.
448

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

7.1.4 Combinacin de estrategias y criterio de parada.


Una posibilidad que ofrecen los algoritmos que estamos presentando es la combinacin de estrategias. Es decir
comentar con una estrategia Voraz y continuar a partir de ah con Simulated Annealing.
En cualquier caso siempre hay que disear un criterio de parada. Algunos de estos criterios son:
Si estamos siguiendo una estrategia Voraz para cuando alcancemos un estado sin alternativas.
Para cuando tengamos una solucin o un nmero dado de ellas.
Parar cuando hayamos superado ms de un nmero dado de iteraciones.

7.2 Detalles de implementacin.


Estrategia Voraz:
El algoritmo escoge una alternativa y pasa al estado siguiente. Repite estos pasos hasta que encuentre el criterio
de parada o cambie de estrategia.
Estrategia Simulated Annealing:
El algoritmo escoge una alternativa y decide si aceptar el cambio o no. Necesitamos hacer una copia del estado
para posteriormente confirmar el cambio o restaurar el estado anterior.
La aceptacin o no depende de la temperatura que va disminuyendo progresivamente. Hay diferentes
posibilidades de ir enfriando la temperatura. Algunas de ellas se han incluido en el cdigo. Para cada temperatura
se hacen un nmero de iteraciones a temperatura constante y se escoge el mejor valor obtenido para pasarlo a
iteracin con el siguiente valor de la temperatura.

Lo primero es precisar la estrategia a seguir:


public static enum Estrategia {Voraz, SimulatedAnnealing}

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();
}

public interface EstadoSA <E extends EstadoSA<E,S,A>, S, A> extends EstadoVZ<E,S,A> {


double getObjetivo();
E copia();
}

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:

void next(A a): Cambia 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.
void ajustaEstrategia(): Implementa el posible cambio de estrategia.
double getIncremento(EstadoSA<S,A> e): Calcula la diferencia entre el valor de la funcin objetivo para

el estado actual y el correspondiente para e.


EstadoSA<S,A> copia(): Obtiene una copia del estado.
void set(EstadoSA<S,A> e): Actualiza el estado con las propiedades dada por e.

Fijados los tipos anteriores podemos implementar el esquema de los algoritmos de aproximacin en una clase.

El esquema de los algoritmos Voraces es de la forma:


public class AlgoritmoVZ<E extends EstadoVZ<E, S, A>, S, A> extends AbstractAlgoritmo {
private ProblemaVZ<E, S, A> problema;
private E estado;
public S solucion = null;
public AlgoritmoVZ(ProblemaVZ<E, S, A> p) {
problema = p;
}
public S getSolucion() {
return solucion;
}
public void ejecuta() {
estado = problema.getEstadoInicial();
while (!estado.condicionDeParada()) {
A a = estado.getAlternativa();
estado = estado.next(a);
}
solucion = estado.getSolucion();
}
}

450

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


El esquema de los algoritmos de Simulated Annealing es de la forma:

public class AlgoritmoSA<E extends EstadoSA<E, S, A>, S, A> extends AbstractAlgoritmo {


private ProblemaSA<E, S, A> problema;
private E
estado;
private E
nextEstado;
public S
solucion = null;
public Set<S>
soluciones;
public E
mejorSolucionGlobalObtenida = null;
public static Integer
numeroMaximoDeIntentos = 10;
public static Integer
numeroDeIteracionesPorIntento = 200;
public static Integer
numeroDeIteracionesALaMismaTemperatura = 10;
public static Integer
numeroDeCambiosAceptados = 0;
public static Integer
numeroDeCambiosNoAceptados = 0;
public static Integer
numeroDeSoluciones = 2;
public static double
temperaturaInicial = 1000;
public static double
alfa = 0.97;
private static double
temperatura;
private int
numeroDeIteraciones;
public AlgoritmoSA(ProblemaSA<E, S, A> p) {
problema
= p;
soluciones = Sets.newHashSet();
}
private void actualizaMejorValor() {
if (estado.getObjetivo() < mejorSolucionGlobalObtenida.getObjetivo()) {
mejorSolucionGlobalObtenida = estado.copia();
}
}
private void nexTemperatura() {
temperatura = alfa * temperatura;
}
private boolean aceptaCambio(double incr) {
return Math2.aceptaBoltzmann(incr, temperatura);
}
public void ejecuta() {
mejorSolucionGlobalObtenida = problema.getEstadoInicial();
for (Integer n = 0; n < numeroMaximoDeIntentos
&& soluciones.size() < numeroDeSoluciones;
n++)
temperatura = temperaturaInicial;
estado = problema.getEstadoInicial();
for (numeroDeIteraciones = 0; numeroDeIteraciones < numeroDeIteracionesPorIntento
&& !estado.condicionDeParada();
numeroDeIteraciones++) {
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);
}
}

451

452

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

7.3 Resumen de Esquemas.

Algoritmo de Optimizacin exacto con filtro: PROGRAMACIN DINMICA

private Map<ProblemaPD<S, A, T>, Sp<A, T>> solucionesParciales;


private ProblemaPD<S, A, T> problema;
private T mejorValor;
private Sp<A, T> pD(ProblemaPD<S, A, T> p) {
Sp<A, T> e = null;
ProblemaPDF<S, A, T> pdf = null;
if (conFiltro)
pdf = (ProblemaPDF<S, A, T>) p;
if (solucionesParciales.containsKey(p)) {
e=solucionesParciales.get(p);
} else if (p.esCasoBase()) {
e = p.getSolucionCasoBase();
solucionesParciales.put(p, e);
actualizaMejorValor(pdf);
} else {
for (A a : filtraRandomize(p, p.getAlternativas())) {
if (!pasaFiltro(pdf, a)) {
continue;
}
alternativasElegidas.add(a);
int numeroDeSubProblemas = p.getNumeroSubProblemas(a);
List<Sp<A, T>> solucionesDeSubProblemas = Lists.newArrayList();
for (int i = 0; i < numeroDeSubProblemas; i++) {
ProblemaPD<S, A, T> pr = p.getSubProblema(a, i);
Sp<A, T> sp = pD(pr);
if (sp == null) {
solucionesDeSubProblemas = null;
break;
}
solucionesDeSubProblemas.add(sp);
}
Sp<A, T> sa = null;
if (solucionesDeSubProblemas != null) {
sa = p.combinaSolucionesParciales(a, solucionesDeSubProblemas);
}
if (sa != null) {
solucionesDeAlternativas.add(sa);
}
}
if (solucionesDeAlternativas != null && !solucionesDeAlternativas.isEmpty()) {
e = p.seleccionaAlternativa(solucionesDeAlternativas);
}
if (e != null) {
e.alternativas = alternativasElegidas;
}
solucionesParciales.put(p, e);
}
return e;
}

453

private boolean pasaFiltro(ProblemaPDF<S, A, T> pdf, A a) {


boolean r = true;
if (conFiltro) {
r = mejorValor == null
|| AlgoritmoPD.tipo == Tipo.Max && pdf.getObjetivoEstimado(a).compareTo(mejorValor) > 0
|| AlgoritmoPD.tipo == Tipo.Min && pdf.getObjetivoEstimado(a).compareTo(mejorValor) < 0;
}
return r;
}

private void actualizaMejorValor(ProblemaPDF<S, A, T> pdf) {


T objetivo = pdf.getObjetivo();
if (conFiltro && (mejorValor == null
|| AlgoritmoPD.tipo == Tipo.Max && objetivo.compareTo(mejorValor) > 0
|| AlgoritmoPD.tipo == Tipo.Min && objetivo.compareTo(mejorValor) < 0)
) {
mejorValor = pdf.getObjetivo();
}
}

public S getSolucionReconstruida(ProblemaPD<S, A, T> pd) {


S s = null;
if (solucionesParciales.containsKey(pd)) {
Sp<A, T> e = solucionesParciales.get(pd);
if (e != null) {
List<S> soluciones = Lists.<S> newArrayList();
for (int i = 0; i < pd.getNumeroSubProblemas(e.alternativa); i++) {
soluciones.add(getSolucionReconstruida(pd.getSubProblema(e.alternativa, i)));
}
s = pd.getSolucion(e.alternativa, soluciones);
}
}
return s;
}

454

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Algoritmo de Optimizacin exacto con filtro: VUELTA ATRS (BACKTRACKING)

private
private
private
private
private

ProblemaBT<S, A>
EstadoBT<S, A>
EstadoBTF<S, A, T>
boolean
T

problema;
estado;
estadoF;
exito = false;
mejorValor;

private void bt() {


if (estado.isFinal()) {
actualizaSoluciones();
if (soloLaPrimeraSolucion && solucion != null)
exito = true;
if (!soloLaPrimeraSolucion && soluciones.size() >= numeroDeSoluciones)
exito = true;
} else {
List<A> alternativasElegidas = Lists.newArrayList();
for (A a : filtraRandomize(estado, estado.getAlternativas())) {
if (conFiltro && !pasaFiltro(a)) {
continue;
}
alternativasElegidas.add(a);
estado.avanza(a);
bt();
estado.retrocede(a);
if (exito)
break;
}
}
}

private void actualizaSoluciones() {


if (conFiltro) {
T objetivo = estadoF.getObjetivo();
if (mejorValor == null
|| AlgoritmoBT.tipo == Tipo.Max && objetivo.compareTo(mejorValor) > 0
|| AlgoritmoBT.tipo == Tipo.Min && objetivo.compareTo(mejorValor) < 0)
{
solucion = estado.getSolucion();
soluciones.add(solucion);
mejorValor = objetivo;
}
} else {
solucion = estado.getSolucion();
soluciones.add(solucion);
}
}

private boolean pasaFiltro(A a)


boolean r = true;
if (conFiltro) {
r = mejorValor ==
|| AlgoritmoBT.tipo == Tipo.Max
|| AlgoritmoBT.tipo == Tipo.Min
}
return r;
}

null
&& estadoF.getObjetivoEstimado(a).compareTo(mejorValor) > 0
&& estadoF.getObjetivoEstimado(a).compareTo(mejorValor) < 0;

455

Algoritmo de Optimizacin con aproximacin sin filtro: VORAZ

private ProblemaVZ<E, S, A> problema;


private E estado;
public S solucion = null;
public void ejecuta() {
estado = problema.getEstadoInicial();
while (!estado.condicionDeParada()) {
A a = estado.getAlternativa();
estado = estado.next(a);
}
solucion = estado.getSolucion();
}

Algoritmo de Optimizacin mnima con aproximacin sin filtro SIMULATED ANNEALING


private
private
private
public
public
public

ProblemaSA<E, S, A> problema;


E
estado;
E
nextEstado;
S
solucion = null;
Set<S> soluciones;
E
mejorSolucionGlobalObtenida = null;

public void ejecuta() {


mejorSolucionGlobalObtenida = problema.getEstadoInicial();
for (int numeroDeIntento = 0;
numeroDeIntento < numeroMaximoDeIntentos && soluciones.size() < numeroDeSoluciones;
numeroDeIntento ++) {
temperatura = temperaturaInicial;
estado = problema.getEstadoInicial();
for (int numeroDeIteracion = 0;
numeroDeIteracion < numeroDeIteracionesPorIntento && !estado.condicionDeParada();
numeroDeIteracion++) {
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++;
}
nextTemperatura();
}
solucion = estado.getSolucion();
if (solucion != null)
soluciones.add(solucion);
}
}
private void actualizaMejorValor() {
if (estado.getObjetivo() < mejorSolucionGlobalObtenida.getObjetivo()) {
mejorSolucionGlobalObtenida = estado.copia();
}
}

456

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

7.4 Problemas Tipo.


Antes de entrar en los detalles de implementacin del problema concreto, vamos a hacer un recorrido sobre el
modelado de problemas, tipos y algoritmos de las tcnicas Voraz y Simulated Annealing.

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.

public interface ProblemaVZ<E extends EstadoVZ<E, S, A>, S, A> {


E getEstadoInicial();
}
public interface EstadoVZ<E extends EstadoVZ<E, S, A>, S, A> {
E next(A a);
A getAlternativa();
S getSolucion();
boolean condicionDeParada();
}

public interface ProblemaSA<E extends EstadoSA<E, S, A>, S, A> {


E getEstadoInicial();
}
public interface EstadoSA<E extends EstadoSA<E, S, A>, S, A> {
E next(A a);
A getAlternativa();
S getSolucion();
boolean condicionDeParada();
double getObjetivo();
E copia();
}

457

Los mtodos de las interfaces tienen el siguiente funcionamiento:


-

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:

public void ejecuta() {


estado = problema.getEstadoInicial();
while (!estado.condicionDeParada()) {
A a = estado.getAlternativa();
estado = estado.next(a);
}
solucion = estado.getSolucion();
}

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


En el caso de Simulated Annealing se escoge una alternativa y se decide si aceptar el cambio o no. Para
implementar la aceptacin o el rechazo del cambio necesitamos hacer una copia del estado para posteriormente
confirmar el cambio o restaurar el estado anterior.
La aceptacin o no depende de la temperatura que va disminuyendo progresivamente. Hay diferentes
posibilidades de ir enfriando la temperatura. Algunas de ellas se han incluido en el cdigo. Para cada temperatura
se hacen un nmero de iteraciones a temperatura constante y se escoge el mejor valor obtenido para pasarlo a
la iteracin con el siguiente valor de temperatura.
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 las clases citadas en los apartados anteriores.

459

7.4.1 Anuncios de Televisin.


Se pide disear un algoritmo Voraz y otro Simulated Annealing para resolver el siguiente problema.

El problema de los anuncios televisivos modela un problema real que se


presenta a menudo en las cadenas de televisin. El objetivo del problema es
encontrar una determinada configuracin que permita a las cadenas de
televisin obtener el mximo beneficio () mediante la emisin de una
secuencia de anuncios despus de las campanadas de fin de ao. Dicho canal
deber proporcionar el tiempo de emisin total que est disponible para
contenido publicitario (en segundos). Adems, la cadena de televisin posee
un listado de los anuncios que le han sido ofertados por parte de las empresas que desean ser publicitadas. Cada
uno de estos anuncios poseer una duracin del contenido (en segundos) y una oferta (en ) que la empresa
publicitaria ha realizado a la cadena de televisin por la emisin de dicho anuncio.
Por ejemplo:
Cliente
Tiempo anuncio (seg)
Precio Unitario (miles de euros)
Incomptibilidades

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

461

public class Anuncio implements Comparable<Anuncio> {


public static Anuncio create(Integer codigo, Integer duracion, Integer precioBase) {
return new Anuncio(codigo, duracion, precioBase);
}
public static Anuncio create(String[] fm) {
return new Anuncio(fm);
}
private Integer codigo;
private Integer duracion;
private Integer precioBase;
private Anuncio(String[]
super();
this.codigo
=
this.duracion
=
this.precioBase =
}

fm) {
new Integer(fm[0]);
new Integer(fm[1]);
new Integer(fm[2]);

private Anuncio(Integer codigo, Integer duracion, Integer precioBase) {


super();
this.codigo
= codigo;
this.duracion
= duracion;
this.precioBase = precioBase;
}
public Integer getDuracion() {
return duracion;
}
public Integer getPrecioBase() {
return precioBase;
}
public Integer getCodigo() {
return codigo;
}
public Double getPrecioUnitario() {
return (precioBase * 1.) / duracion;
}
public Double getPrecio(Integer pos) {
return precioBase * 1000. / pos + 50000;
}
@Override
public int compareTo(Anuncio a) { }
@Override
public String toString() { }
@Override
public int hashCode() { }
@Override
public boolean equals(Object obj) { }
}

462

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


public class ProblemaAnuncios {
public
public
public
public

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

public class ListaDeAnunciosAEmitir {


private
private
private
private
private
private

List<Integer>
Set<Integer>
Integer
Integer
Double
SortedSet<Integer>

anunciosDecididosParaEmitir;
anunciosDecididosParaEmitirSet;
tiempoConsumido;
tiempoRestante;
valor;
anunciosDisponibles;

public static ListaDeAnunciosAEmitir create(List<Integer> anunciosDecididosParaEmitir) {


return new ListaDeAnunciosAEmitir(anunciosDecididosParaEmitir);
}
public static ListaDeAnunciosAEmitir create() {
return new ListaDeAnunciosAEmitir();
}
public static ListaDeAnunciosAEmitir create(ListaDeAnunciosAEmitir ls) {
return new ListaDeAnunciosAEmitir(ls.anunciosDecididosParaEmitir);
}
private ListaDeAnunciosAEmitir() {
this(Lists.<Integer> newArrayList());
}
private ListaDeAnunciosAEmitir(List<Integer> anunciosDecididosParaEmitir) {
this.anunciosDecididosParaEmitir = Lists.newArrayList(anunciosDecididosParaEmitir);
this.anunciosDecididosParaEmitirSet = Sets.newHashSet(anunciosDecididosParaEmitir);
calculaPropiedadesDerivadas();
if (!cumpleRestricciones()) {
throw new IllegalArgumentException("Nos se cumple el invariante");
}
calculaAnunciosDisponibles();
}
private boolean cumpleRestricciones() {
boolean r = true;
for (ParInteger p : calculaRestriccionesNormalizadas(ProblemaAnuncios.restricciones)) {
if (this.anunciosDecididosParaEmitirSet.contains(p.p1) &&
this.anunciosDecididosParaEmitirSet.contains(p.p2))
{
r = false;
break;
}
}
r = r && this.tiempoRestante >= 0;
r = r && this.anunciosDecididosParaEmitir.size() == this.anunciosDecididosParaEmitirSet.size();
return r;
}
private Set<ParInteger> calculaRestriccionesNormalizadas(Set<ParInteger> restricciones) {
Map<Integer, Integer> transform = Maps.newHashMap();
int i = 0;
for (Anuncio a : ProblemaAnuncios.todosLosAnunciosDisponibles) {
transform.put(a.getCodigo(), i);
i++;
}
Set<ParInteger> set = Sets.newHashSet();
for (ParInteger p : restricciones) {
set.add(ParInteger.create(transform.get(p.p1), transform.get(p.p2)));
}
return set;
}
private static Anuncio getAnuncio(int i) {
return ProblemaAnuncios.todosLosAnunciosDisponibles.get(i);
}

464

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


private void calculaPropiedadesDerivadas() {
this.tiempoConsumido = 0;
this.valor = 0.;
for (int i = 0; i < anunciosDecididosParaEmitir.size(); i++) {
Integer a = anunciosDecididosParaEmitir.get(i);
this.valor = this.valor + getAnuncio(a).getPrecio(this.tiempoConsumido + 1);
this.tiempoConsumido = this.tiempoConsumido + getAnuncio(a).getDuracion();
}
this.tiempoRestante = ProblemaAnuncios.tiempoTotal - this.tiempoConsumido;
}
private void calculaAnunciosDisponibles() {
Set<Integer> disponibles = Sets.newHashSet(ProblemaAnuncios.todosLosAnuncios);
disponibles.removeAll(this.anunciosDecididosParaEmitirSet);
for (ParInteger p : calculaRestriccionesNormalizadas(ProblemaAnuncios.restricciones)) {
if (this.anunciosDecididosParaEmitirSet.contains(p.p1)) {
disponibles.remove(p.p2);
}
}
Set<Integer> quitar = Sets.newHashSet();
for (Integer e : disponibles) {
if (getAnuncio(e).getDuracion() > this.tiempoRestante) {
quitar.add(e);
}
}
disponibles.removeAll(quitar);
Comparator<Integer> cmp = new Comparator<Integer>() {
@Override
public int compare(Integer arg0, Integer arg1) {
Anuncio a1 = getAnuncio(arg0);
Anuncio a2 = getAnuncio(arg1);
return a1.compareTo(a2);
}
};
Ordering<Integer> ord = Ordering.from(cmp);
ord = ord.reverse();
this.anunciosDisponibles = Sets.newTreeSet(ord);
this.anunciosDisponibles.addAll(disponibles);
}
public ListaDeAnunciosAEmitir insertar(int pos, Integer e) {
List<Integer> ls = Lists.newArrayList(this.anunciosDecididosParaEmitir);
ls.add(pos, e);
return create(ls);
}
public ListaDeAnunciosAEmitir insertarUltimo(Integer e) {
return insertar(this.anunciosDecididosParaEmitir.size(), e);
}
public ListaDeAnunciosAEmitir eliminar(int pos) {
List<Integer> ls = Lists.newArrayList(this.anunciosDecididosParaEmitir);
ls.remove(pos);
return create(ls);
}
public ListaDeAnunciosAEmitir eliminarUltimo() {
return eliminar(this.anunciosDecididosParaEmitir.size());
}
public ListaDeAnunciosAEmitir intercambiar(int i, int j) {
List<Integer> ls = Lists.newArrayList(this.anunciosDecididosParaEmitir);
Lists2.intercambia(ls, i, j);
return create(ls);
}
public List<Anuncio> getAnunciosDecididosParaEmitir() {
List<Anuncio> ls = Lists.newArrayList();
for (Integer e : this.anunciosDecididosParaEmitir) {
ls.add(getAnuncio(e));
}
return ls;
}

465

public Integer getTiempoConsumido() {


return tiempoConsumido;
}
public Integer getTiempoRestante() {
return tiempoRestante;
}
public Double getValor() {
return valor;
}
public Integer getNumAnunciosAEmitir() {
return this.anunciosDecididosParaEmitir.size();
}
public SortedSet<Integer> getAnunciosDisponibles() {
return this.anunciosDisponibles;
}
public int getNumAnunciosDisponibles() {
return this.anunciosDisponibles.size();
}
@Override
public String toString() {
List<Integer> anunciosDecididosParaEmitirTmp = Lists.newArrayList();
for (Integer a : anunciosDecididosParaEmitir) {
anunciosDecididosParaEmitirTmp.add(getAnuncio(a).getCodigo());
}
List<Integer> anunciosDisponiblesTmp = Lists.newArrayList();
for (Integer a : anunciosDisponibles) {
anunciosDisponiblesTmp.add(getAnuncio(a).getCodigo());
}
return "ListaDeAnunciosAEmitir [anunciosDecididosParaEmitir=" + anunciosDecididosParaEmitirTmp
+ ", tiempoConsumido=" + tiempoConsumido + ", tiempoRestante=" + tiempoRestante
+ ", valor=" + valor + ", anunciosDisponibles=" + anunciosDisponiblesTmp + "]";
}

466

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Solucin: Algoritmo Voraz
Mediante el algoritmo Voraz seremos capaces de obtener una secuencia que permita maximizar los beneficios
de la cadena de televisin, de manera que la proporcin entre el tiempo necesario para la emisin del anuncio
y la oferta del mismo sea lo ms elevada posible. Para modelar el problema, supondremos que existen m
anuncios (a1, a2, , am), los cuales poseern el identificador del cliente, el tiempo necesario para su emisin, la
oferta que se ha realizado por l. El conjunto de anuncios m disponibles para la emisin ser almacenado en una
lista a la que se llamar anunciosDisponibles que ser una propiedad compartida del problema.
Adems debemos conocer el tiempo total que la cadena de televisin ha destinado para contenido publicitario,
a dicha propiedad la llamaremos tiempoTotal. De esta manera, el total del tiempo necesario para emitir todos
los anuncios programados, no puede exceder dicho tiempo de emisin.
Con el fin de representar un determinado estado del problema, sern necesarios las siguientes propiedades
anunciosAEmitir, tiempoRestante, tiempoConsumido y valor que contendr respectivamente los anuncios que
han sido procesados y se van a emitir para la emisin, el tiempo de emisin restante para contenido publicitario,
el tiempo consumido y el beneficio obtenido por la secuencia de anuncios.
Para definir el conjunto de alternativas tambin ser necesaria definir en el estado la propiedad
anunciosParaEscoger. Dicha propiedad contendr la lista de anuncios restantes que no tienen incompatibilidades
con los anuncios ya emitidos y cuya duracin es menor que el tiempo restante. Por tanto, como conjunto de
alternativas, solo consideramos la posibilidad de aadir al final de los anuncios a emitir el primero de los anuncios
a escoger. Tenga en cuenta que los anuncios para escoger estarn ordenados de mayor a menor segn su precio
unitario. Con esta idea pretendemos encontrar una solucin que se acerque al beneficio mximo emitiendo
primero los anuncios con ms precio unitario.
Cada Anuncio podemos modelarlo como:
Duracion [Integer] : Duracin del anuncio en segundos.
Codigo [Integer] : Cdigo del cliente.
PrecioBase [Double] : Precio ofrecido por el cliente.

getPrecio(Integer Posicion) [Double] : Se calcula como: precio = PrecioBase

PrecioUnitario [Double] : Se calcula como:

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:

Encontrar una solucin tal que maximice el valor de Valor.

Alternativas:

Alternativa nica: Escoger el primer anuncio de AnunciosDisponibles si no est vaca.

Estado inicial:

AnunciosDecididosAEmitir = {}

Estado final:

AnunciosDecididosAEmitir

Next:

AnunciosDecididosAEmitir + {a}

468

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


public class
ProblemaAnunciosVZ
extends
ProblemaAnuncios
implements ProblemaVZ<EstadoAnunciosVZ, ListaDeAnunciosAEmitir, Integer>
{
public static ProblemaAnunciosVZ create() {
return new ProblemaAnunciosVZ();
}
private ProblemaAnunciosVZ() {
super();
}
// Devuelve un objeto de tipo EstadoAnunciosVZ.
public EstadoAnunciosVZ getEstadoInicial() {
// VZ inicializa vacio
return EstadoAnunciosVZ.create();
}
}

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

Solucin: Simulated Annealing

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:

Maximizar el valor de Valor.

Alternativas:

Alternativa nica: Escoger el primer anuncio de AnunciosDisponibles si no est vaca.

Estado inicial:

AnunciosDecididosAEmitir

Next:

next ( (inserta, p1, p2) )


next ( (intercambia, p1, p2) )
next ( (elimina, p1) )

470

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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

public class EstadoAnunciosSA


implements EstadoSA<EstadoAnunciosSA, ListaDeAnunciosAEmitir, AlternativaAnuncios>
{
public static EstadoAnunciosSA create(EstadoAnunciosSA e) {
return new EstadoAnunciosSA(ListaDeAnunciosAEmitir.create(e.lista));
}
public static EstadoAnunciosSA create(ListaDeAnunciosAEmitir lista) {
return new EstadoAnunciosSA(lista);
}
public static EstadoAnunciosSA create() {
return new EstadoAnunciosSA(ListaDeAnunciosAEmitir.create());
}
private ListaDeAnunciosAEmitir lista;
private EstadoAnunciosSA(ListaDeAnunciosAEmitir lista) {
super();
this.lista = ListaDeAnunciosAEmitir.create(lista);
}
// Aade la siguiente alternativa al estado.
public EstadoAnunciosSA next(AlternativaAnuncios a) {
ListaDeAnunciosAEmitir listaTmp = null;
// en funcion de la alternativa hace una cosa u otra
if (a.opcion.equals(AlternativaAnuncios.Opcion.Insertar)) {
listaTmp = lista.insertar(a.p1, a.p2);
} else if (a.opcion.equals(AlternativaAnuncios.Opcion.Eliminar)) {
listaTmp = lista.eliminar(a.p1);
} else // (a.opcion.equals(AlternativaAnuncios.Opcion.Intercambiar))
{
listaTmp = lista.intercambiar(a.p1, a.p2);
}
return new EstadoAnunciosSA(listaTmp);
}
// Genera de forma aleatoria las alternativas
public AlternativaAnuncios getAlternativa() {
List<AlternativaAnuncios.Opcion> ls = getTiposDeOpcionesAlternativasPosibles();
AlternativaAnuncios.Opcion op;
AlternativaAnuncios a = null;
int e;
if (ls.size() == 1) { e = 0; }
else if (ls.size() == 2) { e = Math2.escogeEntre(0.5, 0.5); }
else { e = Math2.escogeEntre(0.4, 0.1, 0.5); }
op = ls.get(e);
switch (op) {
// p1: posicin dentro de 'p' donde voy a insertar (Dnde inserto).
// p2: cdigo del elemento elegido (De dnde selecciono).
case Insertar:
ParInteger p = this.getAlternativaInsertar();
a = AlternativaAnuncios.createInsertar(p.p1, p.p2);
break;
case Eliminar:
Integer pE = this.getAlternativaEliminar();
a = AlternativaAnuncios.createEliminar(pE);
break;
case Intercambiar:
ParInteger pInt = this.getAlternativaIntercambiar();
a = AlternativaAnuncios.createIntercambiar(pInt.p1, pInt.p2);
break;
}
return a;
}

472

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


private List<AlternativaAnuncios.Opcion> getTiposDeOpcionesAlternativasPosibles() {
List<AlternativaAnuncios.Opcion> ls = Lists.newArrayList();
for (AlternativaAnuncios.Opcion op : AlternativaAnuncios.Opcion.values()) {
switch (op) {
case Insertar:
if (!this.lista.getAnunciosDisponibles().isEmpty()) {
ls.add(op);
}
break;
case Eliminar:
if (!this.lista.getAnunciosDecididosParaEmitir().isEmpty()) {
ls.add(op);
}
break;
case Intercambiar:
if (this.lista.getAnunciosDecididosParaEmitir().size() >= 2) {
ls.add(op);
}
break;
}
}
return ls;
}
private Integer getAlternativaEliminar() {
return Math2.getEnteroAleatorio
(0, this.lista.getAnunciosDecididosParaEmitir().size());
}
private ParInteger getAlternativaInsertar() {
Integer pos = Math2.getEnteroAleatorio
(0, this.lista.getAnunciosDecididosParaEmitir().size() + 1);
List<Integer> ls = Lists.newArrayList(this.lista.getAnunciosDisponibles());
Integer r = Math2.getEnteroAleatorio(0, ls.size());
return ParInteger.create(pos, ls.get(r));
}
private ParInteger getAlternativaIntercambiar() {
return Math2.getParAleatorioYDistinto
(0, this.lista.getAnunciosDecididosParaEmitir().size());
}
@Override
public ListaDeAnunciosAEmitir getSolucion() {
return lista;
}
@Override
public boolean condicionDeParada() {
return false;
}
@Override
public double getObjetivo() {
return -lista.getValor();
}
@Override
public EstadoAnunciosSA copia() {
return EstadoAnunciosSA.create(this);
}
@Override
public String toString() {
return "EstadoAnunciosSA [lista=" + lista + "]";
}
}

473

public class AlternativaAnuncios {


public enum Opcion { Insertar, Eliminar, Intercambiar }
public Opcion opcion;
public Integer p1;
public Integer p2;
public static AlternativaAnuncios createInsertar(Integer p1, Integer p2) {
return new AlternativaAnuncios(Opcion.Insertar, p1, p2);
}
public static AlternativaAnuncios createEliminar(Integer p1) {
return new AlternativaAnuncios(Opcion.Eliminar, p1, null);
}
public static AlternativaAnuncios createIntercambiar(Integer p1, Integer p2) {
return new AlternativaAnuncios(Opcion.Intercambiar, p1, p2);
}
private AlternativaAnuncios(Opcion opcion, Integer p1, Integer p2) {
super();
this.opcion = opcion;
this.p1
= p1;
this.p2
= p2;
}
@Override
public String toString() {
String s;
if (this.opcion.equals(Opcion.Insertar)
|| this.opcion.equals(Opcion.Intercambiar)) {
s = "AlternativaAnuncios [opcion=" + this.opcion + ","
+ this.p1 + "," + this.p2 + "]";
} else {
s = "AlternativaAnuncios [opcion=" + this.opcion + ","
+ this.p1 + "]";
}
return s;
}
}

474

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

7.4.2 N - Reinas.
[Vase el enunciado completo del problema en el captulo 5]

Recordemos el significado de las propiedades utilizadas:


-

NumR
col
filOcu
dgPpOcu
dgScOcu

Nmero de reinas y dimensin del tablero.


Nmero de reinas ya colocadas igual al tamao de FilOcu.
Lista de filas ocupadas.
Contiene las diagonales principales ya ocupadas.
Contiene las diagonales secundarias ya ocupadas.

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:

2 NumR - |dgPpOcu| - |dgScOcu|

Alternativas:

A = { (p1, p2) : p1 > p2 , p1 [0, |dgPpOcu|), p2 [0, |dgScOcu|) }

Estado inicial:

dgPpOcu = [0, 1, 2, , NumR -1]

Next:

next ( (p1, p2) )

Se intercambian los valores en las posiciones dadas.

475

public class Reina {


private Integer y;
private Integer x;
public static Reina create(int x, int y) {
return new Reina(x, y);
}
Reina(int x, int y) {
super();
this.x = x;
this.y = y;
}
public Integer getY() {
return y;
}
public Integer getX() {
return x;
}
public Integer getDiagonalPrincipal(){
return y-x;
}
public Integer getDiagonalSecundaria(){
return y+x;
}
@Override
public String toString() {
return "[" + x + ","+ y + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((x == null) ? 0 : x.hashCode());
result = prime * result + ((y == null) ? 0 : y.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Reina other = (Reina) obj;
if (x == null) {
if (other.x != null)
return false;
} else if (!x.equals(other.x))
return false;
if (y == null) {
if (other.y != null)
return false;
} else if (!y.equals(other.y))
return false;
return true;
}
}

476

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

public class TableroDeReinas {


public static TableroDeReinas create() {
return new TableroDeReinas();
}
public static TableroDeReinas create(Integer... ls) {
return new TableroDeReinas(ls);
}
public static TableroDeReinas create(List<Integer> ls) {
return new TableroDeReinas(ls);
}
public static TableroDeReinas create(TableroDeReinas t) {
return new TableroDeReinas(t);
}
private
private
private
private

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

public TableroDeReinas remove() {


List<Integer> ls = new ArrayList<Integer>(this.yOcupadas);
int lastIndex = this.yOcupadas.size() - 1;
ls.remove(lastIndex);
return create(ls);
}
public TableroDeReinas intercambia(int x1, int x2) {
int size = this.yOcupadas.size();
Preconditions.checkPositionIndex(x1, size);
Preconditions.checkPositionIndex(x2, size);
List<Integer> ls = new ArrayList<Integer>(this.yOcupadas);
Lists2.intercambia(ls, x1, x2);
return create(ls);
}
/**
* @return Iterable con las filas disponibles
*/
public Iterable<Integer> getFilasPosibles() {
Iterable<Integer> pf = Iterables2.fromArithmeticSequence
(0, ProblemaReinasVZ.numeroDeReinas, 1);
Iterable<Integer> pf2 = Iterables.filter(pf, new Predicate<Integer>() {
@Override
public boolean apply(Integer f) {
Integer c = getYOcupadas().size();
return !getYOcupadas().subList(0, c).contains(f)
&& !getDiagonalesPrincipalesOcupadas().contains(f - c)
&& !getDiagonalesSecundariasOcupadas().contains(f + c);
}
});
return pf2;
}
public List<Reina> getReinas() {
List<Reina> ls = new ArrayList<Reina>();
for (int i = 0; i < this.yOcupadas.size(); i++) {
Reina r = Reina.create(i, this.yOcupadas.get(i));
ls.add(r);
}
return ls;
}
public int getNumDeReinas() {
return this.yOcupadas.size();
}
public List<Integer> getYOcupadas() {
return yOcupadas;
}
public Set<Integer> getDiagonalesPrincipalesOcupadas() {
return diagonalesPrincipalesOcupadas;
}
public Set<Integer> getDiagonalesSecundariasOcupadas() {
return diagonalesSecundariasOcupadas;
}
public TableroDeReinas copia() {
return create(this);
}
public String toString() { };
public int hashCode() { }
public boolean equals(Object obj) { }
}

478

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Solucin: Algoritmo Voraz

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

Solucin: Simulated Annealing

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();

public boolean condicionDeParada() {

return tablero.getObjetivo() == 0.0;

public EstadoReinasSA copia()

return create(tablero.copia());

public double getObjetivo()

return tablero.getObjetivo();

public int hashCode() { }


public boolean equals(Object obj) { }
}

480

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

7.4.3 Camino mnimo.


El problema se enuncia as:
Dado un grafo con peso y dos vrtices del mismo encontrar el camino mnimo desde un vrtice al otro.
Vamos a buscar ahora un algoritmo Voraz para resolver el problema conocido como: Algoritmo de Dijsktra.
Como paso previo disearemos un iterable que, partiendo de un vrtice origen, va recorriendo los vrtices del
grafo escogiendo como siguiente vrtice el ms cercano al origen a travs de caminos formados por vrtices ya
encontrados.
En el estado mantendremos los vrtices previamente encontrados, sus caminos hacia el origen y sus distancias.
Los caminos hacia el origen forman un rbol. Esta informacin la guardamos en un agregado de tuplas de la
forma (V, E, D, C) donde V es un vrtice, E la arista que indica su camino ms corto hacia el origen, D su distancia
hasta el origen siguiendo la arista anterior y C si el camino representado ya es o no el mnimo posible.
Por simplicidad, si t es una tupla entonces representaremos de la forma T = (V, E, D, C) respectivamente por t.v,
t.e, t.d y t.c la primera, la segunda, la tercera y la cuarta componente. El agregado de tuplas anterior lo
mantendremos organizado mediante un Map<V, T> y un mnton de Fibonacci, FibonacciHeap<T> ordenado segn
la componente t.d.
Por m(v) representamos la imagen de v en el Map m, por M+(v, t) representaremos aadir ese par al Map y por
v m si ese vrtice pertenece al dominio del Map.
Un mnton de Fibonacci (f) es una cola de prioridad reordenable y tiene las operaciones siguientes:

min(f): Devuelve la tupla con peso mnimo.


F - t: Elimina el par del montn de Fibonacci.
F + t: Aade la tupla al mnton de Fibonacci.
F / (t, t): Cambia las componentes de la tupla t para convertirla en la t manteniendo el vrtice v,
hacindo decrecer la distancia a d, cambiando la arista a e y reordena el montn de Fibonacci.

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

[Graph<V, E>] Grafo.


[V]
Vrtice origen.

Propiedades del m
Estado:
f

Invariante:

[Mapa de V sobre (V, E, D, C)]


[Montn de Fibonacci formado por tublas de tipo (V, E, D, C)]
Ordenadas segn la componente d.
r = { min(f) | (null si f est vaco) }
a = r.v
Vrtice actual (mnimo de f respecto de v).
A
Conjunto de Aristas que salen de a.
(V, E, D, C) f v m

Estado inicial:

m = { v0, (v0, null, 0.0, false) }


f = { (v0, null, 0.0, false) }

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)

t = (v, e, da, false)


da = m(a).d + w(e)

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


El algoritmo anterior puede implementarse como un iterador. El siguiente vrtice a ser devuelto es el vrtice
actual. Hay vrtice siguiente siempre que f (el montn de Fibonacci) no est vaco. En cada estado los vrtices
que ya hayan ocupado previamente la posicin actual ya tienen calculado el camino mnimo. Este camino se
puede reconstruir a partir del Map m. En efecto cada vrtice mantiene (en la tupla asociada en m) la arista que
define su camino mnimo al origen. Siguiendo esa arista encontramos el vrtice opuesto que nuevamente tiene
su arista asociada, etc. El camino indicado podemos mostrarlo como un valor de del tipo GraphPath<V, E>,
proporcionado por jGraphT.
El montn de Fibonacci es una estructura de datos muy eficiente para implementar colas de prioridad
modificables. Es decir, conjuntos de objetos ordenados segn el valor de una propiedad y dotados de
operaciones de mnimo, eliminacin de mnimo, insercin, borrado, y modificacin (a la baja) del valor de la
propiedad.
Para insertar o eliminar objetos de tipo T en un montn de Fibonacci tenemos que hacerlo a travs de un
FibonacciHeapNode.

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

7.4.4 Algoritmo de Dijsktra.


Como hemos comentado anteriormente el problema del Camino Mnimo se enuncia como dado un grafo con
peso y dos vrtices del mismo, encontrar el camino mnimo desde un vrtice al otro.
El Algoritmo de Dijsktra resuelve de forma eficiente este problema. Ese algoritmo usa el Iterable del Siguiente
Vrtice Ms Cercano (Camino Mnimo) visto previamente. Parte de un vrtice inicial y va recorriendo los vrtices
segn el iterable anterior hasta que encuentra el vrtice final. Posteriormente reconstruye el camino mnimo
segn se ha explicado.

El problema del Camino Mnimo se puede generalizar a este otro;


Dado un grafo con peso encontrar un camino mnimo de un vrtice inicial a los vrtices de un conjunto dado.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

7.4.5 Algoritmo A*.


Los algoritmos A* tienen tambin como objetivo encontrar el camino mnimo entre dos vrtices de un grafo y
son una generalizacin del Algoritmo de Dijsktra. Usan el mismo esquema que el Algoritmo de Dijsktra pero
basndose en una generalizacin del Iterable Siguiente Vrtice Ms Cercano (Camino Mnimo) visto previamente.
A este nuevo iterable lo denomiaremos Iterable Siguiente Vrtice Ms Cercano Generalizado. Solamente
comentaremos los aspectos que lo diferencian del anterior.
Ahora, el coste de un camino del vrtice inicial al actual vendr dado por:
() =

() + () + (, , )

Donde:

w(v): Peso del vrtice.


w(e): Peso de la arista.
w(v, ee, es): Peso de un vrtice relativo a la arista que entra ee y la arista que sale es del mismo.
L: Un camino del vrtice inicial o hasta el actual a.

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

[Graph<V, E>] Grafo.


[V]
Vrtice origen.

Propiedades del m
Estado:
f

Invariante:

[Mapa de V sobre (V, E, D1, D2, C)]


[Montn de Fibonacci formado por tublas de tipo (V, E, D1, D2, C)]
Ordenadas segn la componente D2.
r = { min(f) | (null si f est vaco) }
a = r.v
Vrtice actual.
A
Conjunto de Aristas que salen de a.
(V, E, D1, D2, C) f v m

Estado inicial:

m = { v0, (v0, null, 0.0, false) }


f = { (v0, null, 0.0, false) }

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)

t = (v, e, da, ca, false)


da = m(a).d2 + w(a, ee, es) + w(e) + w(v)
ca = da + h(v)

486

vm
v m m(v).c = true
v m m(v).D2 < ca

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

7.4.6 Algoritmo de Kruskal (Bosque de recubrimiento mnimo).


El problema del recubrimiento mnimo se enuncia como, dado un grafo no dirigido y cuyas aristas tienen pesos,
entonces un bosque de recubrimiento mnimo, de ese grafo, es un subconjunto de sus aristas que forman un
bosque, que incluye a todos sus vrtices y el peso total de todas las aristas en el bosque es el mnimo posible.
Si el grafo es conexo entonces el bosque de recubrimiento mnimo tiene una sola componente que se denomina
rbol de recubrimiento mnimo. Si el grafo no es conexo entonces existe un bosque de recubrimiento mnimo y
un rbol de recubrimiento mnimo para uno de las componentes conexas que forman dicho grafo no conexo.
El Algoritmo de Kruskal busca un bosque de recubrimiento mnimo para un grafo no dirigido, conexo o no. Este
es un algoritmo Voraz. Por tanto, para especificarlo, tenemos que indicar las propiedades compartidas, las
propiedades individuales representadas en el estado, el estado inicial, la eleccin de la alternativa y la transicin
de un estado al siguiente.
Para implementar el algoritmo es conveniente usar una estructura de datos que implementa eficientemente una
secuencia de conjuntos. Es la estructura que llamaremos UnionFind.
class UnionFind<T> {
void addElement(T e);
T find(T e);
void union(T e1, T e2);
}

Sus mtodos son:


void addElement(T e):
T find(T e):
void union(T e1, T e2):

Aade el elemento e en un conjunto nuevo.


Devuelve el representante del conjunto donde est e.
Une en un solo conjunto los conjuntos en los que estn e1 y e2.

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:

next() = (u, j, se) (u, j+1, se)


org
(u, se)
u(ej ) = u(edes
j )
(u', se') = {
org
org
des
(u(ej + edes
j ), se + ej ) u(ej ) u(ej )

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 .

Este algoritmo Voraz mantiene los invariantes:


-

U que contiene todos los vrtices del grafo agrupados en conjuntos.


Los vrtices en cada conjunto ms las aristas el subconjunto de arista en SE que los unen forman un
rbol.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

7.4.7 Algoritmo de Prim (rbol de recubrimiento mnimo).


El Algoritmo de Prim busca un rbol de recubrimiento mnimo para un grafo no dirigido y conexo.
El rbol de recubrimiento mnimo podemos describirlo como un conjunto de aristas.
El algoritmo comienza con un rbol que contiene un vrtice cualquiera del grafo (v0) y una cola de prioridad que
contiene las aristas incidentes en ese vrtice. La funcin de eleccin elige, en cada estado, la primera arista de
la cola de prioridad. En cada transicin se elimina la arista elegida de la cola, se aade la arista al rbol y las
aristas incidentes al vrtice que no perteneca al rbol.
El algoritmo va haciendo un recorrido en anchura del grafo donde los vrtices del rbol construdo en cada
momento ya han sido visitados. Los vrtices de las aristas de la cola de prioridad que no estn en el rbol s han
sido alcanzados pero no visitados. Como todos los algoritmos de recorrido en anchura, este acaba cuando todos
los vrtices del grafo ya han sido visitados. En ese momento la cola ya estar vaca.
El mtodo boolean addEdge() para el tipo de rbol libre (Forest<E,V>) con una nica componente conexa, lo
implementamos tal como se ha explicado antes.

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>]

Conjunto de vrtices ya visitados.


Conjunto de Aristas.
ordenadas por su peso.

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:

next() = (c, cola, se)


(c, cola-r, se)
rorg c rdes c
(c', cola', se') = {(c+rdes , cola-r+a(rdes ,c), se+r) rorg c rdes c
(c+rorg , cola-r+a(rorg ,c), se+r) rorg c rdes c
a(v, c) = aristas salientes de v cuyo extremo alternativo no pertenece a c.

En la notacin anterior representamos por rorg, rdes el origen y el destino de la arista r.


Este algoritmo Voraz mantiene el invariante que los vrtices en c ms las aristas en se forman un rbol de
recubrimiento mnimo del subgrafo cuyos vrtices estn en c.
Cuando el algoritmo acaba c contiene todos los vrtices del grafo si este es conexo. El rbol est definido por las
aristas en se.
489

7.4.8 Recorrido de un grafo en profundidad.


La bsqueda en profundidad tiene varias variantes segn se el visite vrtice padre antes, despus o entre sus
hijos. Las diferentes posibilidades son:
Preorden: Cada vrtice padre se vista antes que cada uno de sus hijos
Postorden: Cada vrtice padre se visita despus que todos sus hijos
Inorden: Si el nmero de hijos de cada vrtice padre, en el rbol de recubrimiento, es dos como mximo
entonces cada vrtice padre se visita despus de sus hijos izquierdos y antes de sus hijos derechos.
Secuencia de recorrido de preorden: F, B, A, D, C, E, G, I, H
(raz, izquierda, derecha)
Secuencia de recorrido de postorden: A, C, E, D, B, H, I, G, F
(izquierda, derecha, raz)
Estos recorridos se pueden aplicar tanto a
grafos dirigidos como a los no dirigidos.

Secuencia de recorrido de inorden: A, B, C, D, E, F, G, H, I


(izquierda, raz, derecha)

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


En el recorrido en profundidad en preorden se saca el vrtice actual de la pila y a partir de l 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
desechamos la arista. Si v no ha sido encontrado previamente aadimos la arista al rbol de recubrimiento
(aadiendo el par (v, e) al Map) y el vrtice v a a la pila.
El iterable en preorden va proporcionando los sucesivos vrtices actuales.
Recorrido iterable de un grafo en profundidad: Preorden
Tcnica:

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

[Graph<V, E>] Grafo.


[V]
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
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.

Usos del recorrido en profundidad:


La bsqueda en profundidad y los rdenes definidos a partir del mismo tienen diferentes aplicaciones:
Obtencin de un bosque de recubrimiento de un grafo, dirigido o no.
Clculo de las componentes conexas de un grafo no dirigido: Para hacerlo recorremos todos los vrtices
del grafo y mantenemos un conjunto de vrtices ya clasificados. Para cada vrtice no clasificado
aadimos a un conjunto los vrtices alcanzables desde l y los incluimos entre los ya clasificados.
Escogemos el siguiente vrtice an no clasificado para formar la segunda componente conexa y as
sucesivamente hasta que clasifiquemos todos los vrtices.
Clculo de las componentes fuertemente conexas de un grafo dirigido.
Clculo de caminos de un vrtice a los dems en un grafo, dirigido o no dirigido, o decidir que no existe
camino. Esencialmente estos caminos (o no existencia de caminos) vienen definidos por el rbol de
recubrimiento asociado al recorrido que empieza en el vrtice dado.
Comprobar si un grafo no dirigido es conexo. No es conexo si existe ms de una componente conexa.
Comprobar si un grafo dirigido es conexo. No es conexo si existe ms de una componente fuertemente
conexa. Si un grafo dirigido es conexo existen caminos entre cada par de vrtices.
Ordenacin topolgica de un grafo dirigido: la ordenacin topolgica de los vrtices de un grafo no
dirigido viene dada por el Postorden Inverso.
Clculo de las componentes dbilmente conexas de un grafo dirigido G. Es calcular las componentes
conexas del grafo G obtenido a partir de G haciendo todas las aristas no dirigidas.

492

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Versin recursiva del recorrido en profundidad: Clasificacin del tipo de aristas de un grafo.
Veamos ahora una versin recursiva del recorrido en profundidad de un grafo para la clasificacin de sus aristas.
Al recorrer el grafo aparecen varios tipos de aristas que pueden ser clasificadas por el algoritmo:
-

Tree edges (aristas del rbol de recubrimiento): Van de un


vrtice a uno de sus hijos en el rbol de recubrimiento.
Forward edges: Van desde un vrtice a alguno de sus
descendientes no inmediatos.
Back edges: Sealan de un vrtice a uno de sus antecesores.
Cross edges: No son ninguno de los tipos anteriores.

La clasificacin de las aristas depende del tipo de recorrido y del


rbol de recubrimiento concreto.
En el caso de que el grafo sea no dirigido slo hay tree edges o back edges. En el caso del recorrido en anchura
slo encontramos tree edges, back edges, y cross edges y no hay forward edges. El recorrido en anchura define
un rbol de recubrimiento con la propiedad de que el camino desde la raz a un vrtice dado tiene el mnimo
nmero de aristas.
Por comodidad definimos dos funciones adicionales sobre los vrtices: d(v) (discovery time) y f(v) (finish time).
La primera nos da, para cada vrtice, el momento en que es el vrtice actual en el recorrido en preorden o en la
preVisita en el recorrido en postorden. La segunda, el momento en que un vrtice y todos sus descendientes han
sido visitados. Es decir, hasta el tiempo de la postVisita en el recorrido, en postorden. Estas funciones toman
valores enteros.
Para ir asignando esos valores disponemos de una variable global, time, inicializada a cero y que incrementa su
valor cada vez que un vrtice est en el tiempo d(v) o f(v). Estas funciones cumplen siempre 1 <= d(v) < f(v) <=
2|V|. Dnde |V| es el nmero de vrtices.
Por facilidad de exposicin posterior, asignamos tres colores (tres estados posibles) a los vrtices segn se van
procesando: blanco, gris y negro. Estos posibles colores para cada vrtice en un tiempo dado time del algoritmo
de recorrido cumplen:
-

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:
-

Tree edge si w es blanco.


Back edge si w es gris.
Forward o Cross si w es negro y:
Forward edge si w es negro y d(v) < d(w) (w fue vrtice actual despus que v).
Cross edge si w es negro y d(v) > d(w) (v fue vrtice actual despus que 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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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.

Aplicaciones del recorrido en profundidad: Obtencin de las componentes fuertemente conexas.


El algoritmo anterior se puede usar dentro de otro que es adecuado para el clculo de las componentes
fuertemente conexas de un grafo dirigido. Recordamos que una componente fuertemente conexa de un grafo
dirigido G = (V, E) es un conjunto maximal de vrtices de G tales que, cada par de vrtices u y v de la misma, son
alcanzables mutuamente. Es decir, existe un camino de u a v y otro de v a u. El esquema del algoritmo es:
dfsSCC(G) {
nc = 1;
dfsAll(G); //
GT = GT;
Inicializar cada vrtice de GT a no visitado;
S = Lista de vrtices de G en Postorden inverso obtenida anteriormente;
for(para cada v en S){
if(v es no visitado){
marcar v como visitado;
dfsPreOrder(v, GT) //poner a visitado cada vrtice alcanzado
nc = nc+1;
// los vrtices visitados desde v forman una comp. fuertemente conexa
}
}
}

Donde GT es el grafo traspuesto a G. Es decir, aquel otro con las aristas invertidas.

Calculando d(v) y f(v)


para cada vrtice:

El postorden inverso es:


D, F, H, G, C, A, B, E
y el grafo traspuesto de G

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

7.4.9 Recorrido de un grafo en anchura.


La bsqueda en anchura visita cada nodo, posteriormente sus hijos, luego los hijos de estos, etc. Es, por lo tanto
un recorrido por niveles o si se quiere, por distancia al nodo origen (asociando peso 1 a cada arista). Primero los
que estn a distancia cero, luego los que estn a distancia 1, etc.
Este recorrido se puede aplicar tanto a grafos dirigidos como a los no dirigidos.
En el grfico se muestra un recorrido en anchura. Dependiendo del orden
en que se recorran los hijos de un nodo hay varios recorridos que cumplen
las restricciones anteriores dado un grafo y un vrtice inicial.
El recorrido en anchura define un rbol de recubrimiento donde las aristas
escogidas definen caminos mnimos desde cada vrtice al inicial
(asociando peso 1 a cada arista). La distancia al vrtice inicial es el nivel de
cada vrtice.
Cuando desde un vrtice se alcanza otro por primera vez a travs de una arista sta se incluye en el rbol de
recubrimiento.
La clase BreadthFirstIterator<V,E> implementa un iterador que hace el recorrido en anchura. Constructores:
BreadthFirstIterator(Graph<V,E> g)
BreadthFirstIterator(Graph<V,E> g, V startVertex)

// Vrtice inicial arbitrario


// Vrtice inicial startVertex

Recorrido iterable de un grafo en anchura


Tcnica:

Voraz

Propiedades
Compartidas:

g
v0

[Graph<V, E>] Grafo.


[V]
Vrtice origen

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

m' = m + (v, e) cola'' = cola' + v


m' = m
cola'' = cola'

vm
vm

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Usos del recorrido en Anchura.
La bsqueda en anchura define un rbol de recubrimiento sobre sobre el grafo y cada uno de los vrtices pueden
ser etiquetados, como en el caso del recorrido en profundidad, por un nmero entero que indica la posicin en
la que es visitado. Este rbol de recubrimiento no es nico porque depende de la forma de aadir los vecinos del
vrtice actual al conjunto de aristas que salen desde el vrtice a.
El recorrido en anchura tiene la propiedad que el camino de la raz a un vrtice en el rbol de recubrimiento
tiene el mnimo nmero de aristas posible.
El recorrido en anchura y el orden definido a partir del mismo tienen diferentes aplicaciones:
Clculo de las componentes conexas de un grafo no dirigido. Una componente conexa est formada por
todos los vrtices alcanzables desde uno dado.
Clculo de caminos de un vrtice a los dems. Esencialmente estos caminos vienen definidos por el rbol de
recubrimiento asociado al recorrido.
Encontrar el camino ms corto entre un vrtice y todos los dems de su componente conexa. Los caminos
definidos por el rbol de recubrimiento son los caminos mnimos (en nmero de aristas) desde el vrtice
origen a los dems.

497

7.4.10 Recorrido de un grafo en orden topolgico.


Es un tipo de recorrido que se aplica a grafos dirigidos. En este recorrido cada vrtice va despus que los vrtices
que le anteceden en el grafo dirigido.
Con las restricciones anteriores hay varios recorridos posibles.
Algunos son:
- 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

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.

Recorrido iterable de un grafo en orden topolgico


Tcnica:

Voraz

Propiedades
Compartidas:

g
v0

[Graph<V, E>] Grafo.


[V]
Vrtice origen.

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 {}

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 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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

7.5 Problemas de exmenes.


7.5.1 El esquiador.
Descripcin del problema:
En una estacin de esqu, un esquiador pretende aprovechar al mximo el
tiempo que estn los telesillas abiertos de manera que pueda esquiar la
mxima distancia por las pistas de nieve. Para poder descender por una pista,
primero hay que subir por un telesilla. En este problema se supondr que cada
telesilla lleva a una sola pista, por lo que se hablar indistintamente del tipo
de dato pista que recoge los dos conceptos. Una pista comienza en la entrada
de un telesilla y termina en el final de la zona esquiable. En el inicio de cada
pista hay un cartel que informa de su longitud esquiable y del tiempo medio
que se tarda en subir el telesilla y bajar esquiando. La fraccin
longitud/tiempo le indica al esquiador con qu pista aprovechara mejor el
tiempo, por tanto, cuando tenga que elegir entre varias, elegir aquella que
haga mayor dicha fraccin.
El esquiador, cuando llega a la montaa tiene disponible ciertas pistas por las que comenzar. Cada pista le llevar
a un lugar diferente permitindole acceder a otras pistas.
Se modelar lo arriba descrito para resolver el problema del esquiador, que consiste en planificar el tiempo
disponible para esquiar maximizando la distancia recorrida en las pistas.
Modelado del problema:
Para simplificar el problema, se tendrn en cuenta las siguientes suposiciones:
1. El tipo de dato Pista representa a un telesilla que lleva hasta una pista de nieve que se va a esquiar. Las
propiedades de Pista son:
a) tiempo: es el tiempo que se consume cuando se recorre dicha pista. Este tiempo es el comprendido
desde que se pone a la cola del telesilla, hasta que se finaliza la pista despus de esquiarla.
b) longitud: es la distancia que el esquiador recorre cuando esqua la pista.
2. Cada pista da acceso a una o a varias pistas. Existir una propiedad compartida (del problema),
accesibilidad, de tipo Map<Pista, SortedSet<Pista>> que, para cada pista, almacena a las que se
pueden acceder una vez terminada, es decir, las alternativas del esquiador.
3. Para comenzar a esquiar, se dispone de un conjunto de pistas a las que puede acceder inicialmente.
Existir otra propiedad compartida (del problema), pistasIniciales, de tipo SortedSet<Pista> que
contendr las pistas que pueden ser accedidas inicialmente.
4. Cada problema tendr una propiedad tiempoTotal que hace referencia al tiempo que las pistas permiten
acceder a los esquiadores.
5. En cada Estado, el esquiador tiene que decidir qu pista elegir de todas las que tiene disponibles en ese
Estado. Elegir aquella pista que tenga mayor longitud/tiempo. Tal y como se indica en la ficha
proporcionada, el orden que se tiene en cuenta en los conjuntos ordenados est en funcin de esa
fraccin, por lo que primero estar la pista que el esquiador elegir. En un Estado cualquiera, el
esquiador podr acceder a una pista siempre y cuando le quede algo de tiempoDisponible.

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.
-

pistasRecorridas: Lista con las pistas 2, 3, 6, 4.

tiempoRestante: Entero con valor igual a -5.

longitudEsquiada: Entero con valor igual a 12.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Solucin:

Apartado 1)

Esquiador
Tcnica:

Voraz

Tamao:

t = tiempoDisponible

Propiedades
Compartidas:

pistasIniciales
accesibilidad
tiempoTotal

Propiedades del pistasRecorridas


Estado:
distanciaRecorrida
tiempoDisponible
Solucin:
SolucionPistas
Alternativas:

[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:

// TODO: Se tenden en cuenta las propiedades longitud y tiempo de cada pista.


h(estado, ai) = ai Aestado con mximo (ailongitud / aitiempo)

Estado Inicial:

// TODO: Habr que considerar una de las pistas iniciales.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

7.5.2 Inversiones en acciones.


Se dispone de un fondo de inversin que dispone de una cantidad de dinero M
para invertir en acciones. Al invertir la cantidad X en una accin el fondo espera
recibir un cierto beneficio o inters. Se desea disear un algoritmo que intente
repartir la cantidad M entre N acciones distintas de forma que se obtenga el
mximo beneficio esperado posible. Para poder resolver el problema se dispone
de una funcin beneficio(i, x) que devuelve el beneficio o inters que obtiene el
fondo al invertir la cantidad x en la accin i.
En el caso de que estemos considerando la accin i y nos reste por invertir la cantidad de C. Una posible estrategia
Voraz de inversin sera invertir la cantidad x {0,1, ... , C} que haga mximo el ratio beneficio(i, x) / x. Este ratio
valdra cero para cualquier cantidad x = 0.
Ejemplo: Suponiendo la siguiente tabla de beneficios (ej: el beneficio de invertir en la accin A2 la cantidad de 3
millones es de 6), podemos calcular las siguientes soluciones:

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

Lacciones ListaAccion Lista de inversiones.


[
ben
entero
Beneficio de la solucin.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Solucin:
Apartado a)
Inversiones Acciones
Tcnica:

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:

Encontrar Sol l, b tal que el nmero de b tenga el mayor valor posible.

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:

(L, B, C, I) (L+aij, B + ai f(k), C-k, I+1)

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

7.5.3 Perecoso y Ingenioso.


Dos amigos, Perezoso e Ingenioso, hacen una apuesta para ver cul de los dos bebe la
mayor cantidad posible de agua. Disponen de una fila de n vasos teniendo en cuenta que
n debe ser par. Cada vaso se representar por vic, donde identificador i [0,
vasosDisponibles.size) y c es la cantidad de agua que contiene cada vaso. Los amigos
beben por turnos donde cada uno en su turno debe elegir el vaso de uno de los extremos
y beberse su contenido. El vaso se retira y el turno pasa al otro amigo. El objetivo de ambos
amigos es beber la mayor cantidad de agua posible.
La estrategia de Perezoso es simple ya que siempre coge el vaso del extremo de la izquierda. Sin embargo,
Ingenioso, prefiere pensrselo un poco ms, de manera que le pide a sus amigos que cursan la asignatura de
ADDA que le diseen un Algoritmo Voraz para que le ayuden a conseguir su objetivo. Adems, Ingenioso prefiere
darle ventaja a su amigo, y por tanto, ser Perezoso el que elija primero.
Un determinado Estado del problema est representado por cuatro propiedades: vasosUsadosIngenioso,
vasosUsadosPerezoso, vasoExtremoIzquierda, vasoExtremoDerecha, cantidadIngenioso y cantidadPerezoso que
contendrn respectivamente, la lista de vasos que bebe Ingenioso, la lista de vasos que bebe Perezoso, la posicin
del vaso del extremo izquierdo actual, la posicin del vaso del extremo derecho actual, la cantidad que bebe
Ingenioso y la cantidad que bebe Perezoso.
Implemente un algoritmo basado en una tcnica Voraz que permita a Ingenioso ganar la apuesta. Tenga en
cuenta que Ingenioso puede coger un vaso del extremo izquierdo o del extremo derecho, por tanto, deber
disear una estrategia que le permita ganar. En cada turno, Ingenioso elige uno de los vasos situado en uno de
los extremos, teniendo en cuenta que Perezoso elige primero y siempre selecciona el vaso situado en el extremo
izquierdo. Una vez elegida la alternativa de Ingenioso, se aade en cada Estado, cada uno de los vasos que bebe
cada uno de los dos amigos a la lista correspondiente, se actualizar la cantidad de agua que bebe cada uno de
ellos y se modificarn las posiciones que indican cul es el vaso del extremo izquierdo siguiente, y cul es el vaso
del extremo derecho siguiente. Note que en cada Estado, ambos amigos elegirn un vaso, es decir, tanto el turno
de Perezoso como el turno de Ingenioso tendr lugar en un solo Estado.
La solucin deber representar los vasos que elige Ingenioso y Perezoso respectivamente, as como la cantidad
de agua total que beben ambos teniendo en cuenta el algoritmo diseado. Habr solucin para el problema
siempre y cuando, la cantidad de agua que beba Ingenioso sea mayor que la cantidad de Perezoso. Si Ingenioso
no consigue ganar a Perezoso mediante el algoritmo Voraz diseado, no habr solucin para el problema.
En el siguiente ejemplo, se muestra la lista de vasos disponibles, la cantidad de agua que contiene cada uno de
los vasos y la posicin que ocupara cada uno en la lista. El algoritmo Voraz determinar que la solucin formada
por los vasos que elegira Ingenioso, teniendo en cuenta que Perezoso elige primero, seran los vasos de las
posiciones 1, 3 y 5 (vasos cuya cantidad es: 8, 9 y 2 respectivamente) sumando una cantidad total de 19 en lugar
de los 16 que bebera Perezoso.

506

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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)

public void ejecuta()

De la clase ProblemaApuestaVZ:
b)

public boolean hayAlternativa(EstadoVasoVZ e)

c)

public Integer getAlternativa(EstadoApuestaVZ e)

De la clase EstadoApuestaVZ:
d)

public void next(Integer a)

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Solucin:
Apartado 1>
Perecoso e Ingenioso
Tcnica:

Voraz

Propiedades
Compartidas:

vasosDisponibles

[Lista<Vaso>]

Propiedades del vasosUsadosIngenioso


[Lista<Vaso>]
Estado:
vasosUsadosPerezoso
[Lista<Vaso>]
vasoExtremoIzquierda
[entero] [0, vasosDisponibles.size() )
vasoExtremoDerecha
[entero] [vasoExtremoIzquierda, vasosDisponibles.size())
cantidadIngenioso
[entero]
cantidadPerezoso
[entero]
Solucin:
SolucionApuesta, solingenioso vasosUsadosIngenioso,cantidadIngenios, , solperecoso vasosUsadoPerecoso,cantidadPerecoso
Alternativas:

Ae = { k | 0 k 1 } donde k es de tipo entero.


k = 0 : Ingenioso coge el vaso del extremo izquierdo.
k = 1 : Ingenioso coge el vaso del extremo derecho.

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:

( {}, {}, 0, vasosDisponibles.size()-1, 0, 0 )

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>

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(EstadoVasoVZ e) {


return e.getVasoExtremoDerecha() - e.getVasoExtremoIzquierda() > 0;
}

c>

public Integer getAlternativa(EstadoApuestaVZ e) {


Integer ret = -1;
Vaso vi = ProblemasApuesta.getVasosDisponibles().get(e.getVasoExtremoIzquierda());
Vaso vd = ProblemasApuesta.getVasosDisponibles().get(e.getVasoExtremoDerecha());
if (vi.compareTo(vd) > 0)
ret = 0;
else
ret = 1;
return ret;
}

d>

public void next(Integer a) {


Integer vasoPerezoso = vasoExtremoIzquierda;
Integer vasoIngenioso;
if (a == 0) {
vasoIngenioso = vasoExtremoIzquierda + 1;
vasoExtremoIzquierda = vasoExtremoIzquierda + 2;
} else {
vasoIngenioso = vasoExtremoDerecha;
vasoExtremoIzquierda++;
vasoExtremoDerecha--;
}
Vaso vi = ProblemasApuesta.getVasosDisponibles().get(vasoIngenioso);
vasosUsadosIngenioso.add(vi);
cantidadIngenioso += vi.getCantidad();
Vaso vp = ProblemasApuesta.getVasosDisponibles().get(vasoPerezoso);
vasosUsadosPerezoso.add(vp);
cantidadPerezoso += vp.getCantidad();
}

510

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

7.5.4 Comit Olmpico de Japn.


Tokio acaba de ser elegida ciudad olmpica para el ao 2020, por lo que el Comit Olmpico de
Japn (JOC) necesita planificar la construccin de los diferentes edificios donde se celebrarn
las olimpiadas. Para el ao 2014, el JOC dispone ya de una cantidad de dinero que quiere
aprovechar para empezar a construir, y para demostrar que estn muy comprometidos con la
eleccin recibida, quieren empezar por los edificios de mejor calidad e importancia posibles.
Para ello, el JOC tiene un catlogo ordenado con varios proyectos, cada proyecto tiene la
siguiente informacin:

Nombre: Nombre del edificio.

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.

Precio: Presupuesto por construir el edificio (en yenes).

Valor de Calidad: Puntuacin entre 0 y 10 dada por un equipo de expertos en edificacin,


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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Solucin:
Apartado 1>
Comit Olmpico de Japn
Tcnica:

Voraz

Propiedades
Compartidas:

proyectosDisponibles
presupuestoTotal

[Lista<Proyecto>]
[entero]

Propiedades del proyectosElegidos


Estado:
proyectosPorElegir
presupuestoDisponible
valorCalidadImportanciaTotal
Solucin:
SolucionJOC

[Lista<Proyecto>]
[Lista<Proyecto>]
[entero]
[Real]

Objetivo:

Encontrar una solucin tal que maximice el valor de valorCalidadImportanciaTotal.

Alternativas:

A = {pry en proyectosPorElegir} tal que pry es el primer elemento de proyectosPorElegir

Estado inicial:

proyectosElegidos

= {}

proyectosPorElegir

= proyectosDisponibles

presupuestoDisponible

= presupuestoTotal

valorCalidadImportanciaTotal = 0.0

Next:

proyectosElegidos

proyectosElegidos + pry

proyectosPorElegir

proyectosPorElegir - pry (filtrar por funcionalidad y presupuesto)

presupuestoDisponible

presupuestoDisponible - pry.precio

valorCalidadImportanciaTotal valorCalidadImportanciaTotal + (pry.valorCalidad * pry.importancia)

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>

private class DistintaFuncionalidadYDentroPresupuesto implements Predicate<Proyecto> {


private Proyecto proyecto;
public DistintaFuncionalidadYDentroPresupuesto(Proyecto p) {
this.proyecto = p;
}
@Override
public boolean apply(Proyecto arg0) {
return (!(arg0.getFuncionalidad().equalsIgnoreCase(proyecto.getFuncionalidad()))
&& arg0.getPrecio() <= presupuestoDisponible);
}
}
@Override
public EstadoJOCAP getEstadoInicial() {
return EstadoJOCAP.create();
}

514

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

7.5.5 Servicio de bicicletas.


ETSIIbici es un servicio de uso de bicicletas que cuenta con una serie de
estaciones repartidas por una ciudad. Existe un tiempo mximo de uso por cada
bicicleta. Trascurrido este tiempo, el usuario no podr continuar el recorrido
salvo que llegue a una estacin antes de que finalice el tiempo mximo permitido
y por tanto, pueda cambiar de bicicleta.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Solucin:

Apartado 1>

Servicio de bicicletas
Tcnica:

Voraz

Propiedades
Compartidas:

estaciones
tiempoMaximo

[Lista<entero>]
[real]

Propiedades del estacionesUsadas


[Lista<EstacionBicicleta>]
Estado:
tiempoRestante
[real]
tiempoRecargado
[real]
estacionActual
[entero]
Solucin:
SolucionEstacionesBicicletas, Sol Lista,Tiempo : Lista que contiene las estaciones en las que se
ha realizado un cambio y Tiempo total renovado, o recargado, durante el recorrido.
Alternativas:

A = {Y, N} o {N} dependiendo del estado (Y = Cambiar de bicicleta, N: No cambiar de bicicleta).

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:

estacionActual estaciones.size()-1 o tiempoRestante 0

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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:

Modifica: Actualiza la asignacin de un cliente C1 con un nuevo apartamento A1 contenido en


apartamentosPorAsignar.

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:
-

public AlternativaInmobiliaria getAlternativa().

public void next(AlternativaInmobiliaria a).

private void calculaSatisfacciones() :

satisfechas

para

cada

cliente

el

Calcula la suma total del nmero de caractersticas


apartamento asignado. Actualiza la propiedad

caractersticasSatisfechas a optimizar.

public double getIncremento(EstadoSA<SolucionInmobiliaria, AlternativaInmobiliaria> e).

Notas:

520

Puede hacer uso del mtodo public static <T> T elementRandom(Iterable<T> it) de la clase
Iterables2.

Utilice el diagrama UML proporcionado para realizar los distintos apartados.

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.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

521

Solucin:

Apartado 1)

Inmobiliaria
Tcnica:

Simulated Annealing

Propiedades
Compartidas:

clientes
apartamentos
totalSatisfacciones
Propiedades del asignaciones
Estado:
apartamentosPorAsignar
caractersticasSatisfechas
Solucin:
SolucionInmobiliaria
Alternativas

A1 = { (modifica, c1, a1)

[List<Cliente>]
[List<Apartamento>]
[Integer]
[Map<Cliente, Apartamento>]
[List<Apartamento>]
[Integer]
: c1 asignaciones.keySet(), a1 apartamentosPorAsignar }

A2 = { (intercambia, c1, c2) : c1 asignaciones.keySet(), c2 asignaciones.keySet() }


A = A1 A2
Estado inicial:

asignaciones + (c, a) | (c clientes, a apartamentos) a asignaciones.values


apartamentosPorAsignar = apartamentos - asignaciones.values
caractersticasSatisfechas = calculaSatisfacciones()

Estado final:

caracteristicasSatisfechas = |cliente.caracteristicas| cliente clientes

Next:

next ((modifica, c1, a1)):

next ((intercambia, c1, c2)):

Apartamento a2 = asignaciones(c1)

Apartamento a1 = asignaciones(c1)

asignaciones - (c1, a2)

Apartamento a2 = asignaciones(c2)

asignaciones + (c1, a1)

asignaciones - (c1, a1)

apartamentosPorAsignar - a1

asignaciones - (c2, a2)

apartamentosPorAsignar + a2

asignaciones + (c1, a2)

calculaSatisfacciones();

asignaciones + (c2, a1)


calculaSatisfacciones();

522

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Apartado 2.a)
public AlternativaInmobiliaria getAlternativa() {
AlternativaInmobiliaria a = null;
while (a == null) {
int ae = Math2.getEnteroAleatorio(0, 2);
// MODIFICA
if (ae == 0 && apartamentosPorAsignar.size() > 0) {
Cliente c = null;
Apartamento ap = null;
c = Iterables2.elementRandom(clientes);
ap = Iterables2.elementRandom(apartamentosPorAsignar);
a = AlternativaInmobiliaria.create(c, ap);
}
// INTERCAMBIA
if (ae == 1) {
Cliente c1 = null;
Cliente c2 = null;
do {
c1 = Iterables2.elementRandom(clientes);
c2 = Iterables2.elementRandom(clientes);
} while (!c1.equals(c2));
a = AlternativaInmobiliaria.create(c1, c2);
}
}
return a;
}

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

7.5.7 Equipo de Ftbol.


Un equipo de ftbol acaba de terminar la temporada actual y necesita planificar los fichajes
de la prxima temporada. El equipo necesita realizar los mejores fichajes posibles dentro
del presupuesto acordado por la junta directiva para este propsito.
El ojeador del equipo ha realizado un catlogo de posibles fichajes con la siguiente
informacin:
-

Nombre:
Precio:
Equipo actual:
Valor de Calidad:
Posicin:

Nombre del jugador.


Cantidad a pagar para hacerse con los servicios del jugador.
Equipo que actualmente posee los servicios del jugador.
Puntuacin entre 0 y 10 dada por el ojeador.
Posicin que ocupa en el terreno de juego.

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)

c) Implemente el siguiente mtodo perteneciente a la clase ProblemaFichajesAP:


-

524

public EstadoAP<SolucionFichajes, Fichaje> getEstadoInicial()

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

525

Solucin:

Apartado a)

Equipo de Fltbol
Tcnica:

Voraz

Propiedades
Compartidas:

Catalogo
presupuestoInicial

[List<Fichaje>] Ordenada por Calidad/Precio.


[Double]
Presupuesto inicial.

Propiedades del fichajesDecididos


Estado:
fichajesPorDecidir
presupuesto
valorTotalCalidad
Solucin:
SolucionFichajes

[List<Fichaje>]
[List<Fichaje>] ordenada por Calidad/Precio.
[Integer]
[Integer]

Objetivo:

Encontrar una solucin tal que maximice valorTotalCalidad.

Alternativas:

A = { fichaje: fichajesPorDecidir } tal que fichaje es el primer valor de fichajesPorDecidir y


valor de fichaje.getPosicion() no aparezca en ningn elemento de fichajesDecididos y
adems, fichaje.getPrecio() presupuesto

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Apartado b)

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)

public EstadoAP<SolucionFichajes, Fichaje> getEstadoInicial() {


return EstadoFichajesAP.create();
}

527

7.5.8 Vrtice coloracin.


El coloreado de grafos (o simplemente coloreado) consiste en asignar un
color a cada vrtice de un grafo de tal manera que dos vrtices que
compartan la misma arista tengan colores diferentes.
El coloreado mnimo consiste en encontrar el mnimo nmero de colores
necesarios para realizar el coloreado y qu asignacin de color debe hacerse
a cada vrtice.
Un planteamiento para resolver el problema mediante Simulated Annealing
es usar estados inconsistentes, es decir, permitir que dos vrtices
conectados por una arista tengan el mismo color, penalizando tal situacin
en la funcin objetivo a optimizar. De esta forma, para la transicin entre
estados basta con un nico operador, que seleccionar un vrtice y un color
a asignar a dicho vrtice.
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 vrtice un color
diferente. Obviamente, para cualquier grafo, el nmero mximo de colores a usar puede tomarse como el
nmero de vrtices del grafo.
Por tanto y, a partir de este modelado y siguiendo el esquema de la tcnica de Simulated Annealing, se pide
resolver las siguientes cuestiones.

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.

Nota: el funcionamiento de algunos mtodos del


siguiente diagrama en el siguiente:

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Apartado 1>

Vrtice coloracin
Tcnica:

Simulated Annealing

Propiedades
Compartidas:

NVERTICES
NMAXCOLORES

[Integer]
[Integer]

Nmero de vrtices del grafo.


Nmero mximo de colores posibles.

Propiedades
bsicas del
Estado:

GRAFO

[GrafoColoreado]

Grafo.

Propiedades
derivadas del
Estado:

NCOLORES
NINCONSISTENCIAS

[Integer]
[Integer]

Nmero de colores usados.


Nmero de par de vrtices conectados con
el mismo color.

Solucin:

GRAFO
NCOLORES

grafo
Nmero de colores

Objetivo:

Minimizar la funcin:

fObj = NCOLORES + NVERTICES x NINCONSISTENCIAS

Estado inicial:

GRAFO
= Asignar a cada vrtice del grafo un color diferente.
NCOLORES
= NVERTICES
NINCONSISTENCIAS = 0
A = { (vertice, color)

Alternativas:

vertice [0, NVERTICES)


color [0, NMAXCOLORES-1) }
Next:

// TODO

// TODO

Next( (vertice, color) ) : (GRAFO)


Se colorea el vrtice especificado
con el color indicado.

// 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:

EstadoXXXSA: Debe contener un atributo de tipo GrafoColoreado.


SolucionXXX: Debe contener, al menos un atributo de tipo GrafoColoreado.

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>

public double getObjetivo() {


return grafoColoreado.getColoresUsados() +
(ProblemaVerticeColoracionSA.numeroVertices * grafoColoreado.getInconsistencias());
}
public SolucionVerticeColoracionSA<V, A> getSolucion() {
return SolucionVerticeColoracionSA.create(grafoColoreado);
}

Solucin con Tupla2<V, Integer>:

public Tupla2<V, Integer> getAlternativa() {


Integer posVertice = Math2.getEnteroAleatorio(0, grafoColoreado.getVertices().size());
V vertice = grafoColoreado.getVertices().get(posVertice);
Integer color = Math2.getEnteroAleatorio(0, ProblemaVerticeColoracionSA.numeroColores);
return Tupla2.create(vertice, color);
}
public EstadoVerticeColoracionSA<V, A> next(Tupla2<V, Integer> t) {
GrafoColoreado<V, A> copia = grafoColoreado.copia();
copia.setColorVertice(t.getP1(), t.getP2());
return create(copia);
}

Solucin con ParInteger:

public ParInteger getAlternativa() {


Integer posVertice = Math2.getEnteroAleatorio(0, grafoColoreado.getVertices().size());
Integer color = Math2.getEnteroAleatorio(0, ProblemaVerticeColoracionSA.numeroColores);
return ParInteger.create(posVertice, color);
}
public EstadoVerticeColoracionSA<V, A> next(ParInteger p) {
GrafoColoreado<V, A> copia = grafoColoreado.copia();
copia.setColorVertice(copia.getVertices().get(p.getP1()), p.getP2());
return create(copia);
}

530

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

7.5.9 Habitaciones de hospital.


El Hospital Virgen del Roco ha decidido modificar la forma en que se
asignan las habitaciones a los pacientes en el rea de maternidad. Tras
realizar un estudio de idoneidad, se ha determinado que los factores ms
influyentes a la hora de determinar el nivel de satisfaccin con la
asignacin de habitaciones para cada una de las madres son los que se
muestran en la siguiente tabla. En la ltima columna se punta del 1 al 5
el impacto que tiene el factor en el nivel de satisfaccin. A mayor impacto,
mejor ser la relacin entre las madres.
Factor
Nacionalidad
Sexo del beb
Habitacin single

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

ir desde 0 hasta el nmero de habitaciones menos 1.


-

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.

Map<Madre, Integer> asignacion.

Este objeto contendr la asignacin realizada de una madre a una


habitacin del hospital representada por el nmero de la habitacin.

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)

El mtodo getNumeroPacientesHabitacion devuelve el nmero de pacientes asignadas a la habitacin


que se pasa por parmetro.
La funcin cumpleRestriccionEspacio() devuelve un boolean indicando si todas las habitaciones
tienen menos de 2 pacientes en su interior.

Nota: Puede hacer uso de las funciones recogidas en el siguiente UML excepto aquellas funciones que deba
implementar.

532

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Solucin:

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:

SolucionAsignacionHospital, que contiene las asignaciones y el nivel de satisfaccin global


de las madres.

Objetivo:

// TODO
Maximizar el valor de la propiedad satisfaccionGlobal.

Estado inicial:
Alternativas:

Asignacin aleatoria de madres a habitaciones, cumpliendo la restriccin de que no puede


haber ms de dos personas por habitacin.
Operadores:
Reasignar (Eliminar una madre de la habitacin donde se encuentra y se le asigna otra).
A = { (r, m, h) : m [ 0, madres.size() ) , h [ 0, habitaciones ) }

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>

public EstadoAsignacionHospitalSA next(AlternativaAsignacionHospital a) {


EstadoAsignacionHospitalSA e = copia();
if (getNumeroPacientesHabitacion(a.habitacion) <= 1) {
e.asignacion.put(ProblemaAsignacionHospital.madres.get(a.madre), a.habitacion);
}
calculaSatisfaccionGlobal();
return e;
}

public double getObjetivo() {


double objetivo = 0;
if (!cumpleRestriccionEspacio()) {
objetivo += ProblemaAsignacionHospital.habitaciones * 5;
}
objetivo -= satisfaccionGlobal;
return objetivo;
}

public AlternativaAsignacionHospital getAlternativa() {


AlternativaAsignacionHospital a = null;
ParEntero pr = new ParEntero(
Math2.getEnteroAleatorio(0, ProblemaAsignacionHospital.madres.size()),
Math2.getEnteroAleatorio(0, ProblemaAsignacionHospital.habitaciones)
);
a = AlternativaAsignacionHospital.createReasignar(pr.madre, pr.habitacion);
return a;
}

public int getNumeroPacientesHabitacion(final Integer habitacion) {


return Iterables.size(Iterables.filter(asignacion.values(),
new Predicate<Integer>() {
public boolean apply(Integer h) {
return h.equals(habitacion);
}
}));
}

534

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

7.6 Problemas de LIGA.


Para todos los problemas se pide:
A. Realice en primer lugar la ficha para determinar las propiedades compartidas del problema, las
propiedades bsicas y derivadas del estado, etc.
B. Realice un algoritmo que resuelva el problema especificado mediante la tcnica de Simulated Annealing.
Para ello, implemente en el siguiente orden las clases con sus correspondientes interfaces (utilice el
modelado de clases visto en prcticas/teora):
1. Implemente la clase XXXXXX que contenga las propiedades de los objetos que contendrn la
informacin acerca de una caractertica.
2. Para implementar el ProblemaSA necesitar definir el tipo de:
EstadoXXXXXXsSA (E).
Solucion XXXXXXs (S)
Alternativa (A).
3. Implemente la clase Problema XXXXXXsSA en la cual se definen compartidas del problema as
como los constructores y mtodos para crear estados iniciales.
4. Implemente la clase Estado XXXXXXsSA que implemente EstadoSA. En dicha clase se
implementarn todos los mtodos definidos en la interfaz EstadoSA.
5. Realice un test de prueba. Utilice el ejemplo proporcionado para validar el resultado obtenido.
En l se indica el fichero que contiene las caractersticas.

535

7.6.1 Seleccin de caractersticas.


Se dispone de un conjunto de ejemplos que est representado por una lista de
caractersticas que lo definen. Cada una de estas caractersticas permite clasificar a
qu tipo pertenece cada uno de los ejemplos con una precisin determinada.
El problema con el que nos encontramos es que en esa lista de caractersticas,
existen algunas que introducen ruido, es decir, que si seleccionamos dos
caractersticas incompatibles entre ellas, la precisin total disminuye.
Por ejemplo, suponga que para conocer a qu tipo pertenece una flor, disponemos de informacin tanto de la
longitud y el ancho del ptalo como de la longitud y el ancho del spalo, y a partir de esas caractersticas
queremos determinar si la flor es de tipo Setosa, Versicolor o Virgnica (wikipedia).
Cada una de estas caractersticas permite definir a qu tipo pertenece la flor con una precisin determinada,
pero si las combinamos entre ellas, la precisin total aumenta, aun cuando entre ellas existan incompatibilidades.
En las siguientes tablas se muestran un ejemplo ilustrativo de caractersticas y de posibles combinaciones:

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

7.6.2 Rendimiento de mviles.


Con el fin de aumentar el rendimiento de los nuevos dispositivos mviles, se ha
diseado un nuevo sistema operativo basado en restricciones que permite predecir
qu recursos sern usados en cada momento y, en funcin de esta prediccin,
minimizar el gasto de energa mediante el apagado y encendido de proveedores de
informacin.
Entendemos por proveedor de informacin una aplicacin, residente en el sistema operativo, que es capaz de
aportar cierta informacin a las aplicaciones que se ejecutan en el dispositivo. Cada proveedor de informacin
permite suministrar informacin a una o ms aplicaciones. De este modo, un proveedor puede cubrir las
necesidades de una serie de aplicaciones del terminal. En el ejemplo, podemos ver que el proveedor P1 aporta
la informacin necesaria para las aplicaciones A, B y D. De igual forma, el proveedor P2 lo hace para las
aplicaciones B y C.
Sin embargo, esta informacin no es gratuita, sino que los proveedores consumen una cierta cantidad de energa.
Esta energa medida en Ah/h es propia de cada proveedor de informacin.
El objetivo es encontrar, a travs de un algoritmo de Simulated Annealing, una configuracin de proveedores
que permita dar cobertura a la mayor parte de las aplicaciones teniendo en cuenta que existe un lmite de
consumo previamente determinado. Esto es, conseguir que con los proveedores seleccionados se puedan
ejecutar la mayor cantidad de aplicaciones que el sistema operativo est procesando sin exceder el lmite de
energa.
Ejemplo: La siguiente tabla muestra un ejemplo de datos de entrada. Para el conjunto de actividades {A, B, C, D,
E, F, G} y el conjunto de proveedores de informacin {P1, P2, P3, P4, P5, P6} con los consumos abajo indicados.
Teniendo en cuenta que el lmite de consumo es por ejemplo 350 Ah/h, la mejor configuracin se proveedores
seleccionados ser {P2, P3, P5}, que dara soporte a las aplicaciones {A, B, C, D, E, F}.

Proveedor
P1
P2
P3
P4
P5
P6

Aplicaciones cubiertas
{A, B, D}
{B, C, E}
{A, D}
{E, G}
{F}
{F, G}

Gasto de batera (Ah/h)


200
130
120
300
70
150

537

7.6.3 Autobuses panormicos.


Una empresa desea implantar un servicio de autobuses panormicos en una
ciudad.
Se dispone de la ubicacin de los diferentes monumentos as como de la
popularidad del mismo obtenida partiendo de una encuesta realizada por
internet.
Con la ubicacin la empresa ha calculado la distancia al punto ms
cercano a un recorrido base proporcionado por las autoridades locales
en funcin de las ubicaciones de los carriles bus y al estado habitual del
trfico. Este recorrido base tiene una duracin de 40 minutos y se realiza
obligatoriamente. Para visitar un monumento el autobs abandona el
recorrido base y lo retoma en el mismo punto. La distancia incluye el
recorrido de ida y vuelta desde el recorrido base al monumento.
La popularidad se mide en los votos recibidos por cada uno de los monumentos.
Por otro lado se desea que el recorrido completo no supere las dos horas lo que permitir tener una cadencia
de mxima 30 minutos por parada con slo 4 autobuses.
Si se supone que la velocidad del autobs es constante de 20 km/h y se desprecia el tiempo que se detenga en
las posibles paradas calcule el recorrido a realizar (con un tiempo total inferior a 2 horas en las que se incluyen
los 40 minutos del recorrido base) que permita a los turistas visitar el conjunto de monumentos de mayor
popularidad.
Nota: Use como unidad de tiempo el minuto.
Ejemplo de datos:
Monumento Votos Distancia
1
2
3
5
6
7
8
9
10

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

7.6.4 Alineacin de fltbol.


El entrenador de un equipo de ftbol quiere un programa que le diga cul es la mejor
alineacin para sacar al campo considerando los jugadores que tiene en el equipo. Para
ello se considerar tanto la calidad de los jugadores como la afinidad de la plantilla, es
decir, cmo de bien se entienden los jugadores entre ellos (dos jugadores se entendern
bien si son de la misma nacionalidad o si sus edades no difieren en ms de un ao). Por
ello, no siempre la mejor alineacin es la que contienen a los mejores jugadores.
Para resolver este problema, tenga en cuenta las siguientes consideraciones:
1) El entrenador siempre juega con una formacin 4-4-2, esto significa que, para crear su alineacin,
necesita 1 portero (P), 4 defensas (DF), 4 mediocentros (M) y 2 delanteros (DL).
2) Cada jugador puede saber jugar en ms de una posicin. En concreto, para cada jugador, se conocer su
nombre (String), su edad (Integer), su nacionalidad (String), las posiciones en las que puede jugar (enum
Posicion {P, DF, M, DL}) y su calidad (Integer entre 0 y 100).
3) La afinidad slo afecta entre posiciones colindantes, es decir, entre el portero y los defensas (4), entre
los defensas y los mediocentros (16), y entre los mediocentros y los delanteros (8), as como entre los
jugadores de una misma posicin, es decir, entre los propios defensas (6), los mediocentros (6) y los
delanteros (1). Por ello, la afinidad de la alineacin es un valor que se mide entre 0 y 41 ya que existen
41 posibles afinidades entre jugadores (4+16+8+6+6+1). Si existe una afinidad entre dos jugadores, se
suma uno, si no, se suma 0.
4) El valor de una alineacin se medir multiplicando la calidad media de los jugadores de la plantilla (valor
entre 0 y 100) por la afinidad de la plantilla (valor entre 0 y 41). Es decir, ser un nmero entre 0 y 4100.
Para expresar este valor entre 0 y 100, se dividir entre 41. La frmula resultante es:
valor_alineacion = calidad

afinidad_alineacion
41

5) El objetivo es maximizar el valor de la alineacin seleccionada.


Implemente un algoritmo de Simulated Annealing para resolver el problema (Considere que siempre existir
solucin). Por ejemplo, considere este equipo:
Nombre
Pepe
Juan
Casillas
Andrs
Roberto
Pedro
Carlos
Sara
Ramn
Cristina
Antonio
Rubn
Marcos

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

Una posible alineacin despus de


ejecutar el algoritmo sera:
P =
DF=
M =
DL=

Casillas
Pepe, Antonio, Andres, Carlos
Cristina, Ramn, Marcos, Juan
Roberto, Sara

Esta solucin tendra un valor de 23,


43 ya que la afinidad es 14 y la calidad
media es de 6863.

539

7.6.5 Plataforma de televisin digital.


El director estratgico de una plataforma de televisin digital dispone de un
presupuesto para realizar diversas actividades de ampliacin que permitan extender
la cobertura de la red de televisin digital. Con el fin de maximizar el nmero de
potenciales clientes que se vean beneficiados por esas medidas, sus asesores
pretenden disear un algoritmo de Simulated Annealing para escoger aquellas
actividades que permitan ampliar la cobertura de la red al nmero mximo de
potenciales clientes que se veran beneficiados por dicha ampliacin.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

7.6.6 El robo del siglo.

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

7.6.7 Buffet de abogados.


Un buffet de abogados ofrece servicios a domicilio y solo posee
un nico abogado que adems tiene una jornada laboral de N
horas. Por tanto, el buffet se ve obligado a elegir entre solo
algunos clientes para ofrecerles sus servicios aunque haya otros
clientes que se queden sin recibirlo. El cliente paga en funcin de
una oferta fija por hora. El objetivo es encontrar, a travs de un
algoritmo de Simulated Annealing aquellos servicios que
maximizan los beneficios del buffet de abogados, aunque para
ello se deje a algunos clientes sin dicho servicio.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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.

8.1.1 Contrato de un tipo y casos de prueba relacionados.


Para cada tipo es conveniente definir un contrato. Un contrato es un documento en el que se define la
funcionalidad ofrecida por un tipo y por lo tanto el uso del mismo por parte de sus posibles clientes. Los detalles
de cmo definir un contrato ya los vimos en captulos anteriores.
El conjunto de casos de prueba se presenta como una tabla. Cada caso de prueba da lugar a un test. Es decir, una
prueba del funcionamiento adecuado de un mtodo en un caso concreto.
En general entendemos por contrato de un tipo tanto el conjunto de aserciones sobre los mtodos del tipo
(invariantes, precondiciones, postcondiciones, condiciones de disparo de excepciones, etc.) como el conjunto de
casos de prueba adicionales. Estos casos de prueba adicionales pueden ser redundantes con las aserciones o
contemplar casusticas no tenidas en cuenta en las mismas. Un contrato ser ms completo que otro si (entre
las aserciones y los casos de prueba) tiene en cuenta ms casusticas de funcionamiento. De las aserciones de
un contrato pueden ser deducidos casos de prueba adicionales.
El conjunto de casos de prueba para un mtodo debe capturar lo mejor posible la casustica de utilizacin del
mtodo. Los casos de prueba incluirn los ejemplos de funcionamiento incluidos en el contrato. Adems incluirn
otros casos de prueba deducidos de las restricciones del contrato.
545

8.1.2 Implementacin de un tipo.


Para implementar la funcionalidad expresada por un contrato en Java, debemos construir una clase que ser la
implementacin del contrato. Una implementacin es una relacin entre un contrato y una clase concreta. La
clase deber cumplir el contrato definido para un tipo dado. Para comprobar que una clase implementa un
contrato se generarn un conjunto de casos de prueba.
Como se ha dicho, un tipo se implementar mediante una clase. En esta debemos definir los atributos,
constructores y mtodos (pblicos y privados) y para ello debemos tomar algunas decisiones.
Al implementar el estado y decidir el nmero de atributos:
-

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.

Al implementar los constructores:


-

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.

Los constructores deben dar valor a todos los atributos.

Debemos tener en cuenta el estado inicial y el invariante del contrato (deben cumplirse al finalizar la
ejecucin del constructor).

Si no puede garantizar el cumplimiento de ambas restricciones ante unos parmetros determinados, se


debe lanzar una excepcin.

Es recomendable que el constructor d un valor inicial a cada uno de los atributos.

Al implementar los mtodos:

546

Debemos respetar las signaturas establecidas en la interfaz.

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.

En la zona no definida en el contrato el mtodo puede ser implementado libremente.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

8.1.3 Requisitos no funcionales de un tipo.


Dado un contrato de un tipo que exprese las propiedades funcionales del mismo es posible encontrar varias
implementaciones posibles. Normalmente cuando queremos usar un tipo junto a las propiedades funcionales
del mismo, dependiendo del contexto, es conveniente exigir unas determinadas propiedades no funcionales para
el mismo. Algunas de estas propiedades no funcionales son la complejidad de los mtodos del tipo. Es decir una
medida del tiempo de respuesta del mtodo en funcin del tamao del objeto a considerar.
Para caracterizar una implementacin concreta desde un punto de vista externo debemos aadir, junto a la
funcionalidad del tipo, la complejidad de sus operaciones.

8.2 Tipos Bsicos.


Presentamos aqu varios tipos que sirven usualmente para implementar muchos de los tipos complejos vistos
previamente. Por ello es necesario conocerlos. Unos vienen proporcionados en el API de Java y otros no. Por
motivos didcticos para algunos tipos bsicos que ya vienen proporcionados en el entorno Java disearemos
otra versin bsica que slo contenga los aspectos esenciales. Estas versiones bsicas las nombraremos
empezando por Basic. Por cada tipo presentaremos tambin un posible constructor o factora del mismo.

8.2.1 Tipo LinkedList (BasicLinkedList).


Es un tipo de datos bsico a partir del cual pueden implementarse otros ms complejos. Se suele denominar lista
enlazada. El tipo es genrico y puede definirse recursivamente. Una lista enlazada es una lista vaca o un
elemento seguido de una lista que llamaremos cola. Una lista enlazada no vaca tiene un primer y un ltimo
elemento.
Propiedades esenciales:

isEmpty(): boolean, Verdadero si la lista est vaca.


size(): int, Nmero de elementos de la lista
get(int index):

E, Devuelve el elemento asociado en la posicin index, precondicin index >= 0 , index


< size(), y si no se cumple la precondicin se dispara la excepcin NoSuchElementException.
set(int index, E e): E, Modifica el elemento en posicin index, precondicin index >= 0 , index < size(),
y si no se cumple la precondicin se dispara la excepcin NoSuchElementException.

Operaciones:

add(E e): boolean, Aade el elemento e al final de la lista y por lo tanto, dicho elemento e, se convierte

en el ltimo elemento de la lista.


add(int index, E e): void, Inserta el elemento e en la posicin index y desplaza a la derecha los
elementos en posiciones mayores.
remove(int index): E, elimina el elemento asociado a la posicin index.

Constructor:

BasicLinkedList(): Construye una lista vaca.

547

Un ejemplo de lista enlazada de enteros es:


El primer elemento es 12, el ltimo 37, etc. Tal como se ve en el ejemplo anterior una lista enlazada es una
secuencia enlazada de entradas. Cada entrada es de un tipo privado que llamaremos BasicLinkedList.Entry<E>.
Este tipo consta de dos propiedades consultables y modificables: Element de tipo E para guardar un elemento y
Next de tipo BasicLinkedList.Entry<E> para guardar la entrada siguiente. El tipo BasicLinkedList puede
implementarse con dos atributos: first y last de tipo Entry<E> que son ambos null o guardan el primer y ltimo
entrada de la lista. Una lista vaca puede implementarse con ambos atributos a null. Podemos aadir, por
eficiencia otros atributos redundantes como size. Con esa implementacin las propiedades Empty, size() y la
operacin add(E) tienen una complejidad constante. Sin embargo, como puede comprobarse, las operaciones
add(int,E), get(int) y remove(int) tienen complejidades lineales en el tamao de la lista.
La implementacin posterior mantiene un invariante de la implementacin. Es decir una restriccin entre el valor
de los atributos elegidos. En este caso si size == 0 (lista vaca) los atributos first y last deben estar a null y
distintos de null en caso contrario. Se aade por eficiencia el atributo size para implementar la propiedad
derivada correspondiente.
El tipo LinkedList, ofrecido por Java, es una implementacin del tipo List basndose en el tipo BasicLinkedList
anterior. El tipo LinkedList implementa, adems de List, otros tipos.
Una posible implementacin sera:

public class BasicLinkedList<E> {


private Entry<E> first;
private Entry<E> last;
private int size;
// invariant size == 0 => first == null && last == null
// invariant size > 0 => first != null && last != null
public BasicLinkedList() {
super();
this.first = null;
this.last = null;
this.size = 0;
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public E get(int index) {
return entryInPos(index).getElement();
}
public E set(int index, E e) {
Entry<E> e1 = entryInPos(index);
E r = e1.getElement();
e1.setElement(e);
return r;
}

548

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

public boolean add(E e) {


Entry<E> e1 = new Entry<E>(e);
if (last == null) {
first = e1;
last = e1;
} else {
last.setNext(e1);
last = e1;
}
size++;
return true;
}
public void add(int index, E e) {
Preconditions.checkPositionIndex(index, size);
Entry<E> ne = new Entry<E>(e);
if (index == size) {
add(e);
} else if (index == 0) {
ne.setNext(first);
first = ne;
} else {
Entry<E> pe = entryInPos(index - 1);
ne.setNext(pe.getNext());
pe.setNext(ne);
}
size++;
}
private Entry<E> entryInPos(int index) {
Preconditions.checkElementIndex(index, size);
Entry<E> pe = first;
for (int p = 0; p < index; p++) {
pe = pe.getNext();
}
return pe;
}
public E remove(int index) {
Preconditions.checkElementIndex(index, size);
Entry<E> e = null;
E element;
if (index == 0) {
e = first;
first = first.getNext();
element = e.getElement();
} else {
Entry<E> pe = entryInPos(index - 1);
element = pe.getNext().getElement();
if (index == size - 1) {
last = pe;
} else {
pe.setNext(pe.getNext().getNext());
}
}
size--;
return element;
}
public String toString() {
String s = "{";
boolean prim = true;
for (Entry<E> e = first; e != null; e = e.getNext()) {
if (prim) {
prim = false;
s = s + e.getElement();
} else {
s = s + "," + e.getElement();
}
}
s = s + "}";
return s;
}

549

public class Entry<F> {


private F element;
private Entry<F> next;
public Entry(F element, Entry<F> next) {
super();
this.element = element;
this.next = next;
}
public Entry(F element) {
super();
this.element = element;
this.next = null;
}
public F getElement() {

return element;

public void setElement(F element) {

this.element = element; }

public Entry<F> getNext() {

return next;

public void setNext(Entry<F> next) {

this.next = next;

}
}

}
}

8.2.2 Tipo Lista dinmica (DynamicArray).


Un dynamic array de tipo E es un tipo de datos similar a un array de tipo E pero dnde podemos cambiar el
tamao en tiempo de ejecucin. Este cambio del tamao en tiempo de ejecucin es una operacin costosa pero
el tipo ofrece otros otras propiedades interesantes.
Las propiedades y operaciones bsicas del tipo son:

isEmpty(): boolean, Verdadero si el array est vaco.


size(): int, Nmero de elementos de la lista.
get(int index): E, Devuelve el elemento asociado a la posicin

index, precondicin index >= 0 , index <

size(), y si no se cumple la precondicin se dispara la excepcin NoSuchElementException.

set(int index, E e):

E, Modifica el elemento asociado a la posicin index, si index es mayor que la


capacidad la aumenta y rellena los huecos a null. El nuevo tamao es index + 1.

Y las operaciones:

add(E e): void, Aade el elemento

e al final de la lista y por lo tanto, dicho elemento e, se convierte en


el ltimo elemento de la lista.
add(int index, E e): void, Inserta el elemento e en la posicin index y desplaza a la derecha los
elementos en posiciones mayores.
remove(int index): E, Elimina el elemento e de la lista.

Y los constructores:

550

DynamicArray(int capacity): Construye un nuevo capacidad capacity y todas las casillas a null.
DynamicArray(int capacity, DynamicArray<E> d):

Construye un nuevo array dinmico con capacidad


capacity, copia los elementos de d en las primeras casillas y pone el resto a null. Tiene como
precondicin que capacity sea mayor que la propiedad capacity de d. Si no se cumple la precondicin se
dispara la excepcin IllegalArgumentException.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Una implementacin posible para este tipo bsico puede ser:

public class DynamicArray<E> {


private
private
private
private
private

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

public boolean add(E e) {


if (size == 0) {
elements = (E[]) Array.newInstance(e.getClass(), capacity);
}
if (size == capacity) {
grow(capacity * GROWING_FACTOR);
}
elements[size] = e;
size++;
return true;
}
public void add(int index, E e) {
Preconditions.checkPositionIndex(index, size);
add(e);
// size ya ha quedado aumentado
for (int i = size - 1; i > index; i--) {
elements[i] = elements[i - 1];
}
elements[index] = e;
}
public E remove(int index) {
Preconditions.checkElementIndex(index, size);
E e = elements[index];
for (int i = index; i < size - 1; i++) {
elements[i] = elements[i + 1];
}
size--;
return e;
}
public E[] toArray() {
E[] r = Arrays.copyOf(elements, size);
return r;
}
public String toString() {
String s = "{";
boolean prim = true;
for (int i = 0; i < size; i++) {
if (prim) {
prim = false;
s = s + elements[i];
} else {
s = s + "," + elements[i];
}
}
s = s + "}";
return s;
}
}

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

8.2.3 Tipo Conjunto de bits (BitSet).


El tipo Arrays de bits (Bitset) sirve para gestionar un vector de bits que puede crecer cuando se le necesite. Es un
tipo bsico ofrecido por el entorno de Java. Esencialmente los valores del tipo son secuencias de bits. Cada bit
representa un valor booleano y puede ser indexado por un entero no negativo {0, 1, 2, ..., n-1}. El valor de cada
bit puede ser consultado, modificado o combinado con otro bit mediante las operaciones lgicas NOT, AND, OR
inclusivo y OR exclusivo. Desde otro punto de vista un BitSet puede considerarse como la definicin de un
conjunto de enteros en el rango [0, n) donde n es el tamao (size). Es decir el nmero de bits que se usan en
cada momento. El conjunto definido por cada valor viene dado por los bits puestos a true. El conjunto vaci
viene representado por todos los bits a false. La cardinalidad por el nmero de bits a true.
Cada BitSet tiene un tamao que es el nmero de bits que usa en cada momento. La implementacin del tipo
hace que la mayora de las operaciones puedan ser llevadas a cabo en tiempo constante. La implementacin
puede hacerse de forma similar a un array dinmico pero ahora accediendo a cada uno de los bits con las
operaciones disponibles para ello. El array que almacena los bits crece de tamao cunado sea necesario.
La funcionalidad que ofrece, como propiedades especficas de BitSet, es:
Mtodo

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()

Nmero de bits a true.

clear()

Coloca todos los bits a false.

flip(int fromIndex, int toIndex)

Hace el complemento sobre el bit indicado ( operacin lgica not


sobre el bit).
Hace el complemento sobre los bits indicados.

get(int bitIndex)

Valor del bit indexado por bitIndex.

get(int fromIndex, int toIndex)

Bitset compuesto por los bits indicados.

flip(int bitIndex)

isEmpty()

Verdadero si existen bits en this y en set indexados por el mismo


entero y con valor true.
Verdadero si todos los bit tienen valor false.

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)

Hace true el bit indexado.

set(int fromIndex, int toIndex)

Hace true desde fromIndex hasta toIndex.

set(int bitIndex, boolean


booleanValue)
set(int fromIndex, int toIndex,
boolean booleanValue)

Coloca el bit especificado al valor indicado.

size()

Devuelve el espacio en bits ocupado por el array.

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)

Coloca el rango de bits especificado al valor indicado.

Los constructores disponibles son:

BitSet(), Crea un array de bit con todos los bits a false.


BitSet(int nbits), Crea un array de bits suficientemente grande para almacenar

nbits que pueden ser

indexado de 0 a nbits-1 y con todos los bits a false.


553

8.2.4 Tipo HashTable (BasicHashTable).


Una tabla hash es un tipo de datos que asocia claves con valores. Desde un punto de vista abstracto podemos
considerarlo con un conjunto de pares clave-valor. La clave de tipo K y los valores de tipo V. Esencialmente
consiste en transformar la clave en un nmero que la tabla hash utiliza para localizar el valor deseado.
El tipo usa un tipo interno BasicHashTable.Entry<K,V>. Los valores de este tipo representan pares clave-valor.
Sus propiedades consultables y modificables son: Key: K y Value: V. Para disear el tipo partimos de dos
elementos: una funcin hash y un array dinmico cuyos elementos son listas enlazadas de pares clave-valor.
Usando lo visto anteriormente este tipo podra ser DynamicArray<LinkedList<Entry<K,V>>>.
La funcin hash tiene el prototipo int hash(int a) y tiene como objetivo convertir el entero a en otro del rango
[0, m) con la mayor dispersin posible. Es decir que si se toman dos enteros distintos la funcin hash los
transforme, con alta probabilidad, en otros dos tambin distintos. Veremos algunos ejemplos de esta funcin.
Asumiendo un nmero n de entradas (pares clave-valor) el mecanismo de la tabla hash consiste en agruparlas
en m grupos identificados por un entero de 0 a m-1. Todas las entradas que estn en el grupo i cumplen la
condicin: i == hash(e.getKey().hashCode()). Donde hashCode() es un mtodo disponible para todos los
objetos en Java y hash() es un mtodo de la tabla hash que transforma un entero cualquiera en otro en el rango
0..m-1. Cada grupo i es un agregado de entradas que modelamos mediante una lista enlazada. Una propiedad
importante del estado de la tabla hash es el factor de carga n/m (load factor). Es el nmero medio de entradas
en cada grupo. Llamaremos capacidad (capacity) al valor m anterior, tamao (size) al n. Asumimos un factor de
carga de referencia (reference load factor).
El mecanismo es el siguiente: a partir de una tabla vaca se van aadiendo entradas. Cada entrada se coloca en
el grupo correspondiente. Cuando se supera el factor de carga de referencia se amplia la capacidad y se sitan,
de nuevo, cada una de las entradas en los nuevos grupos. Este mecanismo interno lo llamaremos rehash.
Mtodo

Descripcin

clear()

Vaca cada grupo, el nm. de grupos a getLengthInitial(), isInitial() a true.

containsKey(Object key)

Verdadero si contiene la clave key.

get(int index)

Grupo de entradas asociadas al nmero index.

get(Object key)

Devuelve el valor asociado a key.

getEntrySize()

Nmero de entradas.

getLength()

Nmero de grupos en que se agrupan las entradas.

getLengthInitial()

Nmero inicial de grupos.

getLoadFactor()

Cociente entre el nmero de entradas y el nmero de grupos.

getLoadFactorMax()

Factor de carga mximo.

getLoadFactorMin()

Factor de carga mnimo.

hash(int key)

Funcin que convierte un entero en otro en el rango 0..getLength()-1.

isInitial()

Comprueba si se ha realizado rehash por primera vez.

put(K key, V value)

Introduce una entrada clave-valor en el map.

rehash()

Cambia el nmero de grupos y redistribuye las entradas en los nuevos grupos.

Remove(Object key)

Elimina la clave y su valor asociado.

size()

Devuelve el nmero de entradas que contiene.

554

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Los constructores son:

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.

Los invariantes que mantiene son:

No existen dos pares clave-valor con la misma clave


Todos los pares en el mismo grupo i cumplen i == hash(e.getKey().hashCode()).

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.

Una implementacin posible es:


public class BasicHashTable<K, V> {
private int capacity;
private final int GROWING_FACTOR = 2;
private int initialCapacityOfGroups;
private int size;
private double loadFactorReference;
private DynamicArray<DynamicArray<Entry<K, V>>> elements;
public BasicHashTable(int capacity, int initialCapacityOfGroups, double loadFactorReference) {
super();
this.capacity = capacity;
this.initialCapacityOfGroups = initialCapacityOfGroups;
this.loadFactorReference = loadFactorReference;
initial();
}
public BasicHashTable() {
super();
this.capacity = 10;
this.initialCapacityOfGroups = 2;
this.loadFactorReference = 0.75;
initial();
}
private void initial() {
elements = new DynamicArray<DynamicArray<Entry<K, V>>>(capacity);
for (int i = 0; i < capacity; i++) {
elements.add(new DynamicArray<Entry<K, V>>(initialCapacityOfGroups));
}
}
private int hash(int a) {
return a % capacity;
}

555

private void rehash(int newCapacity) {


DynamicArray<DynamicArray<Entry<K, V>>> oldElements = elements;
int oldCapacity = capacity;
capacity = newCapacity;
initial();
Entry<K, V> e;
for (int i = 0; i < oldCapacity; i++) {
for (int j = 0; j < oldElements.get(i).size(); j++) {
e = oldElements.get(i).get(j);
put(e);
}
}
}
private double getLoadFactor() {
double sd = size;
return sd / capacity;
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
private Entry<K, V> getEntry(K key) {
int c = hash(key.hashCode());
Entry<K, V> r = null;
for (int i = 0; i < elements.get(c).size(); i++) {
if (key.equals(elements.get(c).get(i).getKey())) {
r = elements.get(c).get(i);
}
}
return r;
}
public V get(K key) {
Entry<K, V> e = getEntry(key);
V r = null;
if (e != null) {
r = e.getValue();
}
return r;
}

public class Entry<K1, V1> {


private K1 key;
private V1 value;
public Entry(K1 key, V1 value) {
super();
this.key = key;
this.value = value;
}

private void put(Entry<K, V> e) {


int c = hash(e.getKey().hashCode());
elements.get(c).add(e);
if (getLoadFactor() > loadFactorReference) {
rehash(capacity * GROWING_FACTOR);
}
}

public K1 getKey() {
return key;
}
public void setKey(K1 key) {
this.key = key;
}

public V put(K key, V value) {


Entry<K, V> e = getEntry(key);
V r = null;
if (e == null) {
put(new Entry<K, V>(key, value));
size++;
} else {
r = e.getValue();
e.setValue(value);
}
return r;
}

public V1 getValue() {
return value;
}
public void setValue(V1 value) {
this.value = value;
}
public String toString() {
return "(" + key + "," + value + ")";
}
}

556

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


public V remove(K key) {
int c = hash(key.hashCode());
V r = null;
int p = -1;
for (int i = 0; i < elements.get(c).size(); i++) {
if (key.equals(elements.get(c).get(i).getKey())) {
r = elements.get(c).get(i).getValue();
p = i;
}
}
if (p >= 0) {
elements.get(c).remove(p);
size--;
}
return r;
}
public String toString() {
String s = "{";
boolean prim = true;
for (int i = 0; i < capacity; i++) {
for (int j = 0; j < elements.get(i).size(); j++) {
if (prim) {
prim = false;
s = s + elements.get(i).get(j);
} else {
s = s + "," + elements.get(i).get(j);
}
}
}
s = s + "}";
return s;
}

8.2.5 Tipo ArrayMap.


Un ArrayMap es un tipo de datos que asocia claves con valores pero donde las claves son enteros no
excesivamente grandes.
Mtodo

Descripcin

void clear()

Hace que ninguna clave tenga asociado un valor.

boolean containsKey(int key)

Verdadero si contiene la clave key.

E get(int index)

Grupo de entradas asociadas al nmero index.

int getCapacity()

Devuelve la capacidad.

E put(int key, V value)

Asocia el valor value a la clave key.

E remove(int key)

Hace que la clave key no tenga asociado ningn valor.

int size()

Devuelve el nmero de entradas.

557

8.3 rboles.

8.3.1 Tipo Tree.


Un rbol (Tree<E>) es un tipo de datos que puede definirse recursivamente. Desde ese punto de vista un rbol
es: un rbol vaco o un rbol con una etiqueta de tipo E y una secuencia de hijos que son tambin rboles. Los
rboles son tipos tiles para implementar muchos otros tipos.
Mtodo

Descripcin

int getNumChildren()

Nmero de hijos.

int size()

Nmero de etiquetas del rbol.

boolean isEmpty()

Verdadero si el rbol est vaco. Es decir no tiene ninguna etiqueta.

boolean isRoot():

Verdadero si es la rbol contiene la raz.

boolean isLeaf()

Verdadero si el nmero de hijos no vaco es 0.

E getLabel()

Devuelve la etiqueta del rbol. Precondicin: que el rbol no est vaco.

void setLabel(E label)

Cambia el valor de la etiqueta. Precondicin: que la etiqueta no sea null.

Tree<E> getParent()

Devuelve el rbol padre.

int getDepth()

La profundidad del rbol. Es decir la longitud del camino hasta la raz. La


raz tiene profundidad 0 y si es vaca -1.

int getHeight()

La altura de un rbol. Es decir la longitud del camino ms largo hasta sus


hojas. La altura de un rbol con una etiqueta y sin hijos es 0. Si es vaco -1.

Tree<E> getElement(int index)

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)

Cambia el valor del rbol ubicado en posicin index y devuelve el antiguo


rbol en esa posicin. Si index es mayor o igual a numChildren() entonces
numChildren queda actualizado a index+1 y los rboles en posiciones no
definidas previamente se hacen vacos. Precondicin: rbol no vaco.

boolean add(Tree<E> element)

Aade un hijo ms a la derecha de los dems. Precondicin: rbol no vaco.

void add
(int index, Tree<E> element)

Aade un hijo en la posicin index y desplaza a la derecha los de ndice


mayor a index. Tiene como precondicin que index sea mayor o igual a
cero y menor o igual a getNumChildren() y que el rbol no est vaco.

Tree<E> remove(int index)

Elimina el hijo en posicin index desplazando a la izquierda los de ndice


mayor. Devuelve el rbol en posicin index. Precondicin: que index sea
0 y < getNumChildren() y que el rbol no est vaco.

558

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Una posible implementacin es:

public class Tree<E> {


private
private
private
private

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

public boolean isLeaf() {


return isEmpty() || getNumChildren() == 0;
}
public E getLabel() {
Preconditions.checkState(!isEmpty());
return label;
}
public void setLabel(E label) {
Preconditions.checkNotNull(label);
this.label = label;
}
public Tree<E> getParent() {
return parent;
}
private void setParent(Tree<E> parent) {
this.parent = parent;
}
public Tree<E> getElement(int index) {
Preconditions.checkState(!isEmpty());
Tree<E> r = elements.get(index);
if (r == null) {
r = new Tree<E>();
}
return r;
}
public Tree<E> setElement(int index, Tree<E> element) {
Preconditions.checkState(!isEmpty());
element.setParent(this);
Tree<E> r = this.elements.set(index, element);
return r;
}
public int getDepth() {
int r = -1;
if (!isEmpty()) {
r = 0;
Tree<E> p = getParent();
while (p != null) {
p = p.getParent();
r++;
}
}
return r;
}
private int getHeight(Tree<E> t) {
int r;
if (t == null || t.isEmpty()) {
r = -1;
} else if (t.getNumChildren() == 0) {
r = 0;
} else {
r = -1;
for (int i = 0; i < t.getNumChildren(); i++) {
r = Math.max(r, getHeight(t.getElement(i)));
}
r++;
}
return r;
}
public int getHeight() {
return getHeight(this);
}

560

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

public boolean add(Tree<E> element) {


Preconditions.checkState(!isEmpty());
element.setParent(this);
boolean r = elements.add(element);
return r;
}
public void add(int index, Tree<E> element) {
Preconditions.checkState(!isEmpty());
element.setParent(this);
elements.add(index, element);
}
public Tree<E> remove(int index) {
Preconditions.checkState(!isEmpty());
Tree<E> r = elements.remove(index);
r.setParent(null);
return r;
}
//
public String toString() {
return toString(this);
}
public E[] toArray() {
E[] r = Arrays.copyOf(this.elements, size);
return r;
}
private String toString(Tree<E> t) {
String r;
boolean prim = true;
if (t == null || t.isEmpty()) {
r = " ";
} else if (t.getNumChildren() == 0) {
r = t.getLabel().toString();
} else {
r = t.getLabel().toString() + ";";
for (int i = 0; i < t.getNumChildren(); i++) {
if (prim) {
r = r + toString(t.getElement(i));
prim = false;
} else {
r = r + "," + toString(t.getElement(i));
}
}
}
return "(" + r + ")";
}
}

561

8.3.2 Funciones sobre rboles.


En la clase Trees agrupamos un conjunto de mtodos adecuados para trabajar con rboles. La funcionalidad de
los mismos es:
Mtodo
Iterable<Tree<E>> breadth(Tree<E> t)

Iterable<Tree<E>> depth(Tree<E> t, int pos)

boolean equals(Tree<E> t1, Tree<E> t2)


boolean isOrdered
(Tree<E> t, Comparator<? super E> cmp)
Iterable<Tree<E>> inOrder(Tree<E> t)
Iterable<Tree<E>> postOrder(Tree<E> t)
Iterable<Tree<E>> preOrder(Tree<E> t)
Tree<E> clone(Tree<E> t)
String toString(Tree<E> t)

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.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

8.3.3 rbol de bsqueda (SearchTree).


Los rboles de bsqueda tambin llamados BST (Binary Search Tree) son estructuras de datos adecuadas para
implementar agregados de datos con operaciones de bsqueda, aadir, eliminar elementos.
Los rboles de bsqueda (SearchTree) son rboles que mantienen ordenadas sus etiquetas.
Suponiendo un orden definido sobre las etiquetas y siendo a la etiqueta de la raz, b una etiqueta cualquiera del
subrbol izquierdo y c una cualquiera del subrbol derecho entonces se cumple b < a < c. Los rboles de
bsqueda tienen operaciones para aadir, eliminar, buscar una etiqueta y contar cuantas hay. Un rbol de
bsqueda dispone, por tanto de los mtodos:
interface SearchTree<E> {
int size();
boolean isEmpty();
E element();
boolean add(E e);
E remove(E e);
E remove();
boolean contains(E e);
Comparator<E> comparator();
}

La semntica de los mtodos ser:


Mtodo
int size()
boolean isEmpty()
E element()
boolean add(E e)
E remove(E e)
E remove(E e)
boolean contains(E e)
Comparator<E> comparator()

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

8.3.4 bol binario ordenado (BinarySortedTree).


Un rbol binario ordenado es un subtipo de rbol binario de bsqueda donde las
etiquetas del subrbol izquierdo son menores que la etiqueta de la raz y esta, a su
vez, menor que las etiquetas en el rbol derecho. Cada subrbol es un rbol
binario ordenado.
En este tipo de rboles la bsqueda de una etiqueta (mtodo contains(E e)) se
implementa siguiendo un camino que parte de la raz y va decidiendo en cada
etiqueta del subrbol correspondiente si parar (si ha encontrado la etiqueta) o
continuar por el subrbol izquierdo o derecho segn que la etiqueta a buscar sea
menor o mayor que la del subrbol. Si se encuentra un rbol vaco es que el agregado no contiene la etiqueta.
La complejidad del caso peor para este mtodo es (n). Donde n es el nmero de etiquetas en el agregado y el
caso peor es cuando el rbol degenera en una lista.
La operacin de aadir (add(E e)) sigue la secuencia anterior y convierte el rbol vaco encontrado en un rbol
con la etiqueta a aadir. El primer elemento del agregado (element()) se encuentra siguiendo el hijo izquierdo
hasta que no haya otro hijo izquierdo. La operacin de eliminar (remove(E e)) elimina la etiqueta e. Primero la
busca, si la encuentra y est en una hoja lo elimina, si el rbol tiene un hijo lo sustituye por su subrbol. En otro
caso el rbol tiene dos hijos. Sea R el valor de la etiqueta a borrar y S la etiqueta siguiente o anterior a la misma
en inorden. Entonces se trata de sustituir R por S y luego eliminar S. Abajo se muestran las dos posibilidades.

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

@inv: numChildren()==2 || numChildren()==0


add(E e)
comparator()

Mtodos que aade

contains(E e)
remove(Object e)
size()

564

Se aade una nueva etiqueta al rbol.


Devuelve el Comparator del rbol.
El rbol contiene una etiqueta igual a e.
Se elimina la etiqueta e del rbol.
Nmero de etiquetas.

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

8.3.5 rbol AVL (AVLTree).


Los rboles AVL (Adelson-Velskii y Landis) son rboles binarios ordenados casi equilibrados. Es decir, adems de
las restricciones de los rboles binarios de bsqueda, se cumple la siguiente restriccin:
En cada subrbol la altura del hijo izquierdo no difiere en ms de una unidad de la altura del hijo derecho.
(@inv: q.from(this).all(#(t)(Math.abs(t.get(0).height(),t.get(1).height())<=1))

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:

Cuando un rbol AVL se desequilibra podemos nos


encontraremos con alguno de los cuatro casos de la
figura de al lado.
Para conseguir equilibrar el rbol debemos transformar
el rbol con las operaciones anteriores segn se indica
en la figura de al lado.

565

8.3.6 rbol Rojo-Negro (RedBlackTree).


Los rboles rojo-negro son rboles binarios ordenados que estn aproximadamente equilibrados en el sentido
que se explica a continuacin.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Una variante que se da al rbol rojo-negro es la de tratarlo como un rbol binario de bsqueda cuyas aristas, en
lugar de nodos, son coloreadas de color rojo o negro, pero esto no produce ninguna diferencia. El color de cada
nodo en la terminologa de este artculo corresponde al color de la arista que une el nodo a su padre, excepto la
raz, que es siempre negra (por la propiedad 2) donde la correspondiente arista no existe.
Al nmero de nodos negros de un camino se le denomina "altura negra".

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

8.3.7 rbol montculo (HeapTree).


Los montculos binarios (HeapTree) son rboles binarios ordenados con las siguientes restricciones:
-

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.

LA eliminacin de la etiqueta en la raz se hace intercambindola por la etiqueta en la posicin ms a la derecha


del ltimo nivel del rbol. Posteriormente, para mantener el invariante, se intercambia la raz por la etiqueta de
alguno de sus hijos.

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):
-

El nodo raz se almacena en la posicin 0 del vector.


Los hijos de un nodo almacenado en la posicin k se
almacenan en las posiciones 2k+1 y 2k+2
respectivamente.

Un rbol binario completo guardado como vector

Por tanto, el padre de un nodo que est en la posicin k ( k > 0 ) est almacenado en la posicin (k - 1) / 2.
568

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

8.3.8 rbol B (BTree).


Un rbol B (BTree) es una generalizacin de los rboles AVL. Es un rbol donde cada etiqueta es de tipo List<E>.
Es decir tenemos asociada a la raz de cada rbol una lista de etiquetas.

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

En el mejor de los casos,la altura de un rbol-B es: logM(n)


En el peor de los casos,la altura de un rbol-B es:log(M/2)(n)
Donde M es el nmero mximo de hijos que puede tener un nodo.
Cada elemento de un nodo interno acta como un valor separador, que lo divide en subrboles. Por ejemplo, si
un nodo interno tiene tres nodos hijo, debe tener dos valores separadores o elementos a1 y a2. Todos los valores
del subrbol izquierdo deben ser menores a a1, todos los valores del subrbol del centro deben estar entre a1 y
a2, y todos los valores del subrbol derecho deben ser mayores a a2.
Los nodos internos de un rbol B, es decir los nodos que no son hoja, usualmente se representan como un
conjunto ordenado de elementos y punteros a los hijos. Cada nodo interno contiene un mximo de U hijos y, con
excepcin del nodo raz, un mnimo de L hijos. Para todos los nodos internos exceptuando la raz, el nmero de
elementos es uno menos que el nmero de punteros a nodos. El nmero de elementos se encuentra entre L-1 y
U-1. El nmero U debe ser 2L o 2L-1, es decir, cada nodo interno est por lo menos a medio llenar. Esta relacin
entre U y L implica que dos nodos que estn a medio llenar pueden juntarse para formar un nodo legal, y un
nodo lleno puede dividirse en dos nodos legales (si es que hay lugar para subir un elemento al nodo padre). Estas
propiedades hacen posible que el rbol B se ajuste para preservar sus propiedades ante la insercin y eliminacin
de elementos.
Los nodos hoja tienen la misma restriccin sobre el nmero de elementos, pero no tienen hijos, y por tanto
carecen de punteros.
El nodo raz tiene lmite superior de nmero de hijos, pero no tiene lmite inferior. Por ejemplo, si hubiera menos
de L-1 elementos en todo el rbol, la raz sera el nico nodo del rbol, y no tendra hijos.
Un rbol B de altura n+1 puede contener U veces por elementos ms que un rbol B de profundidad n, pero el
costo en la bsqueda, insercin y eliminacin crece con la altura del rbol. Como todo rbol balanceado, el
crecimiento del costo es ms lento que el del nmero de elementos.
Algunos rboles balanceados guardan valores slo en los nodos hoja, y por lo tanto sus nodos internos y nodos
hoja son de diferente tipo. Los rboles B guardan valores en cada nodo, y pueden utilizar la misma estructura
para todos los nodos. Sin embargo, como los nodos hoja no tienen hijos, una estructura especial para stos
mejora el funcionamiento.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

8.3.9 rboles de expresiones.


Un caso particular de los rboles binarios son los rboles de expresiones. Son rboles
donde las etiquetas asociadas a nodos internos son operadores y las asociadas a nodos
hoja operados. De forma general los operandos vamos a considerarlos como operadores
con ningn operando. Como por ejemplo, la figura adjunta.
Los rboles de expresin suelen tener, adems de los mtodos de los rboles, mtodos
del tipo: getValue() (para obtener el valor), getType() (para obtener el tipo), check()
para comprobar la consistencia de la expresin, etc.
Los operadores bsicos de los tipos numricos (enteros y decimales) incluyen: suma (+), resta(-), multiplicacin
(*), divisin (/), elevar a potencia (**), cociente ( //), residuo (%), cambio de signo (-), identidad (+).
Los operadores tienen 3 caractersticas:
-

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.

Ilustraremos el concepto de asociatividad con algunos ejemplos.


Caso
Expresin ejemplo
La suma suele ser asociativa por la
izquierda, esto significa que las sumas
2+3+4+5
se resolvern de izquierda a derecha.
La operacin de elevar a potencia (**)
suele ser asociativa por la derecha.

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:
-

Si un operador es conmutativo (a b = b a) entonces es irrelevante, en trminos de resultados, si


ste es asociativo por la izquierda o por la derecha. Pero si el operador no es conmutativo la asociatividad
si es relevante.
Por ejemplo, una expresin como 2 ** 3 ** 2 en un lenguaje en donde el operador ** sea asociativo por
la izquierda producira como resultado 64 (y en otros como Python es 512 !!)

La precedencia se refiere a la prioridad. En muchos lenguajes, la multiplicacin y la divisin tienen mayor


precedencia que la suma y la resta. La exponenciacin tiene mayor precedencia que cualquiera de estos
operadores. Para alterar la precedencia en casi todos los lenguajes de programacin se utilizan los parntesis.
Una expresin entre parntesis se evala primero.
Caso
a

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:

Esta expresin se evaluar de la siguiente forma:

Paso 1
572

Paso 2

Paso 3

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


public class Expression extends Tree<Operator> {
public Expression(Operator label) {
super(label);
}
public Expression(Operator label, Expression... elements) {
super(label, elements);
}
private Object getValue(Expression e) {
Object r;
Preconditions.checkArgument(e != null && !e.isEmpty());
if (e.isLeaf()) {
r = e.getLabel().getValue(null);
} else {
DynamicArray<Object> t = new DynamicArray<Object>();
Expression e1;
for (int i = 0; i < e.getNumChildren(); i++) {
e1 = (Expression) e.getElement(i);
t.add(e1.getValue(e1));
}
Object[] ta = t.toArray();
r = e.getLabel().getValue(ta);
}
return r;
}
public Object getValue() {
return getValue(this);
}
Class<?> getResultType() {
return getLabel().getResultType();
}
private boolean check(Expression e) {
Preconditions.checkArgument(e != null);
boolean r;
if (e.isEmpty()) {
r = false;
} else {
DynamicArray<Class<?>> t = new DynamicArray<Class<?>>();
for (int i = 0; i < e.getNumChildren(); i++) {
t.add(e.getElement(i).getLabel().getResultType());
}
Class<?>[] ta = t.toArray();
r = e.getLabel().check(ta);
}
return r;
}
public boolean check() {
return check(this);
}
public String toString() {
return toString((Expression) this);
}
private String toString(Expression t) {
String r;
boolean prim = true;
if (t.isLeaf()) {
r = t.getLabel().toString();
} else {
r = t.getLabel().toString() + "(";
for (int i = 0; i < t.getNumChildren(); i++) {
if (prim) {
r = r + toString((Expression) t.getElement(i));
prim = false;
} else {
r = r + "," + toString((Expression) t.getElement(i));
}
}
r = r + ")";
}
return r;
}
}

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


El operador + binario sobre nmero reales se puede implementar como:
public class MasBinario implements Operator {
@Override
public Object getValue(Object[] values) {
Double r = ((Double) values[0]) + ((Double) values[1]);
return r;
}
@Override
public Class<?> getResultType() {
return Double.class;
}
@Override
public boolean check(Class<?>[] typesOfValues) {
return typesOfValues.length == 2
&& typesOfValues[0].equals(Double.class)
&& typesOfValues[1].equals(Double.class);
}
}

El operador unario para convertir un dato nmerico de un tipo en otro:


public class ConvTypeNumber implements Operator {
Class<?> inType;
Class<?> outType;
public ConvTypeNumber(Class<?> inType, Class<?> outType) {
super();
this.inType = inType;
this.outType = outType;
}
@Override
public Object getValue(Object[] values) {
Object r = null;
Number n = (Number) values[0];
if (outType.equals(Double.class)) {
r = n.doubleValue();
} else if (outType.equals(Integer.class)) {
r = n.intValue();
} else if (outType.equals(Long.class)) {
r = n.longValue();
} else if (outType.equals(Float.class)) {
r = n.floatValue();
} else {
Preconditions.checkState(r != null);
}
return r;
}
@Override
public Class<?> getResultType() {
return outType;
}
@Override
public boolean check(Class<?>[] typesOfValues) {
return typesOfValues.length == 1 && inType.equals(typesOfValues[0]);
}
}

Un programa principal para comprobar el funcionamiento:


public static void main(String[] args) {
Expression e1 = new Expression(new Id("a", 3.));
Expression e2 = new Expression(new Id("b", 7));
Expression e4 = new Expression(new ConvNumberType(Integer.class, Double.class), e2);
Expression e3 = new Expression(new MasBinario(), e1, e4);
System.out.println(e3.check());
System.out.println(e3.getResultType());
System.out.println(e3.getValue());
System.out.println(e3);
}

575

8.3.10 Arboles sintcticos.


Los rboles sintcticos (los rboles de sintaxis abstracta en general) son una generalizacin de los rboles de
expresiones donde los operadores pueden ser del tipo: while, if-then-else, etc. Pueden servir, entre otras cosas
para representar la estructura abstracta de un programa. Por ejemplo el siguiente segmento de cdigo:
while(b != 0){
if (a > b) {
a = a - b;
} else {
b = b - a;
}
return a;
}

Puede ser representado por el rbol de sintaxis abstracta:

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

8.3.11 Arboles de decisin.


Es un diagrama que representa en forma secuencial condiciones y acciones. Muestra qu condiciones se
consideran en primer lugar, en segundo lugar y as sucesivamente (nodos intermedios) hasta alcanzar una
decisin (nodo hoja). Las ramas son los posibles caminos que se tienen de acuerdo a la decisin tomada.

Ejemplo: Jugamos al tenis? {S , No}

Estos tipos de rboles son clasificadores basados en rboles para instancias


(datos) representados como vectores de caractersticas (features). Los nodos
prueban caractersticas, hay una rama para cada valor de la caracterstica, las
hojas especifican la categora.
Pueden representar cualquier conjuncin (AND) y disyuncin (OR).
Pueden representar cualquier funcin de clasificacin de vectores de caractersticas discretas.
red circle A
Pueden ser rescritas como reglas, i. e. disjunctive normal form (DNF) { blue B; red square B
green C; red triangle C
Sus caractersticas (features) contnuas (reales) pueden ser clasificadas al permitir nodos que dividan una
caracterstica real en dos rangos basados en umbrales (i.e. largo < 3 and largo 3)
Variantes, como los rboles de clasificacin, tienen valores discretos en las ramas, y los rboles de regresin,
permiten outputs reales en las hojas.
Los algoritmos para encontrar rboles consistentes son eficientes para procesar muchos datos de entrenamiento
para tareas de datamining.
Adems, pueden manejar rudo en dichos datos de entrenamiento. Ejemplos:

Diagnostico medico.
Anlisis de riesgo en crdito.
Clasificador de objetos para manipulador de robots.

577

8.3.12 rboles de Huffman.


Representan el cdigo de Huffman para un alfabeto de n smbolos, junto con sus frecuencias de aparicin
asociadas utilizando el menor nmero posible de bits.
Se trata de un rbol binario que tiene cada smbolo en una hoja, y se construye de tal forma que siguindolo
desde la raz a cada una de sus hojas se obtiene el cdigo Huffman asociado.
1. Se crean varios rboles, uno por cada uno de los smbolos del alfabeto, consistiendo cada uno de los rboles
en un nodo sin hijos, y etiquetado cada uno con su smbolo asociado y su frecuencia de aparicin.
2. Se toman los dos rboles de menor frecuencia, y se unen creando un nuevo rbol. La etiqueta de la raz ser
la suma de las frecuencias de las races de los dos rboles que se unen, y cada uno de estos rboles ser un
hijo del nuevo rbol. Tambin se etiquetan las dos ramas del nuevo rbol: con un 0 la de la izquierda, y con
un 1 la de la derecha.
3. Se repite el paso 2 hasta que slo quede un rbol.
Con este rbol se puede conocer el cdigo asociado a un smbolo, as como obtener el smbolo asociado a un
determinado cdigo.
Para obtener el cdigo asociado a un smbolo se debe proceder del siguiente modo:
1.
2.
3.
4.
5.
6.

Comenzar con un cdigo vaco.


Iniciar el recorrido del rbol en la hoja asociada al smbolo.
Comenzar un recorrido del rbol hacia arriba.
Cada vez que se suba un nivel, aadir al cdigo la etiqueta de la rama que se ha recorrido.
Tras llegar a la raz, invertir el cdigo.
El resultado es el cdigo Huffman deseado.

Para obtener un smbolo a partir de un cdigo se debe hacer as:


1.
2.
3.
4.

Comenzar el recorrido del rbol en la raz de ste


Extraer el primer smbolo del cdigo a descodificar
Descender por la rama etiquetada con ese smbolo
Volver al paso 2 hasta que se llegue a una hoja, que ser el smbolo asociado al cdigo

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

8.3.13 Montculo de Fibonacci.


Esta es una estructura de datos compleja pero muy eficiente. Un montculo de Fibonacci es una lista de rboles
(no necesariamente binarios) en cada uno de los cuales la etiqueta de la raz es menor que la de los hijos. Esto
implica que la clave mnima est siempre en la raz de uno de los rboles. Los rboles no tienen una forma
predefinida y en un caso extremo el montn puede tener cada elemento en un rbol separado o en un nico
rbol de profundidad n. Adems los nodos pueden estar marcados o no. Un nodo est marcado si al menos uno
de sus hijos se cort desde que el nodo fue hecho hijo de otro nodo (todas las races estn desmarcadas). La
estructura puede estar consolidada o no. La operacin de consolidacin, que explicaremos a continuacin,
normaliza la lista de rboles para que no haya dos con el mismo nmero de hijos.
Para describir algunas propiedades de esta estructura usaremos la siguiente notacin:
Notacin
n
r(x)
r(H)
t(H)
m(H)
s(t)
tk

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)

Inserta un elemento en el rbol.


Devuelve el elemento con menor ndice del rbol.
Elimina el elemento con menor ndice del rbol.
Aade la estructura del FibonnaciHeap.
Decrementa la clave del elemento del rbol.
Elimina el elemento del rbol.

minimum()
removeMin()
union(FibonacciHeap<T> h)
decreaseKey(T elem, int key)
remove(T elem)

580

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

8.3.14 Tipo UnionFind.


Esta es una estructura muy adecuada para mantener y gestionar un conjunto de subconjuntos disjuntos. Esta
dotada de las operaciones:

void addElement(T e): Crea un conjunto con el elemento e y lo aade a la estructura.


T find(T e): Devuelve el representante del conjunto al que pertenece e. Todos los elementos que estn

en el mismo conjunto tienen el mismo representante.


void union(T e1, T e2): Une en uno los dos conjuntos a los que pertenecen e1 y e2.

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)

Aade un elemento a la estructura.


Devuelve el representante al que pertenece el elemento.
Agrupa dos elementos.
Elimina la conexin existente entre dos elementos.
Elimina las conexiones existentes con un elemento.
Elimina la componente en la que se encuentra el elemento.
Devuelve el nmero de componentes.
Devuelve el ndice de la componente en la que pertenece un elemento.
Devuelve el representante del componente determinado.

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

El cdigo, tal como se ha implementado en jGrapht, es el mostrado en el siguiente cdigo:

public class UnionFind<T> {


private Map<T, T>
parentMap;
private Map<T, Integer> rankMap;
public UnionFind(Set<T> elements) {
parentMap = new HashMap<T, T>();
rankMap = new HashMap<T, Integer>();
for (T element : elements) {
parentMap.put(element, element);
rankMap.put(element, 0);
}
}
public void addElement(T element) {
parentMap.put(element, element);
rankMap.put(element, 0);
}
public T find(T element) {
if (!parentMap.containsKey(element)) {
throw new IllegalArgumentException("elements must be contained in given set");
}
T parent = parentMap.get(element);
if (parent.equals(element)) {
return element;
}
T newParent = find(parent);
parentMap.put(element, newParent);
return newParent;
}
public void union(T element1, T element2) {
if (!parentMap.containsKey(element1) || !parentMap.containsKey(element2)) {
throw new IllegalArgumentException("elements must be contained in given set");
}
T parent1 = find(element1);
T parent2 = find(element2);
if (parent1.equals(parent2)) {
return;
}
int rank1 = rankMap.get(parent1);
int rank2 = rankMap.get(parent2);
if (rank1 > rank2) {
parentMap.put(parent2, parent1);
} else if (rank1 < rank2) {
parentMap.put(parent1, parent2);
} else {
parentMap.put(parent2, parent1);
rankMap.put(parent1, rank1 + 1);
}
}
}

582

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

8.3.15 Imagen Especular de un rbol.


Se define el factor de balance como el alto del subrbol derecho menos el alto del subrbol izquierdo. Entonces
en un rbol AVL, todos los nodos cumplen la propiedad de tener valores del factor de balance iguales a {-1,0,+1}.
Sea nh el mnimo nmero de nodos en un rbol AVL de altura h dada, que se encuentra en su peor caso de
desbalance, si se agrega un nodo, tal que la nueva altura sea (h + 1), dejan de ser AVL.
Los siguientes diagramas ilustran dichos rboles, denominados de Fibonacci, y los factores de balance de sus
nodos, para alturas 0, 1 y 2. Se muestran todos los casos, separados por un eje de simetra; a la derecha del eje
se muestran los desbalanceados por la derecha; y a la izquierda los desbalanceados por la izquierda. Las
imgenes en ambos lados del eje se obtienen como imgenes especulares de las del otro lado.

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.

Por tanto, definimos:


La imagen especular de un rbol no vaco se obtiene intercambiando los hijos izquierdo y derecho, y calculando
recursivamente la imagen especular de ambos.

Esto es,
ImagenEspecular(rbol(izq, elto, der)) = rbol(ImgenEspecular((der), elto, ImagenEspecular(izq))

583

Implementacin:

public class ImagenEspecular {


public static void main(String args[]) {
Tree<Integer>
Tree<Integer>
Tree<Integer>
Tree<Integer>
Tree<Integer>
Tree<Integer>
Tree<Integer>
Tree<Integer>
Tree<Integer>

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

8.4 Vistas y su implementacin.


De una manera muy general podemos decir que las vistas son objetos para los que los valores de sus propiedades
estn ligados a los valores que en cada momento tienen los valores de las propiedades de otro objeto (llamado
objeto ligado).
Las vistas de un objeto nos permiten restringir las operaciones a realizar sobre otro, limitar los elementos a los
que se pueden acceder en un agregado o definir restricciones adicionales a las operaciones del contrato.
El objeto que es vista de otro objeto al que est ligado puede ser del mismo tipo o de otro tipo diferente.
Podemos considerar diferentes tipos de vistas:

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.

8.4.1 Tipo inmutable.


Decimos que un tipo de datos es un tipo inmutable cuando slo tiene mtodos observadores y sus valores no
pueden cambiar. Las diferencias entre los valores de un tipo inmutable y los de una vista no modificable deben
estar claras. En una vista no modificable los valores pueden cambiar si cambian los de la variable ligada aunque
no podamos usar los mtodos modificadores de la vista. Los valores de un tipo inmutable no pueden cambiar
una vez que se crean.

8.4.2 Implementacin de vistas.


Segn la forma de la implementacin podemos clasificar las vistas en acopladas y desacopladas. En las primeras,
vista y objeto ligado estn estrechamente ligados, mientras en el segundo caso la vista y el objeto ligado se
comunican mediante eventos.
Veamos, en primer lugar, ideas para implementar vistas acopladas. Este tipo de vistas se implementan mediante
el Patrn Delegado (Delegate) mas una factora para crear las vistas. Esencialmente para las vistas acopladas se
trata de disear una clase interna, que implemente el tipo adecuado con las precondiciones e invariantes
requeridos y delegue muchos de sus mtodos en los de la clase original. La factora crear y devolver un objeto
de esa clase interna.
585

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

8.4.3 Vistas acopladas: Patrn Delegado.


Este tipo de vistas se implementan mediante el Patrn Delegado (Delegate) ms una factora para crear vistas.
Esencialmente se trata de disear una clase interna, que implemente el tipo adecuado con las precondiciones
e invariantes requeridos y que delegue muchos de sus mtodos en la clase original.

8.4.3.1

Vistas en el API de Java.

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)

Iterador de lista con operaciones adicionales.


Sublista que solo permite trabajar con los elementos de [from, 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)

Incluye los elementos comprendidos desde el primero


hasta el indicado.
Incluye los elementos comprendidos desde el indicado
hasta el ltimo.
Incluye los elementos comprendidos en [from, to).

Igualmente ofrecen vistas NavigableSet, Map, SortedMap, NavigableMap,


Igualmente con los mtodos de la clase Collections (unmodifiableList, unmodifiableSortedSet,
unmodifiableSet, synchronizedList, synchronizedSet, synchronizedSortedSet, etc.) es posible conseguir vistas
no modificables o sincronizadas.
Versiones inmutables para los tipos ms usuales se pueden conseguir en la librera en Guava. Ejemplos son:
ImmutableList, ImmutableSet, etc.

587

8.4.3.2

Clases Forward de Guava.

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.

class NoRemoveList<E> extends ForwardingList<E> {


List<E> l;
public NoRemoveList(List<E> l) {
this.l = l;
}
@Override
protected List<E> delegate() {
return l;
}
public E remove(int index) {
throw new UnsupportedOperationException();
return null;
}
public boolean removeAll() {
throw new UnsupportedOperationException();
return false;
}
public void clear() {
throw new UnsupportedOperationException();
}
}

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


8.4.3.3

Colecciones con restricciones.

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());

8.4.4 Vistas desacopladas: Patrn Observador-Observable.


Existe una segunda forma de crear vistas. Es haciendo que el objeto original produzca un conjunto de eventos,
la vista los reciba y a partir de ellos actualice su estado. Para implementar este mecanismo debemos explicar
previamente el Patrn Observador-Observable. En este patrn existen dos roles: Listenable y Listener. El rol
Listenable ofrece fundamentalmente dos mtodos:
Mtodos del rol Listenable
void addListener(Listener e)
void removeListener(Listener e)

Mtodos del rol Listener


void update(Event e)

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

Implementacin de un tipo ListenableSet<E>:


1. Decidir el evento o eventos que pueden ser de inters para los listeners
2. Diseamos las interfaces adecuadas
3. Implementamos el objeto listenable
4. Implementamos el objeto listener
5. Implementamos el programa principal
El primer paso para que un objeto ofrezca una vista listenable es decidir el evento o eventos que puede ser de
inters para posibles listeners. Posteriormente hacer que el objeto original ofrezca una vista listenable y los
posibles objetos interesados una vista como listeners. Veamos como convertir un conjunto en observable de
los eventos que ocurre cuando se aade o se elimina con xito un elemento.
En primer lugar diseamos las interfaces adecuadas:
public interface ListenableSet<E> extends Set<E> {
void addSetAddListener(SetAddListener<E> s);
void addSetRemoveListener(SetRemoveListener<E> s);
void removeSetAddListener(SetAddListener<E> s);
void removeSetRemoveListener(SetRemoveListener<E> s);
}
public interface SetAddListener<E> {
void elementAdded(SetAddEvent<E> e);
}
public interface SetRemoveListener<E> {
void elementRemoved(SetRemoveEvent<E> e);
}
public class SetAddEvent<E> extends EventObject {
private E element;
public SetAddEvent(Object source, E e) {
super(source);
element = e;
}
public E getElementAdded() {
return element;
}
}
public class SetRemoveEvent<E> extends EventObject {
private E element;
public SetRemoveEvent(Object source, E e) {
super(source);
element = e;
}
public E getElementRemoved() {
return element;
}
}

590

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Luego, las clases que implementan el objeto listenable y listener. Una implementacin para un
ListenableHashSet puede ser:
public class
ListenableHashSet<E>
extends
HashSet<E>
implements ListenableSet<E> {
{
private List<SetAddListener<E>> addListeners;
private List<SetRemoveListener<E>> removeListeners;
public ListenableHashSet() {
super();
addListeners = Lists.newArrayList();
removeListeners = Lists.newArrayList();
}
public ListenableHashSet(Collection<? extends E> arg0) {
super(arg0);
addListeners = Lists.newArrayList();
removeListeners = Lists.newArrayList();
}
public ListenableHashSet(int arg0, float arg1) {
super(arg0, arg1);
addListeners = Lists.newArrayList();
removeListeners = Lists.newArrayList();
}
public ListenableHashSet(int arg0) {
super(arg0);
addListeners = Lists.newArrayList();
removeListeners = Lists.newArrayList();
}
@Override
public void addSetAddListener(SetAddListener<E> s) {
addListeners.add(s);
}
@Override
public void addSetRemoveListener(SetRemoveListener<E> s) {
removeListeners.add(s);
}
@Override
public void removeSetAddListener(SetAddListener<E> s) {
addListeners.remove(s);
}
@Override
public void removeSetRemoveListener(SetRemoveListener<E> s) {
removeListeners.remove(s);
}
private void notifySetAddedListeners(E element) {
SetAddEvent<E> e = new SetAddEvent<E>(this, element);
for (SetAddListener<E> ls : addListeners) {
ls.elementAdded(e);
}
}
private void notifySetRemoveListeners(E element) {
SetRemoveEvent<E> e = new SetRemoveEvent<E>(this, element);
for (SetRemoveListener<E> ls : removeListeners) {
ls.elementRemoved(e);
}
}
public boolean add(E element) {
boolean r = super.add(element);
if (r) {
notifySetAddedListeners(element);
}
return r;
}
@SuppressWarnings("unchecked")
public boolean remove(Object element) {
boolean r = super.remove(element);
if (r) {
notifySetRemoveListeners((E) element);
}
return r;
}
}

591

Y la clase que implementa el listener:


public class
Counter<E>
implements SetAddListener<E>, SetRemoveListener<E>
{
List<E> elementsAdded;
List<E> elementsRemoved;
public Counter() {
super();
this.elementsAdded = Lists.newArrayList();
this.elementsRemoved = Lists.newArrayList();
}
@Override
public void elementRemoved(SetRemoveEvent<E> e) {
elementsRemoved.add(e.getElementRemoved());
}
@Override
public void elementAdded(SetAddEvent<E> e) {
elementsAdded.add(e.getElementAdded());
}
public String toString() {
String s = "";
s = elementsAdded + "\n" + elementsRemoved;
return s;
}
}

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

8.5 Problema Tipo.


Objetivos:

Implementar un tipo en base a otro teniendo en cuenta una serie de restricciones.


Implementar un conjunto de enteros con un rango dado.
Uso de array de bits (Tipo BitSet).

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.

Figura 1. Representacin mediante array de bits

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:

Figura 2. Modelado del tipo ConjuntoEnteros

Figura 3. Interfaz del tipo BitSet

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


Paso 1. Estudio de los mtodos del tipo BitSet.

Antes de comenzar a implementar el tipo ConjuntoEnteros, es necesario conocer el funcionamiento de los


mtodos del tipo BitSet, ya que haremos uso de los mismos para la implementacin de los mtodos de
ConjuntoEnteros.
En la siguiente tabla aparece una descripcin de los mismos. Nos fijaremos con detenimiento en los mtodos:
get, set, or, andNot, and, cardinality, y nextSetBit.

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()

Nmero de bits a true.

clear()

Coloca todos los bits a false.

flip(int bitIndex)

Hace el complemento sobre el bit indicado (operacin lgica not sobre el bit).

flip(int fromIndex, int


toIndex)

Hace el complemento sobre los bits indicados.

get(int bitIndex)

Valor del bit indexado por bitIndex.

get(int fromIndex, int


toIndex)

Bitset compuesto por los bits indicados.

intersect(BitSet set)

Verdadero si existen bits en this y en set indexados por el mismo entero y con
valor true.

isEmpty()

Verdadero si todos los bit tienen valor false.

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)

Hace true el bit indexado.

set(int fromIndex, int


toIndex)

Hace true desde fromIndex hasta toIndex.

set(int bitIndex, boolean


booleanValue)

Coloca el bit especificado al valor indicado.

set(int fromIndex, int


toIndex, boolean
booleanValue)

Coloca el rango de bits especificado al valor indicado.

size()

Devuelve el espacio en bits ocupado por el array.

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

Paso 2. Implementacin del tipo ConjuntoEnteros.

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.

public boolean add(Integer e)

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 todos los elementos del conjunto de enteros.


public boolean contains(Object o)

Comprueba si un determinado elemento pertenece al conjunto. Si el objeto no es una instancia de tipo


Integer, debe lanzar una excepcin.
public boolean isEmpty()

Comprueba si el conjunto de enteros no tiene elementos.


public boolean remove(Object o)

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


public boolean addAll(Collection<? extends Integer> c)

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.

public boolean removeAll(Collection<?> c)

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.

public int size()

Devuelve el nmero de elementos que hay en el conjunto de enteros.

Es de inters estudiar el cdigo de aquellos mtodos que ya vienen implementados:


public boolean containsAll(Collection<?> c)
public boolean retainAll(Collection<?> c)

597

Paso 3. Implementacin del IteradorConjuntoEnteros.

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()

Comprueba si quedan elementos.


public Integer next()

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.

Paso 4. Casos de Prueba.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


public class
ConjuntoEnteros
implements Set<Integer>
{
private Integer inf;
private Integer sup;
private BitSet conjunto = new BitSet();
public ConjuntoEnteros() {
new ConjuntoEnteros(0, 0);
}
public ConjuntoEnteros(Integer inf, Integer sup) {
super();
if (sup <= inf)
throw new IllegalArgumentException();
this.inf = inf;
this.sup = sup;
conjunto = new BitSet(sup - inf);
}
public Integer getInf() {
return inf;
}
public Integer getSup() {
return sup;
}
public BitSet getConjunto() {
return conjunto;
}
@Override
public boolean add(Integer e) { // Elemento del conjunto.
boolean res = false;
if (e < inf || e >= sup)
throw new IllegalArgumentException("Valor a fuera de rango.");
if (!conjunto.get(e - inf)) { // Posicin en el BitSet.
conjunto.set(e - inf);
res = true;
}
return res;
}
@Override
public void clear() {
conjunto.clear();
}
@Override
public boolean contains(Object o) {
return conjunto.get((Integer) o - inf);
}
@Override
public boolean addAll(Collection<? extends Integer> c) {
boolean res = false;
if (c instanceof ConjuntoEnteros) {
ConjuntoEnteros ce = (ConjuntoEnteros) c;
if (ce.getInf() != this.getInf() || ce.getSup() != this.getSup()) {
throw new IllegalArgumentException("Error: ConjuntoEnteros.addAll");
} else {
int sizePrevio = this.getConjunto().size();
this.conjunto.or(ce.getConjunto());
res = sizePrevio != this.getConjunto().size();
}
}
else {
for (int i = 0; i < c.size(); i++) {
res = this.add(i) || res;
}
}
return res;
}

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


@Override
public boolean remove(Object o) {
boolean res = false;
if (o instanceof Integer)
if (!conjunto.get((Integer) o - inf)) {
conjunto.set((Integer) o - inf, false);
res = true;
} else
throw new IllegalArgumentException("Error: ConjuntoEnteros.remove
tipo Integer.");
return res;
}

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

8.6 Problemas de exmenes.


8.6.1 Elecciones municipales.
La clase EleccionesMunicipales almacena la informacin sobre las votaciones de una
convocatoria de consulta popular. Dicha informacin esta almacenada en un nico
atributo t de tipo Table<PartidoPolitico, Municipio, Integer>. Tambin se dispondr
de dos clases PartidoPolitico y Municipio que representa respectivamente a los
partidos polticos y a los municipios que participaron en las votaciones. La cantidad de
votos recibidos por un partido poltico en un determinado municipio viene representada
por un objeto de tipo Integer.
Se pide:
Implementar los siguientes mtodos de la clase EleccionesMunicipales:

Set<PartidoPolitico> getPartidosPoliticos():

Devuelve un conjunto con todos los partidos polticos

que han concurrido en las elecciones.

Set<Municipio> getMunicipios(PartidoPolitico pp):

Devuelve un conjunto con todos los municipios

donde ha concurrido el partido poltico pp.

void setVotos(PartidoPolitico pp, Municipio m, Integer v):

Aade el nmero de votos v que un

determinado partido pp ha obtenido en un municipio m.

Double getPorcentajeDeVotos(PartidoPolitico pp, Municipio m):

Devuelve el porcentaje de votos


obtenido por el partido poltico pp en un municipio m. El valor devuelto estar comprendido entre 0.0
(no obtuvo voto alguno) y 1.0 (obtuvo el 100% de los votos).
boolean esGobernable(Municipio m): Devuelve true si el municipio m es gobernable, en caso contrario
devuelve false. Un municipio ser gobernable si existe algn partido con ms del 50% de los votos.
PartidoPolitico getMasVotado(Municipio m): Devuelve el partido poltico ms votado en el municipio
m.

Si fuesen necesarios dispone de la implementacin de los siguientes mtodos de la clase EleccionesMunicipales:

Set<Municipio> getMunicipios():

Devuelve un conjunto con todos los municipios que han participado

en la consulta popular.

602

Devuelve un conjunto con todos los


partidos polticos que han concurrido en un determinado municipio m.
Integer getVotos(PartidoPolitico pp, Municipio m): Devuelve el nmero de votos obtenidos por el
partido poltico pp en el municipio m. Si el partido no concurri en el municipio devuelve -1.
Integer getVotos(PartidoPolitico pp): Devuelve el nmero de votos obtenidos por el partido poltico
pp.
Set<PartidoPolitico>

getPartidosPoliticos(Municipio

m):

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

603

Solucin:

public class EleccionesMunicipales {


Table<PartidoPolitico, Municipio, Integer> t = null;
public Set<PartidoPolitico> getPartidosPoliticos() {
return t.rowKeySet();
}
public Set<Municipio> getMunicipios(PartidoPolitico pp) {
return t.row(pp).keySet();
}
public void setVotos(PartidoPolitico pp, Municipio m, Integer v) {
t.put(pp, m, v);
}
public Double getPorcentajeDeVotos(PartidoPolitico pp, Municipio m) {
Map<PartidoPolitico, Integer> vm = t.column(m);
double votosMunicipio = Iterables2.sum(vm.values());
double votosPartido = t.get(pp, m);
return votosPartido / votosMunicipio;
}
public boolean esGobernable(Municipio m) {
boolean esGober = false;
for (PartidoPolitico pp : getPartidosPoliticos(m)) {
double por100 = getPorcentajeDeVotos(pp, m);
if (Double.compare(por100, 0.5) > 0) {
esGober = true;
break;
}
}
return esGober;
}
public PartidoPolitico getMasVotado(Municipio m) {
Map<PartidoPolitico, Integer> d = t.column(m);
Ordering<Map.Entry<PartidoPolitico, Integer>> ord =
Ordering.from(new Comparator<Map.Entry<PartidoPolitico, Integer>>() {
public int compare(Entry<PartidoPolitico, Integer> arg0, Entry<PartidoPolitico, Integer> arg1) {
Integer v1 = arg0.getValue();
Integer v2 = arg1.getValue();
return v2.compareTo(v1);
}
});
return ord.max(t.column(m).entrySet()).getKey();
}
}

604

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

8.6.2 bol genealgico.


Realiza un mtodo que recibiendo un Tree<T> y un elemento de tipo T, devuelve una lista con
los primos de dicho elemento.
List<T> primos(Tree<T> tree, T elemento){ }

Suponga que en el rbol no hay elementos repetidos


(cada elemento slo aparece una vez en el rbol
completo). En el siguiente ejemplo, la salida del
mtodo para este rbol y el elemento 10 ser:{11,12}
Se pide: Implementar el mtodo descrito anteriormente.

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;
}

private static <T> Tree<T> buscaAbuelo(Tree<T> raiz, T elemento) {


Tree<T> abuelo = null;
Iterator<Tree<T>> hijos = raiz.getSubTrees().iterator();
boolean esHijo = false;
while (hijos.hasNext() && !esHijo && abuelo == null) {
Tree<T> hijo = hijos.next();
if (hijo.getLabel().equals(elemento)) {
esHijo = true;
} else {
Iterator<Tree<T>> nietos = hijo.getSubTrees().iterator();
while (nietos.hasNext() && abuelo == null) {
if (nietos.next().getLabel().equals(elemento)) {
abuelo = raiz;
}
}
}
}
if (!esHijo && abuelo == null) {
hijos = raiz.getSubTrees().iterator();
while (hijos.hasNext() && abuelo == null) {
abuelo = buscaAbuelo(hijos.next(), elemento);
}
}
return abuelo;
}

605

8.6.3 bol genealgico.


El rbol genealgico que se muestra a continuacin est formado por nodos de tipo Persona.
Suponga que en el rbol no hay elementos repetidos (no puede haber dos personas con el
mismo nombre). A partir de l, realice las siguientes operaciones:

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

8.6.4 bol de directorios.


Un rbol de directorio es una forma de mostrar todos los directorios de una unidad de
almacenamiento (como un disco duro por ejemplo) en forma de estructura de rbol.
El siguiente rbol de directorios contiene nodos de tipo Tree<Directory>.

El tipo Directory contiene varios atributos, entre ellos:


-

Size: Tamao en Kbytes del directorio.


Name: Nombre del directorio.
Type: Indica si el nodo es de tipo Fichero o Carpeta. Las hojas pueden ser de tipo Fichero o Carpeta. Solo
los nodos de tipo Fichero ocupan espacio, el tamao o Size de un nodo tipo Carpeta es 0 por defecto.
Extension: Formato del directorio si es de tipo Fichero. Las Carpetas no tienen extensin, este campo es
vaco para las carpetas.

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.

Nota: El rbol de directorio de entrada no se puede modificar.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos

8.7 Problemas propuestos.


1. Implemente el tipo Set<E> a partir del tipo BasicHashTable<K, V>. Calcule las complejidades de las diferentes
operaciones en funcin del tamao del conjunto.

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

9. Implemente los mtodos de la clase Trees comentados anteriormente.

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.

12. Implemente el tipo SortedSet<E> usando la clase AVLTree anterior.

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.

15. Escriba un algoritmo que tomando como argumento un rbol (Tree<T>),


imprima la informacin contenida en cada nodo por niveles. Es decir,
primero se debe imprimir la informacin de la raz (nivel 0), luego la de
sus hijos (nivel 1), despus la informacin de los hijos de estos (nivel 2) y
as sucesivamente. Por ejemplo, para el siguiente rbol, el resultado
sera: 1 2 3 4 5 6 7 8 9.

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

Miguel Angel Cifredo


macifredo@gmail.com

Anlisis y Diseo de Datos y Algoritmos


17. Para qu pueden ser tiles los rboles B? Ponga un ejemplo.

18. Realice una funcin recursiva que aplicada a un rbol, devuelva un nuevo rbol sea la imagen especular del
primero.

19. Un rbol monodistante de orden N es un rbol que


almacena nmeros enteros en el que la suma de los valores
de los nodos de cada camino que va desde la raz a un nodo
hoja es igual a N. En el siguiente ejemplo se presenta un
rbol monodistante de orden 15. Implemente un mtodo
que compruebe si un rbol es monodistante de un
determinado orden.

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:

La salida del mtodo para este rbol y el elemento 2 ser: {9, 1}

21. Proponga una implementacin posible para el tipo Graph<V, E>. Discuta los casos posibles segn el subtipo
de grafo considerado.
613

Anda mungkin juga menyukai