Les algorithmes gloutons — Greedy Algorithms
Introduction et principe fondamental
« À chaque étape, il choisit le meilleur choix possible selon un critère local, sans jamais remettre en question les choix précédents. »
L'algorithme avance pas à pas, en effectuant des choix immédiats qui semblent les plus avantageux sur le moment — d'où le terme « glouton » (celui qui « mange » tout de suite ce qui paraît le meilleur).
Ce principe repose sur l'idée qu'un bon choix local conduit à une solution globale optimale, ce qui est vrai pour certains problèmes… mais pas pour tous.
Exemple — Chemin de somme maximale
Dans l'arbre binaire ci-dessous, trouver le chemin allant de la racine à une feuille qui donne la plus grande somme des valeurs des nœuds traversés.
8
/ \
2 5
/ \ / \
21 12 16 12Approche gloutonne — à chaque nœud, choisir le fils de plus grande valeur :
- À la racine (8) : 5 > 2 → on choisit le fils droit 5
- Au nœud (5) : 16 > 12 → on choisit le fils gauche 16
Chemin glouton : 8 → 5 → 16 — Somme = 29
Solution optimale — en examinant tous les chemins :
| Chemin | Calcul | Somme |
|---|---|---|
| 8 → 2 → 21 | 8 + 2 + 21 | 31 ← Optimal |
| 8 → 2 → 12 | 8 + 2 + 12 | 22 |
| 8 → 5 → 16 | 8 + 5 + 16 | 29 ← Glouton ✗ |
| 8 → 5 → 12 | 8 + 5 + 12 | 25 |
Bien que le nœud 5 soit plus grand que le nœud 2, ce dernier donne accès à la feuille 21 qui compense largement son désavantage initial. Un sacrifice à court terme peut mener à un bénéfice supérieur à long terme.
Exemple — Rendu de monnaie
On doit rendre 63 DH avec des pièces de valeurs : 1, 2, 5, 10, 20 DH. Stratégie gloutonne : toujours prendre la plus grande pièce ≤ montant restant.
| Reste à rendre | Pièce choisie | Nombre de pièces |
|---|---|---|
| 63 DH | 20 DH | 1 |
| 43 DH | 20 DH | 2 |
| 23 DH | 20 DH | 3 |
| 3 DH | 2 DH | 4 |
| 1 DH | 1 DH | 5 ← Optimal ✓ |
| Approche gloutonne | Solution optimale | |
|---|---|---|
| Décomposition | 4 + 1 + 1 = 6 | 3 + 3 = 6 |
| Nombre de pièces | 3 pièces ✗ | 2 pièces ✓ |
Ce contre-exemple montre que la stratégie gloutonne n'est pas universellement optimale. Quand elle échoue, on recourt à la programmation dynamique.
Structure générale d'un algorithme glouton
- Initialiser la solution (souvent vide).
- Répétertant que la solution n'est pas complète :
- Sélectionner le meilleur choix local selon un critère précis (le plus petit coût, la plus grande valeur, le plus court temps, etc.).
- Vérifier si ce choix est compatible avec les choix déjà effectués.
- Ajouter ce choix à la solution.
- Retourner la solution construite.
def glouton(donnees):
solution = []
while not solution_complete(solution):
choix = meilleur_choix(donnees, solution)
if choix_valide(choix, solution):
solution.append(choix)
return solutionComplexité typique : O(n log n) pour le tri + O(n) pour le parcours = O(n log n) au total.
def algorithme_glouton(donnees):
# ÉTAPE 1 : TRI selon le critère glouton — O(n log n)
donnees_triees = sorted(donnees, key=critere_glouton)
# ÉTAPE 2 : TRAITEMENT LINÉAIRE — O(n)
solution = []
for element in donnees_triees:
if condition_faisabilite(solution, element):
solution.append(element)
return solutionConditions de réussite
Un algorithme glouton donne une solution optimale seulement si le problème satisfait deux propriétés fondamentales :
Le choix fait à l'étape actuelle doit être sans danger (safe) et contribuer directement à l'optimalité finale.
Si on effectue le meilleur choix glouton puis qu'on résout le sous-problème restant de façon optimale, la combinaison donne la solution optimale du problème initial.
| Propriétés vérifiées ? | Résultat | Avantages | Alternative si non |
|---|---|---|---|
| ✅ Les deux | Solution optimale garantie | Simple, efficace, O(n log n), pas de mémoïsation | — |
| ❌ L'une ou les deux | Correcte mais non optimale | — | Programmation dynamique |
Étapes de construction d'un algorithme glouton
Méthodologie en 4 étapes
| Étape | Question clé | Rendu de monnaie | Sac à dos fractionnaire |
|---|---|---|---|
| 1. Définir le problème | Éléments ? Contrainte ? But ? | Pièces, somme = M, min pièces | n objets (vᵢ, wᵢ), poids ≤ C, max valeur |
| 2. Identifier les sous-problèmes | Quelle décision à chaque étape ? | Quelle pièce choisir ? | Quel objet (fraction) ajouter ? |
| 3. Définir la fonction objectif | Que minimise/maximise-t-on ? | Minimiser Σxᵢ | Maximiser Σvᵢ·xᵢ |
| 4. Déterminer le choix glouton | Quel critère local ? | Plus grande pièce ≤ reste | Plus grand ratio vᵢ/wᵢ décroissant |
Étape 1 — Définir le problème
Formuler clairement : quels sont les éléments, la contrainte principale, et le but à atteindre ?
Rendu de monnaie
On dispose de pièces de valeurs données (10, 5, 1 DH…). On veut rendre un montant M en utilisant le minimum de pièces. Objectif : minimiser le nombre de pièces Contrainte : somme des pièces = M
Sac à dos fractionnaire
On dispose de n objets, chacun avec une valeur vᵢ et un poids wᵢ. Le sac a une capacité maximale C. On peut prendre des fractions. Objectif : maximiser la valeur totale Contrainte : somme des poids ≤ C
Étape 3 — Définir la fonction objectif
Sac à dos fractionnaire : maximiser la valeur totale : $$\text{Maximiser } f = \sum_{i=1}^{n} v_i \cdot x_i \quad \text{sous contrainte} \quad \sum_{i=1}^{n} w_i \cdot x_i \le C, \quad 0 \le x_i \le 1$$
Étape 4 — Déterminer le choix glouton
C'est le cœur de l'algorithme : identifier la règle locale qui permet le meilleur choix immédiat. Ce choix doit être simple à évaluer, localement optimal et répété jusqu'à satisfaction des contraintes.
| Problème | Critère glouton | Justification |
|---|---|---|
| Rendu de monnaie | Plus grande pièce \(v_i \le\) reste | Réduit le reste au maximum à chaque étape |
| Sac à dos fractionnaire | Ratio \(r_i = v_i / w_i\) décroissant | Maximise la valeur par unité de poids |
Cas 1 — Ordonnancement de tâches pondérées
On dispose de \(n\) tâches \(T = \{T_1, T_2, \dots, T_n\}\), chacune caractérisée par une durée \(d_i > 0\) et un poids \(p_i\) (importance, priorité).
Objectif : minimiser la somme pondérée des temps d'achèvement : $$\text{Minimiser } \sum_{i=1}^{n} p_i \times C_i$$ avec \(C_i\) = durée cumulée jusqu'à la fin de la tâche \(T_i\). Si les poids varient, il faut terminer plus tôt les tâches les plus importantes.
Intuition : une tâche courte et importante doit passer avant les tâches longues et peu prioritaires pour réduire le temps d'attente pondéré des suivantes.
Exemple — 3 tâches A, B, C
| Tâche | Durée dᵢ | Poids pᵢ | Rapport dᵢ/pᵢ |
|---|---|---|---|
| A | 3 | 2 | 1.5 |
| B | 2 | 4 | 0.5 ← 1er |
| C | 4 | 1 | 4.0 |
Ordre d'exécution (dᵢ/pᵢ croissant) : B → A → C
| Ordre | Tâche | Temps cumulé Cᵢ | pᵢ × Cᵢ |
|---|---|---|---|
| 1 | B | 2 | 4 × 2 = 8 |
| 2 | A | 5 | 2 × 5 = 10 |
| 3 | C | 9 | 1 × 9 = 9 |
| Coût total | 27 ← Optimal | ||
Toute autre permutation donnerait une somme plus grande → la solution gloutonne est optimale.
def ordonnancement_glouton(taches):
"""
Ordonnancement optimal par la règle de Smith.
taches : liste de tuples (nom, durée, poids)
Trie par d_i / p_i croissant → minimise la somme pondérée des Cᵢ.
"""
taches_triees = sorted(taches, key=lambda T: T[1] / T[2])
temps, somme = 0, 0
for nom, d, p in taches_triees:
temps += d
somme += p * temps
return taches_triees, somme
# Exemple
taches = [("A", 3, 2), ("B", 2, 4), ("C", 4, 1)]
ordre, cout = ordonnancement_glouton(taches)
print(f"Ordre : {ordre}")
print(f"Coût total associé = {cout}")Ordre : [('B', 2, 4), ('A', 3, 2), ('C', 4, 1)]
Coût total associé = 27Cas 2 — Sélection d'activités
Algorithme en deux étapes :
- Trier les activités par heure de fin \(f_i\) croissante.
- Parcourir séquentiellement et sélectionner chaque activité compatible avec la dernière choisie (
debut ≥ fin_précédente).
Exemple — 10 activités
Étape 1 — Tri par fin croissante : A1 (f=4), A2 (5), A3 (6), A4 (7), A5 (9), A6 (9), A7 (10), A8 (11), A9 (12), A10 (14)
Étape 2 — Sélection gloutonne :
| Activité | Début dᵢ | Fin fᵢ | Sélectionnée ? | Raison |
|---|---|---|---|---|
| A1 | 1 | 4 | ✅ Oui | Première activité |
| A2 | 3 | 5 | ❌ Non | 3 < 4 (chevauche A1) |
| A3 | 0 | 6 | ❌ Non | 0 < 4 (chevauche A1) |
| A4 | 5 | 7 | ✅ Oui | 5 ≥ 4 (compatible) |
| A5 | 3 | 9 | ❌ Non | 3 < 7 (chevauche A4) |
| A6 | 5 | 9 | ❌ Non | 5 < 7 (chevauche A4) |
| A7 | 6 | 10 | ❌ Non | 6 < 7 (chevauche A4) |
| A8 | 8 | 11 | ✅ Oui | 8 ≥ 7 (compatible) |
| A9 | 8 | 12 | ❌ Non | 8 < 11 (chevauche A8) |
| A10 | 2 | 14 | ❌ Non | Chevauche tout |
Solution gloutonne : {A1, A4, A8} — Nombre maximal d'activités compatibles : 3
Temps : 0 2 4 6 8 10 12 14
| | | | | | | |
A1 : [=========]
A4 : [========]
A8 : [=========]
← 3 activités, aucun chevauchement ✓def selection_activites(activites):
"""
Sélectionne le maximum d'activités compatibles.
activites : liste de tuples (nom, debut, fin)
Trie par heure de fin croissante → choisit celle qui se termine le plus tôt.
"""
activites_triees = sorted(activites, key=lambda x: x[2])
selection = []
fin_precedente = 0
for nom, debut, fin in activites_triees:
if debut >= fin_precedente: # activité compatible
selection.append((nom, debut, fin))
fin_precedente = fin # mettre à jour la dernière fin
return selection
# Exemple
A = [("A1",1,4), ("A2",3,5), ("A3",0,6), ("A4",5,7),
("A5",3,9), ("A6",5,9), ("A7",6,10), ("A8",8,11),
("A9",8,12), ("A10",2,14)]
res = selection_activites(A)
print("Activités sélectionnées :", res)Activités sélectionnées : [('A1', 1, 4), ('A4', 5, 7), ('A8', 8, 11)]Récapitulatif
| Problème | Critère glouton | Objectif | Optimal ? |
|---|---|---|---|
| Rendu de monnaie (système standard) | Plus grande pièce ≤ reste | Min pièces | ✅ Oui |
| Rendu de monnaie (système quelconque) | Plus grande pièce ≤ reste | Min pièces | ❌ Pas toujours |
| Ordonnancement pondéré | Ratio dᵢ/pᵢ croissant (Smith) | Min Σpᵢ·Cᵢ | ✅ Oui |
| Sélection d'activités | Fin fᵢ croissante | Max activités | ✅ Oui |
| Sac à dos fractionnaire | Densité vᵢ/wᵢ décroissante | Max valeur | ✅ Oui |
| Sac à dos entier (0/1) | Densité vᵢ/wᵢ décroissante | Max valeur | ❌ Non → Prog. dyn. |
| Chemin somme max (arbre) | Plus grand fils local | Max somme | ❌ Non → Prog. dyn. |
- Un algorithme glouton fait le meilleur choix local à chaque étape, sans jamais revenir en arrière.
- Il est optimal si et seulement si le problème vérifie la propriété du choix glouton ET la sous-structure optimale.
- La complexité est généralement O(n log n) (dominée par le tri initial).
- Le critère de tri est le cœur du problème : identifier le bon critère garantit l'optimalité.
- Un contre-exemple suffit à montrer qu'un algorithme glouton n'est pas optimal → recourir alors à la programmation dynamique.
- Le glouton est plus simple et rapide que la programmation dynamique quand il est applicable : il ne stocke pas de solutions intermédiaires.
Discussion (0)
Soyez le premier à laisser un commentaire !
Laisser un commentaire
Votre commentaire sera visible après modération.