2009-12-23 9 views
12

Estoy tratando de construir un A* solver para un 15-square puzzle.preguntas con respecto al uso de A * con el rompecabezas de 15 cuadrados

alt text http://i49.tinypic.com/343r8ki.jpg

El objetivo es volver a colocar los azulejos para que aparezcan en sus posiciones naturales. Solo puedes deslizar una ficha a la vez. Cada posible estado del rompecabezas es un nodo en el gráfico de búsqueda.

Para la función h (x), estoy usando una suma agregada, en todas las teselas, de la dislocación de la tesela desde el estado objetivo. En la imagen de arriba, el 5 está en la ubicación 0,0, y pertenece a la ubicación 1,0, por lo tanto, contribuye 1 a la función h (x). La siguiente ficha es la 11, ubicada en 0,1, y pertenece a 2,2, por lo tanto, contribuye 3 a h (x). Y así. EDITAR: Ahora entiendo que esto es lo que ellos llaman "distancia de Manhattan" o "taxicab distance".

He estado usando un conteo de pasos para g (x). En mi implementación, para cualquier nodo en el gráfico de estado, g es solo +1 del g del nodo anterior.

Para encontrar nodos sucesivos, solo examino dónde puedo mover el "agujero" en el rompecabezas. Hay 3 vecinos para el estado del rompecabezas (nodo alias) que se muestra: el agujero puede moverse hacia el norte, el oeste o el este.

Mi búsqueda A * converge a veces en una solución en 20s, a veces 180s, y en ocasiones no converge en absoluto (esperó 10 minutos o más). Creo que h es razonable. Me pregunto si he modelado g correctamente. En otras palabras, ¿es posible que mi función A * esté llegando a un nodo en el gráfico a través de una ruta que no es la ruta más corta?

Tal vez no he esperado lo suficiente? Tal vez 10 minutos no es lo suficientemente largo?

Para una disposición completamente al azar, (suponiendo que no haya problemas de paridad), ¿Cuál es el número promedio de permutaciones que examinará una solución A *? (demuestre las matemáticas)

Voy a buscar errores de lógica en mi código, pero mientras tanto, ¿Algún consejo?

(pd: está hecho en Javascript).

Además, no, esto no es tarea de CompSci. Es solo una cosa de exploración personal. Solo estoy tratando de aprender Javascript.


EDITAR: Me he dado cuenta que el tiempo de ejecución es altamente depende de la heurística. Vi el factor 10x aplicado a la heurística del artículo que alguien mencionó, y me hizo preguntarme: ¿por qué 10 veces? ¿Por qué lineal? Como esto se hace en javascript, podría modificar el código para actualizar dinámicamente una tabla html con el nodo que se está considerando actualmente. Esto me permitió echar un vistazo al algoritmo a medida que avanzaba. Con una heurística de distancia de taxi regular, observé como no lograba converger.

Había 5 y 12 en la fila superior, y seguían dando vueltas. Veía que 1,2,3,4 se arrastraba a la fila superior, pero luego se salían, y otros números se movían hacia arriba. Lo que esperaba ver era que 1,2,3,4 se arrastraba hasta la cima y luego se quedaba allí.

Pensé para mí mismo: esta no es la forma en que resuelvo esto personalmente. Haciendo esto manualmente, resuelvo la fila superior, luego la 2ª fila, luego la 3ª y 4ª filas de forma concurrente.

De modo que modifiqué la función h (x) para ponderar más las filas más altas y las columnas "lefter". El resultado fue que A * convergió mucho más rápido. Ahora se ejecuta en 3 minutos en lugar de "indefinidamente". Con el "vistazo" del que hablé, puedo ver que los números más pequeños se arrastran hasta las filas más altas y se quedan allí. No solo parece ser lo correcto, sino que funciona mucho más rápido.

Estoy en proceso de probar varias variaciones. Parece bastante claro que A * runtime es muy sensible a la heurística. Actualmente, la mejor heurística que he encontrado usa la suma de dislocation * ((4-i) + (4-j)) donde i y j son la fila y la columna, y la dislocación es la distancia del taxi.

Una parte interesante del resultado que obtuve: con una heurística particular, encuentro un camino muy rápido, pero obviamente no es el camino más corto. Creo que esto es porque estoy ponderando la heurística. En un caso, obtuve un camino de 178 pasos en 10s. Mi propio esfuerzo manual produce una solución en 87 movimientos. (mucho más que 10s). Más investigación garantizada.

