web-dev-qa-db-fra.com

Le plus court chemin du chevalier sur l'échiquier

Je me suis entraîné pour une compétition de programmation à venir et je suis tombé sur une question qui me laisse complètement perplexe. Cependant, j’ai l’impression que c’est un concept que je devrais apprendre maintenant plutôt que de me croiser les doigts.

En gros, il s’agit d’une pièce de chevalier sur un échiquier. Vous avez deux entrées: l'emplacement de départ et l'emplacement d'arrivée. Le but est ensuite de calculer et d’imprimer le chemin le plus court que le chevalier puisse emprunter pour se rendre à l’emplacement cible.

Je n'ai jamais traité de choses du plus court chemin et je ne sais même pas par où commencer. Quelle logique est-ce que j'utilise pour m'y attaquer?

P.S. S'ils le souhaitent, ils souhaitent que vous complétiez les mouvements normaux du chevalier en lui permettant également de se déplacer aux quatre coins du carré formé par les (potentiellement) huit mouvements qu'un chevalier peut faire, étant donné que le centre du carré est le l'emplacement du chevalier.

89
Kyle Hughes

Vous avez un graphique ici, où tous les déplacements disponibles sont connectés (valeur = 1) et les déplacements non disponibles sont déconnectés (valeur = 0), la matrice fragmentée serait comme:

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

Et le plus court chemin de deux points d’un graphique peut être trouvé en utilisant http://en.wikipedia.org/wiki/algorithme_Dijkstra

Pseudo-code de la page 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[]

MODIFIER:

  1. comme débile, a déclaré en utilisant le http://en.wikipedia.org/wiki/A*_algorithm peut être plus rapide.
  2. le moyen le plus rapide est de pré-calculer toutes les distances et de l’enregistrer dans une matrice 8x8 complète. Eh bien, j'appellerais cela de la triche, et ne fonctionne que parce que le problème est petit. Mais parfois, les compétitions vérifieront la vitesse d'exécution de votre programme.
  3. Le point principal est que si vous vous préparez pour un concours de programmation, vous devez connaître les algorithmes courants, y compris ceux de Dijkstra. Un bon point de départ est la lecture Introduction to Algorithms ISBN 0-262-03384-4. Ou vous pouvez essayer wikipedia, http://en.wikipedia.org/wiki/List_of_algorithms
26
TiansHUo

EDIT: Voir la réponse de Simon , où il fixe la formule présentée ici.

En fait, il existe une formule O(1)

C’est une image que j’ai faite pour la visualiser (des carrés qu’un chevalier peut atteindre sur Nth déplacer sont peints avec la même couleur). Knight's Move

Pouvez-vous remarquer le motif ici?

Bien que nous puissions voir le motif, il est très difficile de trouver la fonction f( x , y ) qui renvoie le nombre de déplacements nécessaires pour passer du carré ( 0 , 0 ) au carré ( x , y )

Mais voici la formule qui fonctionne quand 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 );
}

Remarque: cette question a été posée le SACO 2007 Day 1
Et les solutions sont ici

47

Voici une solution correcte O(1)), mais dans le cas où le chevalier se déplace comme un chevalier d'échecs uniquement, et sur un échiquier infini:

https://jsfiddle.net/graemian/5qgvr1ba/11/

Pour y parvenir, il est essentiel de noter les tendances qui se dégagent lorsque vous dessinez le tableau. Dans le diagramme ci-dessous, le nombre dans le carré est le nombre minimum de déplacements nécessaires pour atteindre ce carré (vous pouvez utiliser la recherche en largeur d'abord pour trouver ceci):

Patterns

Comme la solution est symétrique entre les axes et les diagonales, je n’ai dessiné que le cas x> = 0 et y> = x.

Le bloc en bas à gauche est la position de départ et les nombres dans les blocs représentent le nombre minimum de déplacements pour atteindre ces blocs.

Il y a 3 modèles à noter:

  • Les groupes verticaux incrémentants bleus de 4
  • Les diagonales rouges "primaires" (elles vont de gauche à droite, comme une barre oblique inverse)
  • Les diagonales vertes "secondaires" (même orientation que le rouge)

(Assurez-vous que les deux ensembles de diagonales sont en haut à gauche. Ils ont un nombre de mouvements constant. Les diagonales en haut à droite sont beaucoup plus complexes.)

Vous pouvez dériver des formules pour chacun. Les blocs jaunes sont des cas spéciaux. La solution devient alors:

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

}

le plus difficile étant les groupes verticaux:

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

}

Voir le violon pour les autres cas.

Peut-être y a-t-il des motifs plus simples ou plus élégants que j'ai manqués? Si oui, j'aimerais les voir. En particulier, je remarque des motifs diagonaux dans les cas bleus verticaux, mais je ne les ai pas explorés. Quoi qu'il en soit, cette solution satisfait toujours la contrainte O(1).

40
Graeme Pyle

Problème très intéressant que j'ai rencontré récemment. Après avoir cherché des solutions, j'ai essayé de récupérer la formule analytique (O(1) time and space complexity) donnée le SACO 2007 Jour 1solutions .

Tout d’abord, je veux apprécier Graeme Pyle pour une très belle visualisation qui m’a aidé à corriger la formule.

Pour une raison quelconque (simplification, beauté ou erreur), ils ont déplacé minus dans l’opérateur floor, ce qui leur a donné une mauvaise formule floor(-a) != -floor(a) for any a.

Voici la formule analytique correcte:

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

La formule fonctionne pour toutes les paires (x, y) (après avoir appliqué les axes et la symétrie diagonale) à l'exception des angles (1,0) et (2,2), qui ne sont pas conformes au modèle et codés en dur dans l'extrait suivant:

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>

Remarque: jQuery utilisé uniquement à titre d’illustration. Pour le code, reportez-vous à la fonction distance.

17
simon

Oui, Dijkstra et BFS vous donneront la réponse, mais je pense que le contexte d'échecs de ce problème fournit des connaissances susceptibles de fournir une solution beaucoup plus rapide qu'un algorithme générique du plus court chemin, en particulier sur un jeu d'échecs infini.

Pour simplifier, décrivons l’échiquier comme étant le plan (x, y). Le but est de trouver le chemin le plus court de (x0, y0) à (x1, y1) en utilisant uniquement les étapes candidates (+ -1, + -2), (+ -2, + -1) et (+ -2 , + -2), comme décrit dans le PS de la question.

Voici la nouvelle observation: dessinez un carré avec des coins (x-4, y-4), (x-4, y + 4), (x + 4, y-4), (x + 4, y + 4) . Cet ensemble (appelez-le S4) contient 32 points. Le plus court chemin de l'un de ces 32 points vers (x, y) nécessite exactement deux déplacements .

Le chemin le plus court parmi l'un des 24 points de l'ensemble S3 (défini de la même manière) vers (x, y) nécessite au moins deux déplacements .

Par conséquent, si | x1-x0 |> 4 ou | y1-y0 |> 4, le plus court chemin de (x0, y0) à (x1, y1) est exactement deux déplacements plus grand que le plus court chemin de (x0, y0) à S4. Et ce dernier problème peut être résolu rapidement avec une simple itération.

Soit N = max (| x1-x0 |, | y1-y0 |). Si N> = 4, le chemin le plus court de (x0, y0) à (x1, y1) est composé de ceil (N/2).

17
Steve Tjoa

La réponse O(1) ci-dessus [ https://stackoverflow.com/a/8778592/4288232 de Mustafa Serdar anlı] ne fonctionne pas vraiment. (Cochez (1,1) ou (3,2) ou (4,4), à l'exception des cas évidents de (1,0) ou (2,2)).

Ci-dessous, une solution beaucoup plus laide (python), qui fonctionne (avec des "tests" ajoutés):

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()
12
Arielr

Ce que vous devez faire, c'est penser aux mouvements possibles du chevalier en tant que graphique, où chaque position sur le plateau est un nœud et les mouvements possibles vers une autre position en tant que Edge. L'algorithme de dijkstra n'est pas nécessaire, car chaque Edge a le même poids ou la même distance (ils sont tous aussi faciles ou courts à faire). Vous pouvez simplement effectuer une recherche BFS à partir de votre point de départ jusqu'à la position finale.

9
Bishnu

Solution des premiers principes en Python

J'ai d'abord rencontré ce problème dans un test de codilité. Ils m'ont donné 30 minutes pour le résoudre - il m'a fallu beaucoup plus de temps pour arriver à ce résultat! Le problème était le suivant: combien de mouvements faut-il pour qu'un chevalier passe de 0,0 à x, y en utilisant uniquement les mouvements légaux de Knight. x et y étaient plus ou moins illimités (nous ne parlons donc pas d'un simple échiquier 8x8).

