====== Problème du sac à dos ====== ===== Qu'est-ce ? ===== Il s'agit d'un problème classique d'algorithmique. C'est le problème du **cambrioleur** : Le cambrioleur doit choisir rapidement quels objets il emporte. Il veut emporter un maximum de valeur mais il doit les mettre dans son sac dont la capacité est limitée. Plus formellement : * On dispose d'un sac de capacité C, * d'un assortiment d'objets ayant tous un poids et une valeur. * On cherche à placer des objets dans le sac en respectant la contrainte de capacité et en maximisant la valeur totale du contenu du sac. **Exemple :** On dispose d'un sac d'une capacité de C = 15 kg et de la liste d'objets suivants : ^ Objet ^ Valeur ^ Poids ^ | A | 126 | 14 | | B | 32 | 2 | | C | 20 | 5 | | D | 5 | 1 | | E | 18 | 6 | | F | 80 | 8 | ===== Algorithme glouton ===== L'algorithme glouton donne une réponse pas forcément optimale au problème posé mais il est simple : - on trie le tableau selon un critère, - on remplit le sac en prenant les objets dans l'ordre. === Valeur décroissante === Par exemple, trions le tableau par valeur décroissante. ^ Objet ^ Valeur ^ Poids ^ | A | 126 | 14 | | F | 80 | 8 | | B | 32 | 2 | | C | 20 | 5 | | E | 18 | 6 | | D | 5 | 1 | Puis complétons le sac en prenant, si possible, les objets dans l'ordre. * On peut prendre A, ce qui occupe 14 kg de la capacité du sac, * comme il ne reste que 1 kg de capacité, on ne peut pas prendre F, B, C, E. * Enfin on peut prendre D. Le sac contiendra A et D totalisant une valeur de 131. === Poids croissant === C'est un autre choix possible. Il aboutira à un contenu de sac différent. ^ Objet ^ Valeur ^ Poids ^ | D | 5 | 1 | | B | 32 | 2 | | C | 20 | 5 | | E | 18 | 6 | | F | 80 | 8 | | A | 126 | 14 | == Rapport valeur / poids décroissant == On peut aussi faire des calculs avec les attributs des objets. Par exemple, il semble raisonnable d'évaluer le rapport valeur / poids afin de choisir en priorité les objets qui apportent de la valeur sans trop encombrer le sac. ^ Objet ^ Valeur ^ Poids ^ rapport V/P ^ | B | 32 | 2 | 16 | | F | 80 | 8 | 10 | | A | 126 | 14 | 9 | | D | 5 | 1 | 5 | | C | 20 | 5 | 4 | | E | 18 | 6 | 3 | Là encore on obtient un sac différent. > Remarquez qu'aucune de ces stratégies ne donne le meilleur sac, mais elles donnent de bons résultats. ===== Implémentation ===== === Les objets === Les items disponibles seront représentés dans un dictionnaire. Par exemple : objets = { "A": { "valeur":126, "poids":14 }, "B": { "valeur":32, "poids":2 }, "C": { "valeur":20, "poids":5 }, "D": { "valeur":5, "poids":1 }, "E": { "valeur":18, "poids":6 }, "F": { "valeur":80, "poids":8 } } === Sac === Le contenu du sac sera lui aussi représenté par un tableau. Par exemple : sac = ["A", "D"] === fonctions utiles === Nous avons besoin des fonctions suivantes : - trier les objets dans un ordre défini, - calculer le poids total d'un sac, - calculer la valeur d'un sac - tester si on peut ajouter un objet dans un sac, - ajouter un objet dans un sac Voici un modèle **à compléter** puis **à exécuter** : # sacados.py # la dictionnaire objets contenant les objets sera une variable globale. def tri(critere): ''' critere: fonction qui pour nom d'objet donné renvoie sa valeur dans le tri exemple : si on tri par valeur décroissante, critere(nom) doit renvoyer la valeur de l'objet la fonction renvoie un tableau avec les noms, par critère décroissant. ''' return def poids(sac): ''' sac: tableau des noms des objets contenus dans le sac renvoie la masse totale contenue dans le sac ''' return 0 def valeur(sac): ''' sac: tableau des noms des objets contenus dans le sac renvoie la valeur totale de ce sac. ''' return 0 def rentre_dans_le_sac(nom, sac, C): ''' nom: nom de l'objet que l'on souhaite ajouter sac: tableau des noms des objets contenus dans le sac C: Capacité max du sac renvoie True si l'objet peut entrer dans le sac, False sinon ''' return False def ajouter_objet_dans_sac(nom, sac): ''' nom: nom de l'objet que l'on souhaite ajouter sac: tableau des noms des objets contenus dans le sac modifie sac en y ajoutant nom ''' return def glouton(C, critere): ''' C: capacité max du sac critere: fonction utilisée dans le tri renvoie le sac obtenu par l'algorithme glouton suivant le critère défini ''' return [] # démonstration C = 15 objets = { "A": { "valeur":126, "poids":14 }, "B": { "valeur":32, "poids":2 }, "C": { "valeur":20, "poids":5 }, "D": { "valeur":5, "poids":1 }, "E": { "valeur":18, "poids":6 }, "F": { "valeur":80, "poids":8 } } # exemple de critère def critere_valeur(nom): obj = objets[nom] return obj["valeur"] sac = glouton(C, critere_valeur) print(sac) print(valeur(sac)) === Fonction lambda === En mathématique, une fonction consiste en l'association d'antécédents et d'images. On peut noter par exemple $x \mapsto 3x + 5$ l'idée que chaque nombre $x$ antécédent sera associer à l'image $3x+5$ : 0 est associé à 5 ; 1 est à 8 ; 2 est associé à 11 ; etc. Notre fonction ''%%critere_valeur%%'' pourrait ainsi se noter $nom \mapsto objets[nom]['valeur']$. On comprendrait ce que fait cette fonction sans la définir avant et sans lui donner de nom, il suffit de savoir que l'antécédent est ''%%nom%%'' et que l'image est ''%%objets[nom]['valeur']%%''. Il existe une notation pour cela en Python : lambda nom: objets[nom]['valeur'] C'est une fonction anonyme prenant comme argument (//antécédent//) ''%%nom%%'' et renvoie (//image//) ''%%objets[nom]['Valeur']%%''. Alors nous pouvons remplacer : # code actuel def critere_valeur(nom): return objets[nom]['valeur'] sac = glouton(objets, critere_valeur) # en utilisant lambda sac = glouton(objets, lambda nom: objets[nom]['valeur']) Le résultat est le même. === Autre critères === Exécutez le programme avec d'autres critères : * rapport valeur / poids décroissant, * poids croissant\\ //Il faut trouver une astuce puisque le tri se fait dans l'ordre décroissant du critère fourni !// Essayez de le faire en utilisant ''%%lambda%%''.