Así que el resultado es que estoy viendo que la convergencia debe ser más rápida, y la ruta definitivamente no es la más corta. Tengo que pensar en esto más.


Código:

var stop = false; 
function Astar(start, goal, callback) { 
    // start and goal are nodes in the graph, represented by 
    // an array of 16 ints. The goal is: [1,2,3,...14,15,0] 
    // Zero represents the hole. 

    // callback is a method to call when finished. This runs a long time, 
    // therefore we need to use setTimeout() to break it up, to avoid 
    // the browser warning like "Stop running this script?" 

    // g is the actual distance traveled from initial node to current node. 
    // h is the heuristic estimate of distance from current to goal. 
    stop = false; 
    start.g = start.dontgo = 0; 

    // calcHeuristic inserts an .h member into the array 
    calcHeuristicDistance(start); 

    // start the stack with one element 
    var closed = [];  // set of nodes already evaluated. 
    var open = [ start ]; // set of nodes to evaluate (start with initial node) 

    var iteration = function() { 
     if (open.length==0) { 
      // no more nodes. Fail. 
      callback(null); 
      return; 
     } 
     var current = open.shift(); // get highest priority node 

     // update the browser with a table representation of the 
     // node being evaluated 
     $("#solution").html(stateToString(current)); 

     // check solution returns true if current == goal 
     if (checkSolution(current,goal)) { 
      // reconstructPath just records the position of the hole 
      // through each node 
      var path= reconstructPath(start,current); 
      callback(path); 
      return; 
     } 

     closed.push(current); 

     // get the set of neighbors. This is 3 or fewer nodes. 
     // (nextStates is optimized to NOT turn directly back on itself) 
     var neighbors = nextStates(current, goal); 

     for (var i=0; i<neighbors.length; i++) { 
      var n = neighbors[i]; 

      // skip this one if we've already visited it 
      if (closed.containsNode(n)) continue; 

      // .g, .h, and .previous get assigned implicitly when 
      // calculating neighbors. n.g is nothing more than 
      // current.g+1 ; 

      // add to the open list 
      if (!open.containsNode(n)) { 
       // slot into the list, in priority order (minimum f first) 
       open.priorityPush(n); 
       n.previous = current; 
      } 
     } 

     if (stop) { 
      callback(null); 
      return; 
     } 

     setTimeout(iteration, 1); 
    }; 

    // kick off the first iteration 
    iteration(); 

    return null; 
} 
+1

Usa la suma de la distancia de las fichas desde su posición de objetivo como la heurística. Es mucho más efectivo. (ver Inteligencia Artificial, un Enfoque Moderno Segunda Edición p.107: El efecto de la precisión heurística en el rendimiento) –

+1

Eso es lo que estoy haciendo. Escribí: * Para la función h (x), estoy usando una suma agregada, en todas las teselas, de la dislocación de la tesela desde el estado objetivo. * Y cuando dices "mucho más efectivo", ¿qué quieres decir? ¿comparado con que? – Cheeso

Respuesta

4

Una búsqueda de estrella A encontrará la solución óptima demostrando que todas las rutas que aún no se han resuelto no se pueden resolver con menos movimientos que la solución actual. No está buscando la solución mejor, pero la solución más rápida. Por lo tanto, puede optimizar su algoritmo devolviendo la primera solución, ponderando el número de movimientos inferiores a su función heurística, y la heurística puede devolver una sobreestimación.

La función heurística generalmente se modela mejor mediante el conflicto Manhattan distance y lineal. La distancia de Manhattan está bien explicada en otras respuestas y en el artículo de Wikipedia, y usted parece tener un control sobre ella.El conflicto lineal agrega dos a la distancia de Manhattan para cada par de bloques que deberían intercambiarse para llegar a una solución. Por ejemplo, si una fila contiene "3 2 1 4", entonces el uno y los tres deben intercambiarse, y uno debería moverse a otra fila para hacerlo.

Usar una base de datos de patrones es una opción que podría ayudarlo a evitar ciertos callejones sin salida, y el uso de la memoria para hacer un rompecabezas de 15 debería ser manejable.

+0

interesante, gracias. – Cheeso

2

