1A.algo - Programmation dynamique et plus court chemin#
Links: notebook
, html, python
, slides, GitHub
La programmation dynamique est une façon des calculs qui revient dans beaucoup d’algorithmes. Elle s’applique dès que ceux-ci peuvent s’écrire de façon récurrente.
from jyquickhelper import add_notebook_menu
add_notebook_menu()
La programmation
dynamique est
une façon de résoudre de manière similaire une classe de problèmes
d’optimisation qui vérifie la même propriété. On suppose qu’il est
possible de découper le problème en plusieurs parties
,
, … Si
est la solution optimale du
problème
, alors chaque partie
,
, … de
cette solution appliquée aux sous-problèmes est aussi optimale.
Par exemple, on cherche le plus court chemin entre les
villes
et
. Si celui-ci passe par la ville
alors les chemins
sont aussi les plus
courts chemins entre les villes
et
. La
démonstration se fait simplement par l’absurde : si la distance
n’est pas optimale alors il est possible de constuire un
chemin plus court entre les villes
et
. Cela
contredit l’hypothèse de départ.
Ces problèmes ont en règle générale une expression simple sous forme de
récurrence : si on sait résoudre le problème pour un échantillon de
taille , on appelle cette solution
alors on peut
facilement la solution
en fonction de
.
Parfois cette récurrence va au delà :
.
Les données#
On récupère le fichier matrix_distance_7398.txt
depuis
matrix_distance_7398.zip
qui contient des distances entre différentes villes (pas toutes).
import pyensae.datasource
pyensae.datasource.download_data("matrix_distance_7398.zip", website = "xd")
['.\matrix_distance_7398.txt']
On peut lire ce fichier soit avec le module pandas introduit lors de la séance 10 TD 10 : DataFrame et Matrice :
import pandas
df = pandas.read_csv("matrix_distance_7398.txt", sep="\t", header=None, names=["v1","v2","distance"])
df.head()
v1 | v2 | distance | |
---|---|---|---|
0 | Boulogne-Billancourt | Beauvais | 85597 |
1 | Courbevoie | Sevran | 26564 |
2 | Colombes | Alfortville | 36843 |
3 | Bagneux | Marcq-En-Baroeul | 233455 |
4 | Suresnes | Gennevilliers | 10443 |
Le membre values
se comporte comme une matrice, une liste de listes
:
matrice = df.values
matrice[:5]
array([['Boulogne-Billancourt', 'Beauvais', 85597],
['Courbevoie', 'Sevran', 26564],
['Colombes', 'Alfortville', 36843],
['Bagneux', 'Marcq-En-Baroeul', 233455],
['Suresnes', 'Gennevilliers', 10443]], dtype=object)
On peut aussi utiliser le petit exemple qui a été présenté lors de la séance 4 sur les fichiers TD 4 : Modules, fichiers, expressions régulières. Les données se présente sous forme de matrice. Les deux premières colonnes sont des chaînes de caractères, la dernière est une valeur numérique qu’il faut convertir.
with open ("matrix_distance_7398.txt", "r") as f :
matrice = [ row.strip(' \n').split('\t') for row in f.readlines() ]
for row in matrice:
row[2] = float(row[2])
print(matrice[:5])
[['Boulogne-Billancourt', 'Beauvais', 85597.0], ['Courbevoie', 'Sevran', 26564.0], ['Colombes', 'Alfortville', 36843.0], ['Bagneux', 'Marcq-En-Baroeul', 233455.0], ['Suresnes', 'Gennevilliers', 10443.0]]
Chaque ligne définit un voyage entre deux villes effectué d’une traite, sans étape. Les accents ont été supprimés du fichier.
Exercice 1#
Construire la liste des villes sans doublons.
Exercice 2#
Constuire un dictionnaire { (a,b) : d, (b,a) : d }
où a,b
sont
des villes et d
la distance qui les sépare ?
On veut calculer la distance entre la ville de Charleville-Mezieres
et Bordeaux
? Est-ce que cette distance existe dans la liste des
distances dont on dispose ?
Algorithme du plus court chemin#
On créé un tableau d[v]
qui contient ou contiendra la distance
optimale entre les villes v
et Charleville-Mezieres
. La valeur
qu’on cherche est d['Bordeaux']
. On initialise le tableau comme suit
:
d['Charleville-Mezieres'] = 0
d[v] = infini
pour tout'Charleville-Mezieres'
.
Exercice 3#
Quelles sont les premières cases qu’on peut remplir facilement ?
Exercice 4#
Soit une ville et une autre
, on s’aperçoit que
. Que proposez-vous de faire ? En déduire
un algorithme qui permet de déterminer la distance la plus courte entre
Charleville-Mezieres et Bordeaux.
Si la solution vous échappe encore, vous pouvez vous inspirer de l’Algorithme de Djikstra.
La répartition des skis#
Ce problème est un exemple pour lequel il faut d’abord prouver que la solution vérifie une certaine propriété avant de pouvoir lui appliquer une solution issue de la programmation dynamique.
skieurs rentrent dans un magasins pour louer 10 paires de
skis (parmi
). On souhaite leur donner à tous une paire qui
leur convient (on suppose que la taille de la paire de skis doit être au
plus proche de la taille du skieurs. On cherche donc à minimiser :
Où est un ensemble de
paires de skis parmi
(un arrangement
pour être plus précis).
A première vue, il faut chercher la solution du problème dans l’ensemble
des arrangements de paires parmi
. Mais si on ordonne
les paires et les skieurs par taille croissantes :
(tailles de
skieurs) et
(tailles de skis), résoudre le problème revient à prendre les skieurs
dans l’ordre croissant et à les placer en face d’une paire dans l’ordre
où elles viennent. C’est comme si on insérait des espaces dans la
séquence des skieurs sans changer l’ordre :
Exercice facultatif#
Il faut d’abord prouver que l’algorithme suggéré ci-dessus permet bien d’obtenir la solution optimale.
Exercice 5#
Après avoir avoir trié les skieurs et les paires par tailles croissantes. On définit :
Où est le meilleur choix possible de
paires
de skis parmi les
premières. Exprimer
par
récurrence (en fonction de
et
. On
suppose qu’un skieur sans paire de ski correspond au cas où la paire est
de taille nulle.
Exercice 6#
Ecrire une fonction qui calcule l’erreur pour la distribution optimale ? On pourra choisir des skieurs et des paires de tailles aléatoires par exemple.
import random
skieurs = [ random.gauss(1.75, 0.1) for i in range(0,10) ]
paires = [ random.gauss(1.75, 0.1) for i in range(0,15) ]
skieurs.sort()
paires.sort()
print(skieurs)
print(paires)
Exercice 7#
Quelle est la meilleure distribution des skis aux skieurs ?
Exercice 8#
Quels sont les coûts des deux algorithmes (plus court chemin et ski) ?
Prolongements : degré de séparation sur Facebook#
Le plus court chemin dans un graphe est un des algorithmes les plus
connus en programmation. Il permet de déterminer la solution en un coût
polynômial - chaque itération est en . La
programmation dynamique caractèrise le passage d’une vision combinatoire
à une compréhension récursif du même problème. Dans le cas du plus court
chemin, l’approche combinatoire consiste à énumérer tous les chemins du
graphe. L’approche dynamique consiste à démontrer que la première
approche combinatoire aboutit à un calcul très redondant. On note
la matrice des longueurs des routes,
s’il n’existe aucune route entre les villes
et
. On suppose que
. La
construction du tableau
d
se définit de manière itérative et
récursive comme suit :
Etape 0
Etape :math:`n`
où
'Charleville-Mezieres'
Tant que l’étape continue à faire des mises à jour
(
diminue), on répète l’étape
. Ce même
algorithme peut être appliqué pour déterminer le degré de
séparation
dans un réseau social. L’agorithme s’applique presque tel quel à
condition de définir ce que sont une ville et une distance entre villes
dans ce nouveau graphe. Vous pouvez tester vos idées sur cet exemple de
graphe Social circles:
Facebook.
L’algorithme de
Dikjstra
calcule le plus court chemin entre deux noeuds d’un graphe, l’algorithme
de
Bellman-Ford
est une variante qui calcule toutes les distances des plus courts chemin
entre deux noeuds d’un graphe.
import pyensae.datasource
files = pyensae.datasource.download_data("facebook.tar.gz",website="http://snap.stanford.edu/data/")
fe = [ f for f in files if "edge" in f ]
fe
Il faut décompresser ce fichier avec 7zip si
vous utilisez pysense < 0.8
. Sous Linux (et Mac), il faudra utiliser
une commande décrite ici tar.
import pandas
df = pandas.read_csv("facebook/1912.edges", sep=" ", names=["v1","v2"])
print(df.shape)
df.head()