O caminho mais curto do cavaleiro no tabuleiro


95

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.


Você poderia esclarecer o PS? Quer dizer, se um cavalo estiver em E4, ele pode se mover para C2, C6, G2 e G6?
Steve Tjoa

Sim, além de seus movimentos normais.
Kyle Hughes

1
Aqui estão algumas análises matemáticas do problema: math.stackexchange.com/questions/104700/…
Graeme Pyle

Respostas:


28

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:

(a1,b3)=1,
(a1,c2)=1,
  .....

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:

function Dijkstra(Graph, source):
   for each vertex v in Graph:           // Initializations
       dist[v] := infinity               // Unknown distance function from source to v
       previous[v] := undefined          // Previous node in optimal path from source
   dist[source] := 0                     // Distance from source to source
   Q := the set of all nodes in Graph
   // All nodes in the graph are unoptimized - thus are in Q
   while Q is not empty:                 // The main loop
       u := vertex in Q with smallest dist[]
       if dist[u] = infinity:
          break                         // all remaining vertices are inaccessible from source
       remove u from Q
       for each neighbor v of u:         // where v has not yet been removed from Q.
           alt := dist[u] + dist_between(u, v) 
           if alt < dist[v]:             // Relax (u,v,a)
               dist[v] := alt
               previous[v] := u
   return dist[]

EDITAR:

  1. como idiota, disse que usar o http://en.wikipedia.org/wiki/A*_algorithm pode ser mais rápido.
  2. a maneira mais rápida é pré-calcular todas as distâncias e salvá-las em uma matriz completa 8x8. bem, eu chamaria isso de trapaça, e só funciona porque o problema é pequeno. Mas às vezes as competições verificarão a velocidade de execução do seu programa.
  3. O ponto principal é que se você está se preparando para uma competição de programação, deve conhecer algoritmos comuns, incluindo o de Dijkstra. Um bom ponto de partida é ler o Introduction to AlgorithmsISBN 0-262-03384-4. Ou você pode tentar a wikipedia, http://en.wikipedia.org/wiki/List_of_algorithms

Isso parece complexo em comparação com a solução de Mustafa abaixo.
lpapp

O que você quer dizer com movimento indisponível? Um cavaleiro pode alcançar qualquer quadrado certo !?
everlasto

51

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). Movimento do Cavaleiro

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

int f( int x , int y )
{
    int delta = x - y;

    if( y > delta )
        return 2 * ( ( y - delta ) / 3 ) + delta;
    else
        return delta - 2 * ( ( delta - y ) / 4 );
}

Nota: Esta pergunta foi feita no SACO 2007 Dia 1
e as soluções estão aqui


8
Alguma chance de você descrever como você elaborou essa fórmula?
kybernetikos

3
Este código funciona? Se um cavalo estiver em posição (0,0) e eu quiser movê-lo para o ponto (1,0). Isso satisfaz 0 <= y <= x. delta = 1-0 = 1. y não é maior que delta (0 <1). Isso significa que vou para o caso else. delta - 2 * ((delta - y) / 4) = 1-2 ((1-0) / 4) = 1-1 / 2 = 1. Não há como mover o cavalo de (0,0) para (1,0) em um movimento. A questão é: este algoritmo funciona? Ou o que estou fazendo de errado?
SimpleApp

3
Parece que funciona apenas para posições possíveis diretas. Mas se o usuário fornecer (2,2) ele retorna 0 e se o usuário fornecer (4,4) ele retorna 2, que estão errados.
yunas de

6
Deve ser 2 * floor((y - delta) / 3) + deltae delta - 2 * floor((delta - y) / 4). Esta é a solução oficial desta página do concurso, mas está errada. Esta primeira equação (de if) 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?
Robo Robok

2
Eu concordo que esta solução não funciona. Veja outras respostas como stackoverflow.com/a/26888893/4288232 para uma solução O (1) de trabalho.
TimSC

45

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):

Padrões

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:

  • Os grupos verticais azuis crescentes de 4
  • As diagonais vermelhas "primárias" (elas vão da parte superior esquerda para a inferior direita, como uma barra invertida)
  • As diagonais verdes "secundárias" (mesma orientação do vermelho)

(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 é:

function getMoveCountO1(x, y) {

    var newXY = simplifyBySymmetry(x, y);

    x = newXY.x;
    y = newXY.y;

    var specialMoveCount = getSpecialCaseMoveCount(x ,y);

    if (specialMoveCount !== undefined)
        return specialMoveCount;

    else if (isVerticalCase(x, y))
        return getVerticalCaseMoveCount(x ,y);

    else if (isPrimaryDiagonalCase(x, y))
        return getPrimaryDiagonalCaseMoveCount(x ,y);

    else if (isSecondaryDiagonalCase(x, y))
        return getSecondaryDiagonalCaseMoveCount(x ,y);

}

sendo o mais difícil os grupos verticais:

function isVerticalCase(x, y) {

    return y >= 2 * x;

}

function getVerticalCaseMoveCount(x, y) {

    var normalizedHeight = getNormalizedHeightForVerticalGroupCase(x, y);

    var groupIndex = Math.floor( normalizedHeight / 4);

    var groupStartMoveCount = groupIndex * 2 + x;

    return groupStartMoveCount + getIndexInVerticalGroup(x, y);

}

function getIndexInVerticalGroup(x, y) {

    return getNormalizedHeightForVerticalGroupCase(x, y) % 4;

}

function getYOffsetForVerticalGroupCase(x) {

    return x * 2;

}

function getNormalizedHeightForVerticalGroupCase(x, y) {

    return y - getYOffsetForVerticalGroupCase(x);

}

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).


