Probleme du cavalier [backtracking]

Description

résolution du problème du cavalier :
trouver une parcours du cavalier sur un jeu d'échec dans lequel il passe
par toutes les cases une et une seule fois.

L'algorithme consite à énumerer toutes les solutions, enfin presque,
on ne visite pas des branches entieres de solutions quand on
sait a l'avance qu'il n'y aura aucune solution.
Des que l'on sait que l'on va aboutir sur une impasse, on retourne
en arrière, on rebrousse chemin, et on passe a une autre solution,
c'est le principe du "backtracking"

L'algorithme peut etre améliorer avec quelques optimisations,
mais je préfére donner l'algorithme brute, directment lié
au principe du "backtracking".

Source / Exemple :


//-------------------------------------------------
// INCLUDES
//-------------------------------------------------
#ifndef _UTIL_H_
	#include "util.h"
#endif // _UTIL_H_

#ifndef _MATH_H_
	#include "math.h"
#endif // _MATH_H_

#ifndef _DATA_H_
	#include "data.h"
#endif // _DATA_H_

#ifndef _WINUTIL_H_
	#include "winutil.h"
#endif // _WINUTIL_H_

//-------------------------------------------------
#define SIZE_CHESSBOARD 8

//-------------------------------------------------
// DATA_WINDOW
//-------------------------------------------------
typedef struct tagDATA_WINDOW
  {
  // handle sur la fenetre
  HWND              hwnd;
  // est-ce-que l'algorithme est fini ou non ? (vraiment utile pour un echiquier de taille < 8*8 ...)
  BOOL              bEndOfAlgo;
  // le damier, ou l'echiquier, une simple matrice d'entier
  //  0 -> case vide
  //  i -> le cheval etait present au i-eme coup
  U32               chessboard[SIZE_CHESSBOARD][SIZE_CHESSBOARD];
  //  quelques informations
  int               nbSol,nbTest;
  // pile des etapes ("STEP")
  STACK             stack;
  }DATA_WINDOW,*P_DATA_WINDOW,**PP_DATA_WINDOW;

//-------------------------------------------------
// permet de representer un couple d'entier
// (une coordonnee dans l'echiquier par exemple)
// qui pourront etre en fait dans une liste de
// couple
typedef struct tagPOSITION
  {
  LINK_DBL_LIST   link;   // le lien de la liste
  int             i,j;    // les coordonnees
  }POSITION,*P_POSITION,**PP_POSITION;

//-------------------------------------------------
// represente une etape
// le programme possede couramment une pile
// d'etape, une etape represente la position
// du cavalier a la <iStep>-eme etape sur la
// case de coordonnee (i,j)
// on construit a chaque fois la liste des cases
// que le cavalier pourra explorer a partir
// de ca position-ci et bien-sur en fonction
// de la configuration de l'echiquier courant
// (s'il est deja passe par un case par exemple...)
typedef struct tagSTEP
  {
  DBL_LIST    lstPos;   // liste des cases a visiter
  int         iStep;    // numero de l'etape
  int         i,j;      // coordonnees
  LINK_STACK  lnkStack; // lien de la pile
  }STEP,*P_STEP,**PP_STEP;

//-------------------------------------------------
// fonction tres stupide, mais tres utile !
// elle permet de rajouter un case a la liste des
// cases q'un cavalier peut aller (dans un etape)
// pour cela on lui fournit l'echiquier, la liste
// et les coordonnees (i,j)
// la valeur ajoutee de cette fonction, c'est que
// c'est elle qui verifie que (i,j) soit bien dans
// l'echiquier (par exemple on pourrait sortir
// du plateau) et que la case est bien vide
void addPos(U32 chessboard[SIZE_CHESSBOARD][SIZE_CHESSBOARD],P_DBL_LIST dblList,int i,int j)
{
if(
    // teste de validite de la plage des (i,j)
    i >= 0 && i < SIZE_CHESSBOARD && j >=0 && j< SIZE_CHESSBOARD
      &&
    // teste que la case est bien vide
    0 == chessboard[i][j]
   )
  {
  // tout est OK, on ajoute :
  P_POSITION pos;
  pos = Malloc(POSITION,1); // creation de la structure par allocation memoire
  pos->i = i; // initialisation des champs
  pos->j = j;
  InsertFirstLinkDblList(dblList,&pos->link); // on insert dans la liste ce nouvel element
  }
} // addPos()

