Problema – EL VORAZ y El Señor de los Anillos
Una manada de orcos ha desembarcado para atacar Rivendel (ciudad protegida por los
humanos), Aragorn (rey de los humanos) necesita definir una estrategia de contraataque en el
menor tiempo posible, por lo que decide utilizar una técnica voraz. Aragorn tiene información
sobre los grupos en los que se han dividido los orcos (n) y cuántos orcos hay en cada uno de los
grupos. Aragorn tiene también disponibles n grupos de hombres para contraatacarlos. Toda
esta información está definida en una clase ProblemaAtaque, la cual tendrá dos arrays de
enteros, uno llamado numOrcos y otro numAliados. En dichos arrays estarán almacenados la
cantidad de individuos que forma cada grupo, orcos y aliados respectivamente. Aragorn tendrá
que decidir qué grupo de aliados se enfrenta a qué grupo de orcos con una técnica voraz para
obtener el mayor número de victorias. Para ello hay que tener en cuenta que una batalla será
ganada si el número de aliados es mayor o igual que el número de orcos.
a)
Rellenar los cuadros del siguiente código para obtener el máximo de victorias. Para
realizar el reparto se utiliza una estrategia, que aunque no es la mejor, recorre cada uno
de los grupos de aliados, y se le asigna (si el posible) el grupo de orcos mayor que sea
menor o igual que el número de aliados. La solución se almacenará en un atributo de la
clase AtaqueVoraz (aliadosContraatacaOrcos); en caso de que se sepa que un grupo de
aliados no ganará la batalla no se les asignará grupo de orcos, y se marcará como -1.
Para resolver el problema utiliza el esquema del proyecto de Eclipse de clase,
ayudándote con el ejemplo expuesto a continuación.
package soluciones.practiAlternativa; import soluciones.EstrategiaSolucion; import problemas.ProblemaAtaque; import esquemas.EsquemaVZ;
public class AtaqueVoraz extends EsquemaVZ implements EstrategiaSolucion { //array que almacena el número de orcos que hay en cada grupo
int[] numOrcos;
// array que almacena el número de aliados que hay en cada grupo int[] numAliados;
/*array donde se tendrá que almacenar a qué grupo de orcos contraataca cada grupo de aliados. El grupo de aliados i contraataca al
grupo de orcos aliadosContraatacaOrcos[i]. Se pondrá -1 si no contraataca.*/ int[] aliadosContraatacaOrcos;
/*en cada iteración del voraz se decide a qué grupo de orcos atacará
cada grupo de aliados; este atributo tendrá el índice del grupo de aliados que se analiza en cada vuelta*/
int aliadosActual;
/*este atributo se usa para realizar la búsqueda del grupo del orcos más apropiado en cada iteración del voraz*/
int orcosAAtacar;
/*este array almacenará los grupos de orcos que ya se han asignado a un grupo de aliados.*/
boolean[] orcosContraatacados;
public AtaqueVoraz(ProblemaAtaque pa) { this.numOrcos = pa.getOrcos(); this.numAliados = pa.getAliados(); }
public void procesamientoInicial() { }
protected void inicializa() {
aliadosContraatacaOrcos = new int[numOrcos.length]; orcosContraatacados = new boolean[numOrcos.length]; for (int i = 0; i < orcosContraatacados.length; i++) { orcosContraatacados[i] = false;
}
aliadosActual = 0; }
protected boolean fin() {
return (aliadosActual == numOrcos.length); }
protected void seleccionaYElimina() { boolean sal = false;
int maxValor = Integer.MIN_VALUE; orcosAAtacar = -1;
for (int i = 0; i < numOrcos.length && !sal; i++) {
// para el aliado actual, el enemigo menor o igual que los // aliados. Ese grupo de orcos no ha podido ser atacado antes if (!orcosContraatacados[i]
&& numOrcos[i] <= numAliados[aliadosActual] && maxValor < numOrcos[i]) {
if (numOrcos[i] == numAliados[aliadosActual]) {
// podemos salir ya se ha encontrado el mejor posible sal = true; } maxValor = numOrcos[i]; orcosAAtacar = i; } } if (orcosAAtacar != -1)
public void solucion() { voraz();
}
public String toString() {
String reparto = "El reparto ha sido:";
for (int i = 0; i < aliadosContraatacaOrcos.length; i++) { if (aliadosContraatacaOrcos[i] != -1) {
reparto += "\nLos aliados " + i + " contraataca a los orcos " + aliadosContraatacaOrcos[i] + "\n";
} }
return reparto; }
public void procesamientoFinal() { }
}
b) Rellenar la clase Batalla, para imprimir por pantalla el problema, la decisión tomada por
Aragorn y el tiempo de ejecución del algoritmo voraz.
package practicas;
import temporizadores.Temporizador; import problemas.ProblemaAtaque;
import soluciones.practiAlternativa.AtaqueVoraz; public class Batalla {
public static void main(String[] args) {
ProblemaAtaque p = new ProblemaAtaque(); Temporizador t = new Temporizador(100); AtaqueVoraz sol = new AtaqueVoraz(p); t.cronometra(sol);
System.out.println("El tiempo de ejecución: " + t.getTiempoMinimo()); System.out.println("Problema: " + p);
System.out.println("Solución: " + sol);
} }
Problema 2 - Voraces
Sea una sucesión de matrices, representada mediante un array, donde el número de filas de cada
matriz coincide con el número de columnas de la anterior. Se desea obtener el número mínimo
de operaciones para calcular el producto de varias matrices. Como es conocido el tamaño de la
matriz resultante de una multiplicación de dos matrices es el número de filas de la primera por
el número de columnas de la segunda. Por ejemplo en el caso de A
10×20* B
20×50= C
10×50el
número de operaciones será 10x20x50=1000.
Aunque el producto de matrices es asociativo (da el mismo resultado independientemente del
orden en el que se realice), no es indiferente, en cuanto a número de operaciones, el orden en el
que se efectúan las multiplicaciones. Por ejemplo, el producto de las cuatro matrices
A
1A
2A
3A
4se puede realizar de cinco maneras: A
1(A
2(A
3A
4)), A
1((A
2A
3) A
4),
(A
1A
2) (A
3A
4), ((A
1A
2) A
3) A
4y (A
1(A
2A
3)) A
4. Si suponemos que sus dimensiones son
respectivamente (10×20), (20×50), (50×1) y (1×100) , (d = {10, 20, 50, 1, 100}), cada una de
las anteriores formas de calcular el producto conducen a 125.000, 23.000, 65.000, 11.500 y
2.200 operaciones, respectivamente. Como podemos observar, realizar el producto de las
matrices de la peor forma posible es 56,8 veces más lento que hacerlo del modo mejor.
La solución en Java para resolver este problema debe plantearse como un algoritmo voraz. Para
implementar el algoritmo voraz disponemos de dos estrategias:
•
Multiplicar primero las matrices A
i×A
i+1cuya dimensión común d
isea la menor entre
todas, y repetir el proceso.
•
Realizar primero la multiplicación de las matrices A
i×A
i+1que requiera menor número
de operaciones (d
i–1d
id
i+1), y repetir el proceso.
El programa propuesto debe calcular las dos posibles soluciones y quedarse con la mejor.
Nota: Debe seguir la organización de paquetes que se ha seguido en clases
import problemas.SecuenciaMatrices;
public class MultSecMatVoraz1 extends EsquemaVZ implements EstrategiaSolucion {
// Contiene las dimensiones de las matrices a multiplicar
private int[] d;
// Atributo privado que indicará el número de operaciones
// que se necesitan realizar
private long numOps;
// Número de matrices que quedan por multiplicar
protected void inicializa() { usadas=0;
}
protected boolean fin() {
boolean fin = (usadas == d.length - 2); return fin;
}
protected void seleccionaYElimina() { int filM1, colM1filM2 , colM2;
filM1 = indicePrimeraDimensionSinUsar(0);
colM1filM2 = indicePrimeraDimensionSinUsar(filM1 + 1); colM2 = indicePrimeraDimensionSinUsar(colM1filM2 + 1); int min = Integer.MAX_VALUE;
while (colM2 > 0) { if (min > d[colM1filM2]) { col_M1_filas_M2 = colM1filM2; min = d[colM1filM2]; valorMult = d[filM1]*d[colM1filM2]*d[colM2]; } filM1 = colM1filM2; colM1filM2 = colM2; colM2 = primeraSinUsar(colM2+1); } }
protected boolean prometedor() { return true; }
protected void anotaEnSolucion() { usadas = usadas + 1;
// Incremento el numOps con las que almacene anteriormente
numOps = numOps + valorMult;
// Si multiplico dos matrices tengo que eliminar del array d la posicion
// que almacena la dimensión de la columna de la primera matriz
// que coincide con la dimensión de la fila de la segunda matriz
d[col_M1_filas_M2] = 0; }
// Busca a partir de la posición i del array d la primera posición diferente // de 0, es decir, la primera dimension que aun no ha sido utilizada
// Devuelve –1 si no encuentra ninguna posición private int indicePrimeraDimensionSinUsar(int i) { int dev = -1;
for (int j = i; j < d.length && dev==-1; j++){ if (d[j]!=0) dev = j; } return dev; }
public String toString() { return "Algoritmo para el cálculo... }
public long getNumOps() { return numOps; }
public void procesamientoInicial() {//sin código}
public void solucion() { voraz();
}
public void procesamientoFinal() { //sin código }
}
public class MultSecMatVoraz2
extends EsquemaVZ implements EstrategiaSolucion {
¡¡¡ ATENCIÓN: APARECEN SÓLO LOS METODOS QUE SON DIFERENTES A MultSecMatVoraz1 !!!
public MultSecMatVoraz2(SecuenciaMatrices sm) { d = (int[]) sm.getDimensiones().clone(); }
protected void seleccionaYElimina() { int filM1, colM1filM2 , colM2;
filM1 = indicePrimeraDimensionSinUsar(0);
colM1filM2 = indicePrimeraDimensionSinUsar(filM1 + 1); colM2 = indicePrimeraDimensionSinUsar(colM1filM2 + 1); int min = Integer.MAX_VALUE;
while (colM2 > 0) {
if (min > d[filM1] * d[colM1filM2] * d[colM2]) { col_M1_filas_M2 = colM1filM2;
min = d[colM1filM2];
valorMult = d[filM1] * d[colM1filM2] * d[colM2]; } filM1 = colM1filM2; colM1filM2 = colM2; colM2 = primeraSinUsar(colM2 + 1); } } }
Problema
3 - Voraces
- Distribución de libros en estanterías mediante técnica voraz
Un librero acaba de abrir un negocio en el que vende libros de diversos temas, que se ofrecen al
público en una serie de estanterías. Dicho librero pretende minimizar el número de estanterías a
utilizar, de forma que se optimice el espacio del local. Esto debe hacerlo teniendo en cuenta que
todos los
libros de una misma temática deberían estar siempre colocados en una misma
estantería. En el caso de que no exista ninguna estantería con capacidad suficiente para una
temática determinada, no se colocará dicha temática. Una estantería puede estar ocupada por
libros de diversas temáticas mientras tenga capacidad para ello. Todas las estanterías tienen la
misma capacidad, y todos los libros ocupan el mismo espacio.
Vamos a modelar el problema a resolver mediante una clase denominada ProblemaLibreria, que
posee los siguientes atributos:
¾
int [] numLibros
.- array ordenado de forma decreciente tal que
numLibros[t]
indica el
número de libros referentes a la temática
t
.
¾
int capacidadEstanterias
.- indica el número de libros capaz de albergar cada una de las
estanterías.
¾
int numMaximoEstanterias
.- número de estanterías que caben en el local.
a)
Completa el código de la clase LibreriaVoraz que aparece a continuación, teniendo en
cuenta que la estrategia voraz que se debe seguir consiste en recorrer las diferentes
temáticas de mayor a menor número de unidades, de forma que se asigne una estantería
a cada temática. La estantería elegida será aquélla que
tenga espacio libre para la
temática completa y que además sea la que tenga menor espacio libre. La solución
se almacenará en el atributo
estanteriaAsignada
de la clase LibreriaVoraz, de forma que
estanteriaAsignada[t]
indica la estantería en la que se ubicará la temática
t
. En el caso de
no encontrar ninguna estantería con capacidad suficiente para una temática determinada
se asignará el valor -1.
package soluciones.libreria; import esquemas.EsquemaVZ;
import soluciones.EstrategiaSolucion; import problemas.ProblemaLibreria;
public class LibreriaVoraz {
/* Array que indica el número de libros que compone cada temática. Se obtiene de la clase ProblemaLibreria */
int[] numLibros;
/* Array que indica el espacio libre que tiene cada una de las estanterías en un momento del algoritmo, de forma que espacioLibreEstanterias[e]= n indica que la estantería e tiene sitio libre para n libros */
int[] espacioLibreEstanterias;
// Capacidad de cada una de las estanterías int capacidadEstanterias;
// Número máximo de estanterías int numMaximoEstanterias;
// Array en el que se almacena la solución al problema int[] estanteriaAsignada;
/* Representa el índice de la temática que se está analizando en cada momento del algoritmo */
int tematicaActual;
/* Se utiliza para buscar la estantería más adecuada para tematicaActual en cada iteración del algoritmo */
int estanteriaAAsignar; public LibreriaVoraz(ProblemaLibreria pl) { this.numLibros = pl.getNumLibros(); this.capacidadEstanterias = pl.getCapacidad(); this.numMaximoEstanterias = pl.getNumMaximoEstanterias(); }
public void procesamientoInicial() { }
protected void inicializa() {
this.espacioLibreEstanterias = new int[numMaximoEstanterias]; for (int i = 0; i < numMaximoEstanterias; i++) {
this.espacioLibreEstanterias[i] = capacidadEstanterias; }
estanteriaAsignada = new int[numLibros.length]; tematicaActual = 0;
}
protected boolean fin() {
return (tematicaActual == numLibros.length); }
protected void seleccionaYElimina() { estanteriaAAsignar = -1;
int minValor = Integer.MAX_VALUE; boolean enc = false;
for (int i = 0; i < numMaximoEstanterias && !enc; i++) {
if (espacioLibreEstanterias[i] >= numLibros[tematicaActual] && espacioLibreEstanterias[i] < minValor) {
minValor = espacioLibreEstanterias[i]; estanteriaAAsignar = i; if(espacioLibreEstanterias[i] == numLibros[tematicaActual]){ enc = true; } } } if (estanteriaAAsignar != -1) { espacioLibreEstanterias[estanteriaAAsignar] = espacioLibreEstanterias[estanteriaAAsignar] - numLibros[tematicaActual]; } }
protected boolean prometedor() { return true;
}
protected void anotaEnSolucion() {
estanteriaAsignada[tematicaActual] = estanteriaAAsignar; tematicaActual++;
}
public void solucion() { voraz();
}
public void procesamientoFinal() { }
Problema 4 – Divide y Vencerás
Vamos a resolver el problema de la búsqueda binaria, visto en la práctica 5, usando el esquema de
Divide y Vencerás, visto en la 7.
La búsqueda binaria consiste en lo siguiente: en la parte de dividir, el subarray que se devuelve es el
formado sólo por el elemento central, el de la izquierda o el de la derecha, dependiendo de si el
elemento central del array es el buscado, es menor que el de la mitad o mayor. En la parte de combina
simplemente se devuelve la solución que se ha recibido. El caso base se dará cuando el subarray esté
compuesto por un solo elemento. La resolución del caso base consistirá en ver si el elemento a buscar
esta en el subarray (que es de un solo elemento); si es así, su posición es la solución; en caso contrario,
se marca que no hay solución devolviendo -1.
Se desea resolver este problema usando la estructura vista en las prácticas.
Solución:
BusquedaBinariaDYV.java (clase que resuelve el problema del modo descrito):
public class BusquedaBinariaDYV package soluciones;
import temporizadores.*; import problemas.Busqueda; import esquemas.EsquemaDYV;
{ extends EsquemaDYV implements EstrategiaSolución
private int[] datos; // el array donde se busca
private int numElem; // número de elemento de datos
private int numBuscado; // el número que se busca
private int resultado; // almacena la posición en la que se encuentra // el elemento buscado o -1 si no se encuentra
public BusquedaBinariaDYV(Busqueda busquedaProblema) {
}
numElem = busquedaProblema.getNumElem(); datos = busquedaProblema.getDatos();
numBuscado = busquedaProblema.getNumBuscado();
public void solucion() {
}
busca();
private void busca() {
// Se prepara el problema:
// Se lanza el divide y vencerás:
ProblemaBusqBin p = new ProblemaBusqBin(); p.izq = 0;
p.der = numElem - 1;
// Y se ve la solución que se ha encontrado:
SolucionBusqBin s = (SolucionBusqBin) dYV(p);
}
resultado = s.pos;
public void procesamientoInicial() {} public void procesamientoFinal() {}
} ProblemaBusqBin pb = (ProblemaBusqBin) p; if (pb.izq >= pb.der) { return true; } else { return false; }
protected Problema[] divide(Problema p) {
}
ProblemaBusqBin[] pbs = new ProblemaBusqBin[1]; pbs[0] = new ProblemaBusqBin();
ProblemaBusqBin pb = (ProblemaBusqBin) p; int medio = (pb.izq + pb.der) / 2;
if (numBuscado == datos[medio]) { pbs[0].izq = medio;
pbs[0].der = medio;
} else if (numBuscado < datos[medio]) { pbs[0].izq = pb.izq; pbs[0].der = medio - 1; } else { pbs[0].izq = medio + 1; pbs[0].der = pb.der; } return pbs;
protected Solucion combina(Solucion[] s) {
}
SolucionBusqBin sol = new SolucionBusqBin(); sol.pos = ((SolucionBusqBin) s[0]).pos; return sol;
protected Solucion resuelveCasoBase(Problema p) {
}
SolucionBusqBin s = new SolucionBusqBin(); s.izq = ((ProblemaBusqBin) p).izq;
s.der = ((ProblemaBusqBin) p).der; if (s.izq == s.der) {
if (datos[s.izq] != numBuscado)
s.pos = -1; // para marcar como no-solución
else
s.pos = izq; }
class ProblemaBusqBin extends Problema { int izq, der;
}
class SolucionBusqBin extends Solucion { int pos;
} }
Problema 5 – Programación Dinámica
Se quiere determinar la composición de la carga de un avión que tiene que transportar
n
tipos de
productos, teniendo un límite de carga
M
. Cada producto tiene unas características diferentes de
peso y beneficio por unidad según quedan recogidas en una tabla:
int[][] productos
(observar el ejemplo adjunto). Nos preguntamos por la composición de la carga del avión para
que el beneficio sea máximo. Queremos resolver el problema utilizando Programación
Dinámica.
Para encontrar la fórmula recursiva, llamamos
beneficioMaximo
a la matriz
bidimensional tal que
beneficioMaximo[i][j]
es el beneficio máximo de meter en el avión
la carga
j
toneladas utilizando los productos entre
0
,
1
, ...,
i
. Por otro lado, para maximizar las
componentes de dicha matriz, téngase en cuenta que para obtener el beneficio máximo con una
carga de
j
toneladas con los productos en
0,..,i
, tendremos dos situaciones posibles:
1)
No meter en el avión unidad alguna del producto
i
, con lo que el beneficio es el dado
por
beneficioMaximo[i-1][j]
.
2)
Meter en el avión al menos una unidad del producto
i
, con lo que el beneficio será
beneficioUnidad[i]+beneficioMaximo[i][j-pesoUnidad[i]]
.
Recordar que deberán tenerse en cuenta las condiciones especiales en las que no puede
aplicarse la fórmula recursiva (donde están incluidos los casos base).
Con todo ello, se pide dar el cuerpo del método
int carga(int[][] productos, int
cargaMaxima)
que se supone se ajusta a los parámetros del problema y que devuelve el
beneficio máximo para los productos y la carga máxima total que admite el avión.
Producto
Peso por unidad Beneficio por unidad
1 2 4
2 3 7
3 5 9
4 4 8
n es 4 y
M
es
8
toneladas (por ejemplo)
int carga(int[][] productos, int cargaMaxima) {int n = productos.length;
int[][] beneficioMaximo = new int[n][cargaMaxima + 1]; for (int i = 0; i < n; i++)
for (int j = 0; j <= cargaMaxima; j++) { if (j == 0)
beneficioMaximo[i][j] = 0;
else if (i == 0 && productos[i][1] > j) beneficioMaximo[i][j] = 0;
else if (i == 0)
Problema 6 – Backtracking
Los organizadores del
first Internacional Symposium on ADA
tienen que organizar las mesas
para la cena de dicho congreso. No saben cómo sentar a la gente, por lo que deciden sentar
juntos a los de edad similar, para ello utilizan la
desviación media
(la media de las diferencias
en valor absoluto de los valores de la variable a la media). Resuelva mediante la técnica de
vuelta atrás la mejor configuración de invitados en las mesas para que estén sentados juntos los
de edad parecida. La desviación de todas las mesas (el resultado del problema) será la suma de
la desviación de cada una de las mesas. El objetivo es que la desviación total sea la menor
posible.
Nota: Las mesas serán tratadas mediante una clase interna
Mesa,
cuyos métodos y atributos son
explicados en el código de la propia clase.
package soluciones; import esquemas.*; import problemas.*;
public class DistribucionMesasBT extends EsquemaBtOptima implements EstrategiaSolucion {
/** Array de las edades de las personas que asisten al congreso */ private int[] edadPersonas;
/** Número de mesas del Congreso */ private int numeroMesas;
/**
* Tamaño de las mesas. Todas son del mismo tamaño, y hay sitio para sentar a * todos los asistentes al congreso
*/
private int tamañoMesas;
private SolucionMesas solucionOptima; private SolucionMesas sol;
public DistribucionMesasBT(ProblemaMesas pm) {
this.edadPersonas = (pm.getEdadPersonas()).clone(); this.numeroMesas = pm.getNumeroMesas();
this.tamañoMesas = pm.getTamañoMesas();
sol = new SolucionMesas(numeroMesas, tamañoMesas); }
public void solucion() {
EtapaMesas x = new EtapaMesas(); x.k = -1;
btOptimo(x); }
public void procesamientoInicial() {
solucionOptima = new SolucionMesas(numeroMesas, tamañoMesas); }
public void procesamientoFinal() { // nada
}
protected boolean esSolucion(Etapa x) { boolean es;
EtapaMesas x1 = (EtapaMesas) x;
es = ((x1.k) + 1 == edadPersonas.length); return es;
}
protected boolean esMejor() { boolean es;
es = (sol.sumaDesviacion < solucionOptima.sumaDesviacion); return es;
protected void actualizaSolucion() {
solucionOptima.mesasInvitados = (Mesa[]) (sol.mesasInvitados).clone(); solucionOptima.sumDesviacion = sol.sumDesviacion;
}
protected Candidatos calculaCandidatos(Etapa x1) { EtapaMesas x = (EtapaMesas) x1;
CandidatosMesas cand = new CandidatosMesas(); if (x.k == edadPersonas.length) { cand.i = 0; cand.iMax = 0; } else { cand.i = -1; cand.iMax = numMesas; } return cand; }
protected boolean quedanCandidatos(Candidatos cand1) { boolean quedan;
CandidatosEntrenador cand = (CandidatosEntrenador) cand1; quedan = (cand.i < cand.iMax); {
return quedan; }
protected Etapa seleccionaCandidato(Candidatos cand1, Etapa xsig1) { CandidatosMesas cand = (CandidatosMesas) cand1;
cand.i++;
EtapaMesas x = (EtapaMesas) x1; EtapaMesas xsig = new EtapaMesas(); xsig.k = x.k+1;
return xsig; }
protected boolean esPrometedor(Candidatos cand1, Etapa x, Etapa xsig) { boolean b = false; if ((sol.mesasInvitados[((CandidatosMesas) cand1).i]).sillasOcupadas == tamañoMesas) { b = false; } else { b = true; } return b; }
protected void anotaSolucion(Candidatos cand1, Etapa x, Etapa xsig1) { CandidatosMesas cand = (CandidatosMesas) cand1;
EtapaMesas xsig = (EtapaMesas) xsig1;
sol.sumaDesviacion -= (sol.mesasInvitados[cand.i]).desviacion(); (sol.mesasInvitados[cand.i]).add(new Integer(xsig.k));
(sol.mesasInvitados[cand.i]).actualizaDesviacion(edadInvitados); sol.sumDesviacion += (sol.mesasInvitados[cand.i]).desviacion(); }
protected void cancelaAnotacion(Candidatos cand1, Etapa x, Etapa xsig1) { CandidatosMesas cand = (CandidatosMesas) cand1;
EtapaMesas xsig = (EtapaMesas) xsig1;
sol.sumDesviacion -= (sol.mesasInvitados[cand.i]).getDesviacion(); (sol.mesasInvitados[cand.i]).delete(new Integer(xsig.k));
/** Sillas ya ocupadas */
int sillasOcupadas; ...
Mesa(int tamañoMaximo) {
this.tamañoMaximo = tamañoMaximo; desviación = 0;
}
void add(int invitado) {
// añade el invitado a la mesa
}
void delete(int invitado) {
// elimina el invitado de la mesa
}
void actualizaDesviacion(int[] edadInvitados) {
// actualiza el atributo desviación con los invitados que tiene sentados y // las edades que se pasan con parámetro de entrada
} }
class SolucionMesas extends Solucion { Mesa[] mesasInvitados;
int sumaDesviacion;
SolucionMesas(int numMesas, int tamañoMesas) { mesasInvitados = new Mesa[numMesas]; for (int i=0; i<numMesas; i++) {
mesasInvitados[i] = new Mesa(tamañoMesa); }
sumaDesviacion = 0; }
}
class EtapaMesa extends Etapa { int k;
}
class CandidatosMesas extends Candidatos { int i;
int iMax; }
Problema 7 – Backtracking
Se tiene una lista de canciones que se desean grabar en un CD. Dichas canciones tienen
duraciones distintas, y puede darse el caso en el que no todas quepan. Por esta razón queremos
buscar la combinación de canciones que aprovechen al máximo la capacidad del CD. Para ello,
vamos a desarrollar un algoritmo que busque dicha combinación que siga un esquema de vuelta
atrás.
Capacidad del CD: 8:20 Combinación óptima: Canción 1, 2:51 Canción 2, 3:20 Canción 5, 1:59 Tiempo total: 8:10 Canción Duración Canción 1 2:51 Canción 2 3:20 Canción 3 2:45 Canción 4 4:32 Canción 5 1:59Se deberán tener en cuenta las siguientes consideraciones:
•
Se dispone de una clase
ListaDeCanciones
que contiene un vector que almacena
instancias de la clase
Cancion
, con el nombre y duración (minutos y segundos) de
cada canción.
•
Se dispone de los siguientes métodos estáticos en la clase
Tiempos
:
static boolean esMayor( int min1, int seg1, int min2, int seg2 ); static boolean esMayorOIgual( int min1, int seg1, int min2, int seg2 ); static boolean esIgual( int min1, int seg1, int min2, int seg2 );
ListaDeCanciones.java
package problemas;
public final class ListaDeCanciones {
private static Cancion[] listaCanciones;
private static int numCanciones;
public ListaDeCanciones(Cancion[] canciones) { this.listaCanciones = canciones;
numCanciones = canciones.length; }
public Cancion[] getCanciones() { return listaCanciones; }
public int getNumCanciones() { return numCanciones; }
public String toString () {...} }
Cancion.java
package problemas;
public class Cancion { // [...]
public Cancion(String nombre, int minutos, int segundos) {...}
Selector.java
package soluciones; import esquemas.*; import problemas.*; import temporizadores.*;
public class Selector extends EsquemaBtOptima implements EstrategiaSolucion { private Cancion[] listaCanciones;
private int numCanciones;
private int maxMinutos, maxSegundos; private SolucionSelector solucionOptima; private SolucionSelector sol;
public Selector(ListaDeCanciones lc, int minutos, int segundos) { this.listaCanciones = lc.getCanciones();
this.numCanciones = lc.getNumCanciones(); this.maxMinutos = minutos;
this.maxSegundos = segundos;
solucionOptima = new SolucionSelector(); sol = new SolucionSelector();
}
public void solucion() {
EtapaSelector e = new EtapaSelector(); e.k = -1;
e.minutosRestantes = maxMinutos; e.segundosRestantes = maxSegundos; solucionOptima = new SolucionSelector(); sol = new SolucionSelector();
btOptimo(e); }
public void procesamientoInicial() {
solucionOptima = new SolucionSelector(); sol = new SolucionSelector();
}
public void procesamientoFinal() {...}
class SolucionSelector extends Solucion { boolean[] canciones; int minutosAcumulados; int segundosAcumulados; SolucionSelector() { this.minutosAcumulados = 0; this.segundosAcumulados = 0;
this.canciones = new boolean[listaCanciones.length]; }
public String toString() {...}
public Cancion[] getSolucion() {...} }
class EtapaSelector extends Etapa { int k;
int minutosRestantes, segundosRestantes;
EtapaSelector() {...} }
class CandidatosSelector extends Candidatos { int i;
int iMax;
CandidatosSelector() {...} }
boolean es = false; EtapaSelector x1 = (EtapaSelector)x; if(x1.k == listaCanciones.length-1) es = true; return es; }
protected boolean esMejor() { boolean es = false; if (Tiempo.esMayor(sol.minutosAcumulados, sol.segundosAcumulados, solucionOptima.minutosAcumulados, solucionOptima.segundosAcumulados)) { es = true; } return es; }
protected void actualizaSolucion() {
solucionOptima.canciones = (boolean[]) sol.canciones.clone(); solucionOptima.minutosAcumulados = sol.minutosAcumulados; solucionOptima.segundosAcumulados = sol.segundosAcumulados; }
protected Candidatos calculaCandidatos(Etapa x1) { EtapaSelector x = (EtapaSelector) x1;
CandidatosSelector cand = new CandidatosSelector(); if (x.k < listaCanciones.length
&& Tiempo.esMayor(x.minutosRestantes, x.segundosRestantes, 0, 0)) { cand.i = -1;
cand.iMax = 1; } else {
cand.i = 0;
// para que no haya más candidatos después de llegar a una // hoja del árbol de expansión
cand.iMax = -1; }
return cand; }
protected boolean quedanCandidatos(Candidatos cand1) { boolean quedan = false;
CandidatosSelector cand = (CandidatosSelector) cand1; if (cand.i < cand.iMax) { quedan = true; } return quedan; }
protected Etapa seleccionaCandidato(Candidatos cand1, Etapa x1) { CandidatosSelector cand = (CandidatosSelector) cand1;
EtapaSelector x = (EtapaSelector) x1;
cand.i++;
EtapaSelector xsig = new EtapaSelector(); xsig.k = x.k + 1;
xsig.minutosRestantes = x.minutosRestantes; xsig.segundosRestantes = x.segundosRestantes; return xsig;
}
protected void anotaSolucion(Candidatos cand1, Etapa x, Etapa xsig1) { CandidatosSelector cand = (CandidatosSelector) cand1;
EtapaSelector xsig = (EtapaSelector) xsig1; if (cand.i==1) { sol.canciones[xsig.k] = true; sol.minutosAcumulados += listaCanciones[xsig.k].getMinutos(); sol.segundosAcumulados += listaCanciones[xsig.k].getSegundos(); if (sol.segundosAcumulados >= 60 ) { sol.minutosAcumulados++; sol.segundosAcumulados -= 60; } xsig.minutosRestantes -= listaCanciones[xsig.k].getMinutos(); xsig.segundosRestantes -= listaCanciones[xsig.k].getSegundos(); if (xsig.segundosRestantes < 0) { xsig.segundosRestantes += 60; xsig.minutosRestantes--; } } else { sol.canciones[xsig.k] = false; } }
protected void cancelaAnotacion(Candidatos cand1, Etapa x, Etapa xsig) { // igual que anotaSolucion, pero a la inversa
}
public String toString() {...} }
Problema – Resolución de sudokus mediante la técnica de backtracking
El juego del Sudoku consiste en rellenar un cuadrado de 9x9 celdas dispuestas en 9 subgrupos
de 3x3 celdas, con números enteros del 1 al 9, atendiendo a la restricción de que no se debe
repetir el mismo número en la misma fila, columna o subgrupo de 9. Un Sudoku dispone de
varias celdas con un valor inicial, de modo que debemos empezar a resolver el problema a partir
de esta solución parcial sin modificar ninguna de las celdas con valores iniciales.
Vamos a modelar el problema a resolver mediante una clase denominada
ProblemaSudoku
,
que posee los siguientes atributos:
¾
int [][] sudoku
.- matriz de enteros que representa el sudoku de entrada. Las celdas que
no estén inicializadas de entrada tendrán como valor un 0.
¾
int inicializadas.-
entero que indica el número de celdas del sudoku que se dan
inicializadas de entrada.
Un posible problema a resolver puede ser el siguiente:
sudoku
=
inicializadas
= 31
0 0 0 0 0 0 0 9 2
0 0 8 0 6 0 0 0 0
6 1 9 5 0 0 8 0 3
3 7 0 0 0 0 0 0 8
9 0 0 7 0 3 0 5 0
1 0 5 6 8 0 0 7 0
0 0 0 3 0 0 0 2 6
7 0 0 0 0 0 9 0 0
0 5 0 1 0 0 4 3 0
Siguiendo el esquema de paquetes visto en prácticas, tendremos una clase
SudokuBt
, que
contendrá la solución de este problema mediante backtracking. Para ello, se han definido las
clases internas
EtapaBt
,
SolucionBt
y
CandidatosBt
de la siguiente forma:
•
EtapaBt
: en cada etapa de nuestro algoritmo, intentaremos darle un valor
válido a una celda del sudoku. Por tanto, los atributos de cada etapa serán:
o
int k.-
número de la etapa
o
int fila.-
fila de la celda que estamos considerando
o
int columna.-
columna de la celda que estamos considerando
•
CandidatosBt
: en cada ocasión, tomaremos como candidatos todos los
enteros entre 1 y 9, ambos inclusive. (Estos valores serán posteriormente
prometedores si no están repetidos en la misma fila y columna de la celda actual,
así como en el subgrupo de 3x3 al que pertenece la misma). Esta clase tiene
como único atributo:
o
int valor
.- indica el entero que se considerará como candidato.
Dicho esto, se pide completar el código de los métodos propuestos.
NOTA: La clase
SudokuBt
cuenta con 4 métodos privados que suponemos ya
implementados:
-
private boolean mismaFila(int f, int v)
: comprueba si en la fila
f
hay
alguna celda con un valor igual a
v
.
-
private boolean mismaColumna(int c, int v)
: comprueba si en la columna
c
hay alguna celda con un valor igual a
v
.
-
private boolean mismoSubgrupo(int f, int c, int v)
: comprueba
si el valor
v
se encuentra en el subgrupo de 3x3 elementos al que pertenece la celda con fila
f
y
columna
c
.
-
private void siguienteCasilla(Etapa x, Etapa xsig)
: calcula, a
package soluciones.segundoParcial; import problemas.ProblemaSudoku;
extends esquemas.EsquemaBtUno implements
soluciones.EstrategiaSolucion public class SudokuBt
{ /**
* Almacena la matriz con el sudoku que queremos resolver. */
int[][] sudoku; /**
* Guarda el número de celdas que están rellenas en el sudoku de entrada */
int inicializadas;
/**
* Almacena las soluciones parciales. */
Solucion sol; /**
* Constructor de la clase. Copia los datos del problema a resolver. */ public SudokuBt(ProblemaSudoku ps) { sudoku = ps.getSudoku(); inicializadas = ps.getInicializadas(); } /**
* Devuelve un booleano indicando si hemos encontrado una solución. *
*/
protected boolean esSolucion(Etapa e) {
return
((SolucionBt)
sol
).
rellenas
==81;
/*también sería válido:
return (((EtapaBt)e).k==81-inicializadas);
*/
} /**
* Calcula los valores candidatos de la etapa e. *
*/
protected Candidatos calculaCandidatos(Etapa e) {
//El constructor de la clase CandidatosBt inicializa valor a 0 return new CandidatosBt();
} /**
* Comprueba si quedan candidatos. *
*/
//Nos desplazamos a la siguiente casilla del sudoku
siguienteCasilla(e,sig);
return
sig;
}
/**
* Comprueba si el candidato es prometedor, según lo comentado en el enunciado. *
*/
protected boolean esPrometedor(Candidatos c, Etapa x, Etapa xsig) {
EtapaBt
etapa=(EtapaBt)x;
int
vactual=((CandidatosBt)c).
valor
;
return
!mismaFila(etapa.
fila
,
vactual)
&&
!mismaColumna(etapa.
columna
,
vactual)
&&
!mismoSubgrupo(etapa.
fila
,
etapa.
columna
,
vactual);
}
/**
* Anota en la solución el valor dado a la celda de la etapa actual. *
*/
protected void anotaSolucion(Candidatos c, Etapa x, Etapa xsig) {
SolucionBt
solBt
=
(SolucionBt)
sol
;
EtapaBt
etapa
=
(EtapaBt)
x;
solBt.
solActual
[etapa.
fila
][etapa.
columna
]=((CandidatosBt)c).
valor
;
solBt.
rellenas
++;
}
/**
* Cancela la última anotación realizada *
*/
protected void cancelaAnotacion(Candidatos c, Etapa x, Etapa xsig) {
SolucionBt
solBt=(SolucionBt)
sol
;
EtapaBt
etapa=(EtapaBt)x;
solBt.
solActual
[etapa.
fila
][etapa.
columna
]=0;
solBt.
rellenas
--;
}
public void procesamientoInicial() {
//El constructor de SolucionBt inicializa la solución parcial con //los datos del problema
this.sol = new SolucionBt(); }
public void procesamientoFinal() {}
public void solucion() {
EtapaBt
inicio=
new
EtapaBt();
inicio.
k
=0;
inicio.
fila
=0;
inicio.
columna
=-1;
//Nos situamos en la primera casilla que contenga un cero
siguienteCasilla(inicio,
inicio);
this
.btUno(inicio);
} }