Algorithmes sur les graphes

Numérique et Sciences Informatiques

donnees

Algorithmes sur les graphes : parcours

Le parcours d’un graphe consiste à visiter tous ses sommets de manière systématique. Deux algorithmes classiques permettent d’effectuer ce parcours :

  • Le parcours en profondeur (Depth-First Search, DFS), qui explore autant que possible un chemin avant de revenir en arrière.
  • Le parcours en largeur (Breadth-First Search, BFS), qui explore d’abord les sommets proches avant de s’éloigner progressivement.

Ces algorithmes maintiennent généralement deux structures :

  • Une liste des sommets déjà rencontrés (déjà_vus), qui évite de revisiter un sommet.
  • Une liste des sommets en cours de traitement (à_traiter), qui suit l’ordre dans lequel les sommets doivent être explorés.

Coût du parcours

Soit n = |V| le nombre de sommets et p = |E| le nombre d’arêtes du graphe.

  • Chaque sommet est inséré au plus une fois dans la liste à_traiter, puis retiré une fois. Si cette liste est implémentée avec une pile (DFS) ou une file (BFS), les opérations d’insertion et de suppression sont en temps constant. Ainsi, la gestion de cette structure est en O(n).
  • Chaque liste d’adjacence est parcourue au plus une fois, ce qui donne un coût de O(p) pour examiner les voisins.
  • Si la vérification d’un sommet dans déjà_vus est en temps constant (ce qui est le cas avec une table de hachage ou un ensemble), alors le coût total du parcours est en O(n + p).

Ce résultat est optimal pour parcourir un graphe représenté par des listes d’adjacence.

Parcours en largeur

L’algorithme de parcours en largeur (Breadth-First Search, BFS) explore un graphe en visitant d’abord tous les sommets situés à une même distance du sommet initial avant de passer aux suivants. Pour cela, il utilise une file d’attente où sont stockés les sommets à traiter.

Principe du BFS

  1. On démarre depuis un sommet s, qui est ajouté à la file et marqué comme découvert.
  2. Tant que la file n’est pas vide :
    • On en retire le premier sommet.
    • On ajoute ses voisins non encore visités à la file, en les marquant comme découverts.
  3. Les sommets sont donc traités par niveau :
    • Tous ceux situés à distance 1 de s sont explorés avant ceux à distance 2, puis distance 3, etc.

Grâce à cette propriété, le BFS est particulièrement utile pour calculer la plus courte distance entre deux sommets dans un graphe non pondéré.

Exemple de parcours en largeur

graphe
Graphe G

Considérons un graphe G :

  • Un parcours en largeur depuis 0 peut donner l'ordre : 0 → 1 → 3 → 6 → 4 → 8 → 5 → 7 → 2.
  • Un parcours en largeur depuis 2 peut donner l'ordre : 2 → 5 → 6 → 7 → 0 → 1 → 3 → 4 → 8.
1. Fonction PARCOURS_LARGEUR(G, s)
2.     Créer une file vide a_traiter
3.     Enfiler le sommet s dans a_traiter
4.     Créer un ensemble découverts
5.     Ajouter s à découverts
6.     Tant que a_traiter n'est pas vide :
7.         Retirer un sommet u de a_traiter (défiler)
8.         Ajouter u à la liste des sommets découverts
9.         
10.        Pour chaque voisin v de u dans G :
11.             Si voisin n'est pas dans découverts :
12.                 Ajouter voisin à découverts
13.                 Enfiler voisin dans a_traiter
14.     
15. Fin de la fonction

Entraînement 1 :

Implémenter une fonction pour un parcours en largeur d'un graphe G à partir d'un sommet S. G est représenté par une liste d'adjacence.

Voir une solution

Parcours en profondeur (DFS)

L’algorithme de parcours en profondeur (Depth First Search, DFS) explore un graphe en suivant un chemin aussi loin que possible avant de revenir en arrière. Pour cela, il utilise une pile où sont stockés les sommets à explorer.

Principe du DFS

  1. On démarre depuis un sommet s, qui est ajouté à la pile et marqué comme visité.
  2. Tant que la pile n’est pas vide :
    • On dépile le dernier sommet ajouté.
    • On explore un de ses voisins non visités.
    • Ce voisin est ajouté à la pile et marqué comme visité.
    • Si un sommet n’a plus de voisins non visités, on remonte en arrière dans la pile.
  3. Le DFS explore donc chaque branche du graphe jusqu’au bout avant d’explorer une autre branche.

Cet algorithme est utile pour :

  • Détecter la présence de cycles dans un graphe.
  • Explorer toutes les connexions d'un graphe ou d’un arbre.

Exemple de parcours en profondeur

Considérons un graphe G :

Graphe représentant un parcours en profondeur
Graphe G

