Exercice 1 : Parcours en largeur (BFS) et profondeur (DFS)
Problème : Exploration avancée de graphes orientés et applications
Section titled “Problème : Exploration avancée de graphes orientés et applications”Matière : Informatique (Filière MP2I)
Thème : Algorithmique des graphes, Parcours en largeur (\text{BFS}) et en profondeur (\text{DFS})
1. Énoncé
Section titled “1. Énoncé”Soit un graphe orienté, où représente l’ensemble des sommets de cardinal et l’ensemble des arcs de cardinal . On suppose que le graphe est représenté en mémoire par des listes d’adjacence : pour chaque sommet , on dispose de la liste de ses successeurs notée .
L’objectif de ce problème est d’étudier l’implémentation, la complexité et des extensions classiques des deux algorithmes fondamentaux de parcours de graphes : le parcours en largeur ( - ) et le parcours en profondeur ( - ). Enfin, une analyse critique des limites de ces modèles en mémoire et en temps sera menée.
2. Questions
Section titled “2. Questions”Partie I : Préliminaires et Parcours en Largeur ()
Section titled “Partie I : Préliminaires et Parcours en Largeur (BFS\text{BFS}BFS)”- Rappeler la structure de données utilisée pour implémenter un parcours en largeur et préciser la complexité temporelle et spatiale de l’algorithme en fonction de et dans le pire des cas.
- Écrire une fonction en OCaml ou en Python (au choix, on adoptera Python pour la suite)
bfs(graphe, source)qui renvoie un dictionnaire ou un tableau des distances minimales (en nombre d’arcs) depuis un sommetsourcevers tous les autres sommets accessibles.
Partie II : Parcours en Profondeur () et Classification des Arcs
Section titled “Partie II : Parcours en Profondeur (DFS\text{DFS}DFS) et Classification des Arcs”- Le parcours en profondeur utilise classiquement une pile (explicite ou implicite via la récursivité). Écrire une fonction récursive
dfs_rec(graphe, u, visites)effectuant un parcours en profondeur à partir du sommet . - On souhaite dater l’entrée et la sortie de chaque sommet lors d’un . Pour tout sommet , on définit :
- : le timestamp lors de la première visite de .
- : le timestamp lorsque tous les successeurs de ont été explorés. Modifier la fonction pour calculer ces dates.
Partie III : Analyse critique et limites du modèle
Section titled “Partie III : Analyse critique et limites du modèle”- Analyse critique : Discuter des limites de ces algorithmes sur des graphes massifs (par exemple, le web entier ou le graphe des réseaux sociaux). Que se passe-t-il en termes de cache CPU, de saturation mémoire, et de validité du modèle statique face à des graphes dynamiques ?
3. Pistes de résolution
Section titled “3. Pistes de résolution”- Pour le BFS : Penser à utiliser une file () pour garantir l’ordre d’exploration par niveaux croissants de distance. Un tableau de booléens
visiteévitera les boucles infinies. - Pour le DFS : Utiliser un compteur global ou passé par référence pour incrémenter les dates d’entrée et de sortie. La classification des arcs (arbres, retour, avance, transverses) repose entièrement sur ces dates et l’état des sommets.
- Pour la critique : S’interroger sur la localité des données en mémoire ( des listes chaînées vs tableaux) et sur la récursivité (risque de dépassement de pile ).
4. Corrigé pas à pas
Section titled “4. Corrigé pas à pas”Partie I : Parcours en Largeur ()
Section titled “Partie I : Parcours en Largeur (BFS\text{BFS}BFS)”Question 1 : Le utilise une file (structure : ).
- Complexité temporelle : car chaque sommet est enfilé/défilé une fois, et chaque arc est examiné une fois.
- Complexité spatiale : pour stocker la file, le tableau des distances et le tableau des visites.
Question 2 :
from collections import deque
def bfs(graphe, source): distances = {source: 0} file = deque([source]) visites = {source}
while file: u = file.popleft() for v in graphe.get(u, []): if v not in visites: visites.add(v) distances[v] = distances[u] + 1 file.append(v)
return distancesPartie II : Parcours en Profondeur () et Horodatage
Section titled “Partie II : Parcours en Profondeur (DFS\text{DFS}DFS) et Horodatage”Question 3 :
def dfs_rec(graphe, u, visites=None): if visites is None: visites = set() visites.add(u) for v in graphe.get(u, []): if v not in visites: dfs_rec(graphe, v, visites) return visitesQuestion 4 : Introduisons un compteur global pour gérer le temps logique :
def dfs_horodatage(graphe): date_dec = {} date_fin = {} temps = 0
def explorer(u): nonlocal temps temps += 1 date_dec[u] = temps for v in graphe.get(u, []): if v not in date_dec: explorer(v) temps += 1 date_fin[u] = temps
for sommet in graphe: if sommet not in date_dec: explorer(sommet)
return date_dec, date_finPartie III : Analyse critique et limites du modèle
Section titled “Partie III : Analyse critique et limites du modèle”Question 5 : L’application naïve des algorithmes et sur des graphes massifs se heurte à plusieurs obstacles fondamentaux :
-
Limites spatiales et mémoire RAM : Les listes d’adjacence implémentées sous forme de pointeurs en mémoire (objets dynamiques en Python ou structures chaînées en C) génèrent une fragmentation mémoire importante. Le surcoût mémoire par sommet et par arc (pointeurs, métadonnées des allocations) rend le stockage d’un graphe à l’échelle du Web impossible sur une machine standard. On doit recourir à des représentations compressées (comme le format - Compressed Sparse Row) ou des architectures distribuées (ex: , ).
-
Déficit de localité cache () : Dans une liste d’adjacence classique, les successeurs d’un sommet sont éparpillés en mémoire. Lors de l’exploration ( ou ), les accès mémoire provoquent de nombreux défauts de cache (), ralentissant considérablement l’exécutable, la latence RAM devenant le goulot d’étranglement principal devant les calculs CPU.
-
Profondeur de récursion et pile d’exécution : Le récursif s’appuie sur la pile d’appels système. Pour un graphe linéaire de taille , le programme subit inévitablement un dépassement de pile (). En pratique, il est impératif d’implémenter le de manière itérative à l’aide d’une pile explicite allouée sur le tas ().
-
Invalidité du modèle statique : Le modèle suppose figé pendant le parcours. Sur des flux de données en temps réel (réseaux sociaux, trafic routier), le graphe évolue constamment pendant l’exécution de l’algorithme, rendant les garanties théoriques (comme l’optimalité des distances en ) caduques sans mécanisme de verrouillage ou de mise à jour incrémentale.