Sí, eso es lo que he oído de este problema se está haciendo. g (x) es la cantidad de diapositivas de mosaico que han tenido lugar, y h (x) es la distancia total que todas las fichas son de sus cuadrados requeridos. No había visto nada usado, pero este enfoque (el Manhattan heuristic) antes de hoy, pero acaba de encontrar esto llamado diagonal shortcut - es posible que desee comprobarlo.

+0

Sí, ese es el enfoque común, pero nunca lo he visto como la "heurística de Manhattan". Siempre lo he visto como la "métrica de taxi" (y en términos matemáticos de fantasía, la norma "L_1"). – jason

+0

En círculos de IA de fantasía, es la "distancia de Manhattan". Para un problema como este, donde las cosas se mueven solo ortogonalmente, es perfectamente adecuado. –

+0

Estoy usando Manhattan. Pregunta sobre el artículo que citó con el atajo diagonal: ¿por qué la H está sobrecargada con 10x? ¿Por qué no es solo abs (currentX-targetX) - abs (currentY-targetY)? – Cheeso

1

¿Qué está utilizando para los datos de prueba? Si es aleatorio, no podrás resolver el acertijo la mitad del tiempo. No es posible cambiar dos mosaicos mientras se mantiene el resto en la misma posición, por lo que si alcanzas casi la posición final pero tienes dos casillas intercambiadas, es posible que no puedas ubicarla en la posición deseada y sin ningún algoritmo de búsqueda. posiblemente puede terminar exitosamente.

En el siglo XIX, el maestro de puzzles americano Sam Loyd vendió estos juguetes con el 15 y 14 al revés, y ofreció un gran premio a cualquiera que pudiera demostrar una solución cambiando las fichas (presumiblemente distintas a la que tengo, destornillador pequeño). En el clima legal de hoy, no sé si se hubiera atrevido.

Una posibilidad sería intentar introducirlo en la configuración correcta o en la configuración 15-14.

+0

La disposición inicial no es aleatoria. Comienzo con el tablero "resuelto" - y luego hago 200 movimientos aleatorios, y lo uso como punto de partida. – Cheeso

+0

Buena idea. Desafortunadamente, me he quedado sin ideas sin tener más oportunidad de examinar el código y ejecutarlo. –

0

Quizás convergerá más rápido si primero disparas para objetivos intermedios. Por ejemplo, solo puntúa las filas superior e derecha. No debería tomar mucho tiempo para colocar esas filas en su lugar, entonces puedes resolver el 3x3 restante.

1

Lo que aprendí

  • al parecer esto es well-known, pero no era para mí: la A * convergencia es muy sensible a la función heurística.
  • si escribo una heurística que pesa las 2 filas superiores con más fuerza que otras filas, converge mucho más rápido, pero la ruta es generalmente mucho más larga.
  • Encontré la función diagonal H (x) que se muestra here para converger mucho más rápido que la distancia de Manhattan, para el rompecabezas de 15 cuadrados.
  • incluso con la función heurística que fomenta la convergencia más rápida, hay una gran variación en el tiempo de ejecución. A veces encuentra el camino en 10 segundos. A veces 10 minutos. Algunas veces más.
  • El número de movimientos requeridos en los caminos encontrado, utilizando la heurística diagonal, oscila entre 30-ish a 110.
1

He programado un algoritmo tal vez (windowsApp) y tengo la siguiente experiencia

1) es más divertido ver el robot en acción si usa una solución (casi) óptima. (Para el observador humano es imposible entender cómo el robot "piensa" y la transacción del caos al orden es repentina)

2) si desea encontrar la solución óptima, su función h() debe subestimar la distancia real. Si lo sobreestimas, no encontrarás el óptimo.

3) El espacio de estado potencial es enorme, 15!/2 (10^12). Si usa una mala función heurística, sus conjuntos de datos crecerán mucho más allá del tamaño de su memoria principal y cada acceso a datos requerirá múltiples accesos a disco. Si esto sucede, el tiempo de ejecución será "infinito".

-2
check this 
import javax.swing.*; 
import java.awt.*; 
import java.awt.event.*; 
import java.lang.Object; 

