Université Paris Diderot - L2 informatique Année 2015–2016 TD d’Éléments d’Algorithmique n◦ 7 Correction sudoku Une grille de sudoku est un tableau à deux dimensions de taille 9 × 9 – on parle aussi de matrice carrée 9 × 9 –, composé de 9 sous-grilles carrées 3 × 3. Une instance de sudoku est un remplissage partiel de la grille par des entiers entre 1 et 9. Une solution d’une instance de sudoku est une grille complétement remplie par des entiers entre 1 et 9, telle que : • chaque ligne, colonne et sous grille de la solution est sans répétition, et contient donc une et une seule fois chaque entier de 1 à 9. • les cases initialement remplies de l’instance ne changent pas de valeur dans la solution. On peut voir une instance de sudoku comme une étape dans la résolution d’une grille. Voici une instance de sudoku et une de ses solutions. 3 1 8 5 7 3 2 4 7 1 6 8 5 9 3 2 5 4 7 1 9 5 4 1 3 6 8 2 5 9 1 7 4 5 9 1 3 4 7 6 2 8 4 1 9 5 8 6 2 3 7 6 7 2 9 3 4 8 1 5 8 3 5 7 2 1 4 6 9 7 2 3 4 1 5 9 8 6 1 8 4 6 9 3 7 5 2 9 5 6 8 7 2 3 4 1 Il existe environ 6, 67 × 1021 solutions distinctes de l’instance vide (la grille sans cases remplies). Plusieurs algorithmes peuvent être tentés, voici ceux proposés par vos camades : • Algorithme naïf : générer toutes les grilles possibles de sudoku. Pour chacune d’entre elle, vérifier qu’elle est valide puis qu’elle est compatible avec la grille pré-remplie. • S’occuper d’abord des sous-grilles. Pour chacune des 9 sous grilles, générer toutes les sous-grilles 3 × 3 (remplies avec des nombre de 1 à 9) possibles et garder que celle qui sont compatibles avec la grille pré-remplie. Puis dans un second temps, essayer toutes les façons d’imbriquer ces 9 sous grilles et garder que celle qui sont valides. • Algorithme «naturel» : parcourir les cases une par une et vérifier pour chacune d’entre elle si on a le choix du nombre à mettre (si par exemple la colonne de la grille est déjà remplie avec 1,4,5, la ligne avec 2,6,9 et le sous carré avec 7,8,9, alors on sait que la case doit être remplie avec un 3). Si c’est le cas la remplir et continuer jusqu’à ce que le sudoku soit rempli. Une telle case n’existant généralement pas, on pourra se contenter de case ayant peu de choix (2 ou 3 possibilités). Voici la correction de l’algorithme naïf. Une grille est un tableau de tableaux (ou une matrice) de taille 9 × 9. 1 1. Algorithme Verif : il attend une grille de sudoku remplie et vérifie si elle est valide, c’est à dire si chaque ligne, colonne ou sous grille de la solution est sans répétition, et contient donc une et une seule fois chaque entier de 1 à 9. Verif(Grille G) booléen b = true Pour i allant de 0 à 8 faire : Pour j allant de 0 à 8 faire : pour k allant de 0 à 8 faire : Si k != j ET G[i][j] == G[i][k] Alors b = false Fin Si Si k!=j ET G[j][i] == G[k][i] alors b = false Fin Si Fin pour Fin pour Fin pour Pour i,j allant de 0 à 2 faire : pour k1,l1 allant de 0 à 2 faire : pour k2,l2 allant de 0 à 2 faire : Si (k1,l1) != (k2,l2) ET G[i+k1][j+l1] == G[i+k2][j+l2] alors b = false Fin Si Fin pour Fin pour Fin pour Retourner (b) 2. Algorithme Compa : il attend une grille G de sudoku complètement remplie et une grille M de sudoku pré-remplie (certaines cases auront des valeurs nulle). Il vérifie que la grille remplie prolonge bien la grille pré-remplie. Compa(Grille G, Grille M) booléen b = true Pour i allant de 0 à 8 faire : Pour j allant de 0 à 8 faire : Si M[i][j] != NULL ET M[i][j] != G[i][j] alors b = false Fin Si Fin Pour Fin pour Rendre (b) 3. Algorithme ArbreSoluce. Il va générer un arbre dont chaque sommet à 9 fils. Chaque sommet représente une façon de remplir partiellement la grille, ses 9 fils étant les 9 façons différentes de remplir une case de plus. @ est la concaténation de deux entiers : 23 @ 6 = 236. Pour cela on va utiliser un algorithme ArbreSoluceAux(int n, arbre A) attendant un arbre et un entier n. 2 ArbreSoluceAux(int n, arbre A) Si n == 0 alors rendre A Sinon Pour i allant de 1 à 9 faire : c = ALLOC() c->Val = A->Val @ i A ->Fils(i) = c ArbreSoluceAux(n-1,c) Fin pour Fin si ArbreSoluce() A = Alloc() ArbreSoluceAux(A,9) retourne(A) 4. Algorithme naïf : Il attend une grille pré-remplie M . Il commence par générer toutes les grilles avec ArbreSoluce, puis les parcours une par une (une grille remplie est une feuille de l’arbre généré par ArbreSoluce), Vérifie qu’elle est compatible avec M et qu’elle est valide. Naif(Grille M) A = ArbreSoluce() Pile P booléen b P.push(A) Tant que P non vide faire : s = P.pull() b = true Pour i allant de 0 à 8 faire : Si s->Fils(i) NON NULL alors P.Push(s->Fils(i)) b = false Fin faire Si b == true alors : G = s->Val Si Compa(G,M) == true ET Verif(G) == true alors : Retourne (G) Fin si Fin si Fin tant Remarquez que nous faisons un parcours préfixe de l’arbre. Le booléen b est juste là pour marquer les feuilles : il vaut false si s a un fils, true si il en a aucun. 3