Parcours de graphes
Temps de lecture25 minEn bref
Résumé de l’article
Dans cet article, vous apprendrez à explorer un graphe en utilisant des algorithmes appelés parcours.
Points clés à retenir
-
Un parcours de graphe explore un graphe un sommet après l’autre pour construire un arbre couvrant du graphe.
-
Le parcours en profondeur (DFS) explore aussi loin que possible et revient en arrière s’il est bloqué.
-
Le parcours en largeur (BFS) explore les sommets par distance croissante depuis le point de départ.
-
Le BFS trouve un chemin le plus court entre un sommet initial et tous les sommets atteignables dans un graphe non pondéré.
-
Terminaison, correction et complexité sont des notions importantes pour évaluer un algorithme.
-
Le BFS et le DFS ont une complexité de $O(n + m)$, soit $O(n)$ dans un labyrinthe PyRat.
Contenu de l’article
Vidéo
Veuillez consulter la vidéo ci-dessous (en anglais, affichez les sous-titres si besoin). Nous fournissons également une traduction du contenu de la vidéo juste après, si vous préférez.
Information
Pour aller plus loin
Propriétés du BFS
Terminaison
L’algorithme BFS sur un graphe fini termine, c’est-à-dire qu’il s’arrêtera à un moment donné (pas de risque de boucle infinie).
Cette propriété n’est pas aussi évidente qu’elle en a l’air. Dès que le graphe contient un cycle, un parcours qui ne retiendrait pas les sommets déjà explorés pourrait tourner en rond indéfiniment. Par exemple, dans un graphe formé de trois sommets $a$, $b$ et $c$ reliés deux à deux, un tel parcours partant de $a$ irait en $b$, puis en $c$, puis à nouveau en $a$, et ainsi de suite. C’est l’ensemble des sommets explorés qui permet d’éviter cette situation.
Preuve
Correction
L’algorithme BFS retourne un chemin le plus court entre un sommet $u$ et tous les autres sommets atteignables depuis $u$ via les arêtes du graphe.
Ici, « le plus court » signifie « avec le moins d’arêtes possible ». Ce résultat n’est donc valable que pour des graphes non pondérés. Dans un graphe pondéré, un chemin avec plus d’arêtes peut avoir une longueur totale plus petite. C’est ce qui se passera dans la prochaine séance, lorsque le labyrinthe contiendra de la boue : il faudra alors utiliser un autre algorithme.
Preuve
Complexité
L’algorithme BFS a une complexité (c’est-à-dire un nombre d’opérations élémentaires) de $O(n + m)$, où $m$ est le nombre d’arêtes du graphe et $n$ est le nombre de sommets.
Dans un labyrinthe PyRat, chaque case a au plus 4 voisins, et on a vu que $m < 2n$ (voir l’article sur les graphes). La complexité est donc $O(n)$ : le temps nécessaire pour parcourir un labyrinthe est proportionnel à son nombre de cases.
Preuve
Important
Cette preuve repose sur deux hypothèses, qui dépendent de la manière dont le code est écrit :
-
Obtenir les voisins d’un sommet $v$ prend un temps proportionnel à $\deg(v)$. C’est le cas si chaque sommet stocke ses voisins, par exemple dans un dictionnaire, comme le fait la classe
Graphde PyRat. Avec une matrice d’adjacence, il faut parcourir une ligne entière de la matrice, soit $n$ cases, pour chaque sommet exploré. La complexité devient alors $O(n^2)$ (voir l’article sur la représentation des graphes). -
Ajouter et retirer un élément de la FIFO prend un temps constant. En Python, ce n’est pas le cas avec une liste :
pop(0)décale tous les éléments restants d’une case, et prend donc un temps proportionnel à la taille de la liste. Pour obtenir une vraie FIFO, on utilise plutôtcollections.deque, dont la méthodepopleft()s’exécute en temps constant.
Dans l’activité pratique, nous utilisons une liste et pop(0) par simplicité. Pour des labyrinthes de la taille de ceux de PyRat, la différence est négligeable, mais voici comment utiliser une deque si vous le souhaitez :
Propriétés du DFS
Terminaison
L’algorithme DFS sur un graphe fini termine.
Preuve
Correction
L’algorithme DFS est tel qu’il visitera tous les sommets atteignables depuis $u$. De plus, la table de routage obtenue donne, pour chacun de ces sommets, un chemin depuis $u$.
Preuve
En revanche, contrairement au BFS, le DFS ne garantit pas que les chemins obtenus soient les plus courts. Reprenons un graphe de trois sommets reliés deux à deux, notés cette fois $u$, $a$ et $b$, et lançons un DFS depuis $u$ :
- On explore $u$, et on ajoute $a$ puis $b$ à la LIFO, avec $u$ pour parent.
- On retire $b$, le dernier élément ajouté. On l’explore, et on ajoute $a$ à la LIFO, avec $b$ pour parent.
- On retire $a$, avec $b$ pour parent. On l’explore, et la table de routage indique donc que $b$ est le parent de $a$.
Le chemin obtenu de $u$ à $a$ passe par $b$ et contient 2 arêtes, alors que l’arête $\{u,a\}$ permet d’y aller directement. Selon l’ordre dans lequel les voisins sont ajoutés, le DFS peut aussi trouver le chemin direct, mais rien ne le garantit. La vidéo en montre un exemple plus frappant, avec un chemin de 7 arêtes entre deux sommets voisins.
Complexité
L’algorithme DFS a une complexité de $O(n + m)$, où $m$ est le nombre d’arêtes du graphe et $n$ est le nombre de sommets.
Preuve
Information
Dans l’activité pratique, une version récursive du DFS vous est également proposée. Chaque appel récursif occupe de la place dans la pile d’appels, et le nombre d’appels imbriqués peut atteindre le nombre de sommets atteignables, par exemple dans un labyrinthe en forme de long couloir. Or, Python limite par défaut cette profondeur à 1000 appels (voir sys.getrecursionlimit()), et lève une RecursionError au-delà. Pour les labyrinthes de taille par défaut (15 × 13, soit au plus 195 cases), ce n’est pas un problème, mais ça peut le devenir pour de très grands labyrinthes. La version avec une LIFO explicite n’a pas cette limite.
Récapitulatif
| BFS | DFS | |
|---|---|---|
| Structure de données | FIFO (file) | LIFO (pile) |
| Termine sur un graphe fini | Oui | Oui |
| Explore tous les sommets atteignables | Oui | Oui |
| Donne des plus courts chemins (graphe non pondéré) | Oui | Pas nécessairement |
| Complexité | $O(n + m)$ | $O(n + m)$ |
| Structure conseillée en Python | collections.deque |
list |
Pour aller encore plus loin
-
Une variante du BFS pour l’accélérer tout en sacrifiant la garantie de trouver le chemin le plus court.
-
Une approche mixte entre DFS et BFS qui combine les avantages des deux.






