Isso não parece lidar com os casos extremos (literais). Se o "0" for o quadrado inferior esquerdo do tabuleiro (a1), você não pode chegar ao espaço "2" mais próximo (b2) em dois movimentos. Porque para fazer isso, seu primeiro movimento teria que ser para o espaço não existente à esquerda de (a3).
John Hascall

Certo, mudei minha resposta para incluir a suposição do tabuleiro de xadrez infinito
Graeme Pyle

@JonatasWalker Explique, não vejo problema indo de (8,0) a (0,0). São necessários 4 movimentos?
Graeme Pyle

Desculpe @GraemePyle, minha culpa, removendo meu comentário.
Jonatas Walker

2
oi @GraemePyle - Concordo com você que esta é a melhor abordagem geral de programação. Ótimo diagrama, a propósito!
Fattie 01 de

22

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 minussinal para o flooroperador e, como resultado, obtiveram a fórmula errada floor(-a) != -floor(a) for any a.

Aqui está a fórmula analítica correta:

var delta = x-y;
if (y > delta) {
    return delta - 2*Math.floor((delta-y)/3);
} else {
    return delta - 2*Math.floor((delta-y)/4);
}

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:

function distance(x,y){
     // axes symmetry 
     x = Math.abs(x);
     y = Math.abs(y);
     // diagonal symmetry 
     if (x < y) {
        t = x;x = y; y = t;
     }
     // 2 corner cases
     if(x==1 && y == 0){
        return 3;
     }
     if(x==2 && y == 2){
        return 4;
     }
    
    // main formula
    var delta = x-y;
		if(y>delta){
  		return delta - 2*Math.floor((delta-y)/3);
  	}
  	else{
  		return delta - 2*Math.floor((delta-y)/4);
  	}
}


$body = $("body");
var html = "";
for (var y = 20; y >= 0; y--){
	html += '<tr>';
	for (var x = 0; x <= 20; x++){
  	html += '<td style="width:20px; border: 1px solid #cecece" id="'+x+'_'+y+'">'+distance(x,y)+'</td>';
  }
  html += '</tr>';
}

html = '<table>'+html+'</table>';
$body.append(html);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Nota: O jQuery usado apenas para ilustração, para ver o código distancefunção.


2
@OlegAbrazhaev A função de "distância" é uma função analítica e pode calcular o número de passos para a posição (x, y) dada no tempo O (1). Basicamente, nesta fórmula não dependemos do tabuleiro (acho que por "tabuleiro infinito" você quer dizer essa propriedade), portanto, funcionará.
simon

2
@simon Alguém pode explicar a fórmula principal, por favor? Estou achando difícil explicar com palavras simples
MarBVI

1
@MarBVI Se nos aproximarmos da linha y = x, podemos diminuir x + y em 3 em cada movimento ficando perto da linha y = x. Se nos aproximarmos da linha y = 0, podemos diminuir x em 2 em cada movimento ficando perto da linha y = 0. É por isso que temos 2 casos, mais precisamente aqui o que quero dizer com próximo a uma linha particular: 1. Por perto de y = linha x, quero dizer seção restrita por y = xey = x / 2 linhas (y> x / 2 ) 2. Por perto de y = 0 linha, quero dizer seção restrita por y = 0 ey = x / 2 linhas (y <x / 2). Pegando tudo acima mencionado e se removermos Math.floor e simplificarmos a fórmula principal, teremos a seguinte fórmula: if (y> x / 2) then {return (x + y) / 3} else {return x / 2}
simon

1
@simon ótimo, isso deixa mais claro ... ty para o seu tempo :)
MarBVI

1
por precaução, o concurso BAPC2017 também teve uma pergunta chamada Knight's Marathon em um tabuleiro infinito que esta fórmula resolve perfeitamente. 2017.bapc.eu/files/preliminaries_problems.pdf
Amir-Mousavi

19

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) .


1
Só eu é que estou confuso com essa resposta? "desenhe um quadrado com cantos (x-4, y-4), (x-4, y + 4), (x + 4, y-4), (x + 4, y + 4). Este conjunto (chamar ele S4) contém 32 pontos ". Não, não tem, ele contém 81, pois é um quadrado 9x9? Além disso, "Seja N = max (| x1-x0 |, | y1-y0 |). Se N> = 4, então o caminho mais curto de (x0, y0) para (x1, y1) tem ceil (N / 2) passos." Isso não é verdade, tome por exemplo x0 = 0, y0 = 0, x1 = 4, y1 = 4, o caminho mais curto é 4, não 2, como a fórmula sugere.
satoshi