Sur ce graphe :

  • Un parcours en profondeur depuis 0 peut donner l'ordre : 0 → 1 → 4 → 7 → 5 → 2 → 6 → 3 → 8.
  • Un parcours en profondeur depuis 2 peut donner l'ordre : 2 → 5 → 6 → 0 → 1 → 4 → 7 → 3→ 8.
1.  Début  Fonction PARCOURS_PROFONDEUR(G, s)
2.      a_traiter ← pile vide  
3.      Empiler s dans a_traiter  
4.      decouverts ← liste vide  
5.  
6.      Tant que a_traiter n'est pas vide faire  
7.          sommet ← Dépiler a_traiter  
8.  
9.          Si sommet n'est pas dans decouverts alors  
10.             Ajouter sommet à decouverts  
11.  
12.             Pour chaque voisin v de G[sommet] triés par ordre décroissant faire  
13.                 Si v n'est pas dans decouverts alors  
14.                     Empiler v dans a_traiter  
15.                 Fin Si  
16.             Fin Pour  
17.  
18.         Fin Si  
19.  
20.     Fin Tant que  
21.  
22.     Retourner decouverts  
23. Fin de la fonction


Cette version utilise la récursivité pour explorer directement les voisins du sommet courant.
1. Fonction DFS_REC(G, s, VISITÉS)
2.     Ajouter s à VISITÉS
3.     Afficher s  (ou l'ajouter à la liste des sommets visités)
4.     
5.     Pour chaque voisin v de s dans G :
6.         Si v n'est pas dans VISITÉS :
7.             Appeler DFS_REC(G, v, VISITÉS)
8.     
9. Fin de la fonction


Entraînement 2 :

Implémente une fonction parcours_profondeur(G, s) qui prend en entrée un graphe G (représenté sous forme de dictionnaire de listes d’adjacence) et un sommet de départ s. La fonction doit afficher l’ordre des sommets visités.

Voir une solution

Cycle dans les graphes

Application : Réseau routier

Entraînement :

Un réseau routier est représenté par un graphe pondéré, qui peut être orienté si les trajets sont dans un sens unique ou si les temps de trajet varient en fonction du sens. Dans ce graphe, les villes sont les sommets et les routes sont les arêtes. Les distances ou temps de trajet sont représentés sur les arêtes.

graphe
Réseau routier simplifié entre plusieurs villes françaises : Aire-sur-Adour, Bordeaux, Cherbourg-en-Cotentin, Clermont-Ferrand, Lyon, Montpellier, Paris, Strasbourg, Toulouse. Sur les arêtes bleues, la vitesse moyenne est de 110 km/h, sur les vertes 95 km/h et sur les rouges 75 km/h.

Entraînement 3 :

Écrire une variable Python sommets représentant les villes dans le graphe.

Entraînement 4 :

Représenter le graphe sous forme d'un dictionnaire graph, où chaque clé est une ville et chaque valeur est la liste de ses voisins (les villes connectées par des routes).

Entraînement 5 :

Représenter le graphe sous forme de matrice d’adjacence matrice, où chaque cellule (i, j) représente la distance ou le temps entre les villes i et j. Si deux villes ne sont pas connectées, la cellule contient un infini ou une valeur indicatif.

Entraînement 6 :

Écrire une fonction villes(graph) qui, à partir d’un graphe représenté par un dictionnaire, renvoie la liste des sommets (villes) dans le graphe.

Entraînement 7 :

Écrire une fonction voisins(graph, ville1) qui renvoie la liste des voisins (villes adjacentes) de la ville ville1 dans un graphe non orienté.

Entraînement 8 :

Écrire une fonction sont_voisins(graph, ville1, ville2) qui renvoie True si les villes ville1 et ville2 sont adjacentes dans le graphe, sinon False.

Entraînement 9 :

Écrire une fonction distance(graph, matrice, ville1, ville2) qui renvoie la distance (ou le temps de trajet) entre les villes ville1 et ville2 si elles sont adjacentes, sinon une valeur indiquant qu’il n’y a pas de connexion directe.

Entraînement 10 :

Écrire une fonction chemin(graph, ville1, ville2) qui retourne la liste des villes à parcourir pour aller de ville1 à ville2. L’utilisation d’un parcours en profondeur est recommandée pour cette tâche.

Entraînement 11 :

Écrire une fonction distance_chemin(graph, matrice, ville1, ville2) qui renvoie la distance totale (ou le temps total) du chemin le plus court entre les villes ville1 et ville2, calculée en utilisant la fonction chemin définie précédemment.

Entraînement 12 :

Implémenter l’algorithme de Dijkstra pour déterminer le plus court chemin entre deux villes du réseau routier (en distance ou en temps, au choix).



Savoir faire

  • Savoir parcourir un graphe en profondeur et en largeur.
  • Savoir repérer la présence d’un cycle dans un graphe
  • Savoir chercher un chemin dans un graphe.

Fiche de cours