On décide de représenter un graphe (orienté, et aux arcs pondérés par des poids positifs) par listes de successeurs sous la forme ci-dessous.
Chaque sommet du graphe est une clé du dictionnaire g, dont la valeur associée est la liste des arcs vers les ses voisins.
Un arc est ici représenté par un tuple contenant le sommet voisin et le poids de l'arc.
g= { 'S': [ ('A', 19), ('B', 8) ], 'A': [ ('B', 14), ('D', 6) ], 'B': [ ('C', 4), ('D', 22) ], 'C': [ ('A', 2), ('D', 10), ('T', 11) ], 'D': [ ('T', 2) ], 'T': [ ] }Ce graphe est dessiné dans les figures de la section ci-dessous. On souhaite implémenter l'algorithme de Dijkstra sur un tel graphe.
Supposons que l'on dispose d'un algorithme de Dijkstra d de type class Dijkstra, construisant un arbre de plus courts chemins sur le graphe g en prenant 'S' pour sommet source. On obtient successivement les étapes suivantes:
À l'initialisation de l'algorithme d:
|
À l'étape 0, le sommet 'S' a une distance réputée finie d.distances['S'] == 0,
ce qui est minimal parmi tous les sommets w tels que d.steps[w] == None,
c'est-à-dire 'S', 'A', 'B', 'C', 'D', 'T'.
Cette distance devient donc définitive.
Sur le dessin ci-contre, cela se traduit par le sommet 'S' colorié en vert.
Les informations des voisins jaunes de 'S' sont mises à jour
('A' et 'B').
On a donc les changements suivants:
d.steps == { 'S':0 , 'A':None, 'B':None, 'C':None, 'D':None, 'T':None } # after step 0 d.parents == { 'S':None, 'A':'S' , 'B':'S' , 'C':None, 'D':None, 'T':None } # after step 0 d.distances == { 'S':0 , 'A':19 , 'B':8 , 'C':Inf , 'D':Inf , 'T':Inf } # after step 0 |
À l'étape 1, le sommet 'B' a une distance réputée finie d.distances['B'] == 8,
ce qui est minimal parmi tous les sommets w tels que d.steps[w] == None,
c'est-à-dire 'A', 'B', 'C', 'D', 'T'.
Cette distance devient donc définitive.
Sur le dessin ci-contre, cela se traduit par le sommet 'B' colorié en vert.
Les informations des voisins jaunes de 'B' sont mises à jour
('C' et 'D').
On a donc les changements suivants:
d.steps == { 'S':0 , 'A':None, 'B':1 , 'C':None, 'D':None, 'T':None } # after step 1 d.parents == { 'S':None, 'A':'S' , 'B':'S' , 'C':'B' , 'D':'B' , 'T':None } # after step 1 d.distances == { 'S':0 , 'A':19 , 'B':8 , 'C':12 , 'D':30 , 'T':Inf } # after step 1 |
À l'étape 2, le sommet 'C' a une distance réputée finie d.distances['C'] == 12,
ce qui est minimal parmi tous les sommets w tels que d.steps[w] == None,
c'est-à-dire 'A', 'C', 'D', 'T'.
Cette distance devient donc définitive.
Sur le dessin ci-contre, cela se traduit par le sommet 'C' colorié en vert.
Les informations des voisins jaunes de 'C' sont mises à jour
('A', 'D' et 'T').
On a donc les changements suivants:
d.steps == { 'S':0 , 'A':None, 'B':1 , 'C':2 , 'D':None, 'T':None } # after step 2 d.parents == { 'S':None, 'A':'C' , 'B':'S' , 'C':'B' , 'D':'C' , 'T':'C' } # after step 2 d.distances == { 'S':0 , 'A':14 , 'B':8 , 'C':12 , 'D':22 , 'T':23 } # after step 2 |
À l'étape 3, le sommet 'A' a une distance réputée finie d.distances['A'] == 14,
ce qui est minimal parmi tous les sommets w tels que d.steps[w] == None,
c'est-à-dire 'A', 'D', 'T'.
Cette distance devient donc définitive.
Sur le dessin ci-contre, cela se traduit par le sommet 'A' colorié en vert.
Les informations des voisins jaunes de 'A' sont mises à jour
('D').
On a donc les changements suivants:
d.steps == { 'S':0 , 'A':3 , 'B':1 , 'C':2 , 'D':None, 'T':None } # after step 3 d.parents == { 'S':None, 'A':'C' , 'B':'S' , 'C':'B' , 'D':'A' , 'T':'C' } # after step 3 d.distances == { 'S':0 , 'A':14 , 'B':8 , 'C':12 , 'D':20 , 'T':23 } # after step 3 |
À l'étape 4, le sommet 'D' a une distance réputée finie d.distances['D'] == 20,
ce qui est minimal parmi tous les sommets w tels que d.steps[w] == None,
c'est-à-dire 'D', 'T'.
Cette distance devient donc définitive.
Sur le dessin ci-contre, cela se traduit par le sommet 'D' colorié en vert.
Les informations des voisins jaunes de 'D' sont mises à jour
('T').
On a donc les changements suivants:
d.steps == { 'S':0 , 'A':3 , 'B':1 , 'C':2 , 'D':4 , 'T':None } # after step 4 d.parents == { 'S':None, 'A':'C' , 'B':'S' , 'C':'B' , 'D':'A' , 'T':'D' } # after step 4 d.distances == { 'S':0 , 'A':14 , 'B':8 , 'C':12 , 'D':20 , 'T':22 } # after step 4 |
À l'étape finale 5, le sommet 'T'
a une distance réputée finie d.distances['T'] == 22,
ce qui est minimal parmi tous les sommets w tels que d.steps[w] == None,
c'est-à-dire 'T' seul.
Cette distance devient donc définitive.
Sur le dessin ci-contre, cela se traduit par le sommet 'T' colorié en vert.
Les informations des voisins jaunes de 'D' sont mises à jour
(mais il n'y en a pas).
On a donc les changements suivants:
d.steps == { 'S':0 , 'A':3 , 'B':1 , 'C':2 , 'D':4 , 'T':5 } # after step 5 d.parents == { 'S':None, 'A':'C' , 'B':'S' , 'C':'B' , 'D':'A' , 'T':'D' } # after step 5 d.distances == { 'S':0 , 'A':14 , 'B':8 , 'C':12 , 'D':20 , 'T':22 } # after step 5 |
On souhaite créer une classe Dijkstra pour l'algorithme de même nom :
class Dijkstra (object): __slots__= ( "graph", "source", "steps", "distances", "parents" ) def __init__ (self): pass def run_step (self, step): pass def run (self): pass
d= Dijkstra (g, 'S') d.run() print (d.steps) print (d.distances) print (d.parents)
L'appel au constructeur d= Dijkstra (g, 'S') construit l'instance d afin que d.graph et d.source soient initialisés avec g et 'S' .
L'appel à la méthode d.run() lance l'algorithme en lui-même et fabrique la solution dans les champs d.steps, d.distances, et d.parents.
Il consiste pour l'essentiel à appliquer une étape via run_step() tant qu'il existe des sommets v avec une distance réputée d.distances[v] finie tel que d.steps[v] == None.
On propose le découpage en méthodes auxiliaires suivant:
class Dijkstra (object): __slots__= ( "graph", "source", "steps", "distances", "parents" ) def __init__ (self, graph, source): pass def run (self): pass def init_tree (self): pass def run_step (self, step): pass def find_min_vertex (self): pass def update_vertex_neighbor (self, vertex, neighbor, arc_length): pass def update_all_vertex_neighbors (self, vertex): pass
On peut tester les étapes successives de l'algorithme avec la fonction de test suivante:
def test_dijkstra(): g= { 'S': [ ('A', 19), ('B', 8) ], 'A': [ ('B', 14), ('D', 6) ], 'B': [ ('C', 4), ('D', 22) ], 'C': [ ('A', 2), ('D', 10), ('T', 11) ], 'D': [ ('T', 2) ], 'T': [ ] } min_vertex_list= [ 'S', 'B', 'C', 'A', 'D', 'T' ] # expected order for steps steps_list = [ { 'S':0 , 'A':None, 'B':None, 'C':None, 'D':None, 'T':None }, # after step 0 { 'S':0 , 'A':None, 'B':1 , 'C':None, 'D':None, 'T':None }, # after step 1 { 'S':0 , 'A':None, 'B':1 , 'C':2 , 'D':None, 'T':None }, # after step 2 { 'S':0 , 'A':3 , 'B':1 , 'C':2 , 'D':None, 'T':None }, # after step 3 { 'S':0 , 'A':3 , 'B':1 , 'C':2 , 'D':4 , 'T':None }, # after step 4 { 'S':0 , 'A':3 , 'B':1 , 'C':2 , 'D':4 , 'T':5 }, # after step 5 ] Inf= math.inf distances_list = [ { 'S':0 , 'A':19 , 'B':8 , 'C':Inf , 'D':Inf , 'T':Inf }, # after step 0 { 'S':0 , 'A':19 , 'B':8 , 'C':12 , 'D':30 , 'T':Inf }, # after step 1 { 'S':0 , 'A':14 , 'B':8 , 'C':12 , 'D':22 , 'T':23 }, # after step 2 { 'S':0 , 'A':14 , 'B':8 , 'C':12 , 'D':20 , 'T':23 }, # after step 3 { 'S':0 , 'A':14 , 'B':8 , 'C':12 , 'D':20 , 'T':22 }, # after step 4 { 'S':0 , 'A':14 , 'B':8 , 'C':12 , 'D':20 , 'T':22 }, # after step 5 ] parents_list = [ { 'S':None, 'A':'S' , 'B':'S' , 'C':None, 'D':None, 'T':None }, # after step 0 { 'S':None, 'A':'S' , 'B':'S' , 'C':'B' , 'D':'B' , 'T':None }, # after step 1 { 'S':None, 'A':'C' , 'B':'S' , 'C':'B' , 'D':'C' , 'T':'C' }, # after step 2 { 'S':None, 'A':'C' , 'B':'S' , 'C':'B' , 'D':'A' , 'T':'C' }, # after step 3 { 'S':None, 'A':'C' , 'B':'S' , 'C':'B' , 'D':'A' , 'T':'D' }, # after step 4 { 'S':None, 'A':'C' , 'B':'S' , 'C':'B' , 'D':'A' , 'T':'D' }, # after step 5 ] d= Dijkstra (g, 'S') d.init_tree() for k in range (0, 6): min_vertex= d.run_step(k) assert min_vertex == min_vertex_list[k] assert d.distances == distances_list[k] assert d.parents == parents_list[k] assert d.steps == steps_list[k] test_dijkstra()