(Figure : voir la version PDF pour la représentation graphique)
{0.35}
(Tableau : voir la version PDF)
{0.35}
graphe = {
"A": ["B", "C"],
"B": ["A", "D"],
"C": ["A", "D"],
"D": ["B", "C"]
}
On explore le plus loin possible avant de revenir en arrière. Utilise une pile (ou la récursion).
def dfs(graphe, depart):
visites = set()
pile = [depart]
while pile:
sommet = pile.pop()
if sommet not in visites:
visites.add(sommet)
for voisin in graphe[sommet]:
pile.append(voisin)
return visites
On explore tous les voisins d'abord, puis les voisins des voisins. Utilise une file.
from collections import deque
def bfs(graphe, depart):
visites = set()
file = deque([depart])
visites.add(depart)
while file:
sommet = file.popleft()
for voisin in graphe[sommet]:
if voisin not in visites:
visites.add(voisin)
file.append(voisin)
return visites
Le BFS donne le plus court chemin (en nombre d'arêtes) dans un graphe non pondéré.
graphe = {
"A": ["B", "C"],
"B": ["A", "C", "D"],
"C": ["A", "B", "D"],
"D": ["B", "C"]
}
(Graphe : voir la version PDF pour la représentation graphique)
def dfs_rec(graphe, sommet, visites=None):
if visites is None:
visites = set()
visites.add(sommet)
print(sommet, end=" ")
for voisin in graphe[sommet]:
if voisin not in visites:
dfs_rec(graphe, voisin, visites)
return visites
# dfs_rec(graphe, "A") donne A B C D (ou autre ordre selon les voisins)
def bfs_chemin(graphe, depart, arrivee):
file = deque([depart])
visites = {depart}
parent = {depart: None}
while file:
sommet = file.popleft()
if sommet == arrivee:
# Reconstruction du chemin
chemin = []
while sommet is not None:
chemin.append(sommet)
sommet = parent[sommet]
return chemin[::-1]
for voisin in graphe[sommet]:
if voisin not in visites:
visites.add(voisin)
parent[voisin] = sommet
file.append(voisin)
return None # pas de chemin
def a_cycle(graphe):
visites = set()
for sommet in graphe:
if sommet not in visites:
if _dfs_cycle(graphe, sommet, None, visites):
return True
return False
def _dfs_cycle(graphe, sommet, parent, visites):
visites.add(sommet)
for voisin in graphe[sommet]:
if voisin not in visites:
if _dfs_cycle(graphe, voisin, sommet, visites):
return True
elif voisin != parent:
return True
return False
def composantes_connexes(graphe):
visites = set()
composantes = []
for sommet in graphe:
if sommet not in visites:
composante = set()
dfs_composante(graphe, sommet, visites, composante)
composantes.append(composante)
return composantes
def dfs_composante(graphe, sommet, visites, composante):
visites.add(sommet)
composante.add(sommet)
for voisin in graphe[sommet]:
if voisin not in visites:
dfs_composante(graphe, voisin, visites, composante)
Soit le graphe non orienté suivant :
(Graphe : voir la version PDF pour la représentation graphique)
1. Matrice d'adjacence :
1 2 3 4 5
1 [ 0, 1, 1, 0, 0 ]
2 [ 1, 0, 0, 1, 0 ]
3 [ 1, 0, 0, 1, 1 ]
4 [ 0, 1, 1, 0, 1 ]
5 [ 0, 0, 1, 1, 0 ]
Matrice symétrique (graphe non orienté).
2. Liste d'adjacence :
{1: [2, 3], 2: [1, 4], 3: [1, 4, 5], 4: [2, 3, 5], 5: [3, 4]}
3. Degrés : sommet 1 : 2 ; sommet 2 : 2 ; sommet 3 : 3 ; sommet 4 : 3 ; sommet 5 : 2.
On vérifie : la somme des degrés vaut $2 + 2 + 3 + 3 + 2 = 12 = 2 × 6$ (deux fois le nombre d'arêtes). ✓
4. Le graphe est connexe (on peut atteindre tout sommet depuis tout autre sommet). Il comporte des cycles, par exemple $1 - 2 - 4 - 3 - 1$ ou $3 - 4 - 5 - 3$.
En utilisant le graphe de l'exercice précédent :
1. DFS depuis 1 (voisins en ordre croissant) :
Ordre : 1, 3, 5, 4, 2.
Remarque : avec la version pile (itérative), le dernier voisin ajouté est visité en premier, ce qui explique que 3 est visité avant 2.
Avec la version récursive (voisins en ordre croissant), on obtiendrait 1, 2, 4, 3, 5.
2. BFS depuis 1 :
Ordre : 1, 2, 3, 4, 5.
3. Plus court chemin de 2 à 5 :
Le BFS depuis 2 visite les sommets par distance croissante :
Le plus court chemin est de longueur 2. Par exemple : $2 - 4 - 5$.
Écrire une fonction distance(graphe, depart, arrivee) qui renvoie la longueur du plus court chemin (en nombre d'arêtes) entre deux sommets d'un graphe non pondéré.
Renvoyer $-1$ si aucun chemin n'existe.
from collections import deque
def distance(graphe, depart, arrivee):
if depart == arrivee:
return 0
visites = {depart}
file = deque([(depart, 0)])
while file:
sommet, dist = file.popleft()
for voisin in graphe[sommet]:
if voisin == arrivee:
return dist + 1
if voisin not in visites:
visites.add(voisin)
file.append((voisin, dist + 1))
return -1
Principe : c'est un BFS classique où l'on stocke la distance dans la file. Le premier chemin trouvé vers arrivee est garanti le plus court (propriété fondamentale du BFS).
Complexité : $O(S + A)$ où $S$ est le nombre de sommets et $A$ le nombre d'arêtes.