Qu'est-ce qu'un algorithme glouton ? Le principe de la myopie stratégique
Imagine que tu sois devant un buffet avec une assiette de taille limitée. Ta stratégie pour maximiser ton plaisir ? Prendre d'abord le morceau de gâteau qui te fait le plus envie, puis le suivant, et ainsi de suite, sans vraiment te soucier de l'ensemble. C'est exactement le principe d'un algorithme glouton (ou greedy algorithm en anglais).
En informatique, et plus particulièrement dans ton programme de NSI, un algorithme glouton est une méthode de résolution de problème qui, à chaque étape, fait le choix localement optimal, c'est-à-dire le meilleur choix sur le moment, avec l'espoir que cet enchaînement de choix locaux conduira à une solution globalement optimale (ou au moins très bonne).
Le cœur de la stratégie gloutonne : "Fais le meilleur choix possible maintenant, et ne regarde pas en arrière."
Cette approche "myope" présente un énorme avantage : la simplicité et la rapidité. Contrairement à des algorithmes qui explorent toutes les possibilités (comme le backtracking), un algorithme glouton avance droit devant lui, ce qui le rend souvent très efficace en temps de calcul. Cependant, cette myopie est aussi son talon d'Achille : elle ne garantit pas toujours la solution parfaite.
Des exemples classiques pour bien comprendre
Voyons deux problèmes célèbres où les algorithmes gloutons brillent, que tu es susceptible de rencontrer en NSI.
Le problème du rendu de monnaie
C'est l'exemple pédagogique par excellence. Le but : rendre une somme donnée avec le minimum de pièces et de billets possibles. Dans le système euro, la stratégie gloutonne est optimale : on prend toujours la plus grande valeur possible qui ne dépasse pas la somme restante à rendre.
Voici un exemple d'implémentation en Python :
def rendu_monnaie(somme_a_rendre, systeme_monnaie=[200, 100, 50, 20, 10, 5, 2, 1]):
"""Retourne une liste du nombre de pièces/billets pour rendre la somme."""
rendu = []
for valeur in systeme_monnaie:
if somme_a_rendre <= 0:
break
nombre = somme_a_rendre // valeur # Division entière
if nombre > 0:
rendu.append((valeur, nombre))
somme_a_rendre -= nombre * valeur
return rendu
# Exemple d'utilisation
print(rendu_monnaie(289))
# Affiche : [(200, 1), (50, 1), (20, 1), (10, 1), (5, 1), (2, 2)]Attention ! Cet algorithme est optimal pour le système euro, mais pas pour n'importe quel système. Si tu avais des pièces de valeurs [30, 20, 1] et que tu devais rendre 40, l'algorithme glouton choisirait 30+1+1+1+1+1+1+1+1+1 (10 pièces) alors que la solution optimale est 20+20 (2 pièces). C'est une leçon cruciale : un algorithme glouton doit être prouvé pour le problème auquel on l'applique.
Le problème du sac à dos (version fractionnaire)
Imagine un cambrioleur (très rationnel) devant des objets de valeurs et poids différents. Son sac a une capacité limitée. Dans la version fractionnaire du problème, il peut prendre des fractions d'objet (comme du sable ou des céréales). La stratégie gloutonne optimale ici est de trier les objets par leur rapport valeur/poids (la "densité" de valeur) et de remplir le sac en commençant par ceux qui ont le meilleur rapport.
Cet algorithme est à la fois simple et optimal pour la version fractionnaire, ce qui en fait un parfait exemple de réussite gloutonne.
Quand utiliser un algorithme glouton ? Les critères à connaître
Tu ne peux pas appliquer un algorithme glouton à n'importe quel problème. Pour qu'il soit valide (et surtout optimal), le problème doit généralement présenter deux propriétés :
- La propriété du choix glouton : On peut construire une solution globale optimale en commençant par un choix local optimal. En clair, faire le meilleur choix sur le moment ne nous enferme pas dans une mauvaise solution plus tard.
- La sous-structure optimale : La solution optimale du problème global contient les solutions optimales de ses sous-problèmes. C'est une propriété qu'il partage avec la programmation dynamique.
Les algorithmes gloutons sont particulièrement adaptés aux problèmes :
- d'ordonnancement (planifier des tâches sur une machine),
- de compression de données (comme le codage de Huffman, au programme de NSI),
- de recherche d'arbres couvrants minimaux (les algorithmes de Prim et Kruskal, que tu verras peut-être),
- de parcours de graphes (comme l'algorithme de Dijkstra pour les plus courts chemins, sous certaines conditions).
Les pièges et les limites : quand le glouton échoue
Comme évoqué, la force du glouton est aussi sa faiblesse. Son incapacité à revenir en arrière ou à anticiper le futur le rend inadapté à de nombreux problèmes. Un exemple célèbre est le problème du voyageur de commerce (trouver le plus court circuit passant par toutes les villes). Une approche gloutonne simple ("va toujours vers la ville la plus proche non visitée") donne souvent un résultat très médiocre.
De même, pour le problème du sac à dos en version 0/1 (où on prend un objet en entier ou pas du tout), l'algorithme glouton basé sur le rapport valeur/poids n'est pas optimal. Il faut alors se tourner vers d'autres paradigmes, comme la programmation dynamique.
La leçon est claire : toujours analyser et, si possible, prouver mathématiquement que l'approche gloutonne est adaptée à ton problème avant de l'implémenter. En NSI, on te demandera souvent de réfléchir à cette optimalité.
Implémentation en Python : structure et conseils
La structure d'un algorithme glouton en Python suit souvent ce schéma :
- Préparation/Tri des données : Souvent, il faut trier les éléments selon un critère pertinent (valeur, ratio, date limite...).
- Initialisation : On prépare la solution (une liste vide, un compteur...).
- Boucle gloutonne : On parcourt les données (souvent dans l'ordre du tri) et à chaque étape, on applique la règle de choix local.
- Vérification de la contrainte : Avant d'ajouter un choix à la solution, on vérifie qu'il respecte les limites du problème (poids du sac, temps disponible...).
Voici un squelette générique :
def algorithme_glouton(donnees):
# 1. Trier les données selon un critère pertinent
donnees_triees = sorted(donnees, key=critere, reverse=True)
solution = [] # 2. Initialisation
capacite_restante = CAPACITE_MAX
# 3. Boucle gloutonne
for element in donnees_triees:
# 4. Vérifier si on peut prendre cet élément
if element respecte les contraintes avec capacite_restante:
solution.append(element)
capacite_restante -= cout(element)
return solutionConseil de debug : Quand ton algorithme glouton donne un mauvais résultat, teste-le sur de petits jeux de données où tu connais la solution optimale. Trace l'exécution pour voir où il fait un choix sous-optimal.
Conclusion : un outil puissant dans ta boîte à outils NSI
Les algorithmes gloutons sont des outils élégants, simples et efficaces pour une classe importante de problèmes. Leur compréhension est fondamentale en NSI, car elle illustre un paradigme algorithmique majeur : faire des choix irrévocables basés sur l'information immédiate.
Retiens ceci : utilise un algorithme glouton quand le problème a les propriétés adéquates et que tu cherches une solution rapide à implémenter et souvent très proche de l'optimum. Mais reste critique et demande-toi toujours : "Est-ce que ce choix localement optimal me garantit le meilleur résultat à la fin ?"
Pour t'entraîner, cherche des exercices sur le rendu de monnaie, l'ordonnancement de tâches avec pénalités ou l'optimisation de ressources. Bon courage dans ton exploration du monde des algorithmes !