Ils voulaient une solution O(1). Je voulais une solution où le programme résolvait clairement le problème (c.-à-d. Je voulais quelque chose de plus évident que le modèle de Graeme - les modèles ont l'habitude de se décomposer vous ne cherchez pas) et je voulais vraiment ne pas avoir à me fier à une formule non argumentée, comme dans la solution de Mustafa

Alors, voici ma solution, pour ce que cela vaut. Commencez, comme d’autres, en remarquant que la solution est symétrique par rapport aux axes et aux diagonales; nous devons donc résoudre uniquement pour 0> = y> = x. Pour simplifier les explications (et le code), je vais inverser le problème: le chevalier commence à x, y et vise 0,0.

Supposons que nous réduisions le problème au voisinage de l'Origine. Nous arriverons à ce que "vicinty" signifie en temps voulu, mais pour le moment, écrivons quelques solutions dans un aide-mémoire (Origine en bas à gauche):

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

Donc, étant donné x, y sur la grille, nous pouvons simplement lire le nombre de déplacements vers l’Origine.

Si nous avons commencé en dehors de la grille, nous devons y revenir. Nous introduisons la 'ligne médiane', qui est la ligne représentée par y = x/2. Tout chevalier à x, y sur cette ligne peut revenir à la feuille de triche en utilisant une série de mouvements de 8 heures (c'est-à-dire: (-2, -1) mouvements). Si x, y se situe au-dessus de la ligne médiane, nous aurons besoin d'une succession de mouvements de 8 heures et de 7 heures et s'il est situé en dessous de la ligne médiane, nous aurons besoin d'une succession de 8 heures et de 10 heures. 'horloge se déplace. Deux choses à noter ici:

  • Ces séquences sont manifestement les plus courts chemins. (Voulez-vous que je le prouve, ou est-ce évident?)
  • Nous ne nous soucions que du nombre de ces déménagements. Nous pouvons combiner les mouvements dans n'importe quel ordre.

Alors, regardons les mouvements au-dessus de la ligne médiane. Ce que nous prétendons, c'est que:

  • (dx; dy) = (2,1; 1,2) (n8; n7) (notation matricielle, sans composition mathématique - le vecteur colonne (dx; dy) est égal à la matrice carrée multipliée par le vecteur colonne (n8; n7) - le nombre de mouvements de 8 heures et le nombre de mouvements de 7 heures), et similaire;

  • (dx; dy) = (2,2; 1, -1) (n8; n10)

Je prétends que dx, dy sera approximativement (x, y), donc (x-dx, y-dy) sera au voisinage de l'Origine (quel que soit le "voisinage" qui s'avère être).

Les deux lignes du code qui calculent ces termes sont la solution, mais elles ont été sélectionnées pour avoir des propriétés utiles:

  • La formule au-dessus de la ligne médiane se déplace de (x, y) vers l'un des nombres (0,0), (1,1) ou (2,2).
  • La formule située au-dessous de la ligne médiane se déplace de (x, y) vers l'un des nombres (0,0), (1,0), (2,0) ou (1,1).

(Voulez-vous des preuves de cela?) Donc, la distance du chevalier sera la somme de n7, n8, n10 et de feuille de triche [x-dx, y-dy], et notre feuille de triche se réduit à ceci:

. . 4
. 2 .
0 3 2

Maintenant, ce n'est pas tout à fait la fin de l'histoire. Regardez les 3 sur la rangée du bas. Les seuls moyens d'y parvenir sont soit:

  • Nous avons commencé là-bas, ou
  • Nous nous sommes déplacés à cet endroit par une séquence de mouvements de 8 heures à 10 heures. Mais si le dernier mouvement était à 8 heures (ce qu’il a le droit d’être, puisque nous pouvons effectuer nos mouvements dans n’importe quel ordre), nous devons avoir franchi (3,1), dont la distance est en réalité de 2 (comme vous pouvez le faire). voir de la feuille de triche originale). Donc, ce que nous devrions faire, c'est de revenir en arrière d'un mouvement de 8 heures, en économisant deux mouvements au total.