1
(1) O conjunto refere-se apenas aos pontos no limite do próprio quadrado. Isso tem 32 pontos / localizações. (2) Quando você leva em consideração o PS do postador sobre movimentos suplementares (veja também os comentários na postagem original), o número mínimo de movimentos passa a ser dois.
Steve Tjoa

Obrigado, isso faz sentido agora :)
satoshi

e se uma placa for infinita? neste caso, apenas o BFS funcionará bem
Oleg Abrazhaev

@SteveTjoa desculpe, não consigo entender por que você mencionou (+ -2, + -2) movimento, já que é impossível para um cavaleiro
Pavel Bely

12

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):

def solve(x,y):
        x = abs(x)
        y = abs(y)
        if y > x:
            temp=y
            y=x
            x=temp  
        if (x==2 and y==2):
            return 4
        if (x==1 and y==0):
            return 3

    if(y == 0 or float(y) / float(x) <= 0.5):
        xClass = x % 4
        if (xClass == 0):
            initX = x/2
        elif(xClass == 1):
            initX = 1 + (x/2)
        elif(xClass == 2):
            initX = 1 + (x/2)
        else:
            initX = 1 + ((x+1)/2)

        if (xClass > 1):
            return initX - (y%2)
        else:
            return initX + (y%2)
    else:
        diagonal = x - ((x-y)/2)
        if((x-y)%2 == 0):
            if (diagonal % 3 == 0):
                return (diagonal/3)*2
            if (diagonal % 3 == 1):
                return ((diagonal/3)*2)+2
            else:
                return ((diagonal/3)*2)+2
        else:
            return ((diagonal/3)*2)+1


def test():
    real=[
    [0,3,2,3,2,3,4,5,4,5,6,7,6,7],
    [3,2,1,2,3,4,3,4,5,6,5,6,7,8],
    [2,1,4,3,2,3,4,5,4,5,6,7,6,7],
    [3,2,3,2,3,4,3,4,5,6,5,6,7,8],
    [2,3,2,3,4,3,4,5,4,5,6,7,6,7],
    [3,4,3,4,3,4,5,4,5,6,5,6,7,8],
    [4,3,4,3,4,5,4,5,6,5,6,7,6,7],
    [5,4,5,4,5,4,5,6,5,6,7,6,7,8],
    [4,5,4,5,4,5,6,5,6,7,6,7,8,7],
    [5,6,5,6,5,6,5,6,7,6,7,8,7,8],
    [6,5,6,5,6,5,6,7,6,7,8,7,8,9],
    [7,6,7,6,7,6,7,6,7,8,7,8,9,8]]

    for x in range(12):
        for y in range(12):
            res = solve(x,y)
            if res!= real[x][y]:
                print (x, y), "failed, and returned", res, "rather than", real[x][y]
            else:
               print (x, y), "worked. Cool!"

test()

10
Referir-se a respostas como aboveou belownão funciona realmente no SO.
Petr Peller

1
Aqui está minha versão em python 2/3. Tentei simplificar a função de resolução, mas não é fácil! gist.github.com/TimSC/8b9a80033f3a22a708a4b9741931c591
TimSC

9

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.


1
+ !, para este problema específico, o BFS é suficiente.
TiansHUo

3
BFS pode ser suficiente, mas um BST simples irá explodir para muitas consultas - você precisará armazenar em cache os quadrados que você visitou. E então o BFS começa a se parecer um pouco com o algoritmo de Dijkstra ...
Charles Stewart,

Qual seria a melhor maneira de rastrear todas as posições que já percorremos para que a árvore BFS só cresça para a frente e quando descobrirmos nós disponíveis de um novo ponto, acabamos não adicionando o nó antigo novamente ... e ficamos presos nele um loop infinito!
Nitish Upreti

Eu aqui suponho que podemos apenas armazenar nossa última posição de cavaleiro?
Nitish Upreti

7

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):

2 1 4 3
3 2 1 2
0 3 2 3

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:

  • Essas sequências são provavelmente os caminhos mais curtos. (Quer que eu prove ou é óbvio?)
  • Nos preocupamos apenas com o número de tais movimentos. Podemos misturar e combinar os movimentos em qualquer ordem.

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:

  • A fórmula acima da linha média move (x, y) para um de (0,0), (1,1) ou (2,2).
  • A fórmula abaixo da linha média move (x, y) para um de (0,0), (1,0), (2,0) ou (1,1).

(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:

. . 4
. 2 .
0 3 2

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:

  • Começamos lá, ou
  • Mudamos para lá, em uma sequência de movimentos de 8 horas e 10 horas. Mas se o último movimento foi às 8 horas (o que tem o direito de ser, visto que podemos fazer nossos movimentos em qualquer ordem), então devemos ter passado por (3,1), cuja distância é na verdade 2 (como você pode veja a folha de cheats original). Portanto, o que devemos fazer é retroceder um movimento das 8 horas, economizando dois movimentos no total.

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:

. . 4
. 2 . 2
0 3 2

(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:

def knightDistance (x, y):
    # normalise the coordinates
    x, y = abs(x), abs(y)
    if (x<y): x, y = y, x
    # now 0 <= y <= x

    # n8 means (-2,-1) (8 o'clock), n7 means (-1,-2) (7 o'clock), n10 means (-2,+1) (10 o'clock)
    if (x>2*y):
        # we're below the midline.  Using 8- & 10-o'clock moves
        n7, n8, n10 = 0,  (x + 2*y)//4,  (x - 2*y + 1)//4
    else:
        # we're above the midline.  Using 7- and 8-o'clock moves
        n7, n8, n10 = (2*y - x)//3, (2*x - y)//3,  0
    x -= 2*n8 + n7 + 2*n10
    y -= n8 + 2*n7 - n10
    # now 0<=x<=2, and y <= x.  Also (x,y) != (2,1)

    # Try to optimise the paths.
    if (x, y)==(1, 0): # hit the  3.  Did we need to?
        if (n8>0): # could have passed through the 2 at 3,1.  Back-up
            x, y = 3, 1; n8-=1;
    if (x, y)==(2, 2): # hit the 4.  Did we need to?
        if (n8>0): # could have passed through a 3 at 4,3.  Back-up, and take 7 o'clock to 2 at 3,1
            x, y = 3, 1; n8-=1; n7+=1

    # Almost there.  Now look up the final leg
    cheatsheet = [[0, 3, 2], [2, None, 2], [4]]
    return n7 + n8 + n10 + cheatsheet [y][x-y]

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:

def validate (n):

    def isSquareReasonable (x, y):
        d, downhills = knightDistance (x, y), 0
        moves = [(1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1), (-2, 1), (-1,  2)]
        for dx, dy in moves:
            dd = knightDistance (x+dx,  y+dy)
            if (dd == d+1): pass
            elif (dd== d-1): downhills += 1
            else: return False;
        return (downhills>0) or (d==0)

    for x in range (0,  n+1):
        for y in range (0,  n+1):
            if not isSquareReasonable (x,  y): raise RuntimeError ("Validation failed")

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?


2
/*
This program takes two sets of cordinates on a 8*8 chessboard, representing the
starting and ending points of a knight's path.
The problem is to print the cordinates that the knight traverses in between, following
the shortest path it can take.
Normally this program is to be implemented using the Djikstra's algorithm(using graphs)
but can also be implemented using the array method.
NOTE:Between 2 points there may be more than one shortest path. This program prints
only one of them.
*/

#include<stdio.h>

#include<stdlib.h>

#include<conio.h>

int m1=0,m2=0;

/*
This array contains three columns and 37 rows:
The rows signify the possible coordinate differences.
The columns 1 and 2 contains the possible permutations of the row and column difference 
between two positions on a chess board;
The column 3 contains the minimum number of steps involved in traversing the knight's 
path with the given permutation*/

int arr[37][3]={{0,0,0},{0,1,3},{0,2,2},{0,3,3},{0,4,2},{0,5,3},{0,6,4},{0,7,5},    {1,1,2},{1,2,1},{1,3,2},{1,4,3},{1,5,4},{1,6,3},{1,7,4},{2,2,4},{2,3,3},{2,4,2},
            {2,5,3},{2,6,3},{2,7,5},{3,3,2},{3,4,3},{3,5,4},{3,6,3},{3,7,4},{4,4,4},{4,5,3},{4,6,4},{4,7,5},{5,5,4},{5,6,5},{5,7,4},{6,6,5},{6,7,5},{7,7,6}};

void printMoves(int,int,int,int,int,int);
void futrLegalMove(int,int,int,int);
main()
{
  printf("KNIGHT'S SHORTEST PATH ON A 8*8 CHESSBOARD :\n");
  printf("------------------------------------------");
  printf("\nThe chessboard may be treated as a 8*8 array here i.e. the (1,1) ");
  printf("\non chessboard is to be referred as (0,0) here and same for (8,8) ");
  printf("\nwhich is to be referred as (7,7) and likewise.\n");
  int ix,iy,fx,fy;
  printf("\nEnter the initial position of the knight :\n");
  scanf("%d%d",&ix,&iy);
  printf("\nEnter the final position to be reached :\n");
  scanf("%d%d",&fx,&fy);
  int px=ix,py=iy;
  int temp;
  int tx,ty;
  printf("\nThe Knight's shortest path is given by :\n\n");
  printf("(%d, %d)",ix,iy);
  futrLegalMove(px,py,m1,m2);
  printMoves(px,py,fx,fy,m1,m2);
   getch();
} 

/*
  This method checkSteps() checks the minimum number of steps involved from current
  position(a & b) to final position(c & d) by looking up in the array arr[][].
*/

int checkSteps(int a,int b,int c,int d)
{  
    int xdiff, ydiff;
    int i, j;
    if(c>a)
        xdiff=c-a;
    else
        xdiff=a-c;
    if(d>b)
        ydiff=d-b;
    else
        ydiff=b-d;
    for(i=0;i<37;i++)
        {
            if(((xdiff==arr[i][0])&&(ydiff==arr[i][1])) || ((xdiff==arr[i][1])&& (ydiff==arr[i] [0])))
            {
                j=arr[i][2];break;
            }
        }

        return j;
}   

/*
This method printMoves() prints all the moves involved.
*/

void printMoves(int px,int py, int fx, int fy,int a,int b)
{    
 int temp;
 int tx,ty;
 int t1,t2;
  while(!((px==fx) && (py==fy)))
  {   
      printf(" --> ");
      temp=checkSteps(px+a,py+b,fx,fy);
      tx=px+a;
      ty=py+b;
      if(!(a==2 && b==1))
      {if((checkSteps(px+2,py+1,fx,fy)<temp) && checkMove(px+2,py+1))
      {temp=checkSteps(px+2,py+1,fx,fy);
       tx=px+2;ty=py+1;}}
      if(!(a==2 && b==-1))
      {if((checkSteps(px+2,py-1,fx,fy)<temp) && checkMove(px+2,py-1))
      {temp=checkSteps(px+2,py-1,fx,fy);
       tx=px+2;ty=py-1;}}
      if(!(a==-2 && b==1))
      {if((checkSteps(px-2,py+1,fx,fy)<temp) && checkMove(px-2,py+1))
      {temp=checkSteps(px-2,py+1,fx,fy);
       tx=px-2;ty=py+1;}}
      if(!(a==-2 && b==-1))
      {if((checkSteps(px-2,py-1,fx,fy)<temp) && checkMove(px-2,py-1))
      {temp=checkSteps(px-2,py-1,fx,fy);
       tx=px-2;ty=py-1;}}
      if(!(a==1 && b==2))
      {if((checkSteps(px+1,py+2,fx,fy)<temp) && checkMove(px+1,py+2))
      {temp=checkSteps(px+1,py+2,fx,fy);
       tx=px+1;ty=py+2;}}
      if(!(a==1 && b==-2))
      {if((checkSteps(px+1,py-2,fx,fy)<temp) && checkMove(px+1,py-2))
      {temp=checkSteps(px+1,py-2,fx,fy);
       tx=px+1;ty=py-2;}}
      if(!(a==-1 && b==2))
      {if((checkSteps(px-1,py+2,fx,fy)<temp) && checkMove(px-1,py+2))
      {temp=checkSteps(px-1,py+2,fx,fy);
       tx=px-1;ty=py+2;}}
      if(!(a==-1 && b==-2))
      {if((checkSteps(px-1,py-2,fx,fy)<temp) && checkMove(px-1,py-2))
      {temp=checkSteps(px-1,py-2,fx,fy);
       tx=px-1;ty=py-2;}}
       t1=tx-px;//the step taken in the current move in the x direction.
       t2=ty-py;//" " " " " " " " " " " " " " " " " " " " " y " " " " ".
       px=tx;
       py=ty;
       printf("(%d, %d)",px,py);
       futrLegalMove(px,py,t1,t2);
       a=m1;
       b=m2;
   }

} 

/*
The method checkMove() checks whether the move in consideration is beyond the scope of
board or not.
*/   

int checkMove(int a, int b)
{
    if(a>7 || b>7 || a<0 || b<0)
        return 0;
    else
        return 1;
}

/*Out of the 8 possible moves, this function futrLegalMove() sets the valid move by
  applying the following constraints
      1. The next move should not be beyond the scope of the board.
      2. The next move should not be the exact opposite of the previous move.
  The 1st constraint is checked by sending all possible moves to the checkMove() 
  method;
  The 2nd constraint is checked by passing as parameters(i.e. a and b) the steps of the 
  previous move and checking whether or not it is the exact opposite of the current move.
*/

void futrLegalMove(int px,int py,int a,int b)
{
     if(checkMove(px+2,py+1) && (a!=-2 && b!=-1))
         m1=2,m2=1;
     else
     {
         if(checkMove(px+2,py-1)&& (a!=-2 && b!=1))
             m1=2,m2=-1;
     else
     {
         if(checkMove(px-2,py+1)&& (a!=2 && b!=-1))
              m1=-2,m2=1;
     else
     {
         if(checkMove(px-2,py-1)&& (a!=2 && b!=1))
               m1=-2,m2=-1;
     else
     {
         if(checkMove(px+1,py+2)&& (b!=-2 && a!=-1))
               m2=2,m1=1;
     else
     {
         if(checkMove(px+1,py-2)&& (a!=-1 && b!=2))
               m2=-2,m1=1;
     else
     {
         if(checkMove(px-1,py+2)&& (a!=1 && b!=-2))
               m2=2,m1=-1;
     else
     {
         if(checkMove(px-1,py-2)&& (a!=1 && b!=2))
               m2=-2,m1=-1;
     }}}}}}}
}

//End of Program.

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).


2

Eu acho que isso também pode te ajudar ..

NumWays(x,y)=1+min(NumWays(x+-2,y-+1),NumWays(x+-1,y+-2)); 

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.


1

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.

#!/usr/local/bin/perl -w

use strict;

my $from = [0,0];
my $to   = [7,7];

my $f_from = flat($from);
my $f_to   = flat($to);

my $max_x = 7;
my $max_y = 7;
my @moves = ([-1,2],[1,2],[2,1],[2,-1],[1,-2],[-1,-2],[-2,-1],[-2,1]);
my %squares = ();
my $i = 0;
my $min = -1;

my @s = ( $from );

while ( @s ) {

   my @n = ();
   $i++;

   foreach my $s ( @s ) {
       unless ( $squares{ flat($s) } ) {
            my @m = moves( $s );
            push @n, @m;
            $squares{ flat($s) } = { i=>$i, n=>{ map {flat($_)=>1} @m }, };

            $min = $i if $squares{ flat($s) }->{n}->{$f_to};
       }
   }

   last if $min > -1;
   @s = @n;
}

show_path( $f_to, $min );

sub show_path {
    my ($s,$i) = @_;

    return if $s eq $f_from;

    print "$i => $f_to\n" if $i == $min;

    foreach my $k ( keys %squares ) {
       if ( $squares{$k}->{i} == $i && $squares{$k}->{n}->{$s} ) {
            $i--;
            print "$i => $k\n";
            show_path( $k, $i );
            last;
       }
    }
}

sub flat { "$_[0]->[0],$_[0]->[1]" }

sub moves {
    my $c = shift;
    my @s = ();

    foreach my $m ( @moves ) {
       my $x = $c->[0] + $m->[0];
       my $y = $c->[1] + $m->[1];

       if ( $x >= 0 && $x <=$max_x && $y >=0 && $y <=$max_y) {
           push @s, [$x, $y];
       }
    }
    return @s;
}

__END__

1
public class Horse {

    private int[][] board;
    private int[] xer = { 2, 1, -1, -2, -2, -1, 1, 2 };
    private int[] yer = { 1, 2, 2, 1, -1, -2, -2, -1 };
    private final static int A_BIG_NUMBER = 10000;
    private final static int UPPER_BOUND = 64;


    public Horse() {
        board =  new int[8][8];
    }

    private int solution(int x, int y, int destx, int desty, int move) {

        if(move == UPPER_BOUND) {
            /* lets put an upper bound to avoid stack overflow */
            return A_BIG_NUMBER;
        }

        if(x == 6 && y ==5) {
            board[6][5] = 1;
            return 1;
        }
        int min = A_BIG_NUMBER;
        for (int i = 0 ; i < xer.length; i++) {
            if (isMoveGood(x + xer[i], y + yer[i])) {
                if(board[x + xer[i]][y + yer[i]] != 0) {
                    min = Integer.min(min, 1 + board[x +xer[i]] [y +yer[i]]);                   
                } else {
                    min = Integer.min(min, 1 + solution(x + xer[i], y + yer[i], destx, desty, move + 1));   
                }                   
            }
        }   
        board[x][y] = min;
        return min;
    }


    private boolean isMoveGood(int x, int y) {
        if (x >= 0 && x < board.length && y >= 0 && y < board.length)
            return true;
        return false;
    }


    public static void main(String[] args) {

        int destX = 6;
        int destY = 7;
        final Horse h = new Horse();
        System.out.println(h.solution(0, 0, destX, destY, 0));
    }
}

0

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:

def getBoardOffset(board)
  return board.length / 2
end

def setMoveCount(x, y, count, board)
  offset = getBoardOffset(board)
  board[y + offset][x + offset] = count
end

def getMoveCount(x, y, board)
    offset = getBoardOffset(board)
    row = board[y + offset]
    return row[x + offset]
end

def isBottomOfVerticalCase(x, y)
    return (y - 2 * x) % 4 == 0
end

def isPrimaryDiagonalCase(x, y)
    return (x + y) % 2 == 0
end

def isSecondaryDiagonalCase(x, y)
    return (x + y) % 2 == 1
end

def simplifyBySymmetry(x, y)
    x = x.abs
    y = y.abs
    if (y < x)
      t = x
      x = y
      y = t
    end
    return {x: x, y: y}
end

def getPrimaryDiagonalCaseMoveCount(x, y)
    var diagonalOffset = y + x
    var diagonalIntersect = diagonalOffset / 2
    return ((diagonalIntersect + 2) / 3).floor * 2
end

def getSpecialCaseMoveCount(x, y)
    specials = [{
            x: 0,
            y: 0,
            d: 0
        },
        {
            x: 0,
            y: 1,
            d: 3
        },
        {
            x: 0,
            y: 2,
            d: 2
        },
        {
            x: 0,
            y: 3,
            d: 3
        },
        {
            x: 2,
            y: 2,
            d: 4
        },
        {
            x: 1,
            y: 1,
            d: 2
        },
        {
            x: 3,
            y: 3,
            d: 2
        }
    ];
    matchingSpecial=nil
    specials.each do |special|
      if (special[:x] == x && special[:y] == y)
        matchingSpecial = special
      end
    end
    if (matchingSpecial)
      return matchingSpecial[:d]
    end
end

def isVerticalCase(x, y)
  return y >= 2 * x
end

def getVerticalCaseMoveCount(x, y)
    normalizedHeight = getNormalizedHeightForVerticalGroupCase(x, y)
    groupIndex = (normalizedHeight/4).floor
    groupStartMoveCount = groupIndex * 2 + x
    return groupStartMoveCount + getIndexInVerticalGroup(x, y)
end

def getIndexInVerticalGroup(x, y)
    return getNormalizedHeightForVerticalGroupCase(x, y) % 4
end

def getYOffsetForVerticalGroupCase(x) 
    return x * 2
end

def getNormalizedHeightForVerticalGroupCase(x, y)
    return y - getYOffsetForVerticalGroupCase(x)
end

def getSecondaryDiagonalCaseMoveCount(x, y)
    diagonalOffset = y + x
    diagonalIntersect = diagonalOffset / 2 - 1
    return ((diagonalIntersect + 2) / 3).floor * 2 + 1
end

def getMoveCountO1(x, y)
    newXY = simplifyBySymmetry(x, y)
    x = newXY[:x]
    y = newXY[:y]
    specialMoveCount = getSpecialCaseMoveCount(x ,y)
    if (specialMoveCount != nil)
      return specialMoveCount
    elsif (isVerticalCase(x, y))
      return getVerticalCaseMoveCount(x ,y)
    elsif (isPrimaryDiagonalCase(x, y))
      return getPrimaryDiagonalCaseMoveCount(x ,y)
    elsif (isSecondaryDiagonalCase(x, y))
      return getSecondaryDiagonalCaseMoveCount(x ,y)
    end
end

def solution(x ,y)
  return getMoveCountO1(x, y)
end


puts solution(0,0)

A única intenção é poupar algum tempo convertendo código se alguém precisar de código completo.


0

aqui está a versão PHP da função de Jules May

function knightDistance($x, $y)
{
    $x = abs($x);
    $y = abs($y);

    if($x < $y)
    {
        $tmp = $x;
        $x = $y;
        $y = $tmp;
    }

    if($x > 2 * $y)
    {
        $n7 = 0;
        $n8 = floor(($x + 2*$y) / 4);
        $n10 = floor(($x - 2*$y +1) / 4);
    }
    else
    {
        $n7 = floor((2*$y - $x) / 3);
        $n8 = floor((2*$x - $y) / 3);
        $n10 = 0;
    }

    $x -= 2 * $n8 + $n7 + 2 * $n10;
    $y -= $n8 + 2 * $n7 - $n10;

    if($x == 1 && $y == 0)
    {
        if($n8 > 0)
        {
            $x = 3;
            $y = 1;
            $n8--;
        }
    }
    if($x == 2 && $y == 2)
    {
        if($n8 > 0)
        {
            $x = 3;
            $y = 1;
            $n8--;
            $n7++;
        }
    }

    $cheatsheet = [[0, 3, 2], [2, 0, 2], [4]];

    return $n7 + $n8 + $n10 + $cheatsheet [$y][$x-$y];
}

0

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.

public class KnightKing2 {
    private static int tempCount = 0;

    public static void main(String[] args) throws IOException {
        Scanner in = new Scanner(System.in);
        int ip1 = Integer.parseInt(in.nextLine().trim());
        int ip2 = Integer.parseInt(in.nextLine().trim());
        int ip3 = Integer.parseInt(in.nextLine().trim());
        int ip4 = Integer.parseInt(in.nextLine().trim());
        in.close();
        int output = getStepCount(ip1, ip2, ip3, ip4);
        System.out.println("Shortest Path :" + tempCount);

    }

    // 2 1 6 5 -> 4
    // 6 6 5 5 -> 2

    public static int getStepCount(int input1, int input2, int input3, int input4) {
        return recurse(0, input1, input2, input3, input4);

    }

    private static int recurse(int count, int tx, int ty, int kx, int ky) {

        if (isSolved(tx, ty, kx, ky)) {
            int ccount = count+1;
            System.out.println("COUNT: "+count+"--"+tx+","+ty+","+ccount);
            if((tempCount==0) || (ccount<=tempCount)){
                tempCount = ccount;
            }
            return ccount;
        }

            if ((tempCount==0 || count < tempCount) && ((tx < kx+2) && (ty < ky+2))) {
                if (!(tx + 2 > 8) && !(ty + 1 > 8)) {
                    rightTop(count, tx, ty, kx, ky);

                }
                if (!(tx + 2 > 8) && !(ty - 1 < 0)) {
                    rightBottom(count, tx, ty, kx, ky);
                }
                if (!(tx + 1 > 8) && !(ty + 2 > 8)) {
                    topRight(count, tx, ty, kx, ky);
                }
                if (!(tx - 1 < 0) && !(ty + 2 > 8)) {
                    topLeft(count, tx, ty, kx, ky);
                }
                if (!(tx + 1 > 8) && !(ty - 2 < 0)) {
                     bottomRight(count, tx, ty, kx, ky);
                }
                if (!(tx - 1 < 0) && !(ty - 2 < 0)) {
                     bottomLeft(count, tx, ty, kx, ky);
                }
                if (!(tx - 2 < 0) && !(ty + 1 > 8)) {
                    leftTop(count, tx, ty, kx, ky);
                }
                if (!(tx - 2 < 0) && !(ty - 1 < 0)) {
                    leftBottom(count, tx, ty, kx, ky);
                }
            }

        return count;

    }

    private static int rightTop(int count, int tx, int ty, int kx, int ky) {
        return count + recurse(count + 1, tx + 2, ty + 1, kx, ky);

    }

    private static int topRight(int count, int tx, int ty, int kx, int ky) {
        return count + recurse(count + 1, tx + 1, ty + 2, kx, ky);
    }

    private static int rightBottom(int count, int tx, int ty, int kx, int ky) {
        return count + recurse(count + 1, tx + 2, ty - 1, kx, ky);
    }

    private static int bottomRight(int count, int tx, int ty, int kx, int ky) {
        return count + recurse(count + 1, tx + 1, ty - 2, kx, ky);
    }

    private static int topLeft(int count, int tx, int ty, int kx, int ky) {
        return count + recurse(count + 1, tx - 1, ty + 2, kx, ky);
    }

    private static int bottomLeft(int count, int tx, int ty, int kx, int ky) {
        return count + recurse(count + 1, tx - 1, ty - 2, kx, ky);
    }

    private static int leftTop(int count, int tx, int ty, int kx, int ky) {
        return count + recurse(count + 1, tx - 2, ty + 1, kx, ky);
    }

    private static int leftBottom(int count, int tx, int ty, int kx, int ky) {
        return count + recurse(count + 1, tx - 2, ty - 1, kx, ky);
    }

    private static boolean isSolved(int tx, int ty, int kx, int ky) {
        boolean solved = false;
        if ((tx == kx) && (ty == ky)) {
            solved = true;
        } else if ((tx + 2 == kx) && (ty + 1 == ky)) { // right top
            solved = true;
        } else if ((tx + 2 == kx) && (ty - 1 == ky)) { // right bottom
            solved = true;
        } else if ((ty + 2 == ky) && (tx + 1 == kx)) {// top right
            solved = true;
        } else if ((ty + 2 == ky) && (tx - 1 == kx)) {// top left
            solved = true;
        } else if ((tx - 2 == kx) && (ty + 1 == ky)) { // left top
            solved = true;
        } else if ((tx - 2 == kx) && (ty - 1 == ky)) {// left bottom
            solved = true;
        } else if ((ty - 2 == ky) && (tx + 1 == kx)) { // bottom right
            solved = true;
        } else if ((ty - 2 == ky) && (tx - 1 == kx)) { // bottom left
            solved = true;
        }

        return solved;
    }

}

1
Ele pode ser otimizado ainda mais para evitar duplicatas.
Arun

-1

Aqui está uma versão C baseada no código Mustafa Serdar Şanlı que funciona para uma placa finita:

#include <stdio.h>
#include <math.h>

#define test(x1, y1, x2, y2) (sx == x1 && sy == y1 &&tx == x2 &&ty == y2) || (sx == x2 && sy == y2 && tx == x1 && ty==y1)

int distance(int sx, int sy, int tx, int ty) {
    int x, y, t;
    double delta;

    // special corner cases 
    if (test(1, 1, 2, 2) || 
        test(7, 7, 8, 8) || 
        test(7, 2, 8, 1) || 
        test(1, 8, 2, 7))
        return 4;

    // axes symmetry 
    x = abs(sx - tx);
    y = abs(sy - ty);

    // diagonal symmetry 
    if (x < y) {
        t = x;
        x = y;
        y = t;
    }

    // 2 corner cases
    if (x == 1 && y == 0)
        return 3;
    if (x == 2 && y == 2)
        return 4;

    // main
    delta = x - y;
    if (y > delta) {
        return (int)(delta - 2 * floor((delta - y) / 3));
    }
    else {
        return (int)(delta - 2 * floor((delta - y) / 4));
    }
}

Teste aqui com prova contra uma solução recursiva


1
Testar um número finito de casos não é uma prova.
BlenderBender
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.