- Ajout des 10 TPs d'évaluation (sans PDF) - Création GLOSSAIRE.md et AMELIORATIONS.md - Corrections f-strings, eval(), sommaires Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
242 lines
8.5 KiB
Markdown
242 lines
8.5 KiB
Markdown
> En informatique, on retrouve fréquemment des problèmes d'optimisation comme par exemple le problème du **rendu de monnaie**. Les algorithmes gloutons sont utilisés pour résoudre ce type de problème.
|
||
|
||
## Le Programme
|
||
|
||
|
||
|
||

|
||
|
||
--------
|
||
|
||
### Définition
|
||
|
||
**Problème d'optimisation** : problème dont on cherche à maximiser (ou minimiser) un résultat.
|
||
|
||
Un ***algorithme glouton*** est un algorithme qui vise à optimiser la résolution d'un problème en utilisant une approche particulière :
|
||
|
||
- On procède par étapes, en effectuant à chaque fois le meilleur choix possible.
|
||
- On ne revient jamais sur un choix déjà fait.
|
||
- Répétez ces étapes jusqu'à ce qu'une solution globale soit obtenue.
|
||
|
||
Selon le problème on utilise une méthode pour résoudre ce dernier. Il peut exister plusieurs instances qui donneront lieu à des résultats différents.
|
||
|
||
À un problème d'optimisation, on associe une fonction objective.
|
||
|
||
Prenons un exemple simple : la liste des 23 joueurs sélectionnés pour la coupe du monde :
|
||
|
||
```
|
||
Didier Desvilles est bien embêté : il doit choisir 23 joueurs pour aller jouer une compétition.
|
||
Dans cette optique, il décide d'utiliser une méthode simple: choisir le meilleur joueur possible pour chaque poste, puis le deuxième meilleur comme remplaçant.
|
||
|
||
Peu importe si les joueurs sélectionnés ne s'entendent pas forcément très bien, comme Olivier Gibrun et Karim Benzemb, l'important c'est de faire le meilleur choix à chaque étape.
|
||
```
|
||
|
||
Un algorithme glouton ne renvoie pas obligatoirement un résultat optimal, dans ce cas-là, on parle ***d'heuristique.*** Il renverra un résultat optimal pour chacun des sous-problèmes.
|
||
|
||
Un algorithme glouton ne revient pas sur un sous-problème déjà traité.
|
||
|
||
---------
|
||
|
||
### Exemple de problème d'optimisation : rendu de monnaie
|
||
|
||
> Le problème du rendu de monnaie s'énonce de la façon suivante : étant donné un système de monnaie (pièces et billets), comment rendre une somme donnée de façon optimale, c'est-à-dire avec le nombre minimal de pièces et billets ?
|
||
|
||
On veut programmer une caisse automatique qui rend de façon optimale la monnaie (le moins de pièces/billets) :
|
||
|
||
#### **Des exemples**
|
||
|
||
**Exemple 1 :**
|
||
- Système de monnaie : `{1, 2, 5, 10}`
|
||
- Recherché : `14`
|
||
- Résultat optimal : `10 + 2 + 2` (3 pièces)
|
||
|
||
**Exemple 2 :**
|
||
- Système de monnaie : `{1, 2, 5, 7, 10}`
|
||
- Recherché : `14`
|
||
- Résultat optimal : `7 + 7` (2 pièces)
|
||
|
||
#### **Algorithme**
|
||
|
||
```python
|
||
def rendu_monnaie(systeme, montant):
|
||
reste = montant
|
||
i = len(systeme) - 1
|
||
resultat = []
|
||
|
||
while reste > 0 and i >= 0:
|
||
# On trouve le nombre de pièces de la valeur courante à rendre
|
||
nb_pieces = reste // systeme[i]
|
||
for _ in range(nb_pieces): # Pour chaque pièce à rendre
|
||
resultat.append(systeme[i]) # On ajoute cette pièce au résultat
|
||
reste -= systeme[i] # On met à jour le reste à rendre
|
||
i -= 1 # On passe à la valeur inférieure dans le système
|
||
|
||
return resultat
|
||
|
||
# Exemples d'utilisation
|
||
systeme_1 = [1, 2, 5, 10]
|
||
systeme_2 = [1, 2, 5, 7, 10]
|
||
|
||
print(rendu_monnaie(systeme_1, 14)) # Devrait afficher [10, 2, 2] qui est optimal pour le système_1
|
||
print(rendu_monnaie(systeme_2, 14)) # Devrait afficher [10, 2, 2] qui est non optimal car [7, 7] est mieux pour le système_2
|
||
|
||
```
|
||
|
||
Force brute avec le ```systeme_2``` pour ```14```:
|
||
|
||
On cherche **toutes** les solutions possibles :
|
||
- \[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1\]
|
||
- \[2, 2, 2, 2, 2, 2, 2\]
|
||
- \[5, 5, 2, 2\]
|
||
- \[7, 7\]
|
||
- \[10, 1, 1, 1, 1\]
|
||
- \[10, 2, 2]
|
||
- ...
|
||
|
||
On parcourt la liste des solutions pour garder la solution la plus optimale (la plus courte) : **\[7, 7\]**.
|
||
|
||
### Exemples de problèmes que l'on peut résoudre par algorithme glouton
|
||
|
||
- **Sac à dos**
|
||
- **Rendu de monnaie**
|
||
- [Gestion d'organisation de classe](https://info.blaisepascal.fr/nsi-algorithmes-gloutons#Un_algorithme_glouton-2)
|
||
- **Voyageur de commerce**.
|
||
|
||
### **Sac à dos**
|
||
|
||
> Un voleur entre dans une maison avec un sac à dos pouvant contenir un poids maximum. Il trouve plusieurs objets, chacun ayant une valeur et un poids. Quels objets doit-il prendre pour maximiser la valeur totale sans dépasser la capacité du sac ?
|
||
|
||
#### Exemple
|
||
|
||
Objets disponibles (valeur, poids) : `[(60, 10), (100, 20), (120, 30)]`
|
||
|
||
Capacité du sac : `50 kg`
|
||
|
||
#### Stratégies gloutonnes possibles
|
||
|
||
1. **Prendre l'objet de plus grande valeur** en premier
|
||
2. **Prendre l'objet le plus léger** en premier
|
||
3. **Prendre l'objet avec le meilleur ratio valeur/poids** en premier (souvent la meilleure stratégie)
|
||
|
||
#### Algorithme (stratégie ratio valeur/poids)
|
||
|
||
```python
|
||
def sac_a_dos_glouton(objets, capacite):
|
||
"""
|
||
objets : liste de tuples (valeur, poids)
|
||
capacite : poids maximum du sac
|
||
"""
|
||
# Calculer le ratio valeur/poids et trier par ratio décroissant
|
||
objets_avec_ratio = [(v, p, v/p) for v, p in objets]
|
||
objets_tries = sorted(objets_avec_ratio, key=lambda x: x[2], reverse=True)
|
||
|
||
poids_total = 0
|
||
valeur_totale = 0
|
||
sac = []
|
||
|
||
for valeur, poids, ratio in objets_tries:
|
||
if poids_total + poids <= capacite:
|
||
sac.append((valeur, poids))
|
||
poids_total += poids
|
||
valeur_totale += valeur
|
||
|
||
return sac, valeur_totale, poids_total
|
||
|
||
# Exemple d'utilisation
|
||
objets = [(60, 10), (100, 20), (120, 30)]
|
||
capacite = 50
|
||
|
||
sac, valeur, poids = sac_a_dos_glouton(objets, capacite)
|
||
print("Objets pris :", sac)
|
||
print("Valeur totale :", valeur, ", Poids total :", poids)
|
||
# Résultat : [(60, 10), (100, 20)] avec valeur=160 et poids=30
|
||
# Note : ce n'est pas optimal ! L'optimal serait [(100, 20), (120, 30)] = 220
|
||
```
|
||
|
||
> **Attention** : L'algorithme glouton ne donne pas toujours la solution optimale pour le problème du sac à dos !
|
||
|
||
---------
|
||
|
||
### **Voyageur de commerce**
|
||
|
||
> Un voyageur de commerce doit visiter plusieurs villes exactement une fois, puis revenir à son point de départ. Quel est le chemin le plus court ?
|
||
|
||
#### Exemple
|
||
|
||
Imaginons 4 villes : A, B, C, D avec les distances suivantes :
|
||
|
||
| De/Vers | A | B | C | D |
|
||
|---------|---|---|---|---|
|
||
| A | - | 10 | 15 | 20 |
|
||
| B | 10 | - | 35 | 25 |
|
||
| C | 15 | 35 | - | 30 |
|
||
| D | 20 | 25 | 30 | - |
|
||
|
||
#### Stratégie gloutonne : "Plus proche voisin"
|
||
|
||
À chaque étape, on se déplace vers la ville non visitée la plus proche.
|
||
|
||
```python
|
||
def voyageur_commerce_glouton(distances, depart=0):
|
||
"""
|
||
distances : matrice des distances entre villes
|
||
depart : indice de la ville de départ
|
||
"""
|
||
n = len(distances)
|
||
visitees = [False] * n
|
||
chemin = [depart]
|
||
visitees[depart] = True
|
||
distance_totale = 0
|
||
|
||
ville_actuelle = depart
|
||
|
||
for _ in range(n - 1):
|
||
# Trouver la ville non visitée la plus proche
|
||
plus_proche = None
|
||
dist_min = float('inf')
|
||
|
||
for ville in range(n):
|
||
if not visitees[ville] and distances[ville_actuelle][ville] < dist_min:
|
||
dist_min = distances[ville_actuelle][ville]
|
||
plus_proche = ville
|
||
|
||
# Se déplacer vers cette ville
|
||
chemin.append(plus_proche)
|
||
visitees[plus_proche] = True
|
||
distance_totale += dist_min
|
||
ville_actuelle = plus_proche
|
||
|
||
# Retour au point de départ
|
||
distance_totale += distances[ville_actuelle][depart]
|
||
chemin.append(depart)
|
||
|
||
return chemin, distance_totale
|
||
|
||
# Exemple d'utilisation
|
||
distances = [
|
||
[0, 10, 15, 20], # Distances depuis A
|
||
[10, 0, 35, 25], # Distances depuis B
|
||
[15, 35, 0, 30], # Distances depuis C
|
||
[20, 25, 30, 0] # Distances depuis D
|
||
]
|
||
|
||
chemin, distance = voyageur_commerce_glouton(distances, depart=0)
|
||
villes = ['A', 'B', 'C', 'D']
|
||
chemin_noms = [villes[i] for i in chemin]
|
||
print("Chemin :", ' -> '.join(chemin_noms))
|
||
print("Distance totale :", distance)
|
||
# Résultat : A -> B -> D -> C -> A avec distance = 80
|
||
```
|
||
|
||
> **Note** : Le problème du voyageur de commerce est un problème **NP-difficile**. Pour un grand nombre de villes, trouver la solution optimale prend un temps exponentiel. L'approche gloutonne donne une solution acceptable rapidement, mais rarement optimale.
|
||
|
||
--------
|
||
|
||
|
||
|
||
Auteur : Florian Mathieu
|
||
|
||
Licence CC BY NC
|
||
|
||
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>
|