Vous êtes sur la page 1sur 6

Algorithmes Gloutons

Savoirs faire :
• Justifier qu’une méthode est gloutonne.
• Donner un contre-exemple pour montrer qu’une méthode gloutonne ne donne pas toujours la solution
optimale.
• Implémenter une méthode gloutonne.

1 Problèmes d’optimisation
1.1 Définition et exemples
Un problème d’optimisation a deux caractéristiques :
• une fonction que l’on doit maximiser ou minimiser ;
• une série de contraintes auxquelles il faut satisfaire.

Exemple 1 : Le problème du rendu du monnaie : on a un système de monétaire, une somme due et une
somme donnée strictement plus grande que la somme due. Comment rendre la somme en utilisant le moins de
pièces et billets possibles ?
• fonction à minimiser : la fonction qui à une somme associe le nombre de pièces et billets.
• contrainte : la somme doit être égal à la somme à rendre c’est-à-dire égale à (somme donnée - somme due).

Exemple 2 : Le problème du sac à dos : on a un sac à dos d’une certaine capacité et un ensemble d’objets
ayant chacun une certaine dimension et une certaine valeur. On ne peut pas emporter touts les objets dans le
sac. Comment choisir les objets à emporter pour maximiser la valeur du sac ?
• fonction à maximiser : la fonction qui à un sous-ensemble d’objets emportés associe la valeur du sac ;
• contrainte : les objets doivent tenir dans le sac.

Exemple 3 : Le problème de la planification d’activités : on a un intervalle de temps et un ensemble


d’activités ayant chacune une heure de début et une heure de fin dans l’intervalle de temps. Certaines activités
ne peuvent pas être planifiées ensembles car elles se déroulent en même temps (une commence avant la fin d’une
autre ; on pourra dire qu’elles sont incompatibles). Comment choisir les activités pour en faire le plus possible
dans cet intervalle de temps ?
• fonction à maximiser : la fonction qui à un sous-ensemble d’activités associe le nombre de celles-ci ;
• contrainte : les activités choisies doivent être compatibles.

Exemple 4 : Le problème du voyageur de commerce : un voyageur de commerce part de chez lui et doit aller
dans un certain nombre de villes pour vendre des produits avant de rentrer chez lui. Comment choisir l’ordre
dans lequel les villes sont visitées pour minimiser le temps passer sur la route (ou la distance parcourue) ?
• fonction à minimiser : la fonction qui à un parcours associe le temps de celui-ci (ou la distance) ;
• contraintes : toutes les villes doivent être visitées et la ville de départ et d’arrivées est la sienne.
1.2 Représentation et algorithme force brute
On peut représenter les solutions envisagées de la résolution d’un problème à l’aide d’un arbre. Par exemple, si
on reprend le problème du rendu de monnaie, avec le système monétaire S = (1, 3, 4) et la somme 6 à rendre cela
donne :

2 3 5

1 0 2 1 2 4

0 1 0 1 0 1 3

0 0 0 0 2

Explications : La racine de l’arbre est la somme à rendre. Chaque nœud de l’arbre est la somme qu’il restera à
rendre après avoir rendu une des pièces possibles du système monétaire. Par exemple, les premiers nœuds après
6 sont, de gauche à droite, 2 car il restera 2 à rendre après avoir rendu une pièce de 4, 3 car il restera 3 à rendre
après avoir rendu une pièce de 3 et 5 car il restera 5 à rendre après avoir rendu une pièce de 1.
Chaque branche de l’arbre correspond donc à une pièce rendue.
Les feuilles, c’est-à-dire les nœuds situés aux extrémités des branches, sont forcément 0, cela voudra dire qu’on
a fini.

Commentaires :
• Pour résoudre le problème d’optimisation, ici il suffit de choisir l’une des plus petites branches de l’arbre.
Ici il n’y en a qu’une, elle est de longueur 2 : le nombre minimal de pièces à utiliser pour rendre la somme
6 avec le système S = (1, 3, 4) est égal à deux, en l’occurrence deux pièces de 3.
• L’algorithme que l’on a utilisé est qualifié de force brute, c’est-à-dire que l’on a déterminé toutes les
possibilités pour pouvoir choisir l’une des meilleures.

