Aller au contenu principal

TP2 - Le problème du sac à dos

Objectifs
  1. Comprendre le concept de la programmation dynamique ;
  2. Mettre en place une solution dynamique ;
  3. Évaluer la complexité algorithmique.
TP

Le TP se fait sur python.

Introduction

Salut à toi, jeune aventurier ! Te voilà dans un univers de type RPG, où ton objectif est... de survivre, le plus longtemps possible.
Pour survivre, tu vas avoir besoin d'objets.

Mais, comme dans tout univers RPG, tu es soumis à des règles de physique élémentaire... Tu ne peux porter sur toi que ce que ton corps peut supporter.
Autrement dit : chaque ressources, objets, possèdent un poids, et tu es soumis à une limite de poids.

Voyons voir si tu as compris ce que j'ai dit.

Tu ne peux porter que 15 kilos, et voici une liste d'objets s'offrant miraculeusement à toi (il faut bien débuter) :

ObjetCasque en boisÉpée en boisVeste en pailleBotte en boisArmure en bois
Poids510235
  1. Tu ne peux prendre qu'un seul objet de chaque. Quel combinaison d'objets te permettrait d'en prendre le maximum ?
Se faire de l'argent 💰

Tout au long de ton aventure, tu seras amené à améliorer ton équipement. Tu pourras en trouver, certains pourront également s'acheter.
Tu l'auras compris, pour pouvoir devenir le plus grand des aventuriers, il va falloir se faire un max de blé (le code de triche chatgpt ne fonctionne pas).

Pour commencer, je te donne ce sac un peu maigrichon, te permettant de porter jusqu'à 10 kilos.

En te baladant dans la ville de Fortdhiver (nom donné au hasard, aucunement en référence au fait qu'on soit dans une montagne), tu trouves un coffre (abandonné, normalement) renfermant les objets suivants, en un seul exemplaire :

ObjetPoidsValeur
Médaille22
Livre ancien15
Bague en diamant26
Boussole34
Dague78
Lingot d'acier57
  1. Sachant que tu ne peux pas dépasser le poids maximal de ton sac, quels objets prendrais-tu, pour maximiser la valeur totale ?

Pour t'aider à choisir les objets les plus rentables, un inconnu te propose un mystérieux artefact (que l'on appellera ordinateur), te permettant, en lui donnant les bonnes instructions, de déterminer automatiquement les objets les plus rentables à prendre, si tu trouves un nouveau coffre.

Pour commencer, il faut matérialiser les objets du coffre sur l'ordinateur.

  1. Les objets du coffre sont définis par un nom, un poids et une valeur. Tu dois créer une classe Objet avec ces propriétés.
  2. Crée maintenant l'ensemble des objets présents dans le coffre, et ajoute-les dans une liste.
Première approche

