He estado practicando para una próxima competencia de programación y me he encontrado con una pregunta que me desconcierta por completo. Sin embargo, siento que es un concepto que debería aprender ahora en lugar de cruzar los dedos y que nunca surge.
Básicamente, se trata de una pieza de caballo en un tablero de ajedrez. Se le dan dos entradas: ubicación inicial y ubicación final. El objetivo es calcular e imprimir el camino más corto que el caballero puede tomar para llegar a la ubicación de destino.
Nunca me he ocupado de cosas que se parezcan al camino más corto y ni siquiera sé por dónde empezar. ¿Qué lógica utilizo para abordar esto?
PD: Si tiene alguna relevancia, quieren que complementes los movimientos normales del caballo permitiéndole también moverse a las cuatro esquinas del cuadrado formado por los (potencialmente) ocho movimientos que un caballo puede hacer, dado que el centro del cuadrado es la ubicación del caballero.
fuente
Respuestas:
Aquí tiene un gráfico, donde todos los movimientos disponibles están conectados (valor = 1) y los movimientos no disponibles están desconectados (valor = 0), la matriz dispersa sería como:
Y la ruta más corta de dos puntos en un gráfico se puede encontrar usando http://en.wikipedia.org/wiki/Dijkstra's_algorithm
Pseudocódigo de la página wikipedia:
EDITAR:
Introduction to Algorithms
ISBN 0-262-03384-4. O puede probar wikipedia, http://en.wikipedia.org/wiki/List_of_algorithmsfuente
EDITAR: Vea la respuesta de Simon , donde arregló la fórmula presentada aquí.
De hecho, existe una fórmula O (1)
Esta es una imagen que hice para visualizarla (los cuadrados que un caballero puede alcanzar en el N- ésimo movimiento están pintados con el mismo color).
¿Puedes notar el patrón aquí?
Aunque podemos ver el patrón, es realmente difícil encontrar la función
f( x , y )
que devuelve el número de movimientos necesarios para pasar de un cuadrado( 0 , 0 )
a otro.( x , y )
Pero aquí está la fórmula que funciona cuando
0 <= y <= x
Nota: Esta pregunta se hizo el día 1 de SACO 2007
y las soluciones están aquí
fuente
2 * floor((y - delta) / 3) + delta
ydelta - 2 * floor((delta - y) / 4)
. Esta es la solución oficial de esta página del concurso, pero está mal. Esta primera ecuación (deif
) devuelve respuestas incorrectas. En el tablero de ajedrez [-1000..1000] x [-1000..1000], que es 2001x2001 grande (pero lógicamente ilimitado), la respuesta dada cuenta 2.669.329 de 4.004.001 campos correctos (66,66%). ¿Alguien conoce la solución de trabajo sin bucles?Aquí hay una solución O (1) correcta, pero para el caso en el que el caballo se mueve como un caballo de ajedrez solamente, y en un tablero de ajedrez infinito:
https://jsfiddle.net/graemian/5qgvr1ba/11/
La clave para encontrar esto es notar los patrones que surgen cuando dibuja el tablero. En el diagrama a continuación, el número en el cuadrado es el número mínimo de movimientos necesarios para llegar a ese cuadrado (puede usar la búsqueda primero en amplitud para encontrar esto):
Debido a que la solución es simétrica entre los ejes y las diagonales, solo dibujé el caso x> = 0 y y> = x.
El bloque de la parte inferior izquierda es la posición inicial y los números en los bloques representan el número mínimo de movimientos para llegar a esos bloques.
Hay 3 patrones para notar:
(Asegúrese de ver ambos conjuntos de diagonales de arriba a la izquierda a abajo a la derecha. Tienen un recuento de movimientos constante. Las diagonales de abajo a la izquierda y arriba a la derecha son mucho más complejas).
Puede derivar fórmulas para cada uno. Los bloques amarillos son casos especiales. Entonces la solución se convierte en:
siendo los más difíciles los grupos verticales:
Vea el violín para los otros casos.
¿Quizás hay patrones más simples o más elegantes que me perdí? Si es así, me encantaría verlos. En particular, noto algunos patrones diagonales en los casos verticales azules, pero no los he explorado. Independientemente, esta solución aún satisface la restricción O (1).
fuente
Problema muy interesante con el que me encontré recientemente. Después de buscar algunas soluciones, intenté recuperar la fórmula analítica (
O(1) time and space complexity
) dada en las soluciones del Día 1 de SACO 2007 .En primer lugar, quiero agradecer a Graeme Pyle por una visualización muy agradable que me ayudó a arreglar la fórmula.
Por alguna razón (tal vez por simplificación o belleza o simplemente por un error) movieron el
minus
signo alfloor
operador, como resultado, obtuvieron una fórmula incorrectafloor(-a) != -floor(a) for any a
.Aquí está la fórmula analítica correcta:
La fórmula funciona para todos los pares (x, y) (después de aplicar ejes y simetría diagonal) excepto los casos de esquina (1,0) y (2,2), que no cumplen con el patrón y están codificados en el siguiente fragmento:
Nota: jQuery se usa solo para ilustración, para el código, consulte la
distance
función.fuente
Sí, Dijkstra y BFS te darán la respuesta, pero creo que el contexto ajedrecístico de este problema proporciona un conocimiento que puede producir una solución mucho más rápida que un algoritmo genérico de ruta más corta, especialmente en un tablero de ajedrez infinito.
Para simplificar, describamos el tablero de ajedrez como el plano (x, y). El objetivo es encontrar la ruta más corta de (x0, y0) a (x1, y1) utilizando solo los pasos candidatos (+ -1, + -2), (+ -2, + -1) y (+ -2 , + -2), como se describe en el PS de la pregunta
Aquí está la nueva observación: dibuje un cuadrado con esquinas (x-4, y-4), (x-4, y + 4), (x + 4, y-4), (x + 4, y + 4) . Este conjunto (llámelo S4) contiene 32 puntos. El camino más corto desde cualquiera de estos 32 puntos hasta (x, y) requiere exactamente dos movimientos .
El camino más corto desde cualquiera de los 24 puntos del conjunto S3 (definido de manera similar) a (x, y) requiere al menos dos movimientos .
Por lo tanto, si | x1-x0 |> 4 o | y1-y0 |> 4, la ruta más corta de (x0, y0) a (x1, y1) es exactamente dos movimientos mayor que la ruta más corta de (x0, y0) a S4. Y el último problema se puede resolver rápidamente con una iteración sencilla.
Sea N = max (| x1-x0 |, | y1-y0 |). Si N> = 4, entonces la ruta más corta de (x0, y0) a (x1, y1) tiene pasos ceil (N / 2) .
fuente
La respuesta O (1) anterior [ https://stackoverflow.com/a/8778592/4288232 por Mustafa Serdar Şanlı] no funciona realmente. (Marque (1,1) o (3,2) o (4,4), aparte de los casos extremos obvios de (1,0) o (2,2)).
A continuación se muestra una solución mucho más fea (python), que funciona (con "pruebas" agregadas):
fuente
above
obelow
no funciona realmente en SO.Lo que debes hacer es pensar en los posibles movimientos del caballo como un gráfico, donde cada posición en el tablero es un nodo y los posibles movimientos a otra posición como un borde. No es necesario el algoritmo de dijkstra, porque cada borde tiene el mismo peso o distancia (todos son tan fáciles o cortos de hacer). Puede hacer una búsqueda BFS desde su punto de partida hasta llegar a la posición final.
fuente
Solución de los primeros principios en Python
Encontré este problema por primera vez en una prueba de codilidad. Me dieron 30 minutos para resolverlo, ¡me tomó mucho más tiempo llegar a este resultado! El problema era: ¿cuántos movimientos se necesitan para que un caballo pase de 0,0 a x, y usando solo los movimientos legales del caballo? xey eran más o menos ilimitados (por lo que no estamos hablando aquí de un tablero de ajedrez simple de 8x8).
Querían una solución O (1). Quería una solución en la que el programa resolviera claramente el problema (es decir, quería algo más obviamente correcto que el patrón de Graeme; los patrones tienen el hábito de romperse donde no estás mirando), y realmente quería no tener que depender de un fórmula indiscutible, como en la solución de Mustafa
Entonces, aquí está mi solución, por lo que vale. Comience, como lo han hecho otros, notando que la solución es simétrica con respecto a los ejes y las diagonales, por lo que solo necesitamos resolver para 0> = y> = x. Para simplificar la explicación (y el código) voy a revertir el problema: el caballo comienza en x, y, y apunta a 0,0.
Supongamos que reducimos el problema a la vecindad del origen. Llegaremos a lo que realmente significa 'vicinty' a su debido tiempo, pero por ahora, escribamos algunas soluciones en una hoja de referencia (el origen en la parte inferior izquierda):
Entonces, dados x, y en la cuadrícula, podemos leer el número de movimientos al origen.
Si hemos comenzado fuera de la red, tenemos que volver a ella. Introducimos la 'línea media', que es la línea representada por y = x / 2. Cualquier caballo en x, y en esa línea puede regresar a la hoja de trucos usando una serie de movimientos de las 8 en punto (es decir: movimientos (-2, -1)). Si x, y se encuentra por encima de la línea media, entonces necesitaremos una sucesión de movimientos de las 8 en punto y las 7 en punto, y si está por debajo de la línea media, necesitaremos una sucesión de las 8 en punto y las 10 en punto. El reloj se mueve. Dos cosas a tener en cuenta aquí:
Entonces, veamos los movimientos por encima de la línea media. Lo que afirmamos es que:
(dx; dy) = (2,1; 1,2) (n8; n7) (notación matricial, sin composición tipográfica matemática - el vector de columna (dx; dy) es igual a la matriz cuadrada multiplicada por el vector de columna (n8; n7) - el número de movimientos de las 8 en punto y el número de movimientos de las 7 en punto), y de manera similar;
(dx; dy) = (2,2; 1, -1) (n8; n10)
Afirmo que dx, dy será aproximadamente (x, y), por lo que (x-dx, y-dy) estará en la vecindad del origen (cualquier 'vecindad' que resulte ser).
Las dos líneas del código que calculan estos términos son la solución a estos, pero se seleccionan para tener algunas propiedades útiles:
(¿Le gustaría tener pruebas de esto?) Entonces, la distancia del Caballero será la suma de n7, n8, n10 y la hoja de referencia [x-dx, y-dy], y nuestra hoja de referencia se reduce a esto:
Ahora bien, este no es el final de la historia. Mira el 3 en la fila inferior. Las únicas formas en que podemos lograr esto son:
Hay una optimización similar con el 4 en la parte superior derecha. Aparte de comenzar allí, la única forma de llegar es moviendo las 8 en punto desde (4,3). Eso no está en la hoja de trucos, pero si estuviera allí, su distancia sería 3, porque podríamos tener las 7 en punto a (3,1), que tiene una distancia de solo 2. Entonces, deberíamos retroceder uno Mueva las 8 en punto, y luego avance una 7 en punto.
Entonces, necesitamos agregar un número más a la hoja de referencia:
(Nota: hay una gran cantidad de optimizaciones de retroceso de (0,1) y (0,2), pero como el solucionador nunca nos llevará allí, no tenemos que preocuparnos por ellas).
Entonces, aquí hay un código de Python para evaluar esto:
Por cierto, si desea conocer una ruta real, este algoritmo también lo proporciona: es simplemente una sucesión de n7 movimientos de las 7 en punto, seguidos de (o intercalados con) n8 movimientos de las 8 en punto, n10 10- se mueve en punto, y cualquier baile es dictado por la hoja de trucos (que, en sí misma, puede estar en una hoja de trucos).
Ahora: Cómo demostrar que esto es correcto. No es suficiente comparar estos resultados con una tabla de respuestas correctas, porque el problema en sí es ilimitado. Pero podemos decir que, si la distancia del Caballo de un cuadrado s es d, entonces si {m} es el conjunto de movimientos legales de s, la distancia del Caballo de (s + m) debe ser d-1 o d + 1 para todos m. (¿Necesita una prueba de esto?) Además, debe haber al menos uno de esos cuadrados cuya distancia sea d-1, a menos que s sea el origen. Entonces, podemos demostrar la exactitud mostrando que esta propiedad se cumple para cada cuadrado. Así:
Alternativamente, podemos probar la exactitud de cualquier cuadrado s siguiendo la ruta desde s cuesta abajo hasta el origen. Primero, verifique que s sea razonable como se indicó anteriormente, luego seleccione cualquier s + m tal que la distancia (s + m) == d-1. Repetir hasta llegar al origen.
Howzat?
fuente
Todavía no he estudiado gráficos ... según el problema de implementarlo a través de simplemente matrices, no pude derivar ninguna otra solución que esta. Traté las posiciones no como rangos y archivos (la notación habitual de ajedrez), sino como índices de matriz. FYI, esto es solo para un tablero de ajedrez de 8 * 8. Cualquier consejo de mejora es siempre bienvenido.
* Los comentarios deberían ser suficientes para su comprensión de la lógica. Sin embargo, siempre puede preguntar.
* Comprobado en el compilador DEV-C ++ 4.9.9.2 (Software Bloodshed).
fuente
Creo que esto también podría ayudarte ...
y el uso de programación dinámica para obtener la solución.
PD: Utiliza un poco el BFS sin tener que tomarse la molestia de declarar los nodos y los bordes del gráfico.
fuente
Aquí hay una solución para este problema particular implementada en Perl. Mostrará uno de los caminos más cortos; puede haber más de uno en algunos casos.
No utilicé ninguno de los algoritmos descritos anteriormente, pero sería bueno compararlo con otras soluciones.
fuente
fuente
Solo código ruby de del jsfiddle de la respuesta Graeme Pyle arriba , seccionado todo el código adicional y convertido a ruby solo para obtener una solución mediante su algoritmo, parece funcionar. Aún probando sin embargo:
La única intención es ahorrarle tiempo a alguien convirtiendo código si alguien necesita código completo.
fuente
aquí está la versión PHP de la función de Jules May
fuente
Aquí está mi programa. Ésta no es una solución perfecta. Hay muchos cambios que realizar en la función de recursividad. Pero este resultado final es perfecto. Intenté optimizar un poco.
fuente
Aquí hay una versión C basada en el código Mustafa Serdar Şanlı que funciona para una placa finita:
Pruébelo aquí con prueba contra una solución recursiva
fuente