Algorithme de Dijsktra


Représentation du graphe par dictionnaire

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.

Déroulé de l'algorithme de Dijkstra

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:

Implémentation de la classe Dijkstra

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()