Les Algorithmes Gloutons: II.1 Une Résolution Brute
Les Algorithmes Gloutons: II.1 Une Résolution Brute
I Introduction
Certains algorithmes mis en oeuvre en informatique visent à résoudre des problèmes combinatoires
du type :
• À partir d’une liste de villes que l’on souhaite visiter et d’un tableau des distances entre
ces villes, trouver le circuit le plus court (dit le problème du voyageur de commerce).
• Un commerçant doit régulièrement commander des pièces et billets à la banque pour sa
caisse, ce qui est une contrainte. Il souhaite trouver une stratégie, si possible valable pour
n’importe quel système de monnaie, afin de rendre le minimum de pièces et billets aux
clients (dit le problème du commerçant ou aussi le problème du rendu de monnaie).
• À partir d’une liste de salles et d’une liste de cours (avec une heure de début et une heure de
fin), voire d’autres contraintes, déterminer un planning minimisant l’utilisation des salles.
Ce sont des problèmes d’optimisation pour lesquels on peut raisonnablement vouloir :
1. déterminer s’il existe une solution au problème posé,
2. trouver une solution (même si elle n’est pas unique) qui minimise ou maximise les critères
donnés.
La résolution des problèmes posés ci-dessus n’est pas forcément possible avec une complexité
temporelle polynomiale 1 et on préférera une stratégie permettant de fournir une solution « la
meilleure possible » selon un certain critère. Ainsi,
Les algorithmes gloutons s’appliquent lorsque :
1. la recherche d’une solution peut se ramener à une succession de choix définitifs qui
produisent petit à petit une solution,
2. on dispose d’une méthode de calcul permettant d’évaluer cette solution.
Selon le problème considéré, l’algorithme glouton sera capable (ou non) de fournir une
solution. Cette solution sera ou non optimale pour le problème considéré.
Remarque : il a été démontré que, pour un grand nombre de villes, le rapport entre la distance
de la solution gloutonne et celle de la solution optimale est dans le pire des cas proportionnel au
logarithme du nombre de villes.
— L’algorithme utilise ensuite une fonction qui parcourt la liste des cours et recherche la
première salle disponible à chaque itération. Si l’une des salles est disponible, il l’inscrit
dans le planning, sinon l’algorithme échoue (les salles sont donc toutes occupées) et il
renvoie cette information d’échec. L’algorithme produit ainsi un planning qui optimise
l’utilisation des salles.
Q14 Écrire allocation_salles(cours:list, nbre_salles:int) -> (bool, dict, int) qui,
à partir de la liste de cours, du nombre de salles disponibles (notées de 0 à nbre_salles − 1)
renvoie un tuple avec un booléen indiquant si l’algorithme a réussi ou échoué, le planning
des cours et le numéro de la dernière salle attribuée (ce qui est utile en cas d’échec).
Par exemple, print(allocation_salles(cours, 10)) renvoie (True, {0: [[4, 9, 10],
[1, 10, 11], [3, 11, 12], [6, 13, 14], [18, 14, 15], [26, 15, 16], [11, 16,
17], [0, 17, 18]], 1: [[8, 9, 10], [17, 10, 11], [9, 11, 12], [21, 13, 14],
[20, 14, 16], [13, 16, 17], [33, 17, 18]], 2: [[10, 9, 10], [22, 10, 11],
[19, 11, 12], [2, 13, 15], [29, 15, 16], [24, 16, 17], [34, 17, 18]],
3: [[27, 8, 10], [28, 10, 11], [30, 11, 12], [5, 13, 15], [12, 15, 17]],
4: [[7, 10, 12], [15, 15, 17]], 5: [[14, 10, 12], [25, 16, 17]],
6: [[16, 10, 12], [31, 16, 18]], 7: [[23, 10, 12]], 8: [[32, 10, 12]],
9: []}, 2).
Corrigé
Q1
5 def permutations ( t : list ) -> list :
6 ''' renvoie une liste contenant les permutations de t '''
7 # Pr é conditions : t est un tableau à 1 dimension
8 # Postconditions : une liste l de listes ; chacune d ' elle contient les
é l é ments de t qui ont é t é permut é s
9 if len ( t ) <= 1:
10 return [ t ]
11 else :
12 p = permutations ( t [1:])
13 l = []
14 for permut in p :
15 for i in range ( len ( permut ) +1) :
16 l . append ( permut [: i ]+[ t [0]]+ permut [ i :])
17 return l
Q2
20 def longueur ( itineraire : list , V : list ) -> int :
21 ''' renvoie la longueur d ' un circuit de la ville 0 à elle - m ê me , en
passant par les villes de la variable itineraire '''
22 # pr é conditions : la variable itineraire est suppos é e non vide
23 # postconditions : un entier positif
24 n = len ( itineraire )
25 v = itineraire [0]
26 d = V [0][ v ] # ville 0 à premi è re ville
27
28 for i in range (1 , n ) : # ville i à ville i +1
29 w = itineraire [ i ]
30 d = d + V [ v ][ w ]
31 v = w
32
Q3
41 from math import inf
42
43 def l ongueu r_min imale ( V : list ) -> int :
44 ''' renvoie la plus petite longueur parmi toutes les longueurs de
circuits '''
45 # pr é conditions : n est un entier naturel non nul
46 # postconditions : un entier positif
47 n = len ( V ) - 1
48 t = permutations ( list ( range (1 , n +1) ) )
49 l = inf
50 for itineraire in t :
51 if longueur ( itineraire , V ) < l :
52 l = longueur ( itineraire , V )
53 return l
54
55 assert lo ngueur _minim ale ( V ) == 709
Q5
60 def plus_proche ( ville : int , V : list , visitees : dict = {0: True }) -> int :
61 ''' renvoie le num é ro de la ville non d é j à parcourue la plus proche
de celle de la variable ville '''
62 # pr é conditions : la variable ville est un num é ro compris entre 1 et
n ; la variable visitees est un ensemble de num é ros de villes
63 # postconditions : un entier positif compris entre 1 et n
64 d = inf # distance minimale
65 pp = ville # ville la plus proche
66 dist_ville = V [ ville ]
67 for v in range ( len ( dist_ville ) ) :
68 if v not in visitees . keys () : # on ne traite que les villes non
visit é es
69 if v != ville and dist_ville [ v ] < d :
70 d = dist_ville [ v ]
71 pp = v
72 return pp
73
74 assert plus_proche (2 , V ) == 3
75 assert plus_proche (2 , V , {0: True , 3: True }) == 4
Q6
78 def voyageur ( V : list ) -> ( list , int ) :
79 ''' renvoie un itin é raire localement optimal et sa longueur via un
algorithme glouton '''
80 # pr é conditions : la variable n est un entier naturel non nul
81 # postconditions : un couple form é d ' une liste de num é ros de villes
et un entier positif d é signant une longueur
82 n = len ( V ) - 1
83 v = 0
84 visitees = {0: True }
85 itineraire = []
86 d = 0
87
88 for i in range ( n ) :
89 w = plus_proche (v , V , visitees ) # v est la ville la plus proche
90 visitees [ w ] = True # on la d é clare visit é e
91 itineraire . append ( w ) # on l ' ajoute à l ' itin é raire
92 d = d + V [ v ][ w ]
93 v = w
94
95 d = d + V [ v ][0]
96 return itineraire , d
97
98 print ( voyageur ( V ) )
Q8
104 def rendu ( monnaie : int , systeme : tuple ) -> ( list , int ) :
105 montant = monnaie
106 ''' renvoie une liste minimale de pi è ces / billets à rendre gr â ce à un
algorithme glouton '''
107 # pr é conditions : la variable monnaie est un entier naturel ( montant
en euros sans centimes ) qui correspond au montant à rendre et la
variable syst è me est un tuple de pi è ces / billets autoris é s
108 # postconditions : un couple form é d ' une liste de pi è ces / billets ( des
entiers positifs ) et un entier positif d é signant la longueur du
tuple
109 i = len ( systeme ) -1
110 t = []
111 while montant > 0:
112 if montant >= systeme [ i ]: # on choisit de fa ç on r é p é t é e la pi è ce
/ le billet le plus é lev é du syst è me de monnaie de r é f é rence
113 montant = montant - systeme [ i ]
114 t . append ( systeme [ i ])
115 else :
116 i = i -1 # quand ce n ' est plus possible , on change de pi è ce /
le billet du syst è me de monnaie de r é f é rence
117 return t , len ( t )
118
Q9 On veut faire comprendre pourquoi l’algorithme glouton proposé devrait fournir une solution
optimale au problème posé :
— Pourquoi l’algorithme se termine-t-il ?
La variable montant est un variant de la boucle while. En effet, si le montant
est supérieur à un valeur de référence du système euros, il décroît strictement via
montant = montant - systeme[i]. Dans le pire des cas, on utilise 1 pièce de 1€
quand le montant est strictement inférieur à 2€, ce qui réduit le montant à 0€. Ainsi,
l’algorithme se finit toujours.
— Dans un rendu optimal pour un montant inférieur à 199€, combien faut-il rendre au
plus de pièces de 1€, de 2€, et ainsi de suite ?
Dans un rendu optimal, pour un montant inférieur à 199€, dès que l’on doit donner 2
pièces de 1€, il est préférable de rendre 1 pièce de 2€ : on doit donc rendre au plus 1
pièce de 1€. Avec le même raisonnement,
• les pièces/billets de 1€, 5€, 10€, et 100€ ne sont donnés qu’au plus 1 fois,
• les pièces/billets de 2€ et 20€ ne sont donnés qu’au plus 2 fois,
• et on ne peut donner à la fois 2 pièces de 2€ et 1 pièce de 1€ (on rendra alors
plutôt 1 billet de 5€) ni donner à la fois 2 billets de 20€ et 1 billet de 10€.
— En déduire qu’il faut forcément rendre un billet de 200€ dès que le montant à rendre
dépasse 200€. Procéder de même pour les montants inférieurs.
• La somme totale des pièces/billets d’un rendu optimal ne peut donc dépasser
1 × 100 + 1 × 50 + 2 × 20 + 1 × 5 + 2 × 2 = 199. Dans un rendu optimal, toute
somme au-dessus de 200€ contiendra donc au moins 1 billet de 200€. Ainsi, si le
montant à rendre est supérieur à 200€, il faut commencer par rendre un billet de
200€.
• De même, la somme totale des pièces/billets d’un rendu optimal avec des pièces et
billets de 1€, 2€, 5€, 10€ et 20€ et 50€ est alors 1 × 50 + 2 × 20 + 1 × 5 + 2 × 2 = 99.
Q12
158 def compatibles ( c1 : list , c2 : list ) -> bool :
159 ''' renvoie True si les intervalles de temps des cours c1 et c2 ne
se chevauchent pas ( ou au plus en 1 valeur ) et False dans le cas
contraire '''
160 # pr é conditions : c1 et c2 s 'é crivent [d , f ] avec d l ' heure de d é but ,
et f l ' heure de fin
161 # postconditions : True ou False
162 d1 , f1 = c1
163 d2 , f2 = c2
164 return d1 >= f2 or d2 >= f1
165
166 # Tests unitaires
167 assert compatibles ([8 ,9] , [9 ,10]) == True
168 assert compatibles ([9 ,11] , [8 ,9]) == True
169 assert compatibles ([8 ,10] , [9 ,10]) == False
170 assert compatibles ([9 ,11] , [8 ,10]) == False
171 assert compatibles ([13 ,14] , [9 ,10]) == True
Q13
174 def s a ll e s _ i n c o m p a t i b l e s ( planning : dict , c0 : list ) -> dict :
175 ''' renvoie l ' ensemble des salles du planning incompatibles avec les
horaires du cours c0 '''
176 # pr é conditions : planning = { s :[[ n1 , d1 , f1 ] ,[ n2 , d2 , f2 ] ,...[ nk , dk , fk
]] ,...} avec s une salle et [ di , fi ] des cr é neaux horaires ; c0 est de
la forme [n ,d , f ] , trois entiers
177 # postconditions : un ensemble de type dict avec des numeros de
salles ( des entiers positifs ) et le bool é en True
178 n0 , d0 , f0 = c0
179 salles_occupees = {}
180 for salle , occupation in planning . items () :
181 for c in occupation :
182 n ,d , f = c
183 if not compatibles ([ d , f ] ,[ d0 , f0 ]) :
184 salles_occupees [ salle ] = True
185 break # on interrompt la boucle for int é rieure
186 return salles_occupees
187
Q14
201 def a llocat ion_s alles ( cours : list , nbre_salles : int ) -> ( bool , dict , int ) :
202 # renvoie un planning d ' allocation des salles pour une liste de
cours gr â ce à un algorithme glouton ; la fonction indique un é ventuel
cas d 'é chec ( trop de cours par rapport aux salles ) ou le succ è s dans
l ' attribution des salles ''
203 # pr é conditions : la variable cours est une liste de cours ( de la
forme [n ,d , f ]) , la variable nbre_salles est un entier naturel
204 # postconditions : un tuple avec un bool é en de succ è s ou d 'é chec de l
' algorithme , un planning de la forme { s :[[ n1 , d1 , f1 ] ,[ n2 , d2 , f2 ] ,...[ nk
, dk , fk ]] ,...} avec s une salle et [ di , fi ] des cr é neaux horaires , et
un entier correspondant au dernier num é ro de salle attribu é ( un
entier naturel , sauf en cas de nbre_salles é gal à 0 o ù la fonction
renvoie -1)
205 planning = dict ()
206 for i in range ( nbre_salles ) :
207 planning [ i ] = []
208
209 for C in cours :
210 # print (C , planning )
211 salles_pb = s a l l e s _ i n c o m p a t i b l e s ( planning , C )
212 # print ( salles_pb )
213 i = 0
214 while i < nbre_salles and i in salles_pb : # recherche premi è re
salle disponible
215 i = i + 1
216
217 if i != nbre_salles : # si une salle est inoccup ée , on l ' affecte
218 planning [ i ]. append ( C )
219 else :
220 return False , planning , planning , i -1 # sinon on indique un
probl è me d ' affectation
221 return True , planning , i # toutes les salles ont é t é affect é es
222