Given the size of the board, the shortest way to Dijkstra does not bring significant advantages over a Breadth-first search, which is simply a search that starts from the root, and then goes exploring each sub-level.
On a very large board, such a search would be problematic, because the number of steps and the structure to store the nodes and the work of the search is exponential.
It turns out that in the practical case, which is an 8 8 board, the possibilities are very limited, so in some cases the BFS ends up finding the destination in even less time than it would take to organize the nodes to apply the Dijkstra short path.
The BFS is practically a search for brute force, but it is well optimized in this case, with the elimination of the houses already visited.
Illustrating a little better
C = cavalo
* = destino
× = casas já visitadas, eliminadas da busca, não vão pra lista
numeros = casas que verificaremos no passo atual
(vão ser os pontos de partida do próximo passo)
? = casas da lista armazenada no passo anterior, que estamos processando
At first, we would search the numbered houses below:
· · · · · 1 · 6
· · · · 2 · · ·
· · · · · · C ·
· · · · 3 · · ·
· · * · · 4 · 5
· · · · · · · ·
· · · · · · · ·
· · · · · · · ·
Since we can’t find destiny:
Then we "move" the horse to each of the houses of the previous step, and repeat the procedure, always eliminating the houses already visited:
· · · · · C · ? · · 2 · · × 2 ? · · × · · × × ?
· · · 1 ? · · 1 · · · × C · · × · · · × × 3 · ×
· · · · 1 · × · · · 2 · × · × · · · × · × · × ·
· · · · ? · · · · · · 2 ? 2 · · · · · × C × · ·
· · * · · ? · ? · · * · · ? · ? · ·[3]· · ? 3 ?
· · · · · · · · · · · · · · · · · · · 3 · 3 · ·
· · · · · · · · · · · · · · · · · · · · · · · ·
· · · · · · · · · · · · · · · · · · · · · · · ·
By chance, in the scenario of the question, we find the destination already in step 2, "subpasso" 3. Nor do we need to test the last houses (subpassos 4, 5 and 6).
The important thing here is to note that despite the exponential nature of the algorithm, the board boundary and the fact that we eliminate the houses already visited make the solution a bit simple (and always solved in one step). Even if some cases take a few more steps, in the second we have already eliminated almost half the board.
Let’s go to the code?
If you want to go straight to the version that only counts the number of moves required, see the end of the answer. To facilitate the understanding of the algorithm I made a more complex version that returns the steps given, not just the count.
I confess that I have no experience with Python, I probably committed a series of style slips, and maybe I ignored obvious optimizations, but I hope that the algorithm, which is what we care about, has become very easy to follow.
If you take the comments and print debugs™ from within the function, you will notice that even with my inexperience, the code has gotten extremely dry:
(I put the prints just so that whoever is testing can measure the efficiency of the algorithm)
def vaiCavalo( origemX, origemY, destinoX, destinoY ):
# criamos uma matriz 8x8 preenchida com False
# para anotar as casas ja testadas
casasTestadas = [[False]*8 for i in range(8)]
# todos os movimentos possiveis do cavalo
movimentos = [[-1,-2],[-2,-1],[-2,1],[-1,2],[1,2],[2,1],[2,-1],[1,-2]]
# o primeiro passo e a origem do cavalo
# X, Y e caminho percorrido ate entao (vazio no comeco)
passos = [[origemX, origemY,[]]]
while True:
proximosPassos = []
for passo in passos:
print("Cavalo atualmente em [", passo[0], ",", passo[1], "], vindo de", passo[2])
# vamos testar todos os movimentos possiveis a partir deste passo
for movimento in movimentos:
x = passo[0]+movimento[0]
y = passo[1]+movimento[1]
# armazenamos o caminho feito ate chegar aqui
caminho = list(passo[2])
caminho.append([passo[0],passo[1]])
# o cavalo chegou ao destino, retornamos o caminho completo
if x == destinoX and y == destinoY:
print(" PRONTO! Chegamos em [", x, y, "]")
caminho.append([x,y])
return caminho
# senao, armazenamos a posicao para a proxima rodada
elif 0 <= x < 8 and 0 <= y < 8 and not casasTestadas[x][y]:
print(" Destino nao encontrado em [", x, y, "], coordenadas na pilha")
proximosPassos.append([x,y,caminho])
# vamos descartar novas tentativas partindo daqui
casasTestadas[x][y] = True
# todos os passos realizados, vamos para os seguintes
passos = proximosPassos
print("\nCaminho feito:", vaiCavalo(3, 2, 3, 3))
See the horse in action on IDEONE.
Same code, no strings
For comparison, follow simplified and reorganized version of the above code.
Almost the same thing, basically without the comments and prints, and with some things inline, but still returning every step:
def vaiCavalo( origem, destino ):
casasTestadas = [[False]*8 for i in range(8)]
passos = [origem+[[]]]
while True:
proximosPassos = []
for passo in passos:
for movimento in [[-1,-2],[-2,-1],[-2,1],[-1,2],[1,2],[2,1],[2,-1],[1,-2]]:
x,y = passo[0]+movimento[0], passo[1]+movimento[1]
if [x,y] == destino:
return passo[2]+[[x,y]]
if 0 <= x < 8 and 0 <= y < 8 and not casasTestadas[x][y]:
proximosPassos.append([x,y,passo[2]+[[x,y]]])
casasTestadas[x][y] = True
passos = proximosPassos
Also in the IDEONE.
And finally, as the question asks, only the count:
def vaiCavalo( origem, destino ):
casasTestadas = [[False]*8 for i in range(8)]
passos = [origem+[0]]
while True:
proximosPassos = []
for passo in passos:
for movimento in [[-1,-2],[-2,-1],[-2,1],[-1,2],[1,2],[2,1],[2,-1],[1,-2]]:
x,y = passo[0]+movimento[0], passo[1]+movimento[1]
if [x,y] == destino:
return passo[2]+1
if 0 <= x < 8 and 0 <= y < 8 and not casasTestadas[x][y]:
proximosPassos.append([x,y,passo[2]+1])
casasTestadas[x][y] = True
passos = proximosPassos
Example of use:
movimentosNecessarios = vaiCavalo([1,1],[1,2])
And, of course, one more IDEONE.
It’s worth a read in this version of the above algorithm, with a much more Academic format, kindly left by colleague @Jjoao, who made an implementation with queue
/deque
. The code was very elegant, by the way.
Note:
It is not part of the problem, but it is interesting to note that the horse does not actually move in "L". The "L" is a way to make it easier for the learner to understand where the horse can go. The horse moves straight to the destination like any other piece. It just happens that his "straight line" doesn’t match the alignment of the board.
Bacco just one more question: I expected a Whopper instead of a stack (like a stack it seemed to me that would eventually be Depth-first)
– JJoao
And here’s another great answer. : ) +1. P.S.: I laughed out loud with the "vaiCavalo". hahahaha
– Luiz Vieira
@Bacco, in http://ideone.com/dfLaQb I simulated a queue (unbreakable functions, queue, if(q) ) and did a rewrite of your algorithm with queues. Although almost equal, it is close to the generic form of Breath-first of Raphos.
– JJoao
@Jjoao grateful, I find it interesting even for me to know better something of who has experience. I went down a more simple path because I wanted to make everything self-contained (no import, inclusive), and returning the path made was one of the objectives, although not in the question. I will read your code calmly, and learn the concepts, very grateful! Then I make a reference in the answer, in case you do not want to reference in your.
– Bacco
only a Disclaimer: I have no experience of python (at scripting level, I really like it is Perl) I am now learning (hence the visit python questions , and try to try alternatives)
– JJoao
@Jjoao has some things in his syntax that I liked a lot, and would probably have adopted if I had thought a little better. The idea of catching dx and dy directly becomes really more readable, for example. It has several interesting details that I liked. To tell you the truth, I understood that its focus was the academic representation of the BFS, but what I liked most were the details around :) - I confess that my code above was my first algorithm in Py, the rest was basically very elementary thing with Pyqt, just to know the "face" doozy.
– Bacco
I am in the same circumstance... I confess that I knew almost everything (for example
0 < x <= 8
) or the[False * 8]
which I evidently stole for my solution :) . Apparently in python the general principle of "if I were to draw the language as which syntax would I choose?" is working as it works?...– JJoao