• No se han encontrado resultados

public class AtaqueVoraz extends EsquemaVZ implements EstrategiaSolucion {

N/A
N/A
Protected

Academic year: 2021

Share "public class AtaqueVoraz extends EsquemaVZ implements EstrategiaSolucion {"

Copied!
24
0
0

Texto completo

(1)

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

(2)

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)

(3)

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

} }

(4)

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×50

el

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

1

A

2

A

3

A

4

se puede realizar de cinco maneras: A

1

(A

2

(A

3

A

4

)), A

1

((A

2

A

3

) A

4

),

(A

1

A

2

) (A

3

A

4

), ((A

1

A

2

) A

3

) A

4

y (A

1

(A

2

A

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+1

cuya dimensión común d

i

sea la menor entre

todas, y repetir el proceso.

Realizar primero la multiplicación de las matrices A

i

×A

i+1

que requiera menor número

de operaciones (d

i–1

d

i

d

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

(5)

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

(6)

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

(7)

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.

(8)

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

(9)

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

(10)

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

(11)

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

} }

(12)

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)

(13)

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;

(14)

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

(15)

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

(16)

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

Se 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) {...}

(17)

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

(18)

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;

(19)

}

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

(20)

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

(21)

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

(22)

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

*/

(23)

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

(24)

inicio.

columna

=-1;

//Nos situamos en la primera casilla que contenga un cero

siguienteCasilla(inicio,

inicio);

this

.btUno(inicio);

} }

Referencias

Documento similar