//-------------------------------------------------
// la creation d'une etape consiste
// a placer le cavalier a la position
// et a construire la liste des cases accessibles
P_STEP createStep(P_DATA_WINDOW data,int i,int j,int iStep)
{
P_STEP step;

// initialisation
step                    = Malloc(STEP,1); // creation de l'etape par allocation memoire
step->iStep             = iStep;  // le numero de l'etape
step->i                 = i;  // coordonnees du cavalier
step->j                 = j;
PushLinkStack(&data->stack,&step->lnkStack); // ajout de l'etape dans la pile des etapes
data->chessboard[i][j]  = iStep; // placement du cavalier sur l'echiquier

InitDblList(&step->lstPos); // initialisation de la liste des positions possibles
// il y a 8 positions accessibles au maximum par le cavalier
// le cavalier peut se deplacer en L :
//  .......
//  ..8.1..
//  .7...2.
//  ...C...
//  .6...3.
//  ..5.4..
//  .......
addPos(data->chessboard,&step->lstPos,i + 1,j + 2); // #1
addPos(data->chessboard,&step->lstPos,i + 2,j + 1); // #2
addPos(data->chessboard,&step->lstPos,i + 2,j - 1); // #3
addPos(data->chessboard,&step->lstPos,i + 1,j - 2); // #4
addPos(data->chessboard,&step->lstPos,i - 1,j - 2); // #5
addPos(data->chessboard,&step->lstPos,i - 2,j - 1); // #6
addPos(data->chessboard,&step->lstPos,i - 2,j + 1); // #7
addPos(data->chessboard,&step->lstPos,i - 1,j + 2); // #8
return step;
} // createStep()

//-------------------------------------------------
// execution d'une etape de l'algorithme
// le principe est simple :
// on prend l'etape courante
// si la liste des cases possibles est vide
// alors on remonte, i.e. on a deja visite
// toutes les possibilites avec ce cavalier sur
// cette position, donc on passe a une autre position
// de ce cavalier.
// sinon la liste n'est pas vide, alors on prend
// la premiere case de la liste, et on deplace le
// cavalier sur cette case, et on appelle recursivement
// l'algorithme sur cette nouvelle case
void algo(P_DATA_WINDOW data)
{
data->bEndOfAlgo = (0 == data->stack.nElem);

if(!data->bEndOfAlgo)
  {
  P_LINK_STACK  curLinkStack;
  P_STEP        curStep;

  data->nbTest  ++; // un passage de plus !

  // on recupere l'etape courante
  curLinkStack  = FirstLinkStack(&data->stack);
  curStep       = FROM_TO(STEP,lnkStack,curLinkStack);
  if(curStep->lstPos.nElem > 0) // on teste la position suivante
    {
    P_LINK_DBL_LIST curLinkDblList;
    P_POSITION      curPos;

    // on recupere le premiere element de la liste
    curLinkDblList  = FirstLinkDblList(&curStep->lstPos);
    curPos          = FROM_TO(POSITION,link,curLinkDblList);
    // on appelle recursivement l'algorithme, i.e.
    // on ajoute un etape sur cette position, en
    // incrementant le numero de l'etape
    createStep(data,curPos->i,curPos->j,curStep->iStep + 1);

    // ici on teste si on trouve (enfin) un solution
    // le teste est simple, si le numero de l'etape
    // est exactement le nombre de case de l'echiquier alors ...
    if(1 + curStep->iStep == SIZE_CHESSBOARD*SIZE_CHESSBOARD)
      {
      P_FILE_UTIL   file;
      char          name[256];
      int           i,j;
      
      data->nbSol ++; // un solution de plus !!! BINGO !!!
      _snprintf(name,COUNT(name),"res\\solution[%d][%d].txt",SIZE_CHESSBOARD,data->nbSol);
      file = FileOpen(name,"wt"); // on ouvre le fichier
      // on ecrit la solution dans le fichier
      for(i=0;i<SIZE_CHESSBOARD;i++)
        {
        for(j=0;j<SIZE_CHESSBOARD;j++)
          {
          FilePrint((file,"  %2d",data->chessboard[i][j]));
          }
        FilePrint((file,"\n")); // saut de ligne
        }
      FileClose(file); // fermeture du fichier
      }

    // on enleve la case de la liste, car on vient de la traiter
    RemoveLinkDblList(&curStep->lstPos,curLinkDblList);
    Free(curPos); // liberation de l'allocation memoire
    }
  else // on remonte car la liste est vide
    {
    CloseDblList(&curStep->lstPos); // on ferme la liste (vide)
    PopLinkStack(&data->stack); // on enleve l'etape
    data->chessboard[curStep->i][curStep->j] = 0; // on efface la position du cavalier
    Free(curStep); // on libere l'allocation memoire
    }
  }
} // algo()