Avec un algorithme force brute, on est certain de résoudre le problème d’optimisation, par contre il est souvent
inutilisable en pratique car le nombre de branches devient très vite gigantesque, c’est-à-dire que sa complexité
est d’ordre exponentielle en la taille des données. Par exemple dans le problème du sac à dos, si on dispose de n
objets, il faudra pour chaque permutation de ces n objets, les ajouter un à un dans l’ordre jusqu’à ce qu’il n’y
ait plus de place dans le sac et regarder la valeur du sac ainsi obtenue. Enfin choisir l’un des sac avec la plus
grande valeur.
Or des permutations de n objets il y en a n! = n × (n − 1) × (n − 2) × · · · × 3 × 2 × 1.

Lorsque l’on observe l’arbre précédent, on se rend compte qu’il y a de nombreuses parties identiques, comme
celles entourées ci-dessous par exemple, ce qui est normal puisqu’on construit ces branches indépendamment les
unes des autres.
6

2 3 5

1 0 2 1 2 4

0 1 0 1 0 1 3

0 0 0 0 2

Or si on pouvait se souvenir de ce qui s’est passé sur les branches déjà construites, cela permettrait de ne pas
se répéter. En effet, une fois qu’on sait comment rendre la somme 3, par exemple, inutile de recommencer.
On pourrait alors obtenir un arbre tronqué. En fonction de l’ordre dans lequel on commence à construire les
branches on pourrait obtenir les deux exemples ci-dessous :

6 6

2 3 5
2 3 5

1 0 2 1 2 4
1 2 4
0 0 1 3

0 1 3

0 2

Cela réduirait considérablement le nombre de calculs. Cette méthode s’appelle la programmation dynamique
est sera étudiée en terminale.
On va envisager une autre possibilité : les algorithmes gloutons
2 Méthode gloutonne
L’idée, encore plus rapide, est de n’explorer qu’une seule branche de l’arbre ! Comme un glouton, petit animal
vorace qui veut satisfaire son appétit au plus vite, on va faire le choix localement optimal, sans revenir en arrière,
en espérant obtenir celui globalement optimal.
Dans le problème du rendu de monnaie, c’est faire comme les commerçants le font depuis très longtemps c’est-
à-dire commencer par rendre la plus grande somme possible et recommencer avec la nouvelle somme à rendre
jusqu’à ce qu’il ne reste plus rien à rendre. À chaque étape, en rendant la plus grande somme possible, on
diminue d’autant plus la somme qu’il restera à rendre et on espère donc bien utiliser le moins de pièces/billets
possibles. On fait bien un choix localement optimal en espérant obtenir celui globalement optimal.
Avec l’exemple précédent, on se rend tout de suite compte du problème :

2 3 5

1 0 2 1 2 4

0 1 0 1 0 1 3

0 0 0 0 2

On ne parcours que la branche rouge. En effet, on commence par rendre la plus grande somme possible, c’est-à-
dire 4, il reste donc 6−4 = 2 à rendre. On rend la plus grande somme possible c’est-à-dire 1, il reste donc 2−1 = 1
à rendre. On rend la plus grande somme possible c’est-à-dire 1 et on a fini.
On va donc rendre trois pièces, une de 4 et deux de 1, or ce n’est pas la solution optimale qui est de rendre
deux pièces de 3 !

Remarques :
• Le système monétaire de la zone euro est dit « canonique », c’est-à-dire que, contrairement à l’exemple
précédent, la méthode gloutonne utilisé pour rendre la monnaie donnera toujours la solution optimale.
• Ce n’était pas le cas du système monétaire anglais avant sa réforme de 1972

L’avantage d’une méthode gloutonne est sa rapidité pour obtenir une solution, l’inconvénient majeur est que
l’on n’est pas certain d’obtenir la solution optimale.
Un simple contre-exemple suffit pour montrer qu’une méthode gloutonne ne donne pas toujours la solution
optimale et il est en général très compliqué de prouver le contraire.