class Puzzle extends JPanel implements ActionListener 
{ 
    JButton[] b = new JButton[16]; 
    Puzzle() 
    { 
     b[0] = new JButton("4"); 
     b[1] = new JButton("11"); 
     b[2] = new JButton("5"); 
     b[3] = new JButton("9"); 
     b[4] = new JButton("1"); 
     b[5] = new JButton("10"); 
     b[6] = new JButton("12"); 
     b[7] = new JButton("13"); 
     b[8] = new JButton("15"); 
     b[9] = new JButton("14"); 
     b[10] = new JButton("3"); 
     b[11] = new JButton("2"); 
     b[12] = new JButton("7"); 
     b[13] = new JButton("8"); 
     b[14] = new JButton("6"); 
     b[15] = new JButton(""); 
     GridLayout grid = new GridLayout(4,4); 
     setLayout(grid); 
     for(int i=0;i<16;i++) 
      add(b[i]); 
     for(int i=0;i<16;i++) 
      b[i].addActionListener(this); 
    } 
    public void actionPerformed(ActionEvent e) 
    { 
     /*if(e.getSource()==b[11]) 
     { 
      if(b[15].getText()=="") 
      { 
       b[15].setText(""); 
      } 
     } 
     else if(e.getSource()==b[3]) 
     { 
      if(b[2].getText()=="") 
      { 
       b[2].setText(""); 
      } 
     }*/ 
     for(int i=0;i<16;i++) 
     { 
      System.out.println(e.getSource()); 
      if(e.getSource()==b[i]) 
      { 
       if(i==5 || i==6 || i==9 || i==10) 
       { 
        if(b[i-1].getText()=="") 
        { 
         b[i-1].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
        else if(b[i+1].getText()=="") 
        { 
         b[i+1].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
        else if(b[i-4].getText()=="") 
        { 
         b[i-4].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
        else if(b[i+4].getText()=="") 
        { 
         b[i+4].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
       } 
       else if(i==4 || i==8) 
       { 
        if(b[i+1].getText()=="") 
        { 
         b[i+1].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
        else if(b[i-4].getText()=="") 
        { 
         b[i-4].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
        else if(b[i+4].getText()=="") 
        { 
         b[i+4].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
       } 
       else if(i==7 || i==11) 
       { 
        if(b[i-1].getText()=="") 
        { 
         b[i-1].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
        else if(b[i-4].getText()=="") 
        { 
         b[i-4].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
        else if(b[i+4].getText()=="") 
        { 
         b[i+4].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
       } 
       if(i==0) 
       { 
        if(b[i+1].getText()=="") 
        { 
         b[i+1].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
        else if(b[i+4].getText()=="") 
        { 
         b[i+4].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
       } 
       if(i==3) 
       { 
        if(b[i-1].getText()=="") 
        { 
         b[i-1].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
        else if(b[i+4].getText()=="") 
        { 
         b[i+4].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
       } 
       if(i==15) 
       { 
        if(b[i-1].getText()=="") 
        { 
         b[i-1].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
        else if(b[i-4].getText()=="") 
        { 
         b[i-4].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
        else if(b[i+4].getText()=="") 
        { 
         b[i+4].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
       } 
       if(i==12) 
       { 
        if(b[i+1].getText()=="") 
        { 
         b[i+1].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
        else if(b[i-4].getText()=="") 
        { 
         b[i-4].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
       } 
       if(i==1 || i==2) 
       { 
        if(b[i+1].getText()=="") 
        { 
         b[i+1].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
        else if(b[i-1].getText()=="") 
        { 
         b[i-1].setText(b[i].getText()); 
         b[i].setText(""); 
        }     
        else if(b[i+4].getText()=="") 
        { 
         b[i+4].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
       } 
       if(i==13 || i==14) 
       { 
        if(b[i+1].getText()=="") 
        { 
         b[i+1].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
        else if(b[i-1].getText()=="") 
        { 
         b[i-1].setText(b[i].getText()); 
         b[i].setText(""); 
        }     
        else if(b[i-4].getText()=="") 
        { 
         b[i-4].setText(b[i].getText()); 
         b[i].setText(""); 
        } 
       } 
      } 
     } 
     //System.out.println(e.getActionCommand()); 
     } 

    public static void main(String[] args) 
    { 
     JFrame frame = new JFrame("15-Puzzle");    

     //frame.setContentPane(panel); 

JComponent newContentPane = new Puzzle(); 
     //newContentPane.setOpaque(true); //content panes must be opaque 
     frame.setContentPane(newContentPane); 





     //panel.add(button); 
     frame.setSize(400,400); 


     frame.setVisible(true); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
    } 
} 
Cuestiones relacionadas