//-------------------------------------------------
// WND_PROC
//-------------------------------------------------
int WndProc(P_MY_WINDOW myWindow,HWND hwnd,UINT iMsg,WPARAM wParam,LPARAM lParam)
{
P_DATA_WINDOW   data;

data = (P_DATA_WINDOW)myWindow->userData;

switch(iMsg)
  {
  // creation de la fenetre
	case WM_CREATE:
    {

    // parametres de la fenetre
    myWindow->bResizedWindow   = TRUE;
    myWindow->bUseBackBuffer   = FALSE;
    myWindow->colorBackGround  = RGB_WHITE;

    // initialisations de la fenetre
    data->hwnd        = hwnd;
    data->bEndOfAlgo  = FALSE;
    data->nbSol       = 0;
    data->nbTest      = 0;

    // initialisation de l'algo.
      {
      int i,j;
      for(i=0;i<SIZE_CHESSBOARD;i++)
        {
        for(j=0;j<SIZE_CHESSBOARD;j++)
          {
          // on vide l'echiquier
          data->chessboard[i][j] = 0;
          }
        }
      }
    InitStack(&data->stack);
    createStep(data,0,0,1); // on mets le cheval dans un coin (par exemple)

    // initialisation du timer
    SetTimer(hwnd,314,250,NULL);

    break;
    }
  case WM_TIMER:
    {
    switch(wParam)
      {
      case 314:
        {
        DWORD t0;
        t0 = GetTickCount();
        while(GetTickCount() < 250 + t0) // on travaille pendant 1/4 de secondes
          {
          algo(data);
          }
        myRepaintWindow(hwnd); // on redessine la fenetre
        break;
        }
      }
    break;
    }
  // les touches
  case WM_KEYDOWN:
    {
    switch((int)wParam)
      {
/*
      // une etape, manuellement
      case 'A':
        algo(data);
        myRepaintWindow(hwnd);
        break;

  • /
default: break; } break; } // le commandes case WM_COMMAND: { switch(LOWORD(wParam)) { } break; } // on dessine case WM_PAINT: { P_MY_HDC hdc; int cx,cy; cx = myWindow->cx; cy = myWindow->cy; hdc = (P_MY_HDC)wParam; SetBkMode(GetHdc(hdc),TRANSPARENT); if(data->bEndOfAlgo) { SetTextColor(GetHdc(hdc),RGB_RED); myPushFont(hdc,10,20,800,"Courier New"); myTextOut(hdc,cx/2,0.5,2*cy/5,0.5,"Fin de l'algorithme"); myTextOut(hdc,cx/2,0.5,3*cy/5,0.5,"%d solutions trouvées",data->nbSol); myPopFont(hdc); } //else { int cxFont,cyFont,dx,dy; int k,i,j; dx = 50; dy = 50; cxFont = 15; cyFont = 30; myPushFont(hdc,cxFont,cyFont,800,"Courier New"); // la grille myPushPen(hdc,PS_SOLID,0,RGB_LIGHTGRAY); for(k=0;k<=SIZE_CHESSBOARD;k++) { myLine(hdc,k*dx,0,k*dx,SIZE_CHESSBOARD*dy,FALSE); myLine(hdc,0,k*dy,SIZE_CHESSBOARD*dx,k*dy,FALSE); } myPopPen(hdc); // les numéros SetTextColor(GetHdc(hdc),RGB_BLACK); for(i=0;i<SIZE_CHESSBOARD;i++) { for(j=0;j<SIZE_CHESSBOARD;j++) { U32 nb; nb = data->chessboard[i][j]; if(0 != nb) { myTextOut(hdc,(2*i+1)*dx/2,0.5,(2*j+1)*dy/2,0.5,"%d",nb); } } } // les informations SetTextColor(GetHdc(hdc),RGB_BLACK); myTextOut(hdc,0,0.,cy-2*cyFont,1.,"nombre d'essais : %d" ,data->nbTest); myTextOut(hdc,0,0.,cy-1*cyFont,1.,"profondeur : %d" ,data->stack.nElem); myTextOut(hdc,0,0.,cy-0*cyFont,1.,"%d solutions trouvées" ,data->nbSol); myPopFont(hdc); } break; } // destruction de la fenetre case WM_DESTROY: { // on vide la pile while(data->stack.nElem > 0) { P_LINK_STACK linkStack; P_STEP step; linkStack = PopLinkStack(&data->stack); // on enleve l'element de la liste step = FROM_TO(STEP,lnkStack,linkStack); // on recupere <STEP> a partir de <LINK_STACK> // on vide la liste de chaque etape de la pile while(step->lstPos.nElem > 0) { P_LINK_DBL_LIST linkDblList; P_POSITION pos; linkDblList = FirstLinkDblList(&step->lstPos); // on recupere un element de la liste (le premier) RemoveLinkDblList(&step->lstPos,linkDblList); // on supprime l'element de la liste pos = FROM_TO(POSITION,link,linkDblList); // on recupere <POSITION> a partir de <LINK_DBL_LIST> Free(pos); // on libere l'allocation memoire } CloseDblList(&step->lstPos); // on ferme la liste Free(step); // on libere l'allocation memoire } CloseStack(&data->stack); // on ferme la pile PostQuitMessage(0); // see you later ... break; } } return 0; } // WndProc() //------------------------------------------------- // WIN_MAIN //------------------------------------------------- int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int iCmdShow ) { WNDCLASSEX wc; RECT rc; P_MY_WINDOW myWindow; HWND hwnd; P_DATA_WINDOW data; MSG msg; // ouverture des librairies InitLibUtil(); InitLibWinutil(hInstance); // initialisation de la classe mySetDefaultWindowClass(&wc,hInstance); wc.lpszClassName = "JCD_PbCavalier"; mySetRectWH(&rc,0,0,500,500); // creation de la fenetre myWindow = myCreateWindow(&wc, " Problème du cavalier", WS_OVERLAPPEDWINDOW, &rc, TRUE, TRUE, NULL, NULL, sizeof(DATA_WINDOW), (void*)lpszCmdLine, WndProc ); data = (P_DATA_WINDOW)myWindow->userData; hwnd = myWindow->hwnd; ShowWindow(hwnd,iCmdShow); UpdateWindow(hwnd); // boucle de messages while(GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); } // fermeture des librairies CloseLibUtil(); CloseLibWinutil(); // tout va bien ! // see you later ... return 0 ; } // WinMain()

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.