Problème du sac à dos
857895
31255030
2008-07-03T10:19:02Z
Amirobot
419214
robot Ajoute: [[fa:مسئله کولهپشتی]]
[[Image:Knapsack.svg|thumb|Le problème du sac à dos : quelles boîtes choisir afin de maximiser la somme emportée tout en ne dépassant pas les 15 kg autorisés ?]]
Le '''problème du sac à dos''', aussi noté '''KP''' (en [[anglais]], ''Knapsack Problem'') est un problème d'[[optimisation combinatoire]].
Il modélise une situation analogue au remplissage d'un [[sac à dos]], ne pouvant supporter plus d'un certain [[poids]], avec tout ou partie d'un ensemble d'objets ayant chacun un poids et une [[Valeur (économie)|valeur]]. Les objets mis dans le sac à dos doivent maximiser la valeur totale, sans dépasser le poids maximum.
== Histoire ==
=== Dans la recherche ===
Le problème du sac à dos est l'un des [[21 problèmes NP-complets de Karp|21 problèmes NP-complets]] de [[Richard Karp]], exposés dans son article de [[1972]].
Il est intensivement étudié depuis le milieu du {{XXe siècle}} et on trouve des références dès [[1897]], dans un article de [[George Ballard Mathews]]<ref>{{en}} G.B. Mathews, ''On the partition of numbers'', Proceedings of the London Mathematical Society, 28:486-490, 1897.</ref>. La formulation du problème est fort simple, mais sa résolution est plus complexe. Les [[algorithmique|algorithme]]s existants peuvent résoudre des instances pratiques de taille importante. Cependant, la structure singulière du problème, et le fait qu'il soit présent en tant que sous-problème d'autres problèmes plus généraux, en font un sujet de choix pour la [[recherche scientifique|recherche]].
=== Complexité et cryptographie ===
Ce problème est à la base du premier algorithme de [[Cryptographie asymétrique|chiffrement asymétrique]] (ou à « clé publique ») présenté par [[Martin Hellman]], [[Ralph Merkle]] et [[Whitfield Diffie]] à l'[[Université de Stanford]] en [[1976]]<ref>{{en}} ''[http://cse.stanford.edu/class/sophomore-college/projects-97/cryptography/history.html Public Key Cryptography]'', dans la partie « History » d'un projet de ''Eric Robert's Sophomore College class'' "The Intellectual Excitement of Computer Science" à l'[[Université de Stanford]]. La publication correspondante est : R.C. Merkle et M.E. Hellman, "Hiding Information and Receipts in Trap Door Knapsacks", Internal Symposium on Information Theory, Cornell University, Ithaca, New York, October 1977.</ref>. Toutefois, même si l'idée est due au problème du sac à dos, [[Rivest Shamir Adleman|RSA]] est considéré comme le premier véritable algorithme de chiffrement asymétrique.
La version [[Théorie de la complexité|NP-difficile]] de ce problème a été utilisée dans des primitives et des protocoles de [[cryptographie]], tels que le [[cryptosystème de Merkle-Hellman]] ou le [[cryptosystème de Chor-Rivest]]. Leur avantage par rapport aux cryptosystèmes asymétriques basés sur la difficulté de [[décomposition en produit de facteurs premiers|factoriser]] est leur rapidité de chiffrement et de déchiffrement.
Cependant, l'algorithme de Hellman, Merkle et Diffie est sujet aux "portes dérobées" algorithmiques, ce qui implique qu'il est « cassé », c'est-à-dire [[cryptanalyse|cryptanalysé]]<ref>{{en}} [[Adi Shamir|A. Shamir]], ''A Polynomial-Time Algorithm for Breaking the Basic Merkle-Hellman Cryptosystem'', IEEE Transactions on Information Theory, Vol. IT-30, pp. 699-704, 1984. (Première publication en avril [[1982]].)</ref>. Le problème du sac à dos est un exemple classique de méprise en ce qui concerne les liens entre la NP-complétude et la cryptographie.
Une version revue de l'algorithme, avec une itération du problème du sac à dos, a alors été présentée, pour être sitôt cassée<ref>{{en}} ''[http://www.math.ohio-state.edu/history/math-matrix/Sp85/Matrix_Sp85 Knapsack Encryption Scheme Broken]'', « Math Matrix », Département de mathématiques de l'[[Ohio State University]], printemps [[1985]], Vol. 1, No. 3.</ref>.
Les algorithmes de chiffrement asymétrique basés sur le sac à dos ont tous été cassés à ce jour, le dernier en date étant celui de Chor-Rivest<ref>{{en}} S. Vaudenay, ''[http://lasecwww.epfl.ch/php_code/publications/search.php?ref=Vau98h Cryptanalysis of the Chor-Rivest Cryptosystem]''.</ref>.
=== Autres domaines concernés ===
On l'utilise aussi pour modéliser les situations suivantes, quelquefois en tant que sous-problème :
* dans des systèmes d'aide à la [[gestion de portefeuille]] : pour équilibrer sélectivité et diversification dans le but de trouver le meilleur rapport entre rendement et risque pour un capital placé sur plusieurs [[actif financier|actifs financiers]] (actions...);
* dans le chargement de [[bateau]] ou d'[[avion]] : tous les bagages à destination doivent être amenés, sans être en surcharge ;
* dans la [[découpage|découpe]] de [[matériau]]x : pour minimiser les chutes lors de la découpe de sections de longueurs diverses dans des barres en fer.
Une autre raison de s'intéresser à ce problème est son apparition dans certaines utilisations de méthodes de [[génération de colonnes]] (ainsi pour le [[problème de bin packing|problème de « bin packing »]]).
Anecdotiquement et justifiant ainsi le nom du problème, un randonneur y est confronté au moment de préparer son périple : le sac à dos a une capacité limitée ; et il faut donc trancher entre prendre, par exemple, deux [[conserve|boîtes de conserve]] et une [[Gourde (récipient)|gourde]] de cinquante centilitres ou une seule boîte de conserve et une gourde d'un litre.
== Énoncé mathématique ==
Les données du problème peuvent être exprimées en termes mathématiques. Les objets sont numérotés par l'indice ''i'' variant de ''1'' à ''n''. Les nombres <math>w_i</math> et <math>p_i</math> représentent respectivement le poids et la valeur de l'objet numéro ''i''. La capacité du sac sera notée ''W''.
Il existe de multiples façons de remplir le sac à dos. Pour décrire l'une d'elles il faut indiquer pour chaque élément s'il est pris ou non. On peut utiliser un codage binaire : l'état du ''i''-ème élément vaudra <math>x_i=1</math> si l'élément est mis dans le sac, ou <math>x_i=0</math> s'il est laissé de côté. Une façon de remplir le sac est donc complètement décrite par un [[vecteur]], appelé vecteur contenu, ou simplement contenu : <math>X=(x_1, x_2, ... , x_n)</math> ; et le poids associé, ainsi que la valeur associée, à ce remplissage, peuvent alors être exprimés comme [[fonction (mathématiques)|fonction]] du vecteur contenu.
Pour un contenu ''X'' donné, la valeur totale contenue dans le sac est naturellement :
:<math>z(X) =\sum_{\{i, \, x_i=1\}} p_i = \sum_{i=1}^n x_ip_i</math>
De même, la somme des poids des objets choisis est :
:<math>w(X)=\sum_{i=1}^n x_iw_i </math>
Le problème peut alors être reformulé comme la recherche d'un vecteur contenu <math>X=(x_1, x_2, \dots, x_n)</math> (les composantes valant 0 ou 1), réalisant le maximum de la fonction valeur totale <math>z(X)</math>, sous la contrainte :
:<math>w(X)=\sum_{i=1}^n x_iw_i \le W</math> (1)
C'est-à-dire que la somme des poids des objets choisis ne dépasse pas la capacité du sac à dos.
En général, on ajoute les contraintes suivantes afin d'éviter les cas singuliers :
* <math>\sum_{i=1}^n w_i > W</math> : on ne peut pas mettre tous les objets ;
* <math>p_i > 0, \forall i \in \{1, \dots, n\}</math> : tout objet apporte un gain ;
* <math>w_i > 0, \forall i \in \{1, \dots, n\}</math> : tout objet consomme des ressources.
Terminologie :
* <math>z(X)</math> est appelée ''fonction objectif'' ;
* tout vecteur <math>X</math> vérifiant la contrainte (1) est dit ''réalisable'' ;
* si la valeur de <math>z(X)</math> est maximale, alors <math>X</math> est dit ''optimal''.
== NP-complétude ==
Le problème de sac à dos peut être représenté sous une forme décisionnelle en remplaçant la maximisation par la question suivante : un entier <math>k</math> étant donné, existe-t-il une valeur des <math>x_i</math> pour laquelle <math>\sum_{i=1}^n p_ix_i \ge k</math>, avec respect de la contrainte ? Il y a un lien entre la version « décision » et la version « optimisation » du problème dans la mesure où s'il existe un algorithme polynomial qui résout la version « décision », alors on peut trouver la valeur maximale pour le problème d'optimisation de manière polynomiale en appliquant itérativement cet algorithme tout en augmentant la valeur de ''k''.
D'une manière similaire, si un algorithme trouve la valeur optimale du problème d'optimisation en un temps polynomial, alors le problème de décision peut être résolu en temps polynomial en comparant la valeur de la solution sortie par cet algorithme avec la valeur de ''k''. Ainsi, les deux versions du problème sont de difficulté similaire.
Sous sa forme décisionnelle, le problème est [[NP-complet]], ce qui signifie qu'il n'existe pas de méthode générale connue pour construire une solution optimale, à part l'examen systématique de toutes les solutions envisageables. Le problème d'optimisation est NP-difficile, sa résolution est au moins aussi difficile que celle du problème de décision, et il n'existe pas d'algorithme polynomial connu qui, étant donné une solution, peut dire si elle est optimale (ce qui reviendrait à dire qu'il n'existe pas de solution avec un <math>k</math> plus grand, donc à résoudre le problème de décision NP-complet).
=== Procédé d'exploration systématique ===
[[Image:Arbre binaire exploration.svg|350px|right|Arbre d'exploration binaire]]
Cet [[Recherche par force brute|examen systématique]] peut être réalisé à l'aide d'un [[arbre (mathématiques)|arbre]] d'exploration binaire tel celui représenté ci-contre (les triangles représentent des sous-arbres).
L'arbre se décrit en descendant depuis le sommet jusqu'au bas des triangles (les feuilles de l'arbre). Chaque case correspond à un unique parcours possible. En suivant les indications portées le long des arêtes de l'arbre, à chaque parcours correspond une suite de valeurs pour <math>x_0, x_1, ..., x_n</math> formant un vecteur contenu. Il est alors possible de reporter dans chaque case la valeur totale et le poids total du contenu correspondant. Il ne reste plus qu'à éliminer les cases qui ne satisfont pas la contrainte, et à choisir parmi celles qui restent celle (ou une de celles) qui donne la plus grande valeur à la fonction objectif.
À chaque fois qu'un objet est ajouté à la liste des objets disponibles, un niveau s'ajoute à l'arbre d'exploration binaire, et le nombre de cases est multiplié par 2. L'exploration de l'arbre et le remplissage des cases ont donc un coût qui [[croissance exponentielle|croît exponentiellement]] avec le nombre ''n'' d'objets.
=== Preuve de la NP-complétude ===
{{Article détaillé|théorie de la complexité}}
Cette preuve de NP-complétude a été présentée par Maichail G. Lagoudakis<ref>{{en}} Michail G. Lagoudakis, [http://www2.isye.gatech.edu/~mlagouda/acadpape.html The 0-1 Knapsack Problem - An Introductory Survey], 1996.</ref> reprenant un article de [[Richard Karp]] et un article de J.E. Savage.
{{boîte déroulante|titre=Détail de la preuve|label=Voir la démonstration|contenu=
La preuve de NP-complétude se fait en utilisant le problème de sac à dos sous la forme d'un problème de décision. Elle se fait en deux étapes, premièrement vérifier que ''(KP)'' appartient à la classe NP et, deuxièmement, montrer que ''(KP)'' est NP-difficile.
Nous utiliserons, pour la preuve d'appartenance à NP-difficile, la version ''somme de sous-ensembles'' (voir les variantes, plus bas), notée ''(SSE)'', une version particulière du sac à dos dans laquelle le profit d'un objet est égal à son poids. Si cette version particulière est NP-difficile, alors ''(KP)'' dans toute sa généralité l'est aussi.
Le problème ''(SSE)'' peut-être obtenu à partir du problème de sac à dos ci-dessus en posant <math>w_i = p_i</math>. En posant W = k, on obtient :
Trouver ''X'' tel que
* <math>\sum_{i=1}^n w_ix_i \ge W</math>
* <math>\sum_{i=1}^n w_ix_i \le W</math>
==== Appartenance à NP ====
Premièrement, nous devons prouver que ''(KP)'' appartient à la classe NP, c’est-à-dire qu'il existe un algorithme polynomial qui, étant donné une solution au problème, peut vérifier que cette solution soit bonne. Pour vérifier une solution, il suffit de calculer la somme des poids des objets choisis et de la comparer avec ''W'', ainsi que la somme de leurs valeurs, à comparer avec <math>k</math>. Le tout est évidemment polynomial. '''''(KP)'' appartient donc à la classe des problèmes NP'''.
==== Appartenance à NP-difficile ====
Nous allons maintenant montrer que ''(SSE)'' est un problème NP-difficile en transformant le [[problème de la couverture exacte]] (noté (EC), de l'anglais ''exact cover'') en un problème ''(SSE)''. Le problème ''(EC)'' s'exprime ainsi :
:Soit ''U'' un ensemble d'éléments et <math>S = \{S_1, \dots, S_n\}</math> un ensemble de sous-ensembles de ''U''. Existe-t-il un sous-ensemble <math>S^*</math> de ''S'' tel que :
:* <math>\bigcup_{s \in S^*} s = U</math> : tous les éléments de ''U'' y soient ;
:* <math>s \cap t = \emptyset~\forall s,t \in S^*, s \ne t</math> : chaque élément de ''U'' n'est que dans un seul des sous-ensembles choisis.
Le problème (EC) est NP-complet. Si nous arrivons à montrer que toute instance de ''(EC)'' peut être transformée polynomialement en une instance de ''(SSE)'' alors nous aurons prouvé que ''(SSE)'' (et donc ''(KP)'') appartient à la classe des problèmes NP-difficiles.
Soit ''I = (U,S)'' une instance quelconque de ''(EC)''. Sans perdre de généralité, nous considérerons que <math>U = \{1, \dots, |U|\}</math>. Nous noterons :
* <math>x_i \in \{0,1\}</math> l'état de l'ensemble <math>S_i</math> (<math>x_i = 1</math> si et seulement si <math>S_i \in S^*</math>) ;
* <math>y_{ij} \in \{0,1\}</math> l'appartenance de la valeur j à l'ensemble <math>S_i</math> (<math>y_{ij} = 1</math> si et seulement si <math>j \in S_i</math>).
Soit <math>b = |U| + 1</matH>. Les variables du problème ''(SSE)'' sont les <math>x_i</math> du problème ''(EC)''. Nous définissons leur poids de la façon suivante :
* <math>w_i = \sum_{j \in U} y_{ij}b^{j-1}</math>.
Nous définissons la capacité ''W'' par
* <math>W = \frac{b^{|U|} - 1}{b - 1} = \sum_{j \in U} b^{j-1}</math>.
Le poids de l'objet ''i'' est une somme de puissances de ''b'' et <math>b^{j-1}</math> apparaît dans <math>w_i</math> si et seulement si <math>j \in s_i</math>. Par conséquent, il y a une correspondance de un à un entre la solution du problème ''(SSE)'' construit et l'instance de ''(EC)''. Chaque valeur <math>w_i</math> se calcule en <math>O(|U|)</math> et la valeur de ''W'' se calcule en ''O(1)''. La transformation a donc une complexité temporelle en <math>O(n|U|)</math>. '''Le problème ''(SSE)'' (et donc le problème ''(KP)'') appartient donc à la classe des problèmes NP-difficiles'''.
==== Conclusion ====
Nous avont prouvé que ''(KP)'' est dans NP et est NP-difficile. Par conséquent, '''le problème ''(KP)'' appartient à la classe des problèmes NP-complets'''.
}}
== Résolution approchée ==
{{Article détaillé|algorithme d'approximation}}
Comme pour la plupart des problèmes NP-complets, il peut être intéressant de trouver des solutions réalisables mais non optimales. De préférence avec une garantie sur l'écart entre la valeur de la solution trouvée et la valeur de la solution optimale.
La terminologie suivante est adoptée :
* on appelle ''efficacité'' d'un objet le rapport de sa valeur sur son poids. Plus la valeur de l'objet est importante par rapport à ce qu'il consomme, plus l'objet est intéressant ;
=== Algorithme glouton ===
L'algorithme le plus simple est un [[algorithme glouton]]. L'idée est d'ajouter en priorité les objets les plus efficaces, jusqu'à saturation du sac :
trier les objets par ordre décroissant d'efficacité
w_conso := 0
'''pour''' i '''de''' 1 '''à''' n
'''si''' w[i] + w_conso <= W '''alors'''
x[i] := 1
w_conso := w_conso + w[i]
'''sinon'''
x[i] := 0
'''fin si'''
'''fin pour'''
[[Image:Knapsack greedy.svg|right|thumb|Les deux phases de l'algorithme glouton. À gauche : tri des boîtes par ordre d'intérêt (ici en dollars par kilogramme). À droite : insertion dans l'ordre des boîtes, si cela est possible. On obtient ici une solution de 11$ pour 11 kg alors que la solution optimale est de 12 $ et 14 kg.]]
==== Analyse de l'algorithme glouton ====
On notera <math>z^*</math> la valeur des solutions optimales.
La solution <math>X</math> retournée par l'algorithme glouton peut être d'aussi mauvaise qualité que possible. Considérons par exemple que nous n'ayons que deux objets à placer dans le sac. Le premier a un profit de 2 et un poids de 1, le deuxième a un profit et un poids tous deux égaux à ''W''. Le premier objet est le plus efficace, il sera choisi en premier et empêchera la prise du second, donnant ainsi une solution de valeur 1 alors que la solution optimale vaut ''W''. Il existe donc des valeurs du problème pour lesquelles le rapport entre la solution trouvée et la solution optimale est aussi proche de zéro que possible.
Il existe d'autres [[algorithme d'approximation|algorithmes d'approximation]] pour le problème de sac à dos permettant d'avoir une solution garantie à une distance <math>k</math> ou à un rapport <math>\epsilon</math> de la qualité de solution optimale. C’est-à-dire que la solution <math>X</math> trouvée est telle que <math>z^* - z(X) \le k</math> ou <math>\frac{z(X)}{z^*} \le 1 - \epsilon</math>. La [[complexité algorithmique|complexité]] de ces algorithmes est, en général, fonction de l'inverse de la qualité attendue ; par exemple <math>O(n^\frac{1}{\epsilon})</math> ou <math>O(n^2 + \frac{1}{\epsilon^2})</math>. Les temps d'exécution peuvent être très conséquents.
=== Métaheuristiques ===
Les méthodes [[métaheuristique]]s comme les [[algorithme génétique|algorithmes génétiques]] ou les optimisations basées sur des [[Algorithme de colonies de fourmis|algorithmes de colonies de fourmis]] permettent d'obtenir une approximation raisonnable tout en évitant de monopoliser trop de ressources.
==== Algorithme génétique ====
[[Image:Knapsack ga.svg|thumb|right|Exemple de l'évolution d'une population avec un algorithme génétique. Les objets sont ceux utilisés pour l'exemple de l'algorithme glouton. Par exemple, le génome (0,1,0,1,0) correspond à une sélection de la boîte de 12 kg et celle de 7 kg.]]
Les [[algorithme génétique|algorithmes génétiques]] sont souvent utilisés dans les problèmes d'optimisation difficiles comme celui du sac à dos. Ils sont relativement faciles à mettre en œuvre et permettent d'obtenir rapidement une solution satisfaisante même si la taille du problème est importante.
On génère une population d'individus dont les chromosomes symbolisent une solution du problème. La représentation d'un individu est binaire puisque chaque objet sera soit retenu, soit écarté du sac. Le nombre de bits dans le génome de chaque individu correspond au nombre d'objets disponibles.
L'optimisation suit les principes habituels de l'algorithme génétique. Les individus sont évalués puis les meilleurs sont retenus pour la reproduction. Selon l'évolution retenue, les opérateurs de reproduction peuvent être plus ou moins complexes (cross-over), des mutations peuvent également intervenir (remplacement d'un 0 par 1 ou l'inverse). On peut également décider de copier le meilleur individu pour la génération suivante (élitisme). Après un certain nombre de générations, la population tend vers un optimum, voire la solution exacte.
==== Algorithmes basés sur les colonies de fourmis ====
[[Image:Knapsack ants.svg|thumb|right|Analogie avec les fourmis : le chemin qui mène à la goutte de miel (très sucrée) reçoit plus de phéromones que ceux qui mènent aux gouttes d'eau peu sucrée, plus grandes mais moins intéressantes pour la colonie qui a des ressources limitées (nombre de fourmis ou emplacement de stockage disponible)]]
Ce concept a été utilisé pour résoudre le problème du sac à dos multidimensionnel où plusieurs contraintes doivent être satisfaites.
Les premiers algorithmes s'appuyaient sur l'idée de l'algorithme glouton : les fourmis sélectionnaient progressivement les objets les plus intéressants. Cette sélection peut varier mais se base toujours sur des traces de phéromones déposées par les fourmis et qui conditionnent les choix ultérieurs. Parmi les solutions proposées, on peut citer le dépôt de phéromone sur les meilleurs objets, le dépôt sur des paires d'objets insérés l'un après l'autre dans la solution ou encore l'ajout de phéromones sur des paires d'objets, indépendamment de l'ordre d'insertion.
Une synthèse réalisée par des chercheurs tunisiens et français a montré que l'algorithme qui consiste à laisser des traces sur les paires d'objets successivement sélectionnés s'avère moins efficace que les variantes qui se focalisent sur un objet ou des paires quelconques <ref>http://www710.univ-lyon1.fr/~csolnon/publications/TSI2006.pdf</ref>. Les améliorations restent toutefois possibles puisque ces algorithmes pourraient être combinés à d'autres métaheuristiques afin de s'approcher de la solution optimale.
== Résolution exacte ==
Le problème du sac à dos, dans sa version classique, a été étudié en profondeur. Il existe donc de nombreuses méthodes aujourd'hui pour le résoudre. La plupart de ces méthodes correspondent à une version améliorée d'une des méthodes suivantes.
=== Programmation dynamique ===
{{Article détaillé|programmation dynamique}}
Le problème du sac à dos possède la propriété de ''sous-structure optimale'', c'est-à-dire que l'on peut construire la solution optimale du problème à ''i'' variables à partir du problème à ''i-1'' variables. Cette propriété permet d'utiliser une méthode de résolution par [[programmation dynamique]].
On notera ''KP(i,c)'' le problème réduit à ''i'' variables et à contenance ''c''. L'idée est la suivante :
Étant donné une variable ''i'' et une contenance ''c'', les solutions optimales de ''KP(i,c)'' sont soit :
* les solutions optimales du problème à ''i-1'' variables avec la même contenance ''c'' (''KP(i-1,c)''), auxquelles on ajoute <math>x_i=0</math> ;
* les solutions optimales du problème à ''i-1'' variables avec la contenance <math>c - w_i</math> (''KP(i-1,<math>c - w_i</math>)''), auxquelles on ajoute <math>x_i=1</math>.
Le problème sac à dos à zéro variable (''KP(0,*)'') a une solution optimale de valeur nulle.
On construit alors une table T[i,c] contenant la valeur des solutions optimales de tout problème ''KP(i,c)'' de la manière suivante :
'''pour''' c '''de''' 0 '''à''' W '''faire'''
T[0,c] := 0
'''fin pour'''
'''pour''' i '''de''' 1 '''à''' n '''faire'''
'''pour''' c '''de''' 0 '''à''' W '''faire'''
'''si''' c>=w[i] '''alors'''
T[i,c] := max( T[i-1,c], T[i-1, c-w[i]] + p[i] )
'''sinon'''
T[i,c] := T[i-1,c]
'''fin si'''
'''fin pour'''
'''fin pour'''
Une fois la table construite, il suffit de démarrer de la case de T[n,W] et de déduire l'état des objets en remontant jusqu'à une case T[0,*].
Cet algorithme a une [[complexité des algorithmes|complexité]] temporelle et spatiale en <math>O(nW)</math>. Cependant, on peut ramener la consommation de [[mémoire informatique|mémoire]] à <math>O(n + W)</math>, voire même, si seule la valeur de la solution optimale est importante, à <math>O(W)</math>. Il a deux avantages :
# rapidité ;
# pas besoin de trier les variables.
et un inconvénient :
# gourmand en mémoire (donc pas de résolution de problèmes de grande taille).
Cette approche nous vient de [[Robert Shaun Garfinkel|Garfinkel]] et [[George Lann Nemhauser|Nemhauser]] (1972)<ref>{{en}} R. S. Garfinkel et G. L. Nemhauser. ''Integer Pogramming''. John Wiley & Sons, New York, 1972. ISBN 0-471-29195-1.</ref>.
=== Procédure de séparation et d'évaluation ===
Comme tout problème combinatoire, le problème de sac à dos peut être résolu à l'aide d'une [[Séparation et évaluation|procédure de séparation et d'évaluation]] (PSE). La [[fonction d'évaluation d'un nœud]] consiste souvent à résoudre le problème en variables continues (voir plus bas). L'implémentation proposée par [[Silvano Martello|Martello]] et [[Paolo Toth|Toth]] (1990) <ref name="martello_toth_90">{{en}} Silvano Martello et Paolo Toth, ''Knapsack Problems: Algorithms and Computer Implementations'', John Wiley & Sons, 1990 {{ISBN|0471924202}}.</ref> est devenue une référence. Elle se distingue par :
* une évaluation des nœuds améliorée ;
* une [[algorithme de recherche|recherche locale]] lorsque la dernière variable ajoutée au sac a amené à un échec ;
* la nécessité d'un effort intellectuel considérable pour comprendre leur [[code source]].
L'avantage de cette méthode est la faible consommation de [[Mémoire informatique|mémoire]].
=== Approches hybrides ===
L'approche hybride n'est pas réellement une nouvelle méthode de résolution. Elle consiste simplement à combiner les deux méthodes précédentes afin d'en tirer tous les avantages. Typiquement, on va appliquer une PSE jusqu'à une profondeur de recherche où le sous-problème sera jugé assez petit pour pouvoir être résolu par programmation dynamique.
Les précurseurs de cette approche sont [[Gérard Plateau|Plateau]] et [[Moussa Elkihel|Elkihel]] (1985) <ref> {{en}} Plateau, Gérard; Elkihel, Moussa. ''A hybrid method for the 0-1 knapsack problem''. Methods Oper. Res. 49, 277-293, 1985.</ref>, suivis par Martello et Toth (1990) <ref name="martello_toth_90" />. Il y a eu d'autres améliorations depuis.
== Variantes ==
Le problème présenté jusqu'ici est, plus précisément, le problème de sac à dos en variables binaires (''01KP''). Il s'agit en fait d'une variante parmi d'autres. Cette section présente ces différentes variantes. Les particularités se font sur le domaine des variables, le nombre de valeurs des objets, le nombre de dimensions du sac, etc. Ces particularités peuvent aussi être combinées.
=== Variables continues ===
Le problème du sac à dos en variables continues (''LKP'') est obtenu en enlevant la contrainte d'intégrité sur les variables. C’est-à-dire que l'on s'autorise à ne prendre qu'une fraction des objets dans le sac à dos : <math>x_i \in [0,1]</math>. Contrairement à ''KP'', ''LKP'' appartient à la [[Complexite_P#Classe_P|classe de complexité P]].
Voici un algorithme permettant de calculer une solution optimale du problème ''LKP'' :
trier les objets en ordre décroissant d'efficacité
i := 1
w_dispo := W
'''tant que''' w_dispo >= w[i] '''faire'''
x[i] := 1
w_dispo := w_dispo - w[i]
i := i + 1
'''fin tant que'''
x[i] := w_dispo / w[i]
'''tant que''' i < n '''faire'''
i := i + 1
x[i] := 0
'''fin tant que'''
On remarquera que la valeur de la solution optimale de ''LKP'' est au plus égale au double de la valeur de la solution optimale du problème ''KP'' correspondant : <math>z^*(LKP) \le 2 \times z^*(KP)</math>
=== Variables entières ===
Dans le problème de sac à dos en variables entières, on considère que l'on a plusieurs exemplaires de chaque objet. Le problème consiste donc à trouver le nombre d'exemplaires à prendre pour chacun.
Si le nombre d'exemplaires est limité, on parlera de sac à dos borné (''BKP''), sinon on parlera de sac à dos non borné (''UKP''). Le problème ''BKP'' peut être transformé en ''01KP'' sans difficulté.
=== Sac à dos multidimensionnel ===
On considère ici que le sac à dos a ''d'' dimensions, avec ''d > 0'' (''d-KP''). Par exemple, on peut imaginer une boîte. Chaque objet a trois dimensions, et il ne faut déborder sur aucune des dimensions. La contrainte (1) est alors remplacée par :
* <math>\sum_{i=1}^n x_iw_i^k \le W^k, \forall k \in \{1,\dots,d\}</math>
==== Utilisation pratique ====
En pratique, la version multidimensionnelle peut servir à modéliser et résoudre le problème du remplissage d'un container dont le volume et la charge maximale sont limitées.
Un autre exemple est celui de la gestion de personnel. Dans une version simplifiée, on estime la productivité ou la compétence de chaque personne (son « poids » dans le problème), et on lui attribue d'autres variables : son coût et sa disponibilité. Chacun de ces paramètres représente une dimension du sac à dos. On définit finalement les contraintes liées à son projet eu égard les paramètres précédents : le budget disponible et le temps imparti pour réaliser le travail. La résolution permet de déterminer quelles personnes doivent être retenues pour réaliser le projet.
=== Sac à dos multi-objectif ===
Une variante du problème consiste, à partir d'objets ayant plusieurs valeurs, à maximiser plusieurs fonctions objectifs, c'est le ''problème du sac à dos multi-objectif'' (''MOKP''). On rentre donc dans le domaine de l'[[optimisation multi-objectif]].
Par exemple, supposons que vous lanciez une [[Entreprise|société]] spécialisée dans les [[Navire de croisière|croisière]]s. Pour vous faire connaître, vous décidez d'inviter des gens célèbres à bord de votre plus beau [[bateau]]. Ce bateau ne peut supporter plus d'une [[Kilogramme|tonne]] de passagers (ce sera la constante ''W''). Chaque passager a une [[masse]] (''w{{ind|i}}''), vous apporte de la [[publicité]] par sa [[wikt:popularité|popularité]] (''p{{ind|i}}{{exp|1}}'' : indice de popularité) et vous demande un [[salaire]] (''p{{ind|i}}{{exp|2}}'' : salaire négatif). Vous voulez, bien sûr, maximiser la publicité apportée et minimiser le salaire total à payer (maximiser le salaire négatif). De plus vous voulez avoir un maximum de gens sur votre bateau (''p{{ind|i}}{{exp|3}}'' = 1). Vous avez donc trois sommes à maximiser.
En termes mathématiques, vous cherchez le vecteur ''X'' de gens célèbres satisfaisant le problème :
* max <math>z^1(X) = \sum_{i=1}^n x_ip_i^1</math> : on veut une popularité maximale ;
* max <math>z^2(X) = \sum_{i=1}^n x_ip_i^2</math> : minimiser le salaire à payer (maximiser le salaire négatif) ;
* max <math>z^3(X) = \sum_{i=1}^n x_ip_i^3</math> : et avoir un maximum de gens sur le bateau
sous contraintes
* <math>\sum_{i=1}^n x_iw_i \le W</math> : le bateau ne doit pas couler.
D'une manière générale, on remplace la fonction objectif du problème initial par une famille de fonctions objectifs :
* max <math>z^j(X) = \sum_{i=1}^n x_ip_i^j, \forall j \in \{1, \dots, m\}</math>
=== Sac à dos quadratique ===
Le problème de sac à dos quadratique est noté ''QKP''.
On a ici un gain <math>g_{ij}</math> supplémentaire lorsque deux objets (''i'' et ''j'') sont pris simultanément. Par exemple, disons que vous souhaitiez maximiser la qualité de votre [[café]] lors d'une expédition avec un sac à dos. On peut comprendre qu'il est plus intéressant d'apporter une [[cuillère]] '''et''' un [[sucre]] plutôt qu'un seul des deux.
La fonction objectif s'écrit alors :
*max <math>z(X) = \sum_{i=1}^n x_ip_i + \sum_{i=1}^n\sum_{j=1}^n x_ix_jg_{ij}</math>
=== Problème de la somme de sous-ensembles ou du ''subset sum'' ===
La particularité du [[problème de la somme de sous-ensembles]] (en anglais : ''subset sum'') est que la valeur et le poids des objets sont identiques (<math>p_i = w_i</math>). C'est un problème important du domaine de la [[cryptographie]], utilisé dans plusieurs systèmes de génération de [[Cryptographie asymétrique|clé publique]].
=== Sac à dos à choix multiple ===
Dans le problème de sac à dos à choix multiple (''MCKP''), les objets sont regroupés en classes, et il ne faut prendre qu'un seul représentant pour chaque classe.
Par exemple, vous êtes en train de confectionner votre [[boîte à outils]]. Si vous avez cinq [[Clef (outils)|clés à molette]]. Vous pouvez soit choisir la plus légère, afin de prendre un [[Marteau (outil)|marteau]] performant, ou alors choisir la clé la plus performante et un marteau bas de gamme, ou alors faire un compromis. L'idée générale est que vous ne pouvez pas prendre plus d'une clé, ni plus d'un marteau.
Si les objets sont rangés en ''k'' classes, on notera <math>N_j, j \in \{1,\dots, k\}</math> l'ensemble des indices des objets appartenant à la classe ''j''. On considère, bien entendu, qu'un objet n'appartient qu'à une unique classe. La formulation du problème devient :
*max <math>z(X) = \sum_{j=1}^k\sum_{i \in N_j} x_i^jp_i^j</math>
sous contraintes :
*<math>\sum_{j=1}^k\sum_{i \in N_j} x_i^jw_i^j \le W</math> : on ne dépasse pas la capacité du sac ;
*<math>\sum_{i \in N_j} x_i^j \le 1, \forall j \in \{1, \dots, k\}</math> : on choisit au plus un représentant de chaque classe.
=== Sac à dos multiple ===
Le problème de sac à dos multiple (''MKP'') consiste à répartir un ensemble d'objets dans plusieurs sacs à dos de capacités différentes. La valeur d'un objet dépend maintenant du sac dans lequel il est placé. Par exemple, on peut considérer qu'un [[euro]] a plus de valeur sur un [[compte d'épargne]] que sur un [[compte courant]].
Si on a ''k'' sacs à dos, on notera <math>x_i^j=1</math> si l'objet ''i'' est placé dans le sac ''j''. La formulation du problème devient :
*max <math>z(X) = \sum_{j=1}^k\sum_{i=1}^n x_i^jp_i^j</math>
sous contraintes
*<math>\sum_{i=1}^n x_i^jw_i \le W^j, \forall j \in \{1, \dots, k\}</math> : on ne dépasse pas la capacité des sacs ;
*<math>\sum_{j=1}^k x_i^j \le 1, \forall i \in \{1, \dots, n\}</math> : un objet n'est mis que dans un sac.
Il existe une variante de ce problème dans laquelle tous les sacs ont la même capacité, on le note ''MKP-I''.
== Voir aussi ==
* [[Problème du voyageur de commerce]]
== Bibliographie ==
* {{en}} Hans Kellerer, Ulright Pferschy et David Pisinger, ''Knapsack Problems'', Springer, 2004 {{ISBN|3-540-40286-1}}.
<references />
{{Portail|informatique|mathématiques}}
{{Article de qualité|oldid=8250149|date=28 août 2006 }}
[[Catégorie:Optimisation]]
[[Catégorie:Analyse combinatoire]]
[[Catégorie:Problème NP-complet|Sac à dos]]
[[Catégorie:Recherche opérationnelle]]
[[Catégorie:Logique mathématique]]
[[cs:Problém batohu]]
[[de:Rucksackproblem]]
[[en:Knapsack problem]]
[[es:Problema de la mochila]]
[[fa:مسئله کولهپشتی]]
[[it:Problema dello zaino]]
[[ja:ナップサック問題]]
[[ko:배낭 문제]]
[[nl:Knapzakprobleem]]
[[pl:Problem plecakowy]]
[[pt:Problema da mochila]]
[[ru:Задача о ранце]]
[[sv:Kappsäcksproblemet]]
[[tr:Knapsack problemi]]
[[vi:Bài toán xếp ba lô]]
[[zh:背包问题]]