Optimiser encore le problème des gratte-ciel

Complément à la formation

Bruno Mermet

I. Quelques pistes

Plusieurs pistes peuvent encore être exploitées pour optimiser encore le programme :

Ne pas prendre les lignes dans l'ordre

Certaines lignes n'ont aucune contrainte, d'autres une, d'autres 2. Commencer par les lignes les plus contraintes permet d'élaguer plus vite l'arbre de résolution.

Certaines contraintes sont plus contraignantes que d'autres : plus est proche d'une valeur moyenne, plus cela est contraignant. Par exemple, sur une grille de 3x3, une contrainte à 1 (seule solution : [1, 2, 3]) est plus contraignante qu'une contrainte à 3 (2 solutions : [3, 1, 2] et [3, 2, 1]) qui est elle-même plus contraignante qu'une contrainte à 2 (3 solutions : [1,3, 2], [2, 1, 3]et [3, 1, 2]).

Ne pas procéder par ligne
Certaines cases n'ont aucune contrainte au départ (pas de contrainte de visibilité, alors que d'autres peuvent être liées à 1, 2, 3, voire 4 contraintes. Il peut être intéressant de privilégier ces cases là. Par ailleurs, toute valeur placée dans une case contraint les cases de la même ligne et de la même colonne. Le nombre de contraintes d'une case va donc évoluer au cours du temps.

Remarques

