Bonjour,
Dans nos leçons précédentes sur la programmation dynamique, nous avons établi une méthode de travail rigoureuse : identifier la sous-structure optimale d'un problème, formuler la relation de récurrence qui en découle, puis l'implémenter via la tabulation. Vous avez appliqué avec succès cette méthode au problème de la plus longue sous-séquence commune (LCS).
Aujourd'hui, nous abordons un autre pilier des algorithmes d'optimisation, tout aussi fondamental pour l'agrégation : le problème du sac à dos 0/1 (0/1 Knapsack Problem). L'objectif de cette leçon est de se concentrer sur la première étape, la plus conceptuelle : formuler la relation de récurrence qui régit la solution de ce problème. Comprendre l'origine de cette formule est essentiel avant de passer à son implémentation.
1. Définition Formelle du Problème
Imaginons que vous êtes un cambrioleur (pour la bonne cause de l'algorithmique !) avec un sac à dos. Vous faites face à une série d'objets, chacun ayant une valeur et un poids. Votre sac a une capacité de poids limitée. Votre objectif est de choisir les objets à emporter pour maximiser la valeur totale, sans que le poids total ne dépasse la capacité du sac.
La contrainte "0/1" est cruciale : pour chaque objet, vous devez soit le prendre en entier (1), soit le laisser (0). Vous ne pouvez pas prendre une fraction d'un objet.
Formellement :
- Un ensemble de objets, numérotés de 1 à .
- Chaque objet a un poids et une valeur .
- Un sac à dos de capacité maximale en poids .
Objectif : Trouver un sous-ensemble d'objets dont la somme des poids est inférieure ou égale à et dont la somme des valeurs est maximale.
Une première intuition pourrait être une approche "gourmande" : prendre en priorité les objets avec le meilleur rapport valeur/poids. Le document suivant montre rapidement pourquoi cette approche, bien qu'optimale pour le sac à dos "fractionnaire", ne l'est pas pour notre cas 0/1.
Dynamic Programming Example: 0/1 Knapsack Problem
Pour bien poser le problème, lisons cette introduction qui définit formellement le sac à dos 0/1 et montre, avec un exemple simple, pourquoi une approche gourmande ne garantit pas la solution optimale.
Lisez la première page, des sections intitulées 'The problem' jusqu'à la question 'Will the greedy approach... produce the optimal solution here?'. Concentrez-vous sur la définition des entrées et sur l'exemple qui invalide la stratégie gourmande.
2. La Logique de la Décision : Inclure ou ne pas Inclure ?
L'échec de l'approche gourmande nous pousse à explorer toutes les solutions possibles. Pour chaque objet, nous avons deux choix : le prendre ou ne pas le prendre. Si nous avons objets, cela nous donne combinaisons possibles. Une approche par force brute consisterait à toutes les évaluer, ce qui est impraticable pour un un tant soit peu grand.
Cependant, cette structure de décision se prête bien à une formulation récursive. L'image suivante illustre l'arbre des décisions possibles.

3. Dérivation de la Relation de Récurrence
L'idée de la programmation dynamique est de résoudre le problème en se basant sur les solutions optimales de sous-problèmes plus simples. Définissons un sous-problème par :
- Le nombre d'objets que nous sommes autorisés à considérer (par exemple, les premiers objets).
- La capacité restante du sac ().
Soit V(i, w) la valeur maximale que l'on peut obtenir en utilisant un sous-ensemble des premiers objets (de 1 à ) avec un sac de capacité . Notre but final est de trouver V(n, W).
Pour calculer V(i, w), concentrons-nous sur le -ème objet. Deux cas se présentent :
-
On décide de NE PAS inclure l'objet .
Dans ce cas, la valeur maximale que l'on peut obtenir est simplement la valeur maximale que l'on pouvait déjà obtenir avec les objets précédents et la même capacité . La valeur est doncV(i-1, w). -
On décide d'inclure l'objet .
Ce n'est possible que si son poids ne dépasse pas la capacité actuelle (c'est-à-dire si ). Si on le prend, on gagne sa valeur . Il nous reste alors à remplir le reste du sac, dont la capacité est maintenant réduite à , avec les objets précédents. La valeur totale pour ce choix est doncv_i + V(i-1, w - p_i).
Puisque nous cherchons la valeur maximale, il nous suffit de prendre le meilleur de ces deux cas.
La vidéo suivante explique ce raisonnement de manière très didactique.
L-5.3: 0/1 Knapsack Problem |Dynamic Programming |Recursive Equation |Recursion Tree Time Complexity
La vidéo suivante décompose le raisonnement qui mène à la relation de récurrence. Elle est extrêmement claire et suit pas à pas la logique de la décision.
Regardez la vidéo de 00:55 à 07:56. Suivez attentivement les étapes : Le principe de base : inclure ou non l'objet n (00:55 - 02:23). Le cas où l'objet n est inclus : le profit augmente, la capacité diminue (02:23 - 04:50). Le cas où l'objet n n'est pas inclus : le profit et la capacité pour le sous-problème restent les mêmes (04:50 - 05:49). La synthèse : on prend le maximum des deux cas, et on gère les conditions aux limites (objet trop lourd, plus d'objets, capacité nulle) (05:49 - 07:56).
4. Formalisation et Interprétation
Synthétisons ce que nous venons de voir en une formule mathématique rigoureuse. En utilisant la notation V(i, w) :
Décortiquons chaque ligne :
- Cas de base (
i=0ouw=0): Si nous n'avons aucun objet à considérer ou si la capacité du sac est nulle, la valeur maximale est évidemment 0. - Cas où l'objet est trop lourd (
p_i > w): Si le poids de l'objet est supérieur à la capacité , nous ne pouvons pas l'inclure. La seule option est de ne pas le prendre. La solution est donc la même que pour le sous-problème avec objets et une capacité . - Le cas général (
p_i \le w): C'est le cœur de la récurrence. Nous avons le choix, et nous prenons l'option qui maximise la valeur :V(i-1, w): La valeur si l'on n'inclut pas l'objet .v_i + V(i-1, w - p_i): La valeur si l'on inclut l'objet .
Pour vous assurer que cette formule est bien claire, je vous invite à faire l'exercice d'interprétation proposé dans le document suivant.
Ce document en français présente la même relation de récurrence de manière très concise et propose un exercice fondamental pour valider votre compréhension. La notation diffère légèrement (Opt(i,j) au lieu de V(i,w)), ce qui est courant.
Lisez la section 1, en vous concentrant sur la relation de récurrence pour 'Opt(i,j)'. Puis, répondez mentalement à la première question de l'Exercice 1.1 : 'Interpréter cette relation de récurrence'. Assurez-vous de pouvoir expliquer chaque ligne de la formule avec vos propres mots.
Conclusion
Vous avez aujourd'hui franchi l'étape la plus importante dans la résolution du problème du sac à dos : la formulation de sa logique interne.
Points clés à retenir :
- Le problème du sac à dos 0/1 est un problème d'optimisation classique qui ne peut être résolu par une approche gourmande simple.
- Le principe de la solution repose sur une décision binaire pour chaque objet : l'inclure ou ne pas l'inclure.
- La relation de récurrence formalise cette décision en comparant la valeur optimale obtenue dans les deux cas, en se basant sur les solutions de sous-problèmes plus petits.
- La définition d'un sous-problème par
(nombre d'objets considérés, capacité restante)est la clé pour établir la propriété de sous-structure optimale.
Dans notre prochaine leçon, armés de cette relation de récurrence, nous passerons à l'étape suivante, que vous connaissez bien : l'implémentation d'une solution par tabulation. Nous construirons une table 2D pour calculer V(i, w) de manière itérative et efficace, ce qui nous donnera la valeur maximale recherchée.