Tenho treinado para uma competição de programação que se aproxima e me deparei com uma pergunta que me deixa completamente perplexo. No entanto, sinto que é um conceito que devo aprender agora, em vez de cruzar os dedos, que nunca venha à tona.
Basicamente, trata-se de uma peça de cavalo em um tabuleiro de xadrez. Você recebe duas entradas: localização inicial e localização final. O objetivo é calcular e imprimir o caminho mais curto que o cavaleiro pode percorrer para chegar ao local de destino.
Nunca lidei com coisas do tipo do caminho mais curto e nem sei por onde começar. Que lógica devo empregar para lidar com isso?
PS Se for de alguma relevância, eles querem que você complemente os movimentos normais do cavalo, permitindo também que ele se mova para os quatro cantos do quadrado formado pelos (potencialmente) oito movimentos que um cavalo pode fazer, dado que o centro do quadrado é a localização do cavaleiro.
fonte
Respostas:
Você tem um gráfico aqui, onde todos os movimentos disponíveis estão conectados (valor = 1), e os movimentos indisponíveis estão desconectados (valor = 0), a matriz esparsa seria como:
E o caminho mais curto de dois pontos em um gráfico pode ser encontrado usando http://en.wikipedia.org/wiki/Dijkstra's_algorithm
Pseudo-código da página wikipedia:
EDITAR:
Introduction to Algorithms
ISBN 0-262-03384-4. Ou você pode tentar a wikipedia, http://en.wikipedia.org/wiki/List_of_algorithmsfonte
EDIT: Veja a resposta de simon , onde ele fixou a fórmula apresentada aqui.
Na verdade, existe uma fórmula O (1)
Esta é uma imagem que fiz para visualizá-la (os quadrados que um cavalo pode alcançar com o enésimo movimento são pintados com a mesma cor).
Você pode notar o padrão aqui?
Embora possamos ver o padrão, é realmente difícil encontrar a função
f( x , y )
que retorna o número de movimentos necessários para ir de um quadrado( 0 , 0 )
para outro( x , y )
Mas aqui está a fórmula que funciona quando
0 <= y <= x
Nota: Esta pergunta foi feita no SACO 2007 Dia 1
e as soluções estão aqui
fonte
2 * floor((y - delta) / 3) + delta
edelta - 2 * floor((delta - y) / 4)
. Esta é a solução oficial desta página do concurso, mas está errada. Esta primeira equação (deif
) retorna respostas erradas. No tabuleiro de xadrez [-1000..1000] x [-1000..1000], que é grande em 2001x2001 (mas logicamente ilimitado), a resposta dada conta 2.669.329 de 4.004.001 campos corretos (66,66%). Alguém conhece a solução de trabalho sem nenhum loop?Aqui está uma solução O (1) correta, mas para o caso em que o cavalo se move apenas como um cavalo de xadrez e em um tabuleiro de xadrez infinito:
https://jsfiddle.net/graemian/5qgvr1ba/11/
A chave para descobrir isso é observar os padrões que surgem quando você desenha o quadro. No diagrama abaixo, o número no quadrado é o número mínimo de movimentos necessários para alcançar aquele quadrado (você pode usar a pesquisa em largura para encontrar isso):
Como a solução é simétrica entre os eixos e as diagonais, desenhei apenas o caso x> = 0 ey> = x.
O bloco inferior esquerdo é a posição inicial e os números nos blocos representam o número mínimo de movimentos para alcançar esses blocos.
Existem 3 padrões a serem observados:
(Certifique-se de ver os dois conjuntos de diagonais como superior esquerdo para inferior direito. Eles têm uma contagem de movimentos constante. As diagonais inferior esquerda superior direita são muito mais complexas.)
Você pode derivar fórmulas para cada um. Os blocos amarelos são casos especiais. Portanto, a solução é:
sendo o mais difícil os grupos verticais:
Veja o violino para os outros casos.
Talvez haja padrões mais simples ou mais elegantes que eu perdi? Se sim, adoraria vê-los. Em particular, noto alguns padrões diagonais nos casos verticais azuis, mas não os explorei. Independentemente disso, essa solução ainda satisfaz a restrição O (1).
fonte
Problema muito interessante que encontrei recentemente. Depois de procurar algumas soluções, tentei recuperar a fórmula analítica (
O(1) time and space complexity
) fornecida nas soluções do dia 1 do SACO 2007 .Em primeiro lugar, quero agradecer a Graeme Pyle pela ótima visualização que me ajudou a corrigir a fórmula.
Por algum motivo (talvez por simplificação, beleza ou apenas um erro), eles mudaram o
minus
sinal para ofloor
operador e, como resultado, obtiveram a fórmula erradafloor(-a) != -floor(a) for any a
.Aqui está a fórmula analítica correta:
A fórmula funciona para todos os pares (x, y) (após a aplicação de eixos e simetria diagonal), exceto (1,0) e (2,2) casos de canto, que não satisfazem o padrão e codificados no seguinte snippet:
Nota: O jQuery usado apenas para ilustração, para ver o código
distance
função.fonte
Sim, Dijkstra e BFS darão a você a resposta, mas acho que o contexto do xadrez desse problema fornece conhecimento que pode gerar uma solução muito mais rápida do que um algoritmo de caminho mais curto genérico, especialmente em um tabuleiro de xadrez infinito.
Para simplificar, vamos descrever o tabuleiro de xadrez como o plano (x, y). O objetivo é encontrar o caminho mais curto de (x0, y0) para (x1, y1) usando apenas as etapas candidatas (+ -1, + -2), (+ -2, + -1) e (+ -2 , + -2), conforme descrito no PS da pergunta
Aqui está a nova observação: desenhe um quadrado com cantos (x-4, y-4), (x-4, y + 4), (x + 4, y-4), (x + 4, y + 4) . Este conjunto (chame-o de S4) contém 32 pontos. O caminho mais curto de qualquer um desses 32 pontos para (x, y) requer exatamente dois movimentos .
O caminho mais curto de qualquer um dos 24 pontos no conjunto S3 (definido de forma semelhante) até (x, y) requer pelo menos dois movimentos .
Portanto, se | x1-x0 |> 4 ou | y1-y0 |> 4, o caminho mais curto de (x0, y0) para (x1, y1) é exatamente dois movimentos maior do que o caminho mais curto de (x0, y0) para S4. E o último problema pode ser resolvido rapidamente com iteração direta.
Seja N = max (| x1-x0 |, | y1-y0 |). Se N> = 4, então o caminho mais curto de (x0, y0) para (x1, y1) tem passos ceil (N / 2) .
fonte
A resposta O (1) acima [ https://stackoverflow.com/a/8778592/4288232 por Mustafa Serdar Şanlı] não está realmente funcionando. (Marque (1,1) ou (3,2) ou (4,4), exceto para os casos extremos óbvios de (1,0) ou (2,2)).
Abaixo está uma solução muito mais feia (python), que funciona (com "testes" adicionados):
fonte
above
oubelow
não funciona realmente no SO.O que você precisa fazer é pensar nos movimentos possíveis do cavalo como um gráfico, onde cada posição no tabuleiro é um nó e os movimentos possíveis para outra posição como uma borda. Não há necessidade do algoritmo de dijkstra, porque todas as arestas têm o mesmo peso ou distância (todas são fáceis ou curtas de fazer). Você pode apenas fazer uma pesquisa BFS do seu ponto de partida até chegar à posição final.
fonte
Solução dos primeiros princípios em Python
Eu encontrei esse problema pela primeira vez em um teste de Codility. Eles me deram 30 minutos para resolvê-lo - demorei muito mais do que isso para chegar a esse resultado! O problema era: quantos movimentos um cavalo leva para ir de 0,0 a x, y usando apenas os movimentos legais do Cavalo. xey eram mais ou menos ilimitados (portanto, não estamos falando aqui de um simples tabuleiro de xadrez 8x8).
Eles queriam uma solução O (1). Eu queria uma solução em que o programa resolvesse claramente o problema (ou seja, eu queria algo mais obviamente certo do que o padrão de Graeme - os padrões têm o hábito de quebrar onde você não está olhando), e eu realmente queria não ter que depender de um fórmula não discutida, como na solução de Mustafá
Então, aqui está minha solução, vale a pena. Comece, como outros fizeram, observando que a solução é simétrica em relação aos eixos e diagonais, portanto, precisamos resolver apenas para 0> = y> = x. Para simplificar a explicação (e o código), vou reverter o problema: o cavalo começa em x, y e tem como objetivo 0,0.
Vamos supor que reduzamos o problema até a vizinhança da origem. Veremos o que 'vicinty' realmente significa no devido tempo, mas por enquanto, vamos apenas escrever algumas soluções em uma folha de cheats (origem no canto inferior esquerdo):
Assim, dados x, y na grade, podemos apenas ler o número de movimentos para a origem.
Se começamos fora da grade, temos que trabalhar para voltar a ela. Introduzimos a 'linha média', que é a linha representada por y = x / 2. Qualquer cavalo em x, y nessa linha pode trabalhar seu caminho de volta para a folha de cheats usando uma série de movimentos de 8 horas (isto é: (-2, -1) movimentos). Se x, y estiver acima da linha média, precisaremos de uma sucessão de movimentos de 8 horas e 7 horas, e se estiver abaixo da linha média, precisaremos de uma sucessão de 8 horas e 10 horas 'relógio se move. Duas coisas a serem observadas aqui:
Então, vamos dar uma olhada nos movimentos acima da linha média. O que estamos afirmando é que:
(dx; dy) = (2,1; 1,2) (n8; n7) (notação de matriz, sem formatação matemática - vetor coluna (dx; dy) é igual à matriz quadrada multiplicada pelo vetor coluna (n8; n7) - o número de movimentos de 8 horas e o número de movimentos de 7 horas), e da mesma forma;
(dx; dy) = (2,2; 1, -1) (n8; n10)
Estou afirmando que dx, dy será aproximadamente (x, y), então (x-dx, y-dy) estará na vizinhança da origem (seja qual for a 'vizinhança' que venha a ser).
As duas linhas no código que calculam esses termos são a solução para eles, mas foram selecionadas para ter algumas propriedades úteis:
(Você gostaria de provas disso?) Então, a distância do Cavalo será a soma de n7, n8, n10 e cheatsheet [x-dx, y-dy], e nosso cheatsheet se reduz a isto:
Agora, este não é exatamente o fim da história. Olhe para o 3 na linha inferior. As únicas maneiras de alcançarmos isso são:
Há uma otimização semelhante a ser obtida com o 4 no canto superior direito. Além de começar aí, a única maneira de chegar lá é por volta das 8 horas de (4,3). Isso não está na folha de cheats, mas se estivesse lá, sua distância seria 3, porque poderíamos ter das 7 horas para (3,1) em vez disso, que tem uma distância de apenas 2. Portanto, devemos voltar um Movimento das 8 horas e, em seguida, avance uma das 7 horas.
Portanto, precisamos adicionar mais um número à folha de cheats:
(Nota: há uma grande quantidade de otimizações de back-tracking de (0,1) e (0,2), mas como o solucionador nunca nos levará até lá, não precisamos nos preocupar com elas.)
Então aqui está algum código Python para avaliar isso:
A propósito, se você quiser saber uma rota real, então este algoritmo também oferece isso: é simplesmente uma sucessão de n7 movimentos das 7 horas, seguidos por (ou intercalados com) n8 movimentos das 8 horas, n10 10- movimentos das horas e qualquer dança ditada pela folha de cheats (que, por si só, pode estar em uma folha de cheats).
Agora: como provar que isso está certo. Não basta comparar esses resultados com uma tabela de respostas certas, porque o problema em si não tem limites. Mas podemos dizer que, se a distância do Cavalo de um quadrado s é d, então se {m} é o conjunto de movimentos legais de s, a distância do Cavalo de (s + m) deve ser d-1 ou d + 1 para todos m. (Você precisa de uma prova disso?) Além disso, deve haver pelo menos um desses quadrados cuja distância seja d-1, a menos que s seja a origem. Portanto, podemos provar a correção mostrando que essa propriedade é válida para todos os quadrados. Portanto:
Alternativamente, podemos provar a exatidão de qualquer um dos quadrados perseguindo a rota da descida de s até a origem. Primeiro, verifique a razoabilidade de s como acima, então selecione qualquer s + m tal que a distância (s + m) == d-1. Repita até chegarmos à origem.
Howzat?
fonte
Eu não estudei gráficos ainda ... de acordo com o problema de implementá-los por meio de matrizes simplesmente, não consegui derivar outra solução além desta. Tratei as posições não como classificações e arquivos (a notação usual do xadrez), mas como índices de matriz. Para sua informação, isso é apenas para um tabuleiro de xadrez 8 * 8. Qualquer conselho de melhoria é sempre bem-vindo.
* Os comentários devem ser suficientes para sua compreensão da lógica. No entanto, você sempre pode perguntar.
* Verificado no compilador DEV-C ++ 4.9.9.2 (Software Bloodshed).
fonte
Eu acho que isso também pode te ajudar ..
e usando a Programação Dinâmica para obter a solução.
PS: Ele meio que usa o BFS sem ter que se dar ao trabalho de declarar os nós e arestas do gráfico.
fonte
Aqui está uma solução para este problema específico implementado em Perl. Ele mostrará um dos caminhos mais curtos - pode haver mais de um em alguns casos.
Não usei nenhum dos algoritmos descritos acima - mas seria bom compará-lo com outras soluções.
fonte
fonte
Apenas o código ruby do jsfiddle de Graeme Pyle acima , distribuído todo o código extra e convertido em ruby apenas para obter a solução por seu algoritmo, parece estar funcionando. Ainda testando:
A única intenção é poupar algum tempo convertendo código se alguém precisar de código completo.
fonte
aqui está a versão PHP da função de Jules May
fonte
Aqui está meu programa. Esta não é uma solução perfeita. Existem muitas mudanças a serem feitas na função de recursão. Mas este resultado final é perfeito. Tentei otimizar um pouco.
fonte
Aqui está uma versão C baseada no código Mustafa Serdar Şanlı que funciona para uma placa finita:
Teste aqui com prova contra uma solução recursiva
fonte