Il y a une optimisation similaire à faire avec le 4 en haut à droite. Hormis le point de départ, le seul moyen de le faire est de partir (4,3) à 8 heures. Ce n'est pas sur la feuille de triche, mais s'il y était, sa distance serait de 3, car nous pourrions avoir 7 heures (3,1) au lieu de cela, ce qui a une distance de seulement 2. Donc, nous devrions revenir en arrière d'un point Déplacez-vous à 8 heures, puis avancez de 7 heures.

Nous devons donc ajouter un numéro supplémentaire à la feuille de recherche:

. . 4
. 2 . 2
0 3 2

(Remarque: il y a toute une série d'optimisations de suivi en arrière de (0,1) et (0,2), mais comme le solveur ne nous y mènera jamais, nous n'avons pas à nous en soucier.)

Donc, voici, voici quelques Python code pour évaluer ceci:

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]

Incidemment, si vous voulez connaître un itinéraire réel, alors cet algorithme le fournit également: il s’agit simplement d’une succession de mouvements n7 à 7 heures, suivis de (ou entrecoupés) de n8 mouvements à 8 heures, n10 10- les heures changent et quelle que soit la danse dictée par la feuille de triche (qui, elle-même, peut être une feuille de triche).

Maintenant: Comment prouver que c'est vrai. Il ne suffit pas de comparer ces résultats à un tableau de bonnes réponses, car le problème lui-même est sans bornes. Mais on peut dire que, si la distance d'un carré s par le Chevalier est d, alors si {m} est l'ensemble des mouvements légaux de s, la distance du chevalier de (s + m) doit être soit d-1, soit d + 1. pour tous m. (En avez-vous besoin d'une preuve?) De plus, il doit y avoir au moins un carré dont la distance est d-1, à moins que s ne soit l'origine. Nous pouvons donc prouver l'exactitude en montrant que cette propriété est valable pour chaque carré. Ainsi:

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

Alternativement, nous pouvons prouver l’exactitude de tout carré s en suivant l’itinéraire de s en descendant jusqu’à l’Origine. Commencez par vérifier le caractère raisonnable de s comme ci-dessus, puis sélectionnez un s + m tel que la distance (s + m) == d-1. Répétez jusqu'à atteindre l'origine.

Howzat?

6
Jules May
/*
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.

Je n'ai pas encore étudié les graphes. En raison du problème de son implémentation au travers de tableaux, je ne pouvais pas en tirer de solution. J'ai traité les positions non pas comme des rangs et des fichiers (la notation habituelle des échecs), mais comme des index de tableau. Pour votre information, ceci est pour un échiquier 8 * 8 seulement. Tout conseil d'amélioration est toujours le bienvenu.

* Les commentaires devraient suffire à votre compréhension de la logique. Cependant, vous pouvez toujours demander.

* Vérifié sur le compilateur DEV-C++ 4.9.9.2 (logiciel Bloodshed).

2
jigsawmnc

Je pense que cela pourrait aussi vous aider ..

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

et en utilisant la programmation dynamique pour obtenir la solution.

P.S: Il utilise un peu le BFS sans avoir à prendre la peine de déclarer les nœuds et les arêtes du graphe.

2
Abhishek
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));
    }
}
1
Rahul Kurup

Voici une solution à ce problème particulier implémenté dans Perl. Il montrera l’un des chemins les plus courts - il peut y en avoir plus d’un dans certains cas.

Je n'ai utilisé aucun des algorithmes décrits ci-dessus - mais il serait bien de le comparer à d'autres solutions.

#!/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
user3150039

voici la PHP version de la fonction 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
Mircea Soaica

Voici une version C basée sur le code Mustafa Serdar Şanlı qui fonctionne pour un tableau de bord:

#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));
    }
}

Testez-le ici avec preuve contre une solution récursive

0
Johan du Toit

Voici mon programme. Ce n'est pas une solution parfaite. Il y a beaucoup de changements à faire dans la fonction de récursivité. Mais ce résultat final est parfait. J'ai essayé d'optimiser un peu.

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;
    }

}
0
Arun

Juste Ruby code de la réponse de Graeme Pyle ci-dessus , rayé tout le code supplémentaire et converti le reste en Ruby juste pour obtenir une solution par son L'algorithme semble fonctionner.

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)

La seule intention est de gagner du temps en convertissant le code si quelqu'un a besoin du code complet.

0