L'une des solutions pour résoudre le problème serait de prendre les objets avec les plus grosses valeurs en premier.

  1. Écris une fonction tri_valeur(liste_objet) qui prend en paramètre une liste d'objets, et retourne la même liste d'objets mais triée par ordre décroissant des valeurs (l'objet avec la valeur la plus grande est le premier élément).
  2. Écris une fonction sac_glouton(liste_objet, poids) qui prend en paramètre une liste d'objet triée et un entier correspondant à la capacité du sac, et qui retourne les objets ayant la plus grosse valeur que l'on peut prendre sans dépasser la capacité du sac.

Un grand sage (chauve et barbu) t'avait expliqué par le passé que ce n'était pas la meilleure des solutions, car tu ne ferais pas les choix optimaux. Il t'avait dit qu'il faut étudier tous les choix possibles pour trouver la meilleure solution.

N'ayant évidemment pas enregistré toutes les informations qu'il t'a donné, il t'a donné un vieux parchemin sur lequel il est écrit ceci :

def sac_arbre(liste_objet, poids, taille_liste=0):
# Cas de base : plus d'objets ou plus de capacité
if ... or ...:
return [], 0 # Aucune valeur ajoutée

# Je ne choisis pas l'objet, je passe au suivant
sans_objet, valeur_sans = sac_arbre(..., ..., ...)

# Je choisis l'objet
objet_choisi = liste_objet[...]

# Mais il est trop lourd
if ... > ...:
return sans_objet, valeur_sans

#Il n'est pas trop lourd, je le prends et recommence avec l'objet d'après
avec_objet, valeur_avec = sac_arbre(..., ..., ...)
#J'augmente la valeur totale avec celle de l'objet choisi
valeur_avec += ...

#Je choisis la valeur la plus avantageuse parmi les 2 que j'ai calculé
if ... > ...:
#Celle avec l'objet pris est avantageuse, je retourne l'objet choisi avec la liste des objets choisis (et la valeur)
return [...] + ..., ...
else:
#Celle sans l'objet est plus avantageuse, je retourne la liste des objets obtenues sans l'objet choisi, et la valeur sans
return ..., ...
  1. Il manque des informations sur le parchemin. À toi de le compléter !

Maintenant que tu as trouvé toutes les solutions possibles, tu remarqueras qu'elle n'est pas très bonne car tu recalcules plusieurs fois les mêmes sous-solutions. Pour gagner (littéralement) en expérience, il va falloir améliorer ce programme et mémoïser les informations.

Avant de programmer, il faut bien en saisir le fonctionnement. Pour comprendre le fonctionnement de la mémoïsation, on se propose d'étudier le tableau suivant :

Taille du sac/Objet012345678910
Dague
Lingot d'acier
Bague en diamant
Livre ancien
Boussole
Médaille

Ce tableau se construit ligne par ligne, puis colonne par colonne. On va compléter dans un premier temps, la valeur maximale que tu auras dans ton sac en n'ayant qu'une dague comme objet. On peut ainsi obtenir le tableau suivant :

Taille/Objet012345678910
Dague00000008888
Lingot d'acier
Bague en diamant
Livre ancien
Boussole
Médaille

Maintenant, si on rajoute le lingot d'acier (toujours en cherchant à maximiser la valeur), on obtient ce tableau :

Taille/Objet012345678910
Dague00000008888
Lingot d'acier00000778888
Bague en diamant
Livre ancien
Boussole
Médaille

Pour une taille de 5 et 6kg, il est plus optimal de prendre le lingot d'acier. Par contre, si on passe à 8kg, il devient plus optimal de prendre la dague.
Dans la colonne 7 du lingot d'acier, le programme doit prendre la valeur maximale entre :

  • La valeur qu'il a trouvé sur la ligne du dessus ;
  • La valeur qu'il a trouvé sur la même ligne au poids précédent ;
  • La somme de l'objet avec la valeur trouvée sur la ligne précédente, à la colonne de la taille du sac moins celle de l'objet en question. Dans ce cas, cela donnerait le maximum entre :
  • 8 (case du dessus, [0][7]) ;
  • 7 (case à gauche, [1][6]) ;
  • 7 (somme de la valeur du lingot avec la case [0][2]).

La valeur maximale se situera ainsi dans la case tout en bas à droite.

  1. Pour connaître la valeur maximale sans avoir à calculer toutes les possibilités, complète le tableau.
  2. Pour programmer cette solution, il va falloir que tu crées une fonction sacADos(liste_objet, poids) dont le but va être de créer la mémoire (le tableau), rempli pour chaque case d'une liste contenant [],0 (liste des objets et valeur). Elle appellera par la suite la fonction que tu as complété précédemment, modifiée.
  3. Reprend la fonction sac_arbre créée précédemment, et modifie-la pour qu'elle prenne en compte la mémoire.
  4. Pour tester la différence (on ne la verra pas en terme de temps sur un poids léger ainsi que peu d'objets), tu vas faire un compteur d'appels de la fonction (à l'aide de variables globales).