import numpy as np
from graph import *
En plus des packages habituels, nous aurons besoin du package math
, pour sa représentation de l'infini
from math import inf
inf
vérifie les propriétés classiques :
print(inf > 9999)
print(inf + 82 == inf)
print(inf + 1 > inf)
print(inf*(-72) == -inf)
True True False True
Un graphe valué est un graphe auquel on ajoute un poids, positif ou nul, sur ses arcs/arêtes.
Un graphe valué peut être représenté par sa matrice de poids M définie de la façon suivante :
$M_{i,j}=+\infty$ si il n'y pas d'arcs/arêtes reliant i à j
$M_{i,j}=$ valuation de l'arc/arête (i, j)
Exemple : le graphe orienté valué $GV1$ a pour matrice de poids $MV1$.
MV1 = np.array([ [inf,2,4,inf,inf],
[inf,inf,7,inf,inf],
[inf,9,inf,7,5],
[inf,inf,inf,inf,inf],
[8,inf,inf,inf,inf] ])
Un graphe valué sera créé grâce à l'attribut weight
, à qui sera affectée la matrice de poids (cet attribut a pour valeur par défaut une liste vide).
gv1 = Graph(weight=MV1)
gv1.successors = {0:{1,2},
1:{2},
2:{1,3,4},
3:set(),
4:{0}}
gv1.display(title="GV1")
# on peut accéder la matrice de poids via l'attribut weight
gv1.weight
array([[inf, 2., 4., inf, inf], [inf, inf, 7., inf, inf], [inf, 9., inf, 7., 5.], [inf, inf, inf, inf, inf], [ 8., inf, inf, inf, inf]])
# on peut aussi modifier un poids existant via l'attribut weight.
gv1.weight[0,1] = 10
print(gv1.weight)
gv1.display(title="GV1")
[[inf 10. 4. inf inf] [inf inf 7. inf inf] [inf 9. inf 7. 5.] [inf inf inf inf inf] [ 8. inf inf inf inf]]
Attention :
Changer un inf
en un nombre réel dans la matrice ne va pas ajouter automatiquement l'arc valué correspondant. Il faudra penser à modifier le dictionnaire contenant les successeurs !
matricepoids_vers_graphe(M)
qui construit directement le graphe orienté valué de matrice de poids $M$.Exemple :
# on donne la matrice MV2 suivante :
MV2 = np.array([ [inf,8,2,inf,inf],
[inf,inf,inf,1,inf],
[inf,5,inf,11,1],
[inf,inf,inf,inf,inf],
[inf,inf,inf,9,inf]])
gv2 = matricepoids_vers_graphe(MV2)
print(gv2.successors)
gv2.display(title = "GV2")
{0: {1, 2}, 1: {3}, 2: {1, 3, 4}, 3: set(), 4: {3}}
définition de bellman(g) M = matrice de poids de g Dist = liste des distances depuis le sommet 0, dont chaque élément est initialisé à l'infini Dist[0] = 0 tant que Dist change pour tout sommet i pour tout sommet j si Dist[i] + M[i,j] < Dist[j] alors Dist[j] = Dist[i] + M[i,j] fin si fin pour fin pour fin tant que retourner Dist
Construire une fonction bellman(g)
, qui renvoie les distances du plus court chemin du sommet $0$ à tous les autres, dans un graphe orienté valué g
, à l'aide de l'algorithme de Bellman.
Tester bellman
avec le graphe gv2
.
bellman(gv2)
[0, 7.0, 2.0, 8.0, 3.0]
définition de dijkstra(g) M = matrice de poids de g Dist = liste des distances depuis le sommet 0, dont chaque élément est initialisé à l'infini Dist[0] = 0 tant que il y a des sommets non marqués choisir un sommet i non marqué, tel que Dist[i] soit minimale marquer i pour tout sommet j non marqué si Dist[i] + M[i,j] < Dist[j] alors Dist[j] = Dist[i] + M[i,j] fin si fin pour tant que retourner Dist
dij(g)
et l'algorithme de Dijkstra.Rq : on pourra par exemple créer une liste marquage
, avec marquage[i]=True
si le sommet i
est marqué, False
sinon.
Exemple :
dij(gv2)
[0, 7.0, 2.0, 8.0, 3.0]
On veut maintenant créer un algorithme du plus long chemin. On supposera donc que le graphe n'a pas de circuits, afin de ne pas boucler à l'infini.
Que doit-on modifier dans l'algorithme de Bellman pour traiter un problème de plus long chemin ?
Créer une fonction bellmanlong(g)
qui renvoie les distances du plus long chemin du sommet 0 à tous les autres.
Exemple :
bellman_long(gv2)
[0, 8.0, 2.0, 13.0, 3.0]
Soit le problème d'ordonnancement suivant :
tâches | contraintes | durée (jours) |
---|---|---|
(0) | début du projet | 0 |
(1) | aucune contrainte | 3 |
(2) | commence au plus tôt après la fin de (3), et 9 jours après la fin de (1) | 6 |
(3) | commence au plus tôt 5 jours après le début du projet, et 4 jours après la fin de (1) | 4 |
(4) | fin du projet | 0 |
On associe à ce problème d'ordonnancement un graphe potentiel-tâche :
les sommets sont les tâches, et un arc (i,j) a une valuation k ssi la tâche j commence au plus tôt k jours après le début de la tâche i.
gA
potentiel-tâche du projet ci-dessus.gA.display(title="potentiel_tache")
L'objectif est de trouver les dates de début au plus tôt de chaque tâche, pour terminer le projet le plus rapidement possible. Il suffit alors de calculer les chemins les plus long depuis le sommet 0 à tous les autres.
[0, 0.0, 12.0, 7.0, 18.0]
reseau.display(title="réseau de trains")
Par exemple, cela couterait 3 millions de relier directement Zérocity à Trois-sur-marne, et 6 millions de relier directement Zérocity à Saint-Quatre.
Le regroupement de communes décide d'autoriser les changements de lignes, et de ne construire que les lignes nécessaires, afin d'assurer un coût total minimal.
Mathématiquement, il s'agit de construire un arbre couvrant de poids minimal, c'est à dire un arbre passant par tous les sommets du graphe, dont la somme des poids des arêtes est minimale. L'algorithme proposé pour résoudre le problème est l'algorithme de Prim.
Dans l'exemple, l'arbre couvrant serait :
définition de prim(g) marquer le sommet 0 arbre = arbre résultat, ne contenant initialement pas d'arêtes. tant qu' il reste des sommets non marqués {x, y} = arête de poids minimal, joignant un sommet marqué x à un sommet non marqué y marquer y ajouter l'arête {x,y} à l'arbre fin tant que retourner arbre
Attention, le graphe étant non orienté, il faut que la matrice de poids soit symétrique !
prim(g)
qui retourne l'arbre couvrant de poids minimum d'un graphe valué non orienté g
.arbre_couvrant = prim(reseau)
arbre_couvrant.display(title="arbre couvrant")
arbre_couvrant.successors
{0: {1, 3}, 1: {0}, 2: {3}, 3: {0, 2, 4}, 4: {3}}
arbre_couvrant.weight
array([[inf, 2., inf, 3., inf], [ 2., inf, inf, inf, inf], [inf, inf, inf, 2., inf], [ 3., inf, 2., inf, 1.], [inf, inf, inf, 1., inf]])