Mais alors pourquoi en parler ? Parce que plusieurs méthodes gloutonnes utilisées donnent des solutions opti-
males comme le rendu de monnaie dans presque tous les systèmes monétaires actuels ou bien l’algorithme de
Dijkstra pour trouver le plus court chemin dans un graphe ; et qu’il permet d’obtenir une solution quand on il
n’y a pas de méthode générale et permet d’obtenir des bornes supérieures. Par exemple l’algorithme de Prim
permettant de colorier un graphe (deux sommets adjacents ne doivent pas avoir la même couleur) ne donnera
pas toujours le nombre minimal de couleur à utiliser mais en donne une borne supérieure.
3 Exemples à connaître
3.1 Rendu de monnaie
On a déjà parlé du rendu de monnaie dans les parties précédentes mais il faut savoir le formaliser et l’implé-
menter.

Les billets et les pièces jouant le même rôle, on suppose pour simplifier qu’il n’y a que des pièces. Un système de
pièces est donc un n -uplet S = (p 1 , p 2 , · · · , p n ), où p i est la valeur de la i -ème pièce. On suppose que ces valeurs
sont des entiers strictement croissants, c’est-à-dire que p 1 < p 2 < · · · < p n , et que p 1 = 1 (sinon certaines sommes
ne peuvent pas être rendues).
Étant donné un système S et un entier positif r , le problème du rendu de monnaie consiste à trouver un n -uplet

n ∑
n
d’entiers positifs X = (x 1 , x 2 , · · · , x n ) qui minimise x i = x 1 + x 2 + · · · + x n sous la contrainte xi p i = r .
i =1 i =1

r représente la somme à rendre et x i le nombre de pièces p i à utiliser.

Une implémentation possible en Python est la suivante :

1 def rendu_monnaie(somme_a_rendre, systeme_monetaire):


2 solution = [0 for i in range(len(systeme_monetaire)]
3 i = len(systeme_monetaire) - 1
4 while somme_a_rendre > 0:
5 valeur = systeme_monetaire[i]
6 if somme_a_rendre < valeur:
7 i = i - 1
8 else:
9 solution[i] = solution[i] + 1
10 somme_a_rendre = somme_a_rendre - valeur
11 return solution

Analyse :
• Ligne 2 : on initialise la solution à une liste de 0. Il y en a autant que le nombre de pièce du système
monétaire ce qui correspondra à X dans la formalisation. On a choisi un type list plutôt qu’un type
tuple pour pouvoir modifier les valeurs.
• Ligne 3 : il faudra commencer par rendre la valeur possible or les valeurs du système monétaire sont
rangées dans l’ordre croissant donc la plus grande somme se trouve à la fin c’est-à-dire à la position
len(systeme_moneteaire) - 1.
• Ligne 4 : il n’y aura pas de boucle infinie puisque la somme à rendre est un nombre entier, il en est de
même des pièces et p 1 = 1 comme précisé dans la formalisation.
• Lignes 6 et 7 : si la somme à rendre est strictement plus petite que la valeur de la pièce alors cette pièce
ne peut pas être utilisée et il faut donc continuer avec la pièce suivante c’est-à-dire celle qui se trouve en
position i-1.
• Ligne 8, 9 et 10 : sinon on peut utiliser cette pièce. On augmente donc de 1 le nombre de pièces de cette
valeur utilisés dans la solution et on diminue d’autant la somme qu’il restera à rendre.
Par contre on ne décrémente pas i puisqu’on pourra peut-être encore utiliser une pièce de cette valeur.

3.2 Problème du sac à dos


On dipose d’un ensemble d’objets E = ((p 1 , v 1 ), · · · , (p n , v n )), où p i et v i représente respectivement le poids (ou
la dimension) et la valeur de l’objet i .
Étant donné un ensemble d’objet E et un nombre positif C , le problème du sac à dos consiste à trouver un

n ∑
n
n -uplet X = (x 1 , x 2 , · · · , x n ) qui maximise x i v i = x 1 v 1 + x 2 v 2 + · · · + x n v n sous la contrainte xi p i ⩽ C .
i =1 i =1
C représente la capacité du sac à dos, x i vaut 1 si on emporte l’objet i et 0 sinon.

Il existe plusieurs méthodes gloutonnes :


• Méthode 1 : On choisit les objets dans l’ordre décroissant de leur valeur.

C’est bien une méthode gloutonne puisque en choisissant à chaque étape l’objet ayant le plus de valeur
on espère bien maximiser la valeur sac à dos.

Une implémentation possible en Python est la suivante :

1 def sac_a_dos_1(capacite_sac, ensemble_objets):


2 ensemble_objets.sort(key = lambda objet : objet[1], reverse = True)
3 solution = [0 for i in range(len(ensemble_objets))]
4 for i in range(len(ensemble_objets):
5 p, v = ensemble_objets[i]
6 if p <= capacite_sac:
7 solution[i] = 1
8 capacite_sac = capacite_sac - p
9 return solution

Analyse :
• Ligne 2 : on tri l’ensemble des objets par ordre décroissant de leur valeur. Comme les objets sont des
couples, il n’y a pas une façon naturelle de les trier (pour vous en rendre compte, un couple peut
représenter les coordonnées d’un point dans un repère donc trier des couples reviendrait à trier des
points mais que voudrait bien dire qu’un point est plus grand qu’un autre ?). On doit donc préciser
une clé de triage, ici par une fonction lambda (c’est une fonction anonyme, c’est-à-dire une fonction
sans nom, que l’on ne souhaite pas réutiliser et que l’on définit à la volée quand le besoin s’en fait
sentir) qui associe à un objet sa valeur qui est bien objet[1]. De plus on met reverse = True pour
dire qu’on veut trier dans l’ordre décroissant.
• Il suffit de parcourir toute cette liste d’objet et pour chacun d’eux si on peut le prendre on le prend.

On se rend facilement compte que cette méthode ne donne pas toujours la solution optimale. Par exemple
avec l’ensemble d’objets E = ((6, 8), (2, 5), (3, 4)) et un sac de capacité 7 on commencerait à prendre l’objet
(6, 8) qui a la plus grande valeur mais il ne restera plus de place pour en mettre un autre. La valeur du
sac ainsi obtenue est 8 alors qu’en ne prenant que les deux autres objets, ce qui serait possible puisqu’ils
ont un poids de 5 < 7 à eux deux, on aurait un sac de valeur 5 + 4 = 9.
• Méthode 2 : On choisit les objets par ordre croissant de leur poids.

C’est bien une méthode gloutonne puisque en choisissant les objets les moins lourds, on espère bien en
prendre le plus possible et ainsi maximiser la valeur du sac.

On se rend facilement compte que cette méthode ne donne toujours pas la solution optimale. Par exemple
avec l’ensemble d’objets E = ((1, 1), (2, 1), (3, 8)) et un sac de capacité 3 on prendrait les deux premiers objets.
La valeur du sac ainsi obtenue est 1 + 1 = 2 alors qu’en prenant uniquement le dernier objet, on aurait un
sac de valeur 8.
• Méthode 3 : Le problème des deux premières méthodes est de ne prendre qu’une seule des caractéristiques
de l’objet en compte. L’objet ayant le plus de valeur peut prendre aussi beaucoup de place et inversement,
de petits objets n’ont peut-être pas beaucoup de valeur.
valeur
On va donc choisir les objets dans l’ordre décroissant du quotient c’est-à-dire qu’on commence
poids
par mettre ceux qui ont une grande valeur pour un petit poids. Une fois de plus on comprends bien
pourquoi c’est une méthode gloutonne et on comprend bien aussi pourquoi elle semble meilleure que les
deux premières.

On laisse en exercice la construction d’un contre-exemple.

Vous aimerez peut-être aussi