Les suggestions des améliorations indiquées ci-dessus impliquent qu'on ne peut plus qu'on ne peut plus travailler sur le même objet lors des appels récursifs (à tout moment, il faut pouvoir prendre en compte ce qui est placé n'importe où sur la grille). Il devient alors indispensable de passer des copies lors des appels récursifs.

II. Exemple de mise en œuvre

Prenons la grille suivante, tirée de Tangente - Jeux & Stratégie :

                  
                  
1               
               3
                  
   2      3   

Calculons les degrés de liberté de chaque case en raisonnant sur 1 niveau. Les voici figurant en rouge (toutes les cases non remplies ont un degré de liberté de 4 : toutes les valeurs sont possibles) :

                  
                  
11333   
         323
   3      2   
   2      3   

La case la plus contrainte est celle marquée d'un 1 : une seule valeur est possible. La grille peut donc être complétée ainsi :
                  
                  
140            
               3
                  
   2      3   

Les degrés de liberté peuvent alors être recalculés (par convention, on mettra 0 pour les cases déjà remplies) :

                  
   3            
10333   
   2   323
   3      2   
   2      3   

Tentons une valeur pour l'une des cases au plus faible degré de liberté (Embranchement 1) :

                  
                  
140            
            103
                  
   2      3   
                  
   3      1   
10331   
   23303
 3      1   
   2      3   

On peut alors remplir toutes les cases n'offrant qu'un degré de liberté :

                  
            40   
140      30   
            103
            20   
   2      3   
                  
   3330   
10220   
   13303
 1330   
   2      3   

On continue, toujours en ne remplissant que les cases n'ayant qu'un degré de liberté :

                  
            40   
140      30   
   20      103
   30      20   
   2      3   
                  
   1330   
10220   
   01103
 0220   
   2      3   

Et ça continue, encore et encore... :

                  
   10      40   
140      30   
   204030103
   30      20   
   2      3   
                  
   0210   
10220   
   00003
 0120   
   2      3   

... C'est que le début, d'accord, d'accord :

                  
   10   2040   
140      30   
   204030103
   3010   20   
   2      3   
                  
   0100   
10110   
   01103
 0010   
   2      3   

On poursuit... :

                  
   10302040   
140201030   
   204030103
   30104020   
   2      3   

... Et on tombe sur la solution.

Il se peut qu'à un moment, on tombe sur une case avec aucune valeur possible. Il faut alors déclencher le back-tracking pour revenir sur le précédent point de choix et essayer une autre valeur possible.

Remarques

Dans l'exemple déroulé ci-dessus, le calcul des espaces de liberté pour une case c(i, j) a été fait en envisageant les différentes possibilités de permutation de la ligne i et de la colonne j, ce qui va au-delà de ce qui a été suggéré dans la première partie. Ceci risquerait par ailleurs d'exploser assez vite avec la taille de la grille (mais permettait de limiter les étapes pour l'explication). Dans les faits, il serait plus judicieux de s'en tenir aux contraintes directes (visibilité et unicité). Cela générerait plus de retour arrière, mais serait au final plus efficace.

III. Conseils pour l'implantation

Le fait de placer une nouvelle valeur sur la grille ne change les degrés de liberté que pour les cases de la même ligne et de la même colonne. Cela signifie donc que, pour gagner du temps, on peut :

À un niveau donné, on peut rempir en un coup toutes les cases dont le degré de liberté est à 1. Cela évite de multiplier les appels récursifs impliquant une copie des 2 grilles (celles des gratte-ciel, et celle des degrés de liberté). Cependant, cela complique un peu la détermination des cases dont il faut recalculer les degrés de liberté.

IV. Influence de la visibilité sur les valeurs possibles

Nous allons commencer par une approche empirique sur une grille de taille 4. Pour simplifier, nous n'étudierons que la visibilité gauche d'une ligne.

Contrainte à 1 :

1               

Liste des possibilités :

140102030   
140103020   
140201030   
140203010   
140301020   
140302010   

Degrés de liberté :

11333   

Contrainte à 2 :

2               

Liste des possibilités :

210402030   
210403020   
220401030   
220403010   
230401020   
230402010   
220104030   
230104020   
230204010   
230102040   
230201040   

Degrés de liberté :

23344   

Contrainte à 3 :

3               

Liste des possibilités :

310204030   
310302040   
310304020   
320103040   
320301040   
320304010   

Degrés de liberté :

32344   

Contrainte à 4 :

4               

Liste des possibilités :

410203040   

Degrés de liberté :

41111   

Résumons cela en détaillant plutôt, pour chacun des cas, les valeurs possibles pour une case :

Liste des possibilités :

1{40}{10, 20, 30}{10, 20, 30}{10, 20, 30}   
2{10, 20, 30}{10, 20, 40}{10, 20, 30, 40}{10, 20, 30, 40}   
3{10, 20}{10, 20, 30}{10, 20, 30, 40}{10, 20, 30, 40}   
4{10}{20}{30}{40}   

Faisons le même travail sur un grille de taille 3... :

1{30}{10, 20}{10, 20}   
2{10, 20}{10,30}{10, 20, 30}   
3{10}{20}{30}   

... et sur une grille de taille 5 :

1{50}{10, 20, 30, 40}{10, 20, 30, 40}{10, 20, 30, 40}{10, 20, 30, 40}   
2{10, 20, 30, 40}{10, 20, 30, 50}{10, 20, 30, 40, 50}{10, 20, 30, 40, 50}{10, 20, 30, 40, 50}   
3{10, 20, 30}{10, 20, 30, 40}{10, 20, 30, 40, 50}{10, 20, 30, 40, 50}{10, 20, 30, 40, 50}   
4{10, 20}{10, 20, 30}{10, 20, 30, 40}{10, 20, 30, 40, 50}{10, 20, 30, 40, 50}   
5{10}{20}{30}{40}{50}   

On en déduit empiriquement une façon de déterminer les possibilités pour une case en fonction du critère de visibilité gauche. Cela est implanté dans le fichier determinerPossibilites.py. Dans de fichier, la fonction recherche_globale permet de déterminer par recherche exhaustive les possibilités pour une ville de taille donnée, et la fonction generer_visibilite_globale permet de générer les mêmes données, cette fois-ci par application de l'algorithme déterminé empiriquement. Avec la taille grandissante de la grille, les temps de calcul deviennent bien sûr rapidement incomparables.