Skip to content
Snippets Groups Projects
Commit 22320c28 authored by Romain Vuillemot's avatar Romain Vuillemot
Browse files

mises à jour des instructions

parent 0b7aca1e
No related branches found
No related tags found
No related merge requests found
%% Cell type:markdown id:52685e4b tags:
%% Cell type:markdown id:7ef4a973 tags:
NAME:
%% Cell type:markdown id:57c2dc5b-0ce7-47e4-bfeb-445c2f90cb6e tags:
# INF TC1 - TD7 (2h) - Rendu de monnaie
%% Cell type:markdown id:2a40aa1b-c3fe-4cd2-9236-3c7aeddedf55 tags:
---
%% Cell type:markdown id:25995ac6-8673-46f2-9c77-37b48a889b73 tags:
## Objectif du TD
Dans ce TD nous allons aborder le problème de rendu de monnaie selon plusieurs méthodes algorithmiques :
- technique dite Gloutonne
- chemin minimal dans un arbre de recherche
- Programmation Dynamique (devoir à rendre)
- Programmation Dynamique (**prochain TD7bis et devoir à rendre**)
Vous serez amené également à créer une structure de données de graphe et une méthode de parcours de celui-ci.
**Les réponses de la partie sur la programmation dynamique feront l'objet d'un autre sujet de TD et d'un rendu sous forme de rapport à rendre sur Moodle.**
%% Cell type:markdown id:c38c9de3-f716-4907-8387-7cf6f4f5e926 tags:
## Le problème de "rendu de monnaie"
Le problème de rendu de monnaie est très fréquent dans la vie de tous les jours et peut être défini comme suit : étant donné un montant, une machine capable de rendre la monnaie doit rendre ce montant au client à partir de pièces (1c à 2€) et de billets. On suppose pour simplifier qu'il n'y a que des pièces en centimes; un billet de 5€ sera représenté comme une pièce de 500 centimes. On supposera dans un premier temps qu'il existe un nombre suffisant (autant que nécessaire) de chaque pièce, mais dans un second temps nous introduirons des contraintes de disponibilité des pièces.
%% Cell type:markdown id:5a29e913-8954-4e88-b477-234c4ee0fd9b tags:
| | **la table S** | **la table D** |
|---------------|------------------------|----------------------------------|
| **indice $i$**| **Valeur ($v_i$)** | **Disponibilité ($d_i$)** |
| 1 | 1c | nombre de pièces de 1c disponibles |
| 2 | 2c | nombre de pièces de 2c disponibles |
| 3 | 5c | ... |
| 4 | 10c | ... |
| 5 | 20c | ... |
| 6 | 50c | ... |
| 7 | 100 (1€) | pièces de 1€ |
| 8 | 200 (2€) | pièces de 2€ |
| 9 | 500 (5€) | billets de 5€ |
| 10 | 1000 | billets de 10€ |
| 11 | 2000 | billets de 20€ |
| 12 | 5000 | billets de 50€ |
| 13 | 10000 | billets de 100€ |
%% Cell type:markdown id:7f845f7b-8bd0-4544-8110-fa5ec1a7a960 tags:
De manière plus formelle, un stock de pièces est un tuple $S=(v_1, v_2, ..., v_n)$ où l'entier $v_i > 0$ est la valeur de la $i^{ème}$ pièce. Pour refléter le fait qu'on a des pièces de 1, 2 et 5c, $S$ contiendra $v_1=1$ (1 centime), $v_2=2, v_3=5$. Le problème de monnaie est un problème d'optimisation combinatoire $(S,M)$ permettant de trouver le tuple $T=(x_1, x_2, ..., x_n)$ avec $x_i \geq 0$ qui minimise $ \sum_{i=1}^n x_i$ sous la contrainte $\sum_{i=1}^n x_i.v_i=M$. Autrement dit, nous souhaitons aussi bien obtenir le montant exact, que minimiser le nombre total de pièces $x_i$ de valeur $v_i$ utilisées. Appelons $Q(S,M) = \sum_{i=1}^n x_i$ la quantité de pièces à rendre pour le montant *M* étant donné le système *S* décrit dans la Table~1. Une solution optimale $Q_{opt}$ à ce problème est telle que *Q(S,M)* soit minimale :
$Q_{opt}(S,M) = min \ \sum_{i=1}^n x_i$.
Dans certaines situations il faudra gérer le nombre de pièces/billets disponibles (la table *D*). Nous noterons *d[i]=k* pour dire : il y a *k* pièces/billets du montant *$v_i$* disponibles (pièces ou billets du montant *v[i]*) à l'indice *i* dans la table *S*. On supposera cependant dans un premier temps qu'il y a un nombre suffisant de chaque pièce/billet dans le tableau S. On supposera également que *S* est ordonné dans un ordre croissant.
%% Cell type:markdown id:077ca702-bc18-4f2d-95e6-86fd4d53ebe7 tags:
## Exemples
**M = 9€:** étant donné S dans la Table ci-dessous, la solution qui minimise le nombre total de pièces rendues à 3 est *T=(0,0,0,0,0,0,0,2,1,0,0,0,0)*. Donc, $Q_{opt}(S,9) = \min \ Q(S,9) = 3$. Détails (avec des pièces $\geq$ 1€) :
| Description | T | Rendu |
|--------------------------------------------------------|----------------------------------------|---------------------------------------------|
| 9 pièces de 1€ et 0 pour toutes les autres | T=(0,0,0,0,0,0,0,9,0,0,0...0) | $\rightarrow$ 9 pièces | $Q(S,9) =9$ |
| $\circ$ 5 $\times$ 1€ + 2 $\times$ 2€, 0 pour les autres | T=(0,0,0,0,0,0,0,5,2,0,0...0) | $\rightarrow$ 7 pièces | $Q(S,9) =7$ |
| $\circ$ 1 $\times$ 1€ + 4 $\times$ 2€, 0 pour les autres | T=(0,0,0,0,0,0,0,1,4,0,0...0) | $\rightarrow$ 5 pièces | $Q(S,9) =5$ |
| $\circ$ 2 $\times$ 2€ + 1 $\times$ 5€, 0 pour les autres | T=(0,0,0,0,0,0,0,0,2,1,0...0) | $\rightarrow$ 3 pièces | **$Q(S,9) =3$** |
| $\circ$ 3 $\times$ 1 + 3 $\times$ 2, 0 pour les autres | T=(0,0,0,0,0,0,0,3,3,0,0...0) | $\rightarrow$ 6 pièces | |
| $\circ$ 4 $\times$ 1 + 1 $\times$ 5, 0 pour les autres | T=(0,0,0,0,0,0,0,4,0,1,0...0) | $\rightarrow$ 5 pièces | |
| $\circ$ etc. sans parler des solutions avec des centimes ! | | | |
<center>
<i>Table 1</i>
</center>
**M = 1989€:** pour rendre la somme de 1989€ pièces (sans les centimes), on aura : $1989 = 500 \times 3 + 488 = 500 \times 3 + 200 \times 2 + 50 \times 1 + 20 \times 1 + 10 \times 1 + 5 \times 1 + 2 \times 2$, soit $3+2+1+1+1 = 8$ grosses pièces (billets) et $1+2 = 3$ pièces.
%% Cell type:markdown id:254f7b95-aaeb-4f96-b8ec-47df74a3c3a3 tags:
## Résolution du problème
L'exemple de la Table 1 est un système *canonique*, c'est à dire qu'en choisissant systématiquement les pièces de plus grande valeur (algorithme glouton) on obtient toujours la solution optimale. Il existe des systèmes pour lesquels c'est moins simple, par exemple $S=(1,7,23)$. Pour **M = 28**, en choisissant en priorité les pièces de plus grande valeur on trouvera $T=(5,0,1)$ (6 pièces), alors que la solution optimale est $T=(0,4,0)$ (4 pièces).
Dans le cas général, ce problème est démontré NP-difficile, c'est-à-dire qu'on ne connaît pas d'algorithme qui puisse le résoudre en complexité polynomiale par rapport à la taille de $S$.
Il existe plusieurs approches :
1. Approche gloutonne (pas toujours optimale)
2. Recherche de chemin de longueur minimale avec (ou sans) un arbre de recherche, ...
3. Programmation Dynamique, ... et autres méthodes algorithmiques (cf. quasi-Dijkstra)
Il existe d'autres approches de résolutions algorithmiques ou non-algorithmiques comme par exemple un système d'équations à optimiser. Nous allons dans ce TD aborder les différentes approches algorithmiques mentionnées ci-dessus.
%% Cell type:markdown id:c8b2e4fe-cbfa-4301-89af-d02ea55473f0 tags:
## Algorithme Glouton
## 1. Algorithme Glouton
Les techniques de programmation gloutonnes ont la particularité de faire des choix locaux à chaque étape de calcul. Cette méthode ne donne pas forcément un nombre minimal de pièces mais elle est simple à comprendre et à implémenter. Son application au problème de rendu de monnaie est simple : on trouve la pièce la plus grande inférieure ou égale à $M$. Soit $v_i$ cette pièce. On utilise ($x_i = M \ div \ v_i$) fois la pièce $v_i$; le reste à traiter sera $M'=(M \mod v_i)$. Ensuite, on recommence suivant le même principe pour satisfaire $M'$. Ce qui donne le pseudo-code d'algorithme suivant (qui fait l'hypothèse d'un nombre illimité de pièces, on ne tient pas compte ici de $D$ : $d_i=\infty$) :
```
Fonction Monnaie_Gloutonne
Entrées : la somme S, M
Sorties : le vecteur T = Q(S,M) : le nombre de pièces nécessaires
M'=M
Répéter
Chercher dans S l'indice i tel que Vi =< M'
Ti = M' div Vi
M' = M' mod Si
Jusqu'à M' = 0
Constituer T avec les Ti utilisés
Q(S,M) = somme de i=1 a i=n de T_i
T est la valeur de sortie de l'algorithme
Fin Monnaie_Gloutonne
```
Pour **M = 236,65€** cet algorithme se déroule de la manière suivante :
$23665 \geq 10000$
- $T_{13}=23665 \ div \ 10000 = 2$ ($T_{13}$ correspond à 100€)
- $M'=23665 \ mod \ 10000 = 3665$
$3665 \geq 2000$
- $T_{11}=3665 \ div \ 2000 = 1$
- $M'=3665 \ mod \ 2000 = 1665$
...
$\rightarrow$ On obtient $T_{1..13}=(0,0,1,1,0,1,1,0,1,1,1,0,2)$ et $Q(S,M) = 9$
A noter qu'en Python les indices commencent à zéro ! Faire -1 sur les indices ci-dessus.
%% Cell type:markdown id:425fe58a-b0bc-41eb-acea-0d88e1daf18f tags:
**Question 1.1 -** Implémenter l'algorithme glouton ci-dessus.
%% Cell type:code id:e3880c0c-4c2c-48c4-96b1-0c95c712b489 tags:
``` python
def gloutonne(S: list, M: int) -> tuple:
"""
Algorithme glouton
Algorithme glouton sans nombre limité de pièces
Args:
- S (list): Une liste d'entier représentant les pièces
- M (int): La somme à atteindre
Returns:
- tuple: (True, Q(S,M)) si succès OU (False, None) si échec.
"""
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:2a39cbfb-d35d-4da7-bbaa-d273bc900c2d tags:
Les tests suivants doivent être validés :
%% Cell type:code id:90cd609d-71d9-41b0-ba28-c66fd94a2524 tags:
``` python
Rep= gloutonne(sorted([1, 7, 23]), 28)
print(Rep[1], ': ', len(Rep[1]),' pièces') if Rep[0] else print('Echec')
# [5, 0, 1] : 3 pièces Un epièce de 23 et 5 pièces de 1
Rep= gloutonne(sorted([7, 23]), 5)
print(Rep[1], ': ', len(Rep[1]),' pièces') if Rep[0] else print('Echec')
# Echec car M < la + petite pièce
Rep= gloutonne(sorted([7, 23]), 8)
print(Rep[1], ': ', len(Rep[1]),' pièces') if Rep[0] else print('Echec')
# Echec car on ne peut jamais faire la monnaie avec cette S
```
%% Cell type:markdown id:c5d61f32-13cc-4176-8f47-b0ca861a74ba tags:
**Question 1.2 -** Proposez une modification du pseudo-code ci-dessus pour prendre en compte un nombre **limité** de pièces (en prenant en compte la table $D$). Implémenter cet algorithme modifié (vous fournirez vos propres valeurs de disponibilité dans $D$).
%% Cell type:code id:91951f93-bf34-4ea9-ad08-76c73ff3ce44 tags:
``` python
def gloutonne_avec_D(S: list, M: int, D: int) -> tuple:
"""
Algorithme glouton avec nombre limité de pièces
Args:
- S (list): Une liste d'entier représentant les pièces
- M (int): La somme ) à atteindre
- D (int): La disponibilité des pièces
Returns:
- tuple: (True, Q(S,M)) si succès OU (False, None) si échec.
"""
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:9786ca50-6bfe-491d-8971-89be91ccfd8b tags:
Les tests suivants doivent être validés :
%% Cell type:code id:a09ba678-bff6-4728-b301-6579f8a62e40 tags:
``` python
Rep= gloutonne_avec_D(sorted([1,7,23]), 28, [10,10,0])
print(Rep[1], ': ', len(Rep[1]),' pièces') if Rep[0] else print('Echec')
# [0, 4, 0] : 3 pièces # On utilise 7 pièces
```
%% Cell type:markdown id:70534a4f-cc60-4c29-b5e6-1392126fc9f5 tags:
Vous noterez que la méthode Gloutonne ne garantit pas que *Q(S, M)* soit minimal comme les tests l'ont montré pour M=28 et S=(1, 7, 23). La méthode Gloutonne utilise un optimum local (choix de la plus grande pièce) qui ne débouche pas forcément sur un optimum global. Cependant, elle est très simple à calculer.
%% Cell type:markdown id:321b284b-a735-47b5-a889-ef52006c967e tags:
## Chemin minimal dans un arbre
## 2. Chemin minimal dans un arbre
Une deuxième méthode consiste à construire toutes les solutions possibles sous forme d'arbre, et ensuite de choisir la solution de manière globale. Cette méthode est plus complexe à mettre en oeuvre mais donne une solution optimale.
%% Cell type:markdown id:431ee4d7-1fa8-4bfd-bf9c-ec0725d08e5b tags:
<center>
<img src="monnaie-graph.png" alt="Image originale" style="height:10cm;">
</center>
Exemple d'arbre de recherche résultant *M=28* et *S=(1,7,23)*. C'est à dire, on a seulement des pièces de 1, 7 et 23 (€ ou cents) en nombre suffisant. Dès qu'on atteint 0, on remonte de ce 0 à la racine pour obtenir la solution minimale donnant le nombre minimal d'arcs entre ce 0 et la racine. On remarque qu'on utilisera 4 pièces de 7c pour avoir 28c. Par ailleurs, on remarque que le choix initial de 23 (à gauche de l'arbre) laisse le montant *M'=5c* à satisfaire lequel impose le choix de 5 pièces de 1c. En bleu les autres chemins choisissant une pièce de 23.
%% Cell type:markdown id:36bc9ae5-81f2-4f12-9b94-03d90db050c0 tags:
On construit un arbre de recherche (arbre des possibilités) dont la racine est *M*. Chaque noeud de l'arbre représente un montant : les noeuds autres que la racine initiale représentent $M' < M$ une fois qu'on aura utilisé une (seule) des pièces de $S$ (parmi 1, 7 ou 23€). La pièce utilisée pour aller de $M$ à $M'$ sera la valeur de l'arc reliant ces deux montants. Cet arbre est donc développé par niveau (en largeur). On arrête de développer un niveau supplémentaire dès qu'un noeud a atteint 0 auquel cas une solution sera le vecteur des valeurs (des arcs) allant de la racine à ce noeud.
Le principe de l'algorithme correspondant à un parcours en largeur dont la version itérative utilise une *File d'attente* est :
```
Fonction Monnaie_graphe
% on suppose qu'il y a un nombre suffisant de chaque pièce/billet dans la tableau S
Entrées : la somme M
Sorties : le vecteur T et Qopt(S,M) le nombre de pièces nécessaires
File F = vide ;
Arbre A contenant un noeud racine dont la valeur = M
Enfiler(M) % la file F contient initialement M
Répéter
M'= défiler()
Pour chaque pièce vi =< M' disponible dans S :
S'il existe dans l'arbre A un noeud dont la valeur est M' - vi
Alors établir un arc étiqueté par vi allant de M' à ce noeud
Sinon
Créer un nouveau noeud de valeur M' - vi dans A et lier ce noeud
à M' par un arc étiqueté par vi
Enfiler ce nouveau noeud
Fin Si
Jusqu'à (M' - vi = 0) ou (F = vide)
Si (F est vide Et M' - vi /= 0)
Alors il y a un problème dans les calculs ! STOP.
Sinon Le dernier noeud créer porte la valeur 0
On remonte de ce noeud à la racine et on comptabilise dans T où
Ti = le nombre d'occurrences des arcs étiquetés vi de la racine
au noeud v de valeur 0
$Q(S,M) = somme de i=1 a i=n de T_i$
Fin si
Fin Monnaie_graphe
```
%% Cell type:markdown id:d15c2fca-64f1-4d6d-b6bf-5edf9b095828 tags:
**Question 2.1 -** Ecrire un algorithme de construction de l'arbre.
%% Cell type:code id:2d2556ed-8be9-416f-a976-8964ccca8061 tags:
``` python
def Monnaie_graphe(S: list, M: int) -> dict:
"""
Génère un graphe représentant les combinaisons de pièces possibles pour atteindre un montant spécifié
en utilisant un ensemble donné de valeurs de pièces.
Args:
- S (list): Une liste d'entiers représentant les valeurs disponibles des pièces.
- M (int): Un entier spécifiant le montant cible à atteindre.
Returns:
- dict: Un graphe (représenté sous forme de dictionnaire)
"""
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:c2709c24-bbe0-4c6a-aaf8-f2e3712131f5 tags:
``` python
A = Monnaie_graphe([1, 2, 5], 7)
print("Arbre créé", A)
```
%% Cell type:markdown id:91d7262d-9a9c-4f22-b60e-6d43267a59bb tags:
Vous devriez pouvoir visualiser votre arbre avec la méthode `plot_graph` (chargez d'abord les dernières cellules du notebook afin d'initialiser cette fonction) :
%% Cell type:code id:701a7359-145f-4077-a8ee-e056eb3447f1 tags:
``` python
plot_graph(A)
```
%% Cell type:markdown id:9de0a303-b587-4901-b0ac-68a7cd04bfc7 tags:
**Question 2.2 -** Ecrire un algorithme de recherche du chemin le plus court dans l'arbre.
%% Cell type:code id:f5f033d8-1d49-49c7-9af7-e1cd59905d96 tags:
``` python
def plus_court_chemin(graph: dict, start: str):
def plus_court_chemin(graph: dict, start: str) -> int:
"""
Calcule les distances les plus courtes depuis un nœud de départ dans un graphe.
Args:
- graph (dict): Un dictionnaire représentant le graphe
- start (str): Le nœud de départ à partir duquel calculer les distances.
Returns:
- int: la taille du chemin
"""
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:7aca69d8-79ed-4f06-bb25-5c42b116cbc8 tags:
``` python
assert plus_court_chemin(A, 3)[0] == 2
```
%% Cell type:markdown id:72832556-e0d7-4959-8d93-2b55145e7c25 tags:
**Question 2.3 -** Compléter l'algorithme précédent avec un mécanisme de backtracking permettant de retourner le plus court chemin utilisé.
%% Cell type:code id:636ed054-38e5-46d1-b44f-c2d861bf1cc0 tags:
``` python
def plus_court_chemin_backtracking(graph: dict, start: str):
def plus_court_chemin_backtracking(graph: dict, start: str) -> list:
"""
Calcule les distances les plus courtes depuis un nœud de départ dans un graphe.
Args:
- graph (dict): Un dictionnaire représentant le graphe
- start (str): Le nœud de départ à partir duquel calculer les distances.
Returns:
- tuple: le chemin utilisé et les montants intermédiaires
- list: le chemin utilisé et les montants intermédiaires
"""
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:b0253f7c-0f07-436c-b72c-2411fb757e47 tags:
``` python
assert plus_court_chemin_backtracking(A, 7) == [7, 5, 0]
```
%% Cell type:markdown id:f358da32-9534-493c-80b1-60d387f89229 tags:
A noter que vous pouvez combiner la construction et la recherche du plus court chemin (en vous arrêtant au premier 0 rencontré). Cependant, cette methode a pour inconvénient d'explorer un très large espace de recherche (qui devient vite très grand). Un autre inconvénient est le calcul de solutions dont on sait qu'elles ne seront pas optimales.
%% Cell type:markdown id:91af94f8-c687-4739-b4fb-ef58cb7e6081 tags:
## Utils
%% Cell type:code id:7cbf74a0-525c-4b6f-b078-5f0eea6fe09d tags:
``` python
from graphviz import Digraph
from IPython.display import display, Image
import graphviz
```
%% Cell type:code id:83f489e1-8fb7-45ec-a906-4d2699c63a46 tags:
``` python
def plot_graph(graph):
dot = graphviz.Digraph()
for node in graph:
dot.node(str(node))
for node, neighbors in graph.items():
for neighbor, weight in neighbors.items():
dot.edge(str(node), str(neighbor), label=str(weight))
display(dot)
```
......
%% Cell type:markdown id:2c7e0986 tags:
NAME:
%% Cell type:markdown id:57c2dc5b-0ce7-47e4-bfeb-445c2f90cb6e tags:
# INF TC1 - TD7bis (2h + 2h AUTO) - Rendu du Rendu de monnaie
%% Cell type:markdown id:2a40aa1b-c3fe-4cd2-9236-3c7aeddedf55 tags:
---
%% Cell type:markdown id:25995ac6-8673-46f2-9c77-37b48a889b73 tags:
## Objectif du TD
Dans ce rendu de TD est d'aborder le problème de rendu de monnaie selon la méthode de **Programmation Dynamique**.
**Vos réponses à ce TD fera l'objet d'un rendu à [déposer sur Moodle](https://pedagogie1.ec-lyon.fr/course/view.php?id=1865).**
## Modalités de rendu du TD
Le rendu sera à déposer sur Moodle une (1) semaine après le dernier TD d'autonomie. Il pourra être réalisé seul ou en binôme. Ce rendu devra comporter :
- Les réponses aux questions de la partie 3 sous forme de notebook ou autre document.
- Un code fonctionnel et les tests appropriés (avec `assert` par exemple) sous forme de notebook ou de fichier Pythons classiques.
- De **nombreux** tests avec plusieurs systèmes de monnaies (canoniques et non canoniques) démontrant l'efficacité de votre approche. **Pensez également à inclure des tests démontrant les limites des solutions que vous proposez.** Pensez à rajouter les types de variables et commentaires dans les fonctions.
- La description de parties de code difficiles (dans le code ou dans des cellules supplémentaires).
- Tout soucis ou point bloquant dans votre code.
- Les diagrammes, exemples et illustration nécessaires (dans ce cas vous devrez créer une archive comprenant les fichiers de code, notebook et les fichiers d'illustration).
- Tout élément et exemple supplémentaire que vous jugerez nécessaires.
%% Cell type:markdown id:1fcb1349-4cae-4f76-b3b6-d9a810225afe tags:
NOM(s) et PRENOM(s) :
Groupe de TD :
%% Cell type:markdown id:c38c9de3-f716-4907-8387-7cf6f4f5e926 tags:
## Rappel: Le problème de "rendu de monnaie"
Le problème de rendu de monnaie est très fréquent dans la vie de tous les jours et peut être défini comme suit : étant donné un montant, une machine capable de rendre la monnaie doit rendre ce montant au client à partir de pièces (1c à 2€) et de billets. On suppose pour simplifier qu'il n'y a que des pièces en centimes; un billet de 5€ sera représenté comme une pièce de 500 centimes. On supposera dans un premier temps qu'il existe un nombre suffisant (autant que nécessaire) de chaque pièce, mais dans un second temps nous introduirons des contraintes de disponibilité des pièces.
%% Cell type:markdown id:5a29e913-8954-4e88-b477-234c4ee0fd9b tags:
| | **la table S** | **la table D** |
|---------------|------------------------|----------------------------------|
| **indice $i$**| **Valeur ($v_i$)** | **Disponibilité ($d_i$)** |
| 1 | 1c | nombre de pièces de 1c disponibles |
| 2 | 2c | nombre de pièces de 2c disponibles |
| 3 | 5c | ... |
| 4 | 10c | ... |
| 5 | 20c | ... |
| 6 | 50c | ... |
| 7 | 100 (1€) | pièces de 1€ |
| 8 | 200 (2€) | pièces de 2€ |
| 9 | 500 (5€) | billets de 5€ |
| 10 | 1000 | billets de 10€ |
| 11 | 2000 | billets de 20€ |
| 12 | 5000 | billets de 50€ |
| 13 | 10000 | billets de 100€ |
%% Cell type:markdown id:7f845f7b-8bd0-4544-8110-fa5ec1a7a960 tags:
De manière plus formelle, un stock de pièces est un tuple $S=(v_1, v_2, ..., v_n)$ où l'entier $v_i > 0$ est la valeur de la $i^{ème}$ pièce. Pour refléter le fait qu'on a des pièces de 1, 2 et 5c, $S$ contiendra $v_1=1$ (1 centime), $v_2=2, v_3=5$. Le problème de monnaie est un problème d'optimisation combinatoire $(S,M)$ permettant de trouver le tuple $T=(x_1, x_2, ..., x_n)$ avec $x_i \geq 0$ qui minimise $ \sum_{i=1}^n x_i$ sous la contrainte $\sum_{i=1}^n x_i.v_i=M$. Autrement dit, nous souhaitons aussi bien obtenir le montant exact, que minimiser le nombre total de pièces $x_i$ de valeur $v_i$ utilisées. Appelons $Q(S,M) = \sum_{i=1}^n x_i$ la quantité de pièces à rendre pour le montant *M* étant donné le système *S* décrit dans la Table~1. Une solution optimale $Q_{opt}$ à ce problème est telle que *Q(S,M)* soit minimale :
$Q_{opt}(S,M) = min \ \sum_{i=1}^n x_i$.
Dans certaines situations il faudra gérer le nombre de pièces/billets disponibles (la table *D*). Nous noterons *d[i]=k* pour dire : il y a *k* pièces/billets du montant *$v_i$* disponibles (pièces ou billets du montant *v[i]*) à l'indice *i* dans la table *S*. On supposera cependant dans un premier temps qu'il y a un nombre suffisant de chaque pièce/billet dans le tableau S. On supposera également que *S* est ordonné dans un ordre croissant.
%% Cell type:markdown id:077ca702-bc18-4f2d-95e6-86fd4d53ebe7 tags:
## Exemples
**M = 9€:** étant donné S dans la Table ci-dessous, la solution qui minimise le nombre total de pièces rendues à 3 est *T=(0,0,0,0,0,0,0,2,1,0,0,0,0)*. Donc, $Q_{opt}(S,9) = \min \ Q(S,9) = 3$. Détails (avec des pièces $\geq$ 1€) :
| Description | T | Rendu |
|--------------------------------------------------------|----------------------------------------|---------------------------------------------|
| 9 pièces de 1€ et 0 pour toutes les autres | T=(0,0,0,0,0,0,0,9,0,0,0...0) | $\rightarrow$ 9 pièces | $Q(S,9) =9$ |
| $\circ$ 5 $\times$ 1€ + 2 $\times$ 2€, 0 pour les autres | T=(0,0,0,0,0,0,0,5,2,0,0...0) | $\rightarrow$ 7 pièces | $Q(S,9) =7$ |
| $\circ$ 1 $\times$ 1€ + 4 $\times$ 2€, 0 pour les autres | T=(0,0,0,0,0,0,0,1,4,0,0...0) | $\rightarrow$ 5 pièces | $Q(S,9) =5$ |
| $\circ$ 2 $\times$ 2€ + 1 $\times$ 5€, 0 pour les autres | T=(0,0,0,0,0,0,0,0,2,1,0...0) | $\rightarrow$ 3 pièces | **$Q(S,9) =3$** |
| $\circ$ 3 $\times$ 1 + 3 $\times$ 2, 0 pour les autres | T=(0,0,0,0,0,0,0,3,3,0,0...0) | $\rightarrow$ 6 pièces | |
| $\circ$ 4 $\times$ 1 + 1 $\times$ 5, 0 pour les autres | T=(0,0,0,0,0,0,0,4,0,1,0...0) | $\rightarrow$ 5 pièces | |
| $\circ$ etc. sans parler des solutions avec des centimes ! | | | |
<center>
<i>Table 1</i>
</center>
**M = 1989€:** pour rendre la somme de 1989€ pièces (sans les centimes), on aura : $1989 = 500 \times 3 + 488 = 500 \times 3 + 200 \times 2 + 50 \times 1 + 20 \times 1 + 10 \times 1 + 5 \times 1 + 2 \times 2$, soit $3+2+1+1+1 = 8$ grosses pièces (billets) et $1+2 = 3$ pièces.
%% Cell type:markdown id:6c6aef6c-c6b7-4376-a0dd-06346df476df tags:
## 3. Algorithme de Programmation Dynamique (Rendu du TD)
Nous introduisons une troisième et dernière méthode de résolution qui se base sur la programmation dynamique dont les principes sont :
1) identifier une formule récursive pour résoudre le problème de façon incrémentale,
2) résoudre le problème pour des conditions aux bords,
3) itérer pour résoudre le problème complet.
Supposons que l'on puisse, pour le montant *M*, savoir calculer une solution optimale pour tout montant $M' < M$. Pour satisfaire *M*, il faudra alors prendre une (seule) pièce $v_i$ supplémentaire parmi les *n* pièces disponibles. Une fois cette pièce choisie, le reste $M' = M-v_i$ est forcément inférieur à *M* et on sait qu'on peut calculer un nombre optimal de pièces pour *M'*. Par conséquent :
%% Cell type:markdown id:53272a7f-ff93-4143-83cd-f33e1750b0b2 tags:
$Q_{opt}(i,m) = min
\begin{cases}
1 + Q_{opt}(i, m - v_i) \quad si \ (m - v_i) \geq 0 \qquad \text{ on utilise une pièce de type i de
valeur $v_i$}\\
Q_{opt}(i-1, m) \qquad \quad si \ i \geq 1 \qquad \qquad \quad \text{ on n'utilise pas la pièce de type i,
essayons i-1}
\end{cases}$
L'inconvénient de cette méthode est que chaque appel à $Q_{opt}$ fait deux appels à lui-même, donc le nombre d'opérations nécessaires est exponentiel à la taille de *M*. Pour éviter cela ferons appel au principe de *mémoïsation* de la programmation dynamique, en stockant les résultats intermédiaires dans une matrice $mat[|S|][M]$ (Figure 2).
<center>
<img src="monnaie-progdyn.png" alt="Image originale" style="height:5cm;">
</center>
<center>
<i>Figure 2</i>
</center>
Illustration de la résolution par programmation dynamique. L'ordre de remplissage des cellules (de haut en bas, de gauche à droite) est représenté en vert.
Les colonnes de la matrice sont les valeurs de M qu'on doit atteindre. Les lignes sont les pièces dont on dispose pour atteindre chaque valeur de M. En première ligne, on ne dispose d'aucune pièce, en ligne 2 on dispose d'une infinité de pièces de 1c, en ligne 3 on dispose d'une infinité de pièces de 1c et de 3c, etc. Les cellules de la matrice indiquent le nombre minimal de pièces à utiliser parmi celles autorisées par la ligne courante, pour atteindre la valeur en colonne. Ainsi on peut lire dans la matrice que pour payer 6 centimes avec des pièces de 1, 3 et 4 il faut utiliser 2 pièces (2 $\times$ 3c). Le remplissage de chaque cellule se fait en calculant le minimum de deux voisins (illustrés avec des flèches rouges). Si un voisin est hors de la matrice, il ne compte pas dans le calcul du minimum. Le pseudo-code de construction de la matrice peut s'écrire de la façon suivante :
```
fonction Progdyn(S, M) : S est le stock des pièces, M est le montant
soit mat la matrice d'indices [0, |S|] x [0 , M]
pour i = 0 à |S| faire
pour m = 0 à M faire
si m = 0 alors
mat[i][m] = 0
sinon si i = 0 alors
mat[i][m] = infini
sinon
mat[i][m] = min(
si m - S[i-1] >= 0 alors 1 + mat[i][m - S[i-1]] sinon infini
si i >= 1 alors mat[i-1][m] sinon infini
)
renvoyer mat [|S|][M]
```
%% Cell type:markdown id:1287c1b0-4900-4b18-ab59-5719f867e18d tags:
**Question 3.1 -** Proposer une solution Python de cette méthode. Dans une première étape, trouver simplement le nombre minimal de pièces. Renvoyer `None` si il n'existe pas de solution possible.
%% Cell type:code id:fc6d6123-9d62-4d58-a9c7-dd0540c77d9c tags:
``` python
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:90172789-470c-452d-96ff-8adc56d8c134 tags:
**Question 3.2 -** Modifier votre solution pour renvoyer également les pièces utilisées.
%% Cell type:code id:51354054-3292-48c6-8d43-8f42ffd11127 tags:
``` python
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:d544f60e-b75c-4b8f-a695-7b9ec7bd138b tags:
**Question 3.3 -** On souhaite à présent implémenter en Programmation Dynamique la limite du stock de pièces présentée pour les exercices 1.2 et 1.3. Pour ce faire on modifie la formule de $Q_{opt}$ donnée précédemment (valable ici uniquement pour $i\geq1$) :
$Q_{opt}(i,m) = min
\begin{cases}
Q_{opt}(i-1, m) \qquad \qquad \qquad \qquad \qquad \qquad \qquad \text{ on n'utilise aucune pièce de type i}\\
1 + Q_{opt}(i-1, m-v_i) \quad si\ d_i\geq1\ et\ (m-v_i) \geq 0 \qquad \text{ on utilise 1 pièce de type i}\\
2 + Q_{opt}(i-1, m-2v_i) \quad si\ d_i\geq2\ et\ (m-2v_i) \geq 0 \quad \text{ on utilise 2 pièces de type i}\\
3 + Q_{opt}(i-1, m-3v_i) \quad si\ d_i\geq3\ et\ (m-3v_i) \geq 0 \quad \text{ on utilise 3 pièces de type i}\\
...
\end{cases}
$
Implémentez la limite de pièces avec cette nouvelle formule pour l'algorithme en Programmation Dynamique. Notez ici qu'on ne va plus chercher les valeurs sur la même ligne dans la matrice, mais qu'on remonte _systématiquement_ d'une ligne, pour qu'après l'utilisation de $k$ pièces de valeur $v_i$, on ne puisse plus ajouter d'autres pièces de cette même valeur.
%% Cell type:code id:8c05a462-3d75-48be-903c-3fa922bbd63d tags:
``` python
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:5ee21bfb-c169-48d0-93cc-75c6be089d03 tags:
**Question 3.4 -** On dispose à présent du poids de chaque pièce en plus de sa valeur (Table 2), et on cherche à minimiser le poids total des pièces pour atteindre la valeur M. À l'aide d'une matrice analogue à la Figure 2, trouvez le poids minimal pour atteindre la valeur M=7. Notez qu'on ne cherchera plus à minimiser le nombre de pièces utilisées, seul le critère de poids compte pour cet exercice.
| S (valeur de chaque pièce) | P (poids de chaque pièce) |
|---------------------------|---------------------------|
| 1c | 2.30g |
| 2c | 3.06g |
| 5c | 3.92g |
| 10c | 4.10g |
| 20c | 5.74g |
| 50c | 7.80g |
| 100 (1€) | 7.50g |
| 200 (2€) | 8.50g |
| 500 (5€) | 0.6g |
| 1000 | 0.7g |
| 2000 | 0.8g |
| 5000 | 0.9g |
| 10000 | 1g |
<center>
<i>Table 2</i>
</center>
%% Cell type:code id:3ecdadb6-b735-47cd-ba9e-d79abb285648 tags:
``` python
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:b48169e9-ada6-4f03-a24c-28745caa99bd tags:
**Question 3.5 -** Implémentez à présent un algorithme de Programmation Dynamique qui calcule le poids minimal pour atteindre une valeur M. Étant donné qu'avec le système monétaire en Table 2 la minimisation du nombre de pièces minimise aussi le poids, vous pourrez tester votre algorithme avec un système plus complexe tel que donné en Table 3. Essayez par exemple avec la valeur $M=6$.
| S (valeur de chaque pièce) | P (poids de chaque pièce) |
|---------------------------|---------------------------|
| 1c | 10g |
| 3c | 27g |
| 4c | 32g |
| 7c | 55g |
<center>
<i>Table 3</i>
</center>
%% Cell type:code id:6e72a851-5698-4cd8-85ae-b830b8469ea9 tags:
``` python
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:d1970f87-775a-4374-9947-2ae1cff39418 tags:
**Question 3.6 -** On propose l'algorithme ci-dessous qui tente de résoudre le problème de façon gloutonne. Implémentez cet algorithme. Pour quelles valeurs de M ($\leq$ 20) avec la Table 3 donne-t-il une solution différente de l'algorithme à Programmation Dynamique ?
```
Fonction Poids_Gloutonne
Entrées : liste S, liste P, entier M
Sortie : poids minimal pour atteindre M
Soit L la liste des tuples (Pi/Si, Si, Pi), triée croissante selon le premier
élément de chaque tuple
M'=M
res = 0
Répéter
Chercher dans L le premier triplet (r, s, p) tel que s <= M'
res = res + p * (M' // s)
M' = M' % s
Jusqu'à M' = 0
res est la valeur de sortie de l'algorithme
Fin Poids_Gloutonne
```
%% Cell type:code id:b7f93bbc-c1d7-4de4-8470-961a68050d9e tags:
``` python
# YOUR CODE HERE
raise NotImplementedError()
```
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment