web-dev-qa-db-fra.com

Sudoku résolveur en Java, utilisant le retour arrière et la récursivité

Je suis en train de programmer un solveur de Sudoku en Java pour une grille 9x9.

J'ai des méthodes pour:

  • imprimer la grille

  • initialiser le tableau avec des valeurs données

  • tester les conflits (si le même numéro est dans la même ligne ou dans la même sous-grille 3x3)

  • une méthode pour placer les chiffres, un par un, ce qui nécessite le plus de travail.

Avant d’entrer dans les détails avec cette méthode, gardez à l’esprit que je dois utiliser la récursion pour la résoudre, ainsi que les retours en arrière (regardez l’applet ici à titre d’exemple http://www.heimetli.ch/ffh/simplifiedsudoku. html )

De plus, je résous ce Sudoku en me déplaçant verticalement en commençant par le haut à gauche, en passant par la première colonne, puis par la deuxième colonne, etc.

Jusqu'à présent, j'ai les éléments suivants:

public boolean placeNumber(int column){

    if (column == SUDOKU_SIZE){  // we have went through all the columns, game is over

        return true;

    }

    else
    {
        int row=0;  //takes you to the top of the row each time

        while (row < SUDOKU_SIZE)    loops through the column downwards, one by one
        {

            if (puzzle[row][column]==0){  //skips any entries already in there (the given values)

                puzzle[row][column]=1;   //starts with one

                while(conflictsTest(row,column)){   //conflictsTest is the method I wrote, which checks if the given parameters are in conflict with another number

                    puzzle[row][column] += 1;  

                }


           //BACK TRACKING 

                placeNumber(column);      //recursive call

            }
            else{
              row++;                  // row already has a number given, so skip it
            }
        }

        column++;              // move on to second column
        placeNumber(column);

    }
    return false; // no solutions to this puzzle
}

Je pense que le reste de mon code doit aller à BACKTRACKING.

J'ai pensé à quelque chose comme:

  • si la valeur est 10, remettez cette valeur à zéro, remontez une ligne et incrémentez cette valeur de 1

Cette "stratégie" de retour en arrière ne fonctionne pas exactement pour plusieurs raisons:

  1. et si la rangée précédente était une valeur donnée (autrement dit, je ne suis pas censé l'incrémenter ni la toucher, mais plutôt revenir à la dernière valeur que j'ai placée là-bas)

  2. et si la valeur précédente était 9. Et si j'incrémentais cela de 1, nous sommes maintenant à 10, ce qui ne fonctionnera pas.

Quelqu'un peut-il m'aider s'il vous plaît?

24
Carleton U

Je ne sais pas comment vous allez résoudre le sudoku, mais même si vous utilisez la méthode de la force brute (et cela me semble ce que vous décrivez), vous devriez considérer que votre structure de données n'est pas appropriée.

Je veux dire par là que chaque cellule ne devrait pas être simplement un nombre, mais un ensemble de nombres (qui peuvent éventuellement être placés là).

Par conséquent, les nombres donnés seront représentés par des ensembles de singleton, alors que les nombres vides peuvent être initialisés avec {1,2,3,4,5,6,7,8,9}. Et ensuite, l'objectif est de réduire les cellules non singleton jusqu'à ce que toutes les cellules soient singletons.

(Notez que, tout en résolvant un sudoku avec un crayon et du papier, on écrit souvent de petits nombres dans les cellules vierges pour garder une trace des nombres possibles là-bas, dans la mesure où on les a résolus.)

Et ensuite, lorsque vous "essayez le numéro suivant", vous prenez le numéro suivant de la série. Les cellules données n'ont pas de numéro suivant, vous ne pouvez donc pas les changer. De cette façon, les difficultés que vous décrivez disparaissent (un peu, au moins).

------ EDIT, APRÈS AVOIR APPRIS QUE CETTE FORCE BRUTE IS NÉCESSAIRE.

Votre professeur veut évidemment vous apprendre les merveilles de la récursion. Très bien!

Dans ce cas, nous devons simplement savoir quelles cellules sont données et quelles cellules ne le sont pas.

Un moyen simple et pratique qui pourrait être utilisé ici est de placer un 0 dans n'importe quelle cellule non donnée, car les cellules données sont par définition l'une des cellules 1,2,3,4,5,6,7,8,9.

Voyons maintenant comment faire fonctionner la force brute récursive.

Nous avons pour objectif de résoudre un sudoku avec n cellules vides. Si nous avions une fonction qui résoudrait un sudoku avec n-1 cellules vides (ou signalerait que ce n'est pas soluble), alors cette tâche serait facile:

let c be some empty cell.
let f be the function that solves a sudoku with one empty cell less.
for i in 1..9
   check if i can be placed in c without conflict
   if not continue with next i
   place i in c
   if f() == SOLVED then return SOLVED
return NOTSOLVABLE

Ce pseudo-code sélectionne une cellule vide, puis essaie tous les nombres qui y correspondent. Parce qu'un sudoku n'a - par définition - qu'une seule solution, il n'y a que les cas suivants:

  • nous avons choisi le bon numéro. Ensuite, f() trouvera le reste de la solution et retournera SOLVED.
  • nous avons choisi un mauvais numéro: f() signalera que le sudoku ne peut pas être résolu avec ce mauvais numéro dans notre cellule.
  • nous avons vérifié tous les numéros, mais personne n’était correct: nous avons alors un sudoku insoluble et nous le signalons à notre interlocuteur.

Inutile de dire que l'algorithme repose sur l'hypothèse que nous ne plaçons que des nombres qui ne sont pas en conflit avec l'état actuel. Par exemple, nous ne plaçons pas 9 dans la même rangée, colonne ou case, il existe déjà un 9.

Si nous réfléchissons maintenant à la fonction mystérieuse et pourtant inconnue de notre fonction f(), il s'avère que ce sera presque la même chose que ce que nous avons déjà!
Le seul cas que nous n’ayons pas encore pris en compte est un sudoku avec 0 cellule vide. Cela signifie que, si nous constatons qu'il n'y a plus de cellules vides, nous savons que nous venons de résoudre le sudoku et que nous rentrons simplement comme RÉSOLU.

C'est l'astuce courante lors de l'écriture d'une fonction récursive supposée résoudre un problème. Nous, nous écrivons resolvons (), et nous savons que le problème peut être résolu. Par conséquent, nous pouvons déjà utiliser la fonction que nous sommes en train d'écrire tant que nous nous assurons qu'à chaque récursion, le problème se rapproche d'une manière ou d'une autre de la solution. À la fin, nous atteignons le cas de base, où nous pouvons donner la solution sans autre récursivité.

Dans notre cas, nous savons que le Sudoku est soluble, nous savons en outre qu’il a exactement un solution. En plaçant un morceau dans une cellule vide, nous nous rapprochons de la solution (ou du diagnostic qu'il n'y en a pas) et donnons récursivement le nouveau problème, plus petit, à la fonction que nous sommes en train d'écrire. Le cas de base est le "Sudoku avec 0 cellules vides" qui en fait est la solution .

(Les choses deviennent un peu plus compliquées s'il y a beaucoup de solutions possibles, mais nous en resterons à la leçon suivante.)

7
Ingo

Tout d'abord , une suggestion d'optimisation: lorsque vous vérifiez si le nombre que vous allez mettre dans une cellule est déjà présent dans la même ligne, la même colonne ou la même mini-puce, vous n'avez pas à exécuter de boucle ou autre. comme ça. Vous pouvez effectuer une vérification instantanée en indexant un tableau.

Considérez 3 tableaux à deux dimensions booléens 9x9:

boolean row[9][9], col[9][9], minigrid[9][9]

Nous utiliserons le premier tableau pour vérifier si un nombre est présent dans la même ligne, le deuxième tableau pour vérifier si un nombre est présent dans la même colonne et le troisième pour la mini-grille.

Supposons que vous vouliez mettre un nombre n dans votre cellulaire i , j . Vous allez vérifier si row [i] [n-1] est vrai. Si oui, alors i th la ligne contient déjà n. De même, vous vérifierez si col [j] [n-1] et minigrid [réseau_num] [n-1] est vrai.

Ici gridnum est l’indice de la mini-grille, où se trouve la cellule dans laquelle vous voulez insérer un nombre. Pour calculer le numéro de mini-grille de la cellule i, j , divisez i & j par 3, multipliez le partie intégrante de ancien avec 3, et l'ajouter à la partie intégrante de ce dernier.

Voici à quoi ça ressemble:

gridnum = (i/3)*3 + j/3

En regardant les valeurs de i/3 et j/3 pour tous les i et j, vous aurez une idée de la façon dont cela fonctionne. De même, si vous mettez un nombre dans une cellule, mettez également à jour les tableaux. Par exemple. ligne [i] [n-1] = true

S'il y a une partie que vous ne comprenez pas, postez un commentaire et je modifierai ma réponse pour l'expliquer.

Deuxièmement , utiliser la récursivité et le retour arrière pour résoudre ceci est assez facile.

boolean F( i, j) // invoke this function with i = j = 0
{
If i > 8: return true // solved

for n in 1..9
 {
 check if n exists in row, column, or mini grid by the method I described

 if it does: pass ( skip this iteration )

 if it doesn't
  {
   grid[i][j] = n
   update row[][], col[][], minigrid[][]

   if F( if j is 8 then i+1 else i, if j is 8 then 0 else j+1 ) return true // solved
  }
 }
 return false // no number could be entered in cell i,j
}
4
Rushil Paul

je suggère de passer à la fois la ligne et la colonne en cours à la méthode récursive, puis de rechercher tous les nombres autorisés pour CET cellule. Pour chaque nombre autorisé, appelez la méthode de manière récursive pour la colonne suivante (ou la ligne suivante si elle se trouve dans la dernière colonne) et annulez le déplacement s'il est en avance. à une piste morte

public boolean fillCell(int r, int c) {
    if last row and last cell {
        //report success
        return true
    }
    for i 1 to 9 {
        if can place i on r, c {
            board[r][c] = i
            if !fillCell(next empty row, next empty column) { //DONT change r or c here or you will not be able to undo the move
                board[r][c] = 0
            }
            /*
            else {
                return true; //returning true here will make it stop after 1 solution is found, doing nothing will keep looking for other solutions also
            }
            */

        }
    }
    return false        
}
1
Vincent Vanmarsenille

Je voudrais vérifier chaque cellule et revenir en arrière une étape de récursivité si aucune solution ne peut être trouvée.

Plus en détail: Aller à la cellule suivante, si la valeur x == 0, vérifier si x + 1 serait valide, si vrai, aller à la cellule suivante en appelant la méthode de manière récursive avec la cellule suivante possible. Si le numéro n'est pas valide, vérifiez x + 2, etc., si aucun numéro n'est valide, renvoyez la valeur false et répétez l'étape x + 1 lors de l'appel précédent. Si vous frappez une cellule avec un numéro à l'intérieur, n'appelez pas la récursivité mais passez directement à la suivante, vous n'avez donc pas besoin de marquer les cellules déjà entrées.

Pseudo code:

fillcell cell
 while cell is not 0
  cell = next cell
 while cell value < 10
  increase cell value by one
  if cell is valid 
    if fillcell next cell is true
      return true
return false

Pas sûr que cela soit correct mais cela devrait montrer l'idée.

0
morja

quelques idées qui pourraient être utiles (concernant la récursivité et les retours en arrière)

//some attributes you might need for storing e.g. the configuration to track back to.

boolean legal(Configuration configuration) {

}

int partSolution(Configuration configuration) {
  if (legal(configuration))
    return partSolution(nextConfiguration())
  else
    return partSolution(previousConfiguration())    
}

Configuration nextConfiguration() {
 //based on the current configuration and the previous tried ones,
 //return the next possible configuration:
 //next number to enter, next cell to visit
}

Configuration previousConfiguration() {
 //backtrack
}

solve () {
  call partSolution with start configuration while partSolution < 9x9
}

écrire une classe de configuration qui contient la grille avec les nombres entrés et d'autres attributs comme la taille et le nombre entrés et réfléchir à ce qui est nécessaire

0
niklas