M07 - Théorie des graphes : matrices, chemins, circuits, niveaux, Dijkstra

Objectif

Lire et construire la matrice d’adjacence, compter les chemins de longueur avec , trouver les circuits et la fermeture transitive, ranger par niveaux, et calculer un plus court chemin (Dijkstra - la base du routage OSPF).


1. Vocabulaire

TermeDéfinition
SommetUn point (routeur, page web, tâche…)
ArêteLiaison non orientée (graphe non orienté)
ArcLiaison orientée (graphe orienté)
Successeur / prédécesseur de tel que / tel que
CheminSuite d’arcs qui s’enchaînent ; longueur = nombre d’arcs
CircuitChemin qui revient à son point de départ (boucle = circuit de longueur 1)
Graphe pondéréChaque arc a un poids (distance, coût, durée)
DegréNombre d’arêtes touchant un sommet (somme des degrés = 2 × nb d’arêtes)

2. Matrice d’adjacence

s’il existe un arc de vers , sinon 0 (sommets dans l’ordre alphabétique).

flowchart LR
  A((A)) --> B((B))
  A --> C((C))
  B --> D((D))
  D --> A
  D --> E((E))
  E --> D

LectureDonne
Ligne Successeurs de (somme = nombre de successeurs)
Colonne Prédécesseurs de
Matrice symétriqueGraphe non orienté
Ligne de 0Sommet sans successeur (ici C)

3. Puissances : compter les chemins

Théorème

Le terme de = nombre de chemins de longueur de vers . Somme de la diagonale de = nombre de circuits de longueur (comptés depuis chaque sommet de départ).

4. Fermeture transitive

( = OU terme à terme, = puissance booléenne). il existe au moins un chemin de vers .

  • Un 1 sur la diagonale de → le sommet est sur un circuit.
  • Diagonale entièrement nulle → graphe sans circuit (nécessaire pour un ordonnancement → M08 - Ordonnancement MPM).

5. Niveaux (graphe sans circuit)

Niveau 0 = sommets sans prédécesseur ; on les retire ; les nouveaux sommets sans prédécesseur forment le niveau 1, etc.

Exemple BTS 2019 (A→B, A→C, B→D, C→E, D→E, D→F, E→F) :

NiveauSommets
0A
1B, C
2D
3E
4F

Sa fermeture transitive a une diagonale nulle → aucun circuit ✅.

6. Plus court chemin : algorithme de Dijkstra

Principe : on part de la source à distance 0 ; à chaque étape on fixe le sommet non fixé de plus petite distance, puis on met à jour ses voisins si on trouve plus court en passant par lui. (Poids positifs uniquement.)

flowchart LR
  A((A)) ---|4| B((B))
  A ---|2| C((C))
  C ---|1| B
  B ---|5| D((D))
  C ---|8| D
  C ---|10| E((E))
  D ---|2| E
  D ---|6| F((F))
  E ---|2| F
ÉtapeFixéABCDEF
0A04 (A)2 (A)∞∞∞
1C–3 (C)2 (A)10 (C)12 (C)∞
2B–3 (C)–8 (B)12 (C)∞
3D–––8 (B)10 (D)14 (D)
4E––––10 (D)12 (E)
5F–––––12 (E)

On remonte les prédécesseurs depuis F : F ← E ← D ← B ← C ← A → A → C → B → D → E → F, longueur 12.

En réseau

OSPF utilise exactement Dijkstra (« Shortest Path First ») : chaque routeur calcule l’arbre des plus courts chemins, avec comme poids le coût des liens (inverse du débit). → P02 - Routage dynamique (RIP, OSPF)

7. Scripts

scripts/maths/graphes_matrices.py - matrice, successeurs/prédécesseurs, , circuits, fermeture transitive, niveaux :

python3 graphes_matrices.py "A>B A>C B>D C>E D>E D>F E>F" 3

scripts/maths/dijkstra.py - tableau d’étapes + chemin :

python3 dijkstra.py A F "A-B:4 A-C:2 C-B:1 B-D:5 C-D:8 C-E:10 D-E:2 D-F:6 E-F:2"
#!/usr/bin/env python3
"""Dijkstra minimal (le fichier complet affiche le tableau d'étapes)."""
import heapq
 
 
def dijkstra(graphe: dict, depart: str):
    distances = {s: float("inf") for s in graphe}
    predecesseurs = {s: None for s in graphe}
    distances[depart] = 0
    file_priorite = [(0, depart)]
    while file_priorite:
        distance_courante, sommet = heapq.heappop(file_priorite)
        if distance_courante > distances[sommet]:
            continue
        for voisin, poids in graphe[sommet].items():
            if distance_courante + poids < distances[voisin]:
                distances[voisin] = distance_courante + poids
                predecesseurs[voisin] = sommet
                heapq.heappush(file_priorite, (distances[voisin], voisin))
    return distances, predecesseurs
 
 
graphe_exemple = {}
for origine, destination, poids in [("A","B",4), ("A","C",2), ("C","B",1), ("B","D",5), ("C","D",8),
                                    ("C","E",10), ("D","E",2), ("D","F",6), ("E","F",2)]:
    graphe_exemple.setdefault(origine, {})[destination] = poids     # graphe non orienté :
    graphe_exemple.setdefault(destination, {})[origine] = poids     # on ajoute les deux sens
distances, predecesseurs = dijkstra(graphe_exemple, "A")
chemin, sommet = [], "F"
while sommet:
    chemin.insert(0, sommet)
    sommet = predecesseurs[sommet]
print(" → ".join(chemin), "=", distances["F"])     # A → C → B → D → E → F = 12

Voir aussi