Table des matières
Compter des combinaisons
Supposons que j'ai deux dés à 6 faces, que je les lance et que je fasse le total.
Intuitivement, on devine que le score 7 est plus probable que le score 12. En effet, pour obtenir 12, le seul moyen est de faire un double 6 tandis que l'on aura un score de 7 avec 2 et 5 ou encore avec 4 et 3.
Ainsi on peut compter qu'avec deux dés à 6 faces, on pourra obtenir 12 d'une seule façon alors qu'il y a 6 façons de faire 7. Ce résultat pourrait être utilisé en probabilité : on dirait qu'il y a 6 fois plus de chances d'obtenir 7 que d'obtenir 12.
Dans ce TD, l'objectif est d'écrire une fonction qui
- reçoit
n, le nombre de dés, au moins 1, - reçoit
s, le score visé, au moins 0, - reçoit
f, le nombre de faces par dé, au moins 2 (les numéros sur les faces vont de 1 àf) - renvoie le nombre de façons d'obtenir
sen sommantndés deffaces.
Je vous propose quelques approches. Seules la dernière est efficace pour de grandes valeurs de n.
Exhaustif
Première approche, on liste toutes les combinaisons possibles et on compte celles qui produisent le score désiré.
FONCTION comptage
ENTRÉES: n, s, f
DÉBUT
initialiser compteur à 0
POUR CHAQUE combinaison possible, FAIRE
calculer la somme pour cette combinaison
SI la somme est égale à s ALORS
ajouter 1 à compteur
FIN
FIN
RENVOYER compteur
FIN
Le problème de cette solution est que le nombre de combinaisons augmente exponentiellement. Avec des dés à 6 faces, pour 2 dés, il y a $6^2 = 36$ combinaisons. Avec 3 dés, c'est $6^3 = 216$ ; avec 5 dés, c'est $6^5 = 7\,776$, avec 10 dés c'est $6^{10} = 60\,466\,176$ et avec 20 dés c'est $6^{20} = 3\,656\,158\,440\,062\,976$…
Récursif
Supposons que je veuille obtenir 40 avec 10 dés à 6 faces.
- Je pourrais faire 39 avec les 9 premiers dés et 1 avec le 10e,
- ou bien 38 avec les 9 premiers et 2 avec le 10e,
- …
- ou bien 34 avec les 8 premiers et 6 avec le 10e.
Le nombre de combinaison donnant 40 avec 10 dés à 6 faces sera donc le nombre de combinaisons donnant 39 avec 9 dés plus le nombre de combinaisons donnant 38 avec 9 dés, plus … le nombre de combinaisons donnant 34 avec 9 dés.
On généraliser la formule ainsi :
$$nombre(n, s, f) = nombre(n-1, s-1, f) + nombre(n-1, s-2, f) + \cdots + nombre(n-1, s-f, f) = \sum_{i = 1}^f nombre(n-1, s - i, f)$$
Mais si $f > s$ on aura parfois $s - i < 0$ ce qui pourrait être ennuyeux. On convient donc que $nombre(n, s, f) = 0$ si $s < 0$, ce qui est tout à fait naturel.
De même, on calcule le cas pour $n$ en utilisant le cas $n-1$. Comme dans une récurrence, il faut un cas de base. Ce cas de base sera le cas $n = 0$. On dira que $nombre(0,0,f) = 1$ (c'est à dire 1 si $n$ et $s$ valent 0) et 0 sinon.
FONCTION comptage
ENTRÉES: n, s, f
DÉBUT
SI s < 0 ALORS
RENVOYER 0
FIN
SI n est 0 ALORS
SI s est 0 ALORS
RENVOYER 1
SINON
RENVOYER 0
FIN
FIN
initialiser somme à 0
POUR i ALLANT DE 1 à f, FAIRE
calculer comptage avec n-1, s-i et f
et ajouter le résultat à somme
FIN
RENVOYER somme
FIN
Ce cas est plus simple d'un certain point de vue : on n'a pas le soucis de générer les différentes combinaisons. Mais comme souvent avec la récursivité, la simplicité apparente cache des problèmes.
Ici, le gros problème est que l'on va refaire inutilement beaucoup de calculs. Prenez l'exemple $n = 3$ et $s = 10$ par exemple et essayez de suivre la récurrence, vous verrez que la fonction comptage est appelée plusieurs fois avec les mêmes arguments. Ce problème grandit exponentiellement et rend la méthode impraticable très rapidement.
Approche dynamique
L'approche dynamique consiste à reprendre la récursivité mais en sauvegardant les résultats de calculs de façon à ne pas répéter inutilement toujours les mêmes calculs.
On pourrait ainsi proposer cette méthode presque identique à la précédente mais avec une mémoire en plus :
memoire = dictionnaire vide
FONCTION comptage
ENTRÉES: n, s, f
DÉBUT
SI s < 0 ALORS
RENVOYER 0
FIN
SI n est 0 ALORS
SI s est 0 ALORS
RENVOYER 1
SINON
RENVOYER 0
FIN
FIN
SI (n, s, f) présent dans memoire ALORS
RENVOYER memoire pour (n, s, f)
FIN
initialiser somme à 0
POUR i ALLANT DE 1 à f, FAIRE
calculer comptage avec n-1, s-i et f
et ajouter le résultat à somme
FIN
écrire somme dans memoire, avec la clé (n, s, f)
RENVOYER somme
FIN
Vous voyez que le dictionnaire permet de sauvegarder des résultats antérieurs.
Une telle approche est très séduisante. Mais elle nécessite une structure compliquée : le dictionnaire. Certains langages n'en disposent pas et on peut préférer une autre approche plus proche de ce qui est proposé dans la programmation dynamique.
Cette méthode passe par la création d'un tableau tab à deux dimensions tel que tab[i][j] contienne le nombre de combinaisons avec i dés pour obtenir le score j. Donc la valeur cherchée est tab[n][s].
On sait que tab[0][j] vaut 1 si j == 0 et 0 sinon. La première ligne du tableau est donc connue. L'algorithme consiste à remplir les lignes l'une après l'autre.
FONCTION comptage
ENTRÉES: n, s, f
DÉBUT
créer un tableau de n + 1 lignes et s + 1 colonnes plein de 0
mettre 1 en [0][0]
POUR CHAQUE ligne de 1 à n FAIRE
POUR CHAQUE colonne de 1 à s FAIRE
cumuler les valeurs allant de [ligne-1][colonne - f] jusqu'à [ligne-1][colonne-1], compris
écrire le cumul en [ligne][colonne]
FIN
FIN
RENVOYER la valeur en [n][s]
À faire
Faire l'implémentation et testez.
Pour les terminales, dans le cadre de la programmation dynamique, vous préférerez bien sûr la dernière approche mais il serait intéressant de faire des comparatifs de performances.
