- Modéliser un problème d'optimisation sous forme de sous-problèmes ;
- Identifier la redondance des calculs dans une approche récursive naïve ;
- Mettre en place la mémoïsation pour obtenir un algorithme dynamique efficace ;
- Comparer la complexité avant et après optimisation.
Les questions se répondent sur papier.
Le TP se fait sur Python.
TP3 — Le problème des découpes de planches
La scierie Borges Jaumont, dans les Vosges, cherche à optimiser le prix de vente de ses planches.
À partir d'une planche de longueur donnée, l'entreprise peut la découper en plusieurs morceaux de longueurs entières. Chaque longueur a un prix de vente fixe, donné dans le tableau ci-dessous :
| Longueur | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Prix (€) | 0 | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
Objectif : déterminer comment découper une planche pour maximiser le revenu total.
Par exemple, une planche de longueur 4 vaut 9 € vendue entière, mais si on la découpe en deux morceaux de longueur 2, on obtient 5 + 5 = 10 €.
Exploration manuelle
- 1
En listant toutes les façons de découper une planche de longueur 3, compléter le tableau suivant :
Découpe Calcul du prix Prix total 3 p[3] 8 € 2 + 1 p[2] + p[1] ... 1 + 1 + 1 ... ... - 2
Faire de même pour une planche de longueur 4. Combien de découpes différentes existe-t-il ?
- 3
Quelle est la découpe optimale pour une planche de longueur 4 ? Justifier.
- 4
En observant vos résultats, expliquer pourquoi ce problème peut se décomposer en sous-problèmes : comment le prix optimal d'une planche de longueur
ndépend-il des prix optimaux de planches plus courtes ?
Approche récursive naïve
- 1
On note
coupe(n)la fonction permettant d'obtenir le prix maximal pour une planche de longueurn. Quel est le cas d'arrêt de cette fonction récursive ? - 2
Si on fait une première coupe de longueur
idans une planche de longueurn, on obtient un morceau de longueuriet un reste de longueurn - i. Exprimercoupe(n)en fonction decoupe(n-i)etp[i]. - 3
Dessiner un arbre d'appels récursifs pour
coupe(4).
Repérer dans cet arbre les sous-problèmes qui sont calculés plusieurs fois.
C'est le cœur du problème de l'approche naïve.
- 4
Compléter la fonction récursive
coupe(n, p)ci-dessous :p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30]
def coupe(n: int, p: list)->int:
if ... :
return ...
else:
resultat = ...
for i in range(..., ...) :
resultat = max( ... , ... + ... )
return ... - 5
Tester votre fonction avec
coupe(4, p). Retrouve-t-on le résultat de la partie 1 ? - 6
Un employé exécute
coupe(11, p)et obtient l'erreur :IndexError: list index out of range. D'où vient-elle ? Corriger le problème. - 7
Ce même employé tente
coupe(30, p)et attend plus d'une heure. En vous appuyant sur l'arbre de la question 3, expliquer pourquoi cette approche est inefficace.
Approche dynamique par mémoïsation
L'idée est simple : on ne calcule jamais deux fois la même chose.
Chaque fois qu'on calcule coupe(n), on stocke le résultat dans une liste mem.
Avant de calculer, on vérifie d'abord si le résultat est déjà connu.
- 1
On initialise la mémoire grâce à l'instruction :
mem = [0] * (n + 1). Que fait-elle ? - 2
Expliquer pourquoi la valeur 0 pourrait poser problème. Quelle valeur pourrait-on donner pour indiquer qu'une valeur n'a pas encore été calculée ?
- 3
Schématiser le remplissage de
mempourcoupe(4)en indiquant dans quel ordre les cases sont calculées et ce qu'elles contiennent à la fin.mem[0] mem[1] mem[2] mem[3] mem[4] 0 ? ? ? ? - 4
Recopier et compléter le programme suivant permettant de résoudre le problème de découpe de planche en utilisant la mémoïsation :
def planche_memo(n: int, p: list) -> int:
"""
Crée la mémoire et retourne le prix optimal d'une planche de longueur n.
"""
mem = ... # Initialisation : -1 signifie "non calculé"
return coupe_dyna(..., ..., ...)
def coupe_dyna(n: int, p: list, mem: list) -> int:
"""
Retourne le meilleur prix pour une planche de longueur n,
en utilisant la mémoïsation.
"""
if n == 0:
return ...
elif mem[n] != ...: # Valeur déjà calculée ?
return ...
else:
resultat = ...
for i in range(..., ...):
if i < len(p):
resultat = max(..., ...)
mem[n] = ...
return ... - 5
Tester
planche_memo(30, p). Comparer le temps d'exécution avec la version naïve. - 6
Quel est l'ordre de grandeur de la complexité de cet algorithme ? La comparer avec la version naïve.
Pour aller plus loin (facultatif)
- 18
Modifier
coupe_dynapour qu'elle retourne non seulement le prix optimal, mais aussi la liste des longueurs correspondant à la découpe optimale. - 19
Tester votre fonction pour n = 4, 8, 10 et vérifier que les prix correspondent bien à vos calculs de la partie 1.
- 20
Est-il possible que plusieurs découpes donnent le même prix optimal ? Donner un exemple.