Bonjour,
Dans la leçon précédente, nous avons posé le vocabulaire et les fondations de la théorie des graphes, en nous concentrant sur leurs représentations en machine. Nous avons conclu que les listes d'adjacence sont généralement la structure de données à privilégier, notamment pour les graphes creux que l'on rencontre fréquemment.
Aujourd'hui, nous passons de la statique à la dynamique : comment explorer un graphe de manière systématique ? Nous allons étudier notre premier algorithme de parcours, le parcours en largeur (en anglais Breadth-First Search ou BFS). L'objectif est non seulement d'implémenter ce parcours, mais surtout de comprendre et d'utiliser sa propriété la plus remarquable : sa capacité à calculer les plus courts chemins dans un graphe non pondéré.
1. Le principe du parcours en largeur (BFS)
Imaginez que vous êtes au centre d'un labyrinthe et que vous cherchez la sortie la plus proche. Une stratégie serait d'explorer d'abord toutes les salles directement connectées à votre position. Puis, depuis ces salles, explorer toutes les nouvelles salles adjacentes, et ainsi de suite, en vous éloignant progressivement, couche par couche. C'est précisément l'intuition du BFS.
Le BFS explore un graphe par "vagues" successives à partir d'un sommet source s.
- Il visite
s. - Il visite tous les voisins de
s. - Il visite tous les voisins non encore visités des voisins de
s. - Et ainsi de suite, jusqu'à ce que tous les sommets accessibles depuis
saient été visités.
Pour gérer cette exploration par "couches", l'algorithme s'appuie sur une structure de données que vous connaissez : la file (queue en anglais), qui suit le principe "Premier entré, premier sorti" (FIFO).
Pour une analogie visuelle très parlante de ce mécanisme, je vous invite à regarder le début de la vidéo suivante.
Breadth First Search (BFS): Visualized and Explained
La vidéo "Breadth First Search (BFS): Visualized and Explained" de la chaîne Reducible utilise une analogie simple mais puissante pour illustrer pourquoi le BFS garantit la découverte des plus courts chemins.
Regardez le segment de 00:47 à 02:26. Concentrez-vous sur l'analogie de la "balle et des ficelles" et sur la manière dont elle met en évidence les "couches" ou niveaux de distance par rapport au sommet de départ.
Cette exploration par niveaux est la raison pour laquelle le BFS est l'algorithme idéal pour trouver le plus court chemin en nombre d'arêtes. Le premier chemin trouvé par le BFS pour atteindre un sommet v depuis un sommet s est nécessairement le plus court, car tout autre chemin passerait par des sommets situés dans des couches plus éloignées ou de même niveau, et serait donc plus long ou de longueur égale.
2. L'algorithme BFS pas à pas
Voyons maintenant comment l'algorithme opère concrètement, en utilisant une file et un moyen de marquer les sommets déjà visités pour éviter les traitements redondants et les boucles infinies.
L'algorithme général est le suivant :
- Initialiser une file vide et y ajouter le sommet de départ
s. - Marquer
scomme visité. On utilise souvent un tableauvisiteet on peut aussi stocker la distance de chaque sommet àsdans un tableaudistance. On initialisedistance[s] = 0. - Tant que la file n'est pas vide :
a. Défiler un sommetu.
b. Pour chaque voisinvdeu:
i. Sivn'a pas été visité :
- Marquervcomme visité.
- Mettre à jour sa distance :distance[v] = distance[u] + 1.
- Enfilerv.
Pour voir cet algorithme en action, la vidéo ci-dessous détaille une exécution pas à pas.
Breadth First Search Algorithm | Shortest Path | Graph Theory
La vidéo "Breadth First Search Algorithm" de WilliamFiset montre une animation claire du fonctionnement de la file et du marquage des sommets.
Regardez les segments de 00:40 à 01:23 pour l'intuition des couches, puis de 01:23 à 03:05 pour l'exemple détaillé avec la file. Observez comment les sommets sont ajoutés à la file et comment le marquage (les nœuds grisés) empêche de les ajouter à nouveau.
L'image suivante résume parfaitement une exécution sur un graphe orienté.

3. Du parcours au calcul des plus courts chemins
Le parcours simple nous dit si un sommet est accessible, mais pour l'Agrégation, il est crucial de pouvoir exploiter ce parcours. L'application principale du BFS est le calcul des plus courts chemins en termes de nombre d'arêtes.
Comme vu précédemment, la distance d'un nouveau sommet v découvert depuis u est simplement distance[u] + 1. Cela nous donne la longueur du plus court chemin.
Mais comment reconstruire le chemin lui-même ?
On utilise pour cela un tableau supplémentaire, predecesseur. Lorsque l'on découvre un sommet v depuis u, on mémorise ce fait en stockant predecesseur[v] = u. Une fois le BFS terminé (ou la destination atteinte), il suffit de remonter la chaîne des prédécesseurs depuis le sommet d'arrivée jusqu'au sommet de départ pour reconstituer le chemin.
La vidéo de WilliamFiset explique très bien cette double procédure : une première passe BFS pour calculer les distances et les prédécesseurs, et une seconde passe pour reconstruire le chemin.
Breadth First Search Algorithm | Shortest Path | Graph Theory
Cette partie de la vidéo formalise l'algorithme avec du pseudo-code et explique la phase de reconstruction du chemin.
Regardez les segments de 03:05 à 07:08. Portez une attention particulière à l'initialisation et l'utilisation des trois structures de données : la file (queue), le tableau des visités (visited), et le tableau des prédécesseurs (prev). Comprenez comment la fonction reconstructPath utilise le tableau prev pour retrouver le chemin à l'envers.
4. Formalisation et Implémentation
Pour une préparation au niveau de l'Agrégation, la maîtrise du pseudo-code formel est indispensable. Le document suivant présente l'algorithme de manière rigoureuse.
Cours 2 - Plus courts chemins a origine unique
Le polycopié de cours de Christophe Crespelle (Université Côte D'azur) présente le BFS de manière formelle, avec sa preuve et son analyse.
Lisez attentivement les pages 9 à 11 pour la définition du problème et le pseudo-code de l'Algorithme 1. Puis, lisez la page 16 pour l'analyse de complexité. Ne vous attardez pas sur la preuve (pages 13-15) pour l'instant, mais sachez qu'elle existe et repose sur les propriétés de l'exploration par couches.
Analyse de complexité :
Comme indiqué dans le document, la complexité du BFS est un résultat fondamental à connaître :
- Complexité temporelle : (ou avec sommets et arêtes). Chaque sommet est enfilé/défilé une seule fois (), et chaque arête est examinée une seule fois (ou deux pour un graphe non orienté), d'où .
- Complexité spatiale : . Dans le pire cas, la file peut contenir presque tous les sommets. Les tableaux
visite,distanceetpredecesseuroccupent également un espace proportionnel au nombre de sommets.
Implémentation en Python :
Étant donné votre aisance en Python, l'implémentation est directe. La structure la plus efficace pour une file est collections.deque. Voici un squelette d'implémentation pour un graphe représenté par une liste d'adjacence.
from collections import deque
def bfs_shortest_path(graph, start, end):
# graph: dictionnaire {sommet: [voisins]}
# start, end: sommets de départ et d'arrivée
if start == end:
return [start]
queue = deque([(start, [start])]) # La file stocke (sommet_courant, chemin_jusqu'ici)
visited = {start}
while queue:
current_node, path = queue.popleft()
for neighbor in graph.get(current_node, []):
if neighbor not in visited:
if neighbor == end:
return path + [neighbor] # Chemin trouvé
visited.add(neighbor)
new_path = path + [neighbor]
queue.append((neighbor, new_path))
return None # Aucun chemin trouvé
Note : Cette implémentation stocke le chemin entier dans la file, ce qui peut être gourmand en mémoire pour de longs chemins. L'approche avec un tableau de prédécesseurs est généralement plus efficace en espace.
5. Exercice d'application
Un cas d'usage classique du BFS est la résolution de problèmes sur des grilles (labyrinthes, jeux, etc.), où la grille peut être vue comme un graphe implicite : chaque case est un sommet, et les arêtes relient les cases adjacentes praticables.
Problème :
Considérez une matrice binaire représentant une carte, où 1 est un chemin praticable et 0 un obstacle. Vous partez de la case en haut à gauche (0, 0) et devez atteindre la case en bas à droite.

Votre tâche :
- Modélisation : Décrivez comment vous modéliseriez ce problème en termes de graphe. Quels sont les sommets ? Comment définiriez-vous les arêtes (ou la relation de voisinage) ?
- Algorithme : Adaptez l'algorithme BFS pour résoudre ce problème. Comment gérez-vous les coordonnées
(ligne, colonne)? Comment la conditionif v not in visitedse traduit-elle ? - Trace : Sur une petite grille 3x3 de votre choix avec un obstacle, tracez les premières étapes du BFS : l'état de la file et l'ensemble des cases visitées.
Prenez environ 15 minutes pour réfléchir à ces questions et esquisser une solution sur papier. Il n'est pas nécessaire de coder la solution complète pour cet exercice, l'objectif est de bien maîtriser l'adaptation du modèle.
Conclusion
Aujourd'hui, nous avons ajouté un outil puissant et fondamental à votre arsenal algorithmique : le parcours en largeur.
Points clés à retenir :
- Le BFS explore un graphe couche par couche à l'aide d'une file.
- Sa propriété fondamentale est de trouver le plus court chemin en nombre d'arêtes dans un graphe non pondéré.
- Pour reconstruire le chemin, on utilise un tableau de prédécesseurs.
- Sa complexité, , le rend très efficace, en particulier sur les graphes creux représentés par des listes d'adjacence.
- Le BFS est un algorithme polyvalent qui s'applique à de nombreux problèmes au-delà des graphes explicites, comme les problèmes sur des grilles.
Prochaine étape :
Dans la leçon suivante, nous étudierons le "frère" du BFS : le parcours en profondeur (DFS). Nous verrons qu'en changeant simplement la file pour une pile, nous obtenons un comportement de parcours radicalement différent, avec ses propres applications distinctes comme la détection de cycles ou le tri topologique.