Professional Documents
Culture Documents
Contenido:
1. RESPUESTAS A LAS CUESTIONES PLANTEADAS EN EL ENUNCIADO.
1.1. 1.2. 1.3. 1.4. 1.5. Caractersticas de la aplicacin del esquema al problema planteado Descripcin del grafo asociado al espacio de soluciones Funcin de coste para la seleccin del nodo ms prometedor Clculo de una cota del valor de las soluciones generadas Estructura de datos utilizada para el almacenamiento de los nodos no explorados 1.6. Pseudocdigo del esquema y de su instanciacin al problema 1.7. Coste computacional del programa desarrollado 2. EJEMPLO DE EJECUCIN PARA EL CASO DE PRUEBA.
3. ESTUDIO DEL COSTE DEL ALGORITMO. 4. LISTADO DEL CDIGO FUENTE COMPLETO.
4.1. Clase Puzzle 4.2. Clase Resuelve 4.3. Clase Nodo 4.4. Clase Arbol 4.5. Clase Traduce
que ninguno de ellos es igual a su padre, mediante el descarte del movimiento opuesto al que gener el nodo evaluado, y que ninguno de ellos ha sido ya evaluado, por medio de la exploracin de un rbol binario de bsqueda (ordenado por el valor de las casillas de cada puzzle comprobadas en orden ascendente). Se utiliza una estructura diferente a la de la seleccin del nodo ms prometedor debido al diferente criterio de bsqueda a utilizar: ordenados por coste en la seleccin del nodo (varios nodos diferentes pueden tener el mismo coste) y por el valor de las casillas (que pretendemos que nunca sea igual). 1
1.2.- Descripcin del grafo asociado al espacio de soluciones. Las soluciones se distribuyen en un rbol cuya raz es el puzzle inicial. A partir de ella se desarrollan las diferentes ramas, que sern generadas expandiendo cada uno de los nodos. Los hijos de un nodo se generan al ir moviendo las fichas del puzzle padre; tomando como referencia el caso de prueba, los movimientos aparentes del hueco podrn ser: Arriba (norte), intercambio de posicin con el 5. Abajo (sur), intercambio de posicin con el 8. 1 5 2 Derecha (este), intercambio de posicin con el 3. Izquierda (oeste), intercambio de posicin con el 4. 4 3 De esta manera tenemos un mximo de cuatro movimientos a partir de cada nodo. Segn hemos visto en el punto 1.1.c, debemos rechazar siempre al menos uno de los movimientos posibles del hueco, aquel que deshaga el ltimo realizado. Esto nos da un mximo de tres hijos para cada nodo distinto del raz (que no tiene un movimiento anterior), sin tener en cuenta la posicin del nodo, que puede hacer descartar a su vez los movimientos que lo puedan sacar del tablero. 7 8 6 Por lo tanto, el rbol asociado al espacio de soluciones ser un rbol ternario, con la excepcin del primer nivel de descendientes, que podr ser de cuatro hijos.
1.3.- Funcin de coste para seleccionar el nodo ms prometedor. Dado que lo que buscamos es la solucin con menos movimientos realizados, la funcin de coste la estableceremos como la suma de dos factores, los movimientos realizados hasta llegar al nodo evaluado y una estimacin de los movimientos restantes hasta alcanzar la solucin. fcoste=(movimientos realizados) + fmovimientos estimados Para el clculo aproximado de los movimientos restantes se usar la suma de las distancias de Manhattan de cada ficha a su posicin final. sta se calcula en nuestro caso contando las filas y columnas que diferencian las posiciones actual y final de cada ficha. Dar un nmero de movimientos cercano al real pero no exacto, ya que no tiene en cuenta que al mover una ficha pueden descolocarse otras (y acercarse a su posicin final o alejarse de la misma). 2
distancia=Math.abs((pos/numFilas)-(posfinal/numFilas)) + Math.abs((pos%numFilas)-(posfinal%numFilas));
pos, posfinal: son las posiciones de la ficha dentro del array de datos del nodo y de la solucin, respectivamente. pos/numFilas da la fila de la casilla; pos%numColumnas da la columna.
Como ejemplo presentamos el caso de prueba, resoluble en cuatro movimientos: Valores 1 4 7 8 5 2 3 6 distancia = 4 0 0 0 0 Distancias 1 1 1 1
0 movs. hechos
coste = 4
Por tanto, para seleccionar el nodo ms prometedor usaremos una estrategia de mnimo coste (LC), implementada en la ordenacin de la estructura de datos en la que se almacenarn los nodos an no desarrollados: la extraccin del nodo a analizar en cada iteracin debe proporcionar el nodo de menor coste de los almacenados. 1.4.- Clculo de una cota del valor de las soluciones que se encontrarn generando sucesores a un determinado nodo. Inicialmente tomaremos como cota un nmero de movimientos que depende de la magnitud de n; tomaremos 1 para n=1 (trivial), 6 para n=2 (1) , 31 para n=3 (2) , 80 para n=4 (3) y 1000 para n>4. A esta cota le aadimos un margen de trabajo de 2, dado que a veces la funcin de coste sobreestima ligeramente los movimientos restantes, como acabamos de ver en 1.3. Una vez encontrada la primera solucin, se toma como cota el nmero de movimientos de la misma, para desarrollar slamente nodos con coste menor o igual que el de la solucin encontrada. 1.5.- Estructura de datos utilizada para almacenar en orden de prioridad los nodos creados y an no explorados. Se utiliza un montculo de mnimos, implementado mediante la clase PriorityQueue de Java 5.0. La insercin y la extraccin de la raz las realiza en O(log(n)). La comprobacin de estado vaco y del nmero de nodos contenidos se obtiene en tiempo constante (4) . Para la implementacin de la estrategia LC nos bastar con extraer la raz como seleccin del nodo ms prometedor. Tras esa extraccin, la clase automticamente restaura la condicin de montculo de mnimos.
(1) (2)
http://mathworld.wolfram.com/15Puzzle.html http://www.zib.de/reinefeld/bib/93ijcai.pdf (3) http://www.ifor.math.ethz.ch/publications/1999_parallelsearchbenchzram.ps (4) Java 2.0 Platform Standard Edition 5.0 API Specification
En nuestro caso en particular no necesitamos el uso de una cota inferior, ya que lo que buscamos es un tablero resuelto, comprobado tras la llamada a es-solucin (nodo). Como hemos comentado antes, usaremos dos estructuras para almacenar los nodos, un montculo para los nodos no visitados y otro para los que ya hemos evaluado; la funcin expandir debe comprobar que los hijos que genere no han sido ya visitados, para evitar ciclos. Incluimos el pseudocdigo de la funcin expandir en la presentacin de la instanciacin del problema.
1.7.- Anlisis del coste computacional del programa desarrollado. Para el clculo del coste existe la dificultad de no conocer a priori el tamao del problema. El tamao del puzzle es siempre conocido, n = nmero de filas, y sabemos que el nmero mximo de nodos del rbol de sucesores del puzzle inicial es n2!/2 (5) . La estrategia LC pretende reducir al mnimo el nmero de nodos visitados (y, por lo tanto, expandidos) dentro del citado rbol mediante el uso de una funcin
(5)
http://kevingong.com/Math/SixteenPuzzle.html
heurstica h que escoge el nodo ms prometedor mediante la estimacin del nmero de movimientos necesarios. Pero no puede calcular cuntos nodos se debern visitar para encontrar la solucin. Las llamadas ms costosas son las realizadas al montculo (insercin y extraccin de la raz) y al rbol de nodos visitados (insercin y bsqueda), y su coste depende en ambos casos del nmero de nodos generados. Por ello calcularemos el coste del programa con relacin a ese nmero, del que inicialmente slo conocemos su valor mximo. Llamando u al nmero total de nodos evaluados, m al nmero de nodos en cada momento en el montculo y a al de los nodos en el rbol, podemos calcular si suponemos siempre tres hijos generados:
u
T(u)= 5 + 1 +
(1 + log m + k
i=1
T(n)= 7 +
i=1
(4 log m + k
+ 4 log a
Intentando una aproximacin mejor, tratemos de relacionar m y a con u. Las inserciones y comprobaciones en el rbol a, de orden logartmico, se hacen tras evaluar cada nodo, por lo que podemos afirmar que a valdr en cada iteracin i-1. Realizando los clculos tenemos que la serie de los log a vale: log 0 + log 1 + ... + log (u-1) . Dado que la primera insercin es sobre un rbol vaco, por lo tanto de coste constante, podemos sustituir ese log 0 por k. Por tanto, k + log 1 + ... + log (u-1); por las propiedades de los logaritmos, k + log (1 * 2 * ... * (u-1))=k + log ((u-1)!) Para el clculo de las operaicones sobre m, de coste tambin logartmico, debemos tener en cuenta que este valor ir modificndose a lo largo de las pasadas por el bucle while, insertando entre 0 y 3 nodos y extrayendo uno por cada iteracin. En cada momento m valdr u-a = u-(i-1). Sustituyendo tenemos log u + log (u-1) + ... + log 1 = log (u!). Por ello T(n)= 7 + 4 log (u !) + k3 u + 4 (k4 + log ((u-1)!)). Aplicando la regla del mximo, T(u) es de O(u), siendo u el nmero de nodos generados, tomando como valor mximo u = n2!/2.
Analizado: Padre Montculo:{ } Analizado: A Montculo:{D, B, C} Analizado: AA Montculo:{AB, D, C, B} Analizado: AAA Montculo:{D, AB, C, B} Analizado:AAAA Mont:{AAAB,D,C,B,AB} Analizado: AAAB Montculo: {AB, D, C, B} Analizado: AB Montculo: {B, D, C} Analizado: B Montculo: {C, D} Analizado: C Montculo: { D} Analizado: D Montculo: { }
Mejor solucion encontrada: 4 movimientos. Tiempo: 0 seg. 0 mils. Tableros generados: 9. Tableros analizados: 10. Tableros podados: 5. 152 4*3 786 1*2 453 786 12* 453 786 123 45* 786 123 456 78* Nodo padre. Genera cuatro hijos A,B,C,D de coste 4,6,6 y 6, respectivamente. Tras insertarlos, el montculo es: {A4,B6,C6,D6}
Tras extraer A, el montculo queda: {D6,B6,C6} A genera dos hijos AA, AB con coste 4 y 6, respectivamente. Tras insertarlos, el montculo es: {AA4,D6,C6,B6,AB6} Tras extraer AA, el montculo queda: {AB6,D6,C6,B6} AA genera un hijo AAA de coste 4. Tras insertarlo, el montculo es: {AAA4,AB6,C6,B6,D6} Tras extraer AAA, el montculo queda as: {D6,AB6,C6,B6} AAA genera dos hijos AAAA y AAAB, de coste 4 y 6, respectivamente. Tras insertarlos, el montculo queda as: {AAAA4,D6,C6,B6,AB6,AAAB6} Tras extraer AAAA, el montculo queda as: {AAAB6,D6,C6,B6,AB6} Se encuentra AAAA como solucin, la cota pasa a ser 4. Al ir extrayendo los nodos restantes del montculo, no se expanden y se podan por ser su coste > 4.
T(n)= 5 + 1 +
if (!fichero){ //si no se pasa un archivo de entrada inicio=Traduce.pasaATipoNodo(); //leemos la entrada estndar }else{ //si se proporciona el archivo inicio=Traduce.pasaATipoNodo(nombreArchivo); //lo leemos
}//if else if (t){ //si se pas t, comprobamos la matriz de entrada if(!inicio.compruebaMatriz(t)){ //si no es correcta System.out.println ( "Matriz de entrada incorrecta."); System.exit(1); }else if (parametros==1){ //si slo se pasa t System.out.println ( "Matriz de entrada correcta."); System.exit(0); }//if !inicio
}//if t // si se pasa a, activamos traza y lo ponemos en pantalla if (fichero && a) System.out.println("Archivo: " + nombreArchivo); if (a) System.out.println ( "Traza activada."); Resuelve.puzzle(inicio, a); }//fin main }//fin clase puzzle //resolvemos el puzzle
v2=System.currentTimeMillis(); int segundos=(int)(v2-v1)/1000; int mils=(int)(v2-v1)%1000; if ((resultado==null)||(!resultado.esSolucion())){ //si RyP devuelve un puzzle no resuelto entrada.presentaPuzzle();
System.out.println("\nNo se ha encontrado solucion."); if (traza){ //comentarios si traza=true System.out.println("Tiempo: " + segundos + " seg. " + mils + " mils."); System.out.println("Tableros analizados: " + numAnalizados + ".\nTableros podados: " + numPodados + "."); }//if traza System.exit(0); //salimos del programa //si est resuelto presentamos la solucin
}else{
if (traza){ //comentarios si traza=true System.out.println(); System.out.println("Mejor solucion encontrada: " + resultado.devuelveMovimientos()+ " movimientos."); System.out.println("Tiempo: " + segundos + " seg. " + mils + " mils."); System.out.println("Tableros generados: " + numGenerados + ".\nTableros analizados: " + numAnalizados + ".\nTableros podados: " + numPodados + "."); }//if traza presentaSolucion(candidato,resultado.devuelveCamino()); }//if candidato }//fin de puzzle // algoritmo Ramificacin y Poda public static Nodo RyP(Nodo candidato, boolean traza){ Arbol vistos=new Arbol(); //rbol de bsqueda para los nodos ya vistos PriorityQueue <Nodo> monticulo=new PriorityQueue <Nodo>(); //montculo para los nodos an sin analizar ArrayList <Nodo> hijos=new ArrayList<Nodo>(); //almacena los hijos Nodo n,solucion=candidato; monticulo.offer(candidato); //aadimos candidato al montculo switch (Nodo.devuelveNumFilas()){ //ajusta la cota segn n al case 1: //nmero mximo de movimientos cota=maxCota=1;break; //segn el nmero de casillas case 2: //ms un margen de trabajo cota=maxCota=6+5;break;
10
case 3: cota=maxCota=31+5;break; case 4: cota=maxCota=80+5;break; default: cota=maxCota=1000;break; }//switch if (traza){ System.out.println("N= " + Nodo.devuelveNumFilas() + ". Cota= " + cota + "."); }//if traza while (!monticulo.isEmpty()){ //mientras haya nodos sin analizar n=monticulo.poll(); numAnalizados++; if ((traza)&&(numAnalizados%2000==0)){ //comentarios si traza=true System.out.println("Analizados: " + numAnalizados + ". Coste: " + n.devuelveCoste()+ ". Podados: " + numPodados + ". Sin analizar: " + monticulo.size()); }//if traza if (n.esSolucion()){ //si n es solucin solucion=n; cota=solucion.devuelveMovimientos(); //ajustamos la cota al nmero de movimientos de la solucin //para la poda posterior if (traza){ //comentarios si traza=true System.out.println("Primera solucion encontrada, " + n.devuelveMovimientos() + " movimientos. Cota= " + cota + "."); }//if traza }else if (n.esAceptable(cota)){ //si n es aceptable hijos=expandir(n,vistos); //expandimos el rbol //hijos= el array de hijos de n que devuelve 'expandir' while (!hijos.isEmpty()){ //insertamos en el montculo, ordenados segn el coste try { monticulo.offer(hijos.remove(0)); } catch (OutOfMemoryError e) { System.out.println("Error al insertar un nodo.2 +"Falta memoria."); System.exit(2); }//try catch }//while }else{ numPodados++; //poda si no es aceptable n
11
//(ocurre tras el ajuste de la cota con la primera solucin) }//if n.esSolucion try { //se aade n al rbol de nodos ya vistos ordenados segn los datos vistos.insertarPorDatos(n); } catch (OutOfMemoryError e) { System.out.println("Error al insertar un nodo. " + "Falta memoria."); System.exit(2); }//try catch }//while return solucion; //Devuelve la mejor solucin encontrada hasta llegar al lmite. //Dar error "Solucin no encontrada" si devuelve el nodo inicial. }//RyP // // // // // desarrolla los hijos factibles del nodo padre, y los inserta en la lista de posibles soluciones si no han sido ya comprobados (mediante el rblo vistos) y si son viables (mediante esAceptable se comprueba que su coste estimado no sobrepasa la cota) devuelve la lista de hijos generados para introducir en 'nuevos' public static ArrayList <Nodo> expandir(Nodo padre,Arbol vistos){ ArrayList <Nodo> lista=new ArrayList <Nodo>(); //Array para devolver los hijos generados char ultimoMov; String historia = padre.devuelveCamino(); int tamao=historia.length(); if (tamao!=0){ //localiza el ltimo movimiento hecho ultimoMov=historia.charAt(tamao-1); }else{ ultimoMov='J'; //si es el tablero inicial } boolean[] movPosibles=padre.devuelveMovPosibles(); if ((movPosibles[0]) && (ultimoMov!='S')){ //mov N Nodo hijo=(Nodo) padre.clone(); hijo.mueveN(); if ((!vistos.buscar(hijo))&&(hijo.esAceptable(cota))){ lista.add(hijo); numGenerados++; }else{ numGenerados++; numPodados++; } }//mov N if ((movPosibles[1])&& (ultimoMov!='N')){ //mov S
12
Nodo hijo=(Nodo) padre.clone(); hijo.mueveS(); if ((!vistos.buscar(hijo))&&(hijo.esAceptable(cota))){ lista.add(hijo); numGenerados++; }else{ numGenerados++; numPodados++; }//mov S if ((movPosibles[2])&& (ultimoMov!='O')){ Nodo hijo=(Nodo) padre.clone(); hijo.mueveE(); if ((!vistos.buscar(hijo))&&(hijo.esAceptable(cota))){ lista.add(hijo); numGenerados++; } else{ numGenerados++; numPodados++; }//mov E if ((movPosibles[3])&& (ultimoMov!='E')){ Nodo hijo=(Nodo) padre.clone(); hijo.mueveO(); if ((!vistos.buscar(hijo))&&(hijo.esAceptable(cota))){ lista.add(hijo); numGenerados++; }else{ numGenerados++; numPodados++; }//mov O return lista; }//expandir // //
} //mov E
} //mov O
presenta la solucin desde el tablero inicial, mediante el movimiento del hueco segn el camino almacenado public static void presentaSolucion(Nodo inicio, String camino){ inicio.presentaPuzzle(); //presenta el nodo inicial int tope=camino.length(); //n de movimientos int contador=0; while (contador<tope){ char movimiento=camino.charAt(contador); switch (movimiento){ //mueve y presenta segn el movimiento ledo case 'N': inicio.mueveN();inicio.presentaPuzzle();break; case 'S': inicio.mueveS();inicio.presentaPuzzle();break; case 'E': inicio.mueveE();inicio.presentaPuzzle();break; case 'O':
13
// atributos de instancia private String camino; private int colocadas; private int movimientos; private int coste; private int datos[]=new int[numCasillas]; private int posHueco;
//almacena los movimientos del hueco //n de fichas colocadas //n de movimientos realizados //coste de cada nodo //array de int con las piezas //posicin del hueco en el array
private boolean movPosibles[]=new boolean[4]; //array de booleanos con // los movimientos posibles del hueco en orden Norte, Sur, Este, Oeste. public Nodo izda, dcha; // constructor public Nodo(){ for (int a= 0;a<numCasillas;a++){ datos[a]=0; }//for a movimientos=0; camino=""; }//fin del constructor de Nodo //para el rbol
//
14
for (int a=0;a<numCasillas;a++){ if (a%numFilas==0) System.out.println(); int numero=datos[a]; if (numero==blanco) { System.out.print(relleno + "* "); //sustituye n^2 por '*' }else{ if (numero<10){ System.out.print(relleno + numero + " " ); }else{ System.out.print(numero + " " ); }//if numero<10 }//if numero==blanco }//for a System.out.println(); //lnea en blanco por debajo del puzzle }//fin de presentaPuzzle // devuelve el nmero de casillas rellenas public int devuelveColocadas(){ return colocadas; }//fin de devuelveColocadas // devuelve un String que almacena el camino segn los movimientos del hueco public String devuelveCamino(){ return camino; }//devuelveCamino devuelve un array de booleanos con los movimientos posibles del hueco en orden Norte, Sur, Este, Oeste. public boolean[] devuelveMovPosibles(){ return movPosibles; }//devuelveMovPosibles dvuelve un entero con el nmero de movimientos realizados public int devuelveMovimientos(){ return movimientos; }//devuelveMovimientos devuelve un entero con el nmero de filas del puzzle
// //
//
//
15
public static int devuelveNumFilas(){ return numFilas; }//devuelveNumFilas // devuelve un entero con el nmero de columnas del puzzle public static int devuelveNumColumnas(){ return numColumnas; }//devuelveNumColumnas devuelve un entero con el nmero total de casillas del puzzle public static int devuelveNumCasillas(){ return numCasillas; }//devuelveNumCasillas devuelve un entero el mnimo valor que puede tener una casilla. Es 1. Se usa en la comprobacin del archivo de entrada public static int devuelveMinValor(){ return minValor; }//devuelve minValor devuelve un entero con el mximo valor que puede tener una casilla. Es n^2-1 Se usa en la comprobacin del archivo de entrada public static int devuelveTopeValor(){ return topeValor; }//devuelveTopeValor devuelve el int coste public int devuelveCoste(){ return coste; }//devuelveCoste devuelve el array de enteros con las casillas public int[] devuelveDatos(){ return datos; }//devuelveDatos se cambia el nmero de filas public static void ponerNumFilas(int filas){ numFilas=filas; }//ponerNumFilas se cambia el nmero de columnas public static void ponerNumColumnas(int columnas){ numColumnas=columnas; }//ponerNumColumnas se cambia el nmero de casillas public static void ponerNumCasillas(int casillas){
//
// //
// //
//
//
//
//
//
16
numCasillas=casillas; }//ponerNumCasillas // se cambia el valor mximo de casilla public static void ponerTopeValor(int valor){ topeValor=valor; }//ponerTopeValor se cambia el valor del blanco. Es n^2 public static void ponerBlanco(int valor){ blanco=valor; }//ponerBlanco calcula el coste del nodo. Los movimientos realizados hasta el momento mas la suma de las distancias Manhattan de las casillas (devuelta por calculaDistancias) public void actualizaCoste(){ coste=movimientos+calculaDistancias(); }//actualizaCoste devuelve true si el coste del nodo es inferior a la cota public boolean esAceptable(int cota){ if (this.coste>=cota) return false; else return true; }//esAceptable compara los valores de las casillas del nodo con el array de enteros 'datos' devuelve 1 si 'datos' es mayor casilla a casilla que el nodo; -1 si es menor y 0 si es igual. Se usa para la construccin y bsqueda en el rbol binario. public int compara(int[] datos){ for (int i=0;i<numCasillas;i++){ if (this.datos[i]<datos[i]) return 1; else if (this.datos[i]>datos[i]) return -1; } return 0; }//compara
//
// // //
//
// // //
//inserta una ficha en una casilla public void inserta(int posit, int dato){ datos[posit]=dato; }//fin inserta // actualiza los datos de cada tablero, se usa tras mover el hueco public void actualizaPuzzle(){ sumaColocadas(); //cuenta las casillas en posicin correcta actualizaCoste(); //recalcula el coste posHueco=devuelvePosHueco(); //actualiza el array de booleanos movPosibles
17
if (posHueco/numFilas>0) {movPosibles[0]=true; //norte }else movPosibles[0]=false; if (posHueco/numFilas<numFilas-1) {movPosibles[1]=true;//sur }else movPosibles[1]=false; if (posHueco%numColumnas<numColumnas-1) {movPosibles[2]=true;//este }else movPosibles[2]=false; if ((posHueco%numColumnas>0) ){movPosibles[3]=true;//oeste }else movPosibles[3]=false; }//actualizaPuzzle // devuelve la posicin del hueco public int devuelvePosHueco(){ for (int a=0;a<numCasillas;a++){ int valor=datos[a]; if (valor==blanco){ return a; }//if valor }//for a return blanco; }//devuelvePosHueco
//ndices de fila
// //
calcula y devuelve las distancias Manhattan de cada ficha. Para el clculo del coste public int calculaDistancias(){ int distancia=0,distanciaA,distanciaB,dato; for (int pos=0;pos<numCasillas;pos++){ dato=this.datos[pos]; if ((dato!=blanco)&&(dato!=pos+1)){ //se compara con pos+1 al ir el array de 0 a n^2 distanciaA=(((pos/numFilas))-((dato-1)/numFilas)); distanciaB=(pos%numFilas)-((dato-1)%numFilas); distancia=distancia+Math.abs(distanciaA)+Math.abs(distanciaB); }//if dato }//for return distancia; }//calculaDistancias
//
calcula y devuelve las inversiones en cada tablero. public int calculaInversiones(){ int inversiones=0,dato,datoB; for (int pos=0;pos<numCasillas;pos++){ dato=this.datos[pos]; if (dato!=blanco) { for (int y=pos+1;y<numCasillas;y++){ datoB=this.datos[y]; if ((datoB!=blanco)&&(datoB<dato))inversiones++; }//for
18
}//if dato }//for if (numFilas%2==0){ int filaHueco=(this.devuelvePosHueco()/numFilas)+1; return inversiones+filaHueco; }else return inversiones; }//calculaInversiones // Devuelve true si el tablero tiene solucin posible public boolean tieneSolucion(){ return this.calculaInversiones()%2==0; }
//
movimientos del hueco public void mueveN(){ if (movPosibles[0]){ inserta(posHueco,datos[posHueco-numFilas]); inserta(posHueco-numFilas,blanco); //intercambio de posiciones posHueco=posHueco-numFilas; //actualiza posHueco movimientos++; //incrementa movimientos camino=camino+"N"; //actualiza el camino actualizaPuzzle(); //actualiza los datos del tablero }else{ System.out.println("Error: movimiento al N fuera de tablero." + "No se realiza."); //si no se puede realizar el movimiento }//if }//fin mueveN public void mueveS(){ if (movPosibles[1]){ inserta(posHueco,datos[posHueco+numFilas]); inserta(posHueco+numFilas,blanco); posHueco=posHueco+numFilas; movimientos++; camino=camino+"S"; actualizaPuzzle(); }else{ System.out.println("Error: movimiento al S fuera de tablero. " + "No se realiza."); }//if }//fin mueveS public void mueveE(){ if (movPosibles[2]){ inserta(posHueco,datos[posHueco+1]); inserta(posHueco+1,blanco); posHueco++; movimientos++;
19
camino=camino+"E"; actualizaPuzzle(); }else{ System.out.println("Error: movimiento al E fuera de tablero." + "No se realiza."); }//if }//fin mueveE public void mueveO(){ if (movPosibles[3]){ inserta(posHueco,datos[posHueco-1]); inserta(posHueco-1,blanco); posHueco--; movimientos++; camino=camino+"O"; actualizaPuzzle(); }else{ System.out.println("Error: movimiento al O fuera de tablero." + "No se realiza."); }//if }//fin mueveO // devuelve true si el nodo es solucion, si todas sus casillas estn colocadas public boolean esSolucion(){ if (this.colocadas==numCasillas){ return true; }else return false; }//esSolucion devuelve 'true' si no hay valores repetidos y estn definidas todas las casillas. Devuelve 'False' si hay error. Se le pasa como parmetro el booleano 't', que indica si se est en modo traza; si es as, se presenta el mensaje de error (si lo hubiera) public boolean compruebaMatriz(boolean t){ //comprueba si todos los nmeros estn presentes for (int a=0;a<numCasillas;a++){ //ndices de fila int valor=datos[a]; if (valor==0){ if (t) System.out.println("Falta un numero de ficha."); return false; }//if valor }//for a //comprueba si hay nmeros repetidos o si falta alguno for (int num=1;num<topeValor+1;num++){ boolean presente=false; for (int a=0;a<numCasillas;a++){
// // // // //
//ndices de fila
20
int valor=datos[a]; if ((presente)&&(valor==num)){ //si num ya est presente if (t) { //informacin extra si traza activada System.out.println("El "+ num + " repetido."); presentaPuzzle(); }// if t return false; //matriz errnea }else if (valor==num){ presente=true; }//if presente }//for a if (!presente){ if (t) { //si no se ha encontrada el nmero System.out.print("El "+ num + " sin colocar."); presentaPuzzle(); }//if t return false; } }//for num return true; //matriz correcta }//fin compruebaMatriz // Repasa toda la tabla y cuenta las fichas colocadas public void sumaColocadas(){ int suma=0; for (int a=0;a<numCasillas;a++){ if (datos[a]==a+1) suma++; }//for a colocadas=suma; }//fin sumaColocadas // implementacin de compareTo public int compareTo (Object nodo){ //ordena inicialmente por el coste int costeActual = this.devuelveCoste(); int costeNuevo = ((Nodo)nodo).devuelveCoste(); int valor = costeActual > costeNuevo ? 1 : costeActual==costeNuevo ? 0 : -1; //a igualdad de coste, favorece al de menos movimientos hechos if (valor==0){ int movActual=this.devuelveMovimientos(); int movNuevo=((Nodo)nodo).devuelveMovimientos(); //matriz errnea
21
return movActual > movNuevo ? -1 : movActual==movNuevo ? 0 : 1; } return valor; }//compareTo // // sobreescritura de Object.clone para evitar variar los datos al manipular la copia de un puzzle public Object clone() { Nodo temp; try { //para capturar posibles fallos de memoria al clonar temp = new Nodo(); for (int a=0;a<numCasillas;a++){ int dato=this.datos[a]; //insertamos los datos uno a uno temp.inserta(a,dato); }//for a temp.actualizaPuzzle(); temp.camino=this.camino; temp.movimientos=this.movimientos; return temp; } catch (OutOfMemoryError e) { System.out.println("Error al crear un nodo. Falta memoria."); System.exit(2); return null; }//trycatch }//fin clone
//
22
public boolean buscar(Nodo nodo){ boolean encontrado=false; Nodo actual=raiz; //comenzamos por la raiz int []datosNodo=nodo.devuelveDatos(); while ((!encontrado)&&(actual!=null)){ //mientras queden nodos por ver int comp=actual.compara(datosNodo); if (comp>0){ //si el nuevo es mayor que actual, busca dcha actual=actual.dcha; }else if (comp<0){ //si el nuevo es menor que actual, busca izda actual=actual.izda; } else encontrado=true; //si es igual, encontrado }//while return encontrado; }//buscar // // inserta ordenando por los datos del nodo (usado para el rbol de nodos ya evaluados) public boolean insertarPorDatos(Nodo nodo){ boolean insertado=false; Nodo actual=raiz; //comenzamos en la raz if (actual==null){ //si el rbol est vaco, insertamos como raz raiz=nodo;return true; } int []datosNodo=nodo.devuelveDatos(); while ((!insertado)&&(actual!=null)){ //mientras queden nodos por ver int comp=actual.compara(datosNodo); if (comp>0){ //si el nodo actual es menor if (actual.dcha!=null){ //si tiene hijo dcho,sigue bsqueda dcha actual=actual.dcha; }else{ actual.dcha=nodo; //si no tiene hijo dcho,inserta dcha insertado=true;break; }//if actual.dcha }else if (comp<0){ //si actual es menor if (actual.izda!=null){ //si tiene hjo izdo, busca izda actual=actual.izda; }else{ actual.izda=nodo; //si no, inserta izda insertado=true;break; }//if actual.dcha } else { //si el nodo es igual no se inserta break; }//if comp }//while if (insertado) numNodos++;
23
return insertado; }//insertar por datos // devuelve true si el rbol est vaco public boolean esVacio(){ return (raiz==null); }//esVacio devuelve el nmero de nodos del rbol public int devuelveNumNodos(){ return numNodos; }//devuelveNumNodos
//
}//clase Arbol
24
//
Si es a travs de entrada estndar o pipeline no se le pasan argumentos. public static Nodo pasaATipoNodo(){ BufferedReader datos=new BufferedReader (new InputStreamReader (System.in)); // abrimos el lector de entrada estndar try{ if (System.in.available()==0){ //si la entrada estandar esta vaca System.out.println("Entrada estandar o por 'pipe' vacia."); System.exit( 3 ) ; //Fallo 3= error en archivo de entrada } } catch ( IOException io ) { //si hay error en la entrada estandar System.out.println("Fallo en la entrada estandar o pipe."); System.exit( 3 ) ; //Fallo 3= error en archivo de entrada } return traduce(datos); //devolvemos un TipoSudoku con los valores de la entrada }
// //
Dimensiona el array de datos con respecto al nmero de filas de la entrada y fija las variables de Nodo referidas a n. Devuelve un vector public static Vector<String> dimensiona(BufferedReader datosEntrada){ Vector <String> entrada = new Vector <String>(); int tamanoEntrada=0; try{ String fila=datosEntrada.readLine(); while ( fila != null ) { //aadimos al vector todas las lneas no vacas if ((fila.length()!=0)&&(fila.charAt(0)!='#')){ entrada.addElement(fila); } //y leemos la siguiente lnea fila = datosEntrada.readLine(); }//while //Definimos el tamao del puzzle con las primera lnea de la entrada for (int a=0;a<entrada.size();a++){ String linea = (String)entrada.elementAt(a); //linea es un string con la fila while ((linea.length()!=0)&&(linea.charAt(0)==' ')){ linea=linea.replaceFirst(" ", ""); //elimina los espacios iniciales }//while
25
if (linea.length()==0){ errorEntrada("Una lnea slo contiene espacios."); } String [] numero=linea.split("\\s+"); //dividimos la linea tamanoEntrada=numero.length; }// for a if (tamanoEntrada<1) errorEntrada("No hay lneas vlidas."); Nodo.ponerNumFilas(tamanoEntrada); Nodo.ponerNumColumnas(tamanoEntrada); Nodo.ponerNumCasillas(tamanoEntrada*tamanoEntrada); Nodo.ponerTopeValor((tamanoEntrada*tamanoEntrada)-1); Nodo.ponerBlanco(tamanoEntrada*tamanoEntrada); if (tamanoEntrada==1) { Nodo.ponerTopeValor(1); Nodo.ponerBlanco(0); } //caso especial de tamao 1
} catch ( IOException io ) { //si hay error al abrir la entrada System.out.println( "Error al procesar los datos de entrada." ) ; System.exit( 2 ) ; //Fallo 2= error en entrada }//try catch return entrada; }//fin dimensiona // // // si la matriz de entrada es correcta (nmero de filas y columnas iguales, los dgitos son de 1 a n^2-1 o asteriscos y las lneas que empiezan por '#' son comentarios) devuelve una matriz de int Nodo. public static Nodo traduce(BufferedReader datosEntrada){ Vector <String> entrada; entrada=dimensiona(datosEntrada); //fija los lmites Nodo prueba=new Nodo(); boolean yaHayBlanco=false; int filasEntrada=entrada.size(); for (int a=0;a<filasEntrada ;a++){ String linea = (String)entrada.elementAt(a); //linea es un string con la fila while ((linea.length()!=0)&&(linea.charAt(0)==' ')){ linea=linea.replaceFirst(" ", "");//elimina los espacios iniciales }//while if (linea.length()==0){ errorEntrada("Una lnea slo contiene espacios."); }
26
String [] numero=linea.split("\\s+"); //dividimos la linea if (numero.length!=Nodo.devuelveNumColumnas()) { errorEntrada("Incorrecto numero de datos en la fila " + (a+1) + ":\nDeberia haber " + Nodo.devuelveNumColumnas() + "."); }//if for (int bb=0;bb<numero.length;bb++){ if (numero[bb].equals("*")){ if (!yaHayBlanco){ prueba.inserta((a*Nodo.devuelveNumFilas())+ bb,Nodo.devuelveTopeValor() +1); yaHayBlanco=true; }else{ errorEntrada("Mas de un blanco en el puzzle."); }//if yaHayBlanco } else { //si no es asterisco try{ int proximo=Integer.parseInt(numero[bb]); if ((proximo>=Nodo.devuelveMinValor())&& (proximo<Nodo.devuelveTopeValor()+1)){ prueba.inserta((a*Nodo.devuelveNumF ilas())+bb,proximo); }else{ //fuera de rango errorEntrada("Valor fuera de rango: " + proximo + ". Debe estar entre " + Nodo.devuelveMinValor() + " y " + Nodo.devuelveTopeValor()+"." ); } }catch(Exception e){ //si fallo en conversion a int errorEntrada("Valor extrao en la " + "entrada: '" + numero[bb] + "'"); }//try catch }//if numero }//for bb }//for a return prueba; }//fin traduce //sale del programa con el mensaje recibido en el String 'error' public static void errorEntrada(String error){ System.out.println("Matriz de entrada incorrecta:\n" + error); System.exit(1); //Fallo 1= matriz de entrada incompleta o incorrecta }//fin errorEntrada
27
// //
Muestra los crditos y la sintaxis. Se han evitado los acentos para evitar conflictos entre la codificacin UNICODE del java y el ASCII de la consola public static void ayuda(){ System.out.println("Puzzle."); System.out.println("DIEGO J. SANCHEZ CAAMAO. DNI 12385191-J"); System.out.println("Centro Asociado de Albacete."); System.out.println(); System.out.println("Formato de la linea de parametros: " + "java puzzle [-t] [-a] [-h] [fichero]"); System.out.println(); System.out.println("-t: Realiza un test de correccion al puzzle de " + "entrada."); System.out.println(" Si es incompleto o incorrecto, devuelve 1." + "\n En caso contrario, devuelve 0."); System.out.println(); System.out.println("-a: Modo traza. "); System.out.println(" Muestra toda la secuencia de tableros que se "+ "van generando\n hasta alcanzar la solucion con menor " + "numero de movimientos."); System.out.println(); System.out.println("-h: Modo ayuda. Muestra sintaxis y creditos."); System.out.println(); System.out.println("fichero: archivo con la matriz de entrada."); System.out.println(); }//fin ayuda
28