Devoir n°3
Graphes et parcours en largeur
1h20
Ce devoir est composé de deux exercices indépendants. Le sujet ne sera pas ramassé : toutes les réponses doivent être notées sur votre copie.
Exercice 1 : généralités et représentations
Soit le graphe de la figure 1 ci-dessous.
1. Vocabulaire
1.a. Ce graphe est-il orienté ou non orienté ?
1.b. Donner l’ordre et la taille de ce graphe.
1.c. Existe-t-il un chemin entre n’importe quelle paire de sommets de ce graphe ? Justifier.
2. Voisins et degrés
2.a. Donner les successeurs et les prédécesseurs du sommet C.
2.b. Donner les degrés entrant et sortant du sommet E, puis son degré.
2.c. Quel sommet a un degré sortant nul ?
3. Chemins
3.a. Donner un chemin de longueur 4 allant de A à F.
3.b. Donner un cycle de longueur 3 passant par A.
3.c. Quelle est la distance de A à F ? Justifier.
4. Représentations (les sommets sont toujours rangés dans l’ordre alphabétique)
4.a. Écrire en Python la liste d’adjacence de ce graphe, sous la forme d’un dictionnaire nommé graphe.
4.b. Écrire en Python la matrice d’adjacence de ce graphe, sous la forme d’une liste de listes nommée M.
5. Code à compléter
On suppose que la variable graphe contient la liste d’adjacence de la question 4.a (en cas de difficulté, on pourra s’aider de la figure 1). On souhaite écrire deux fonctions.
def taille(graphe):
t = 0
for voisins in graphe.______():
t = t + ______(voisins)
return t
def degre_entrant(graphe, sommet):
d = 0
for s in graphe:
if ______ in graphe[s]:
d = ______
return d
5.a. Compléter les quatre espaces ______.
5.b. Que renvoie l’appel degre_entrant(graphe, "C") ?
6. Graphe valué
Le graphe de la figure 2 représente des distances (en km) entre quatre villages.
6. Donner la matrice d’adjacence de ce graphe valué, sous la forme d’une liste de listes. On code l’absence de route par None.
Exercice 2 : parcours en largeur
Un réseau local compte huit machines numérotées de 1 à 8. Deux machines sont reliées si elles sont directement connectées par un câble. Ce réseau est modélisé par le graphe non orienté de la figure 3.
Dans tout l’exercice, lorsqu’un sommet a plusieurs voisins à explorer, on les traite par ordre croissant.
1. Principe
1.a. Quelle structure de données utilise le parcours en largeur pour mémoriser les sommets découverts qui restent à explorer ? Quel est le principe de fonctionnement de cette structure de données ?
1.b. À quoi sert le marquage des sommets (leur couleur) ? Que risquerait-il de se passer sans lui ?
2. Déroulement à la main
On effectue un parcours en largeur du graphe depuis le sommet 1. Recopier et compléter le tableau ci-dessous (ajuster le nombre de lignes nécessaires).
| Sommet défilé | Sommets découverts | Contenu de la file |
|---|---|---|
| (initialisation) | 1 | [1] |
3. Arbre de parcours
3.a. Dessiner l’arbre de parcours en largeur obtenu depuis le sommet 1.
3.b. D’après cet arbre, quelle est la distance entre les sommets 1 et 6 ? Donner le chemin le plus court correspondant.
3.c. Le sommet 5 est relié à la fois au sommet 2 et au sommet 3. Pourquoi est-il pourtant un fils du sommet 2 dans l’arbre ?
4. Connexité
4.a. Quels sommets du graphe n’apparaissent pas dans l’arbre de parcours ?
4.b. Que peut-on en conclure sur la connexité du graphe ?
4.c. Combien d’arbres obtiendrait-on si on souhaitait parcourir tous les sommets du graphe ? Comment faudrait-il procéder ?
5. Code à compléter
On souhaite implémenter le parcours en largeur. La fonction renvoie un dictionnaire dont les clés sont les sommets du graphe et dont les valeurs sont des dictionnaires donnant la couleur, le parent et la distance de chaque sommet.
def parcours_largeur(graphe, depart):
parcours = {}
parcours[depart] = {"couleur": "gris", "parent": None, "distance": 0}
for sommet in graphe:
if sommet != depart:
parcours[sommet] = {"couleur": "blanc", "parent": None, "distance": None}
file = [depart]
while file != []:
sommet_visite = file.______(0)
for voisin in ______:
if parcours[voisin]["couleur"] == ______:
parcours[voisin]["couleur"] = "gris"
parcours[voisin]["parent"] = sommet_visite
parcours[voisin]["distance"] = ______
file.append(voisin)
parcours[sommet_visite]["couleur"] = ______
return parcours
5.a. Compléter les cinq espaces ______.
5.b. On exécute p = parcours_largeur(graphe, 1). Quelle est la valeur de p[6] ? Quelle est celle de p[7] ?
6. Application
Compléter la fonction suivante, qui renvoie True s’il existe un chemin entre s1 et s2 dans le graphe, et False sinon. Que renvoie est_accessible(graphe, 1, 8) ?
def est_accessible(graphe, s1, s2):
parcours = parcours_largeur(graphe, s1)
return parcours[s2]["couleur"] == ______
Correction
Exercice 1 : généralités et représentations
1.a. Orienté : les connexions sont des arcs qui ont un sens (X suit Y).
Justification non attendue
1.b. Ordre 6 (six sommets) et taille 9 (neuf arcs).
1.c. Non : le sommet F n’a aucun successeur, donc il n’existe aucun chemin de F vers un autre sommet.
2.a. Successeurs de C : B et E. Prédécesseurs de C : A et E.
2.b. Degré entrant de E : 2 (C et D). Degré sortant : 2 (C et F). Degré : 4.
-50 % si une erreur
2.c. Le sommet F.
3.a. A → B → D → E → F (tout chemin valide de 4 arcs est accepté).
3.b. A → B → D → A.
3.c. Distance 3 : le chemin A → C → E → F. Aucun chemin de longueur 1 ou 2 n’atteint F.
4.a. Liste d’adjacences
graphe = {
"A": ["B", "C"],
"B": ["D"],
"C": ["B", "E"],
"D": ["A", "E"],
"E": ["C", "F"],
"F": []
}
4.b. Matrice d’adjacences
M = [
[0, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0],
[0, 1, 0, 0, 1, 0],
[1, 0, 0, 0, 1, 0],
[0, 0, 1, 0, 0, 1],
[0, 0, 0, 0, 0, 0]
]
5.a Code à compléter
def taille(graphe):
t = 0
for voisins in graphe.values():
t = t + len(voisins)
return t
def degre_entrant(graphe, sommet):
d = 0
for s in graphe:
if sommet in graphe[s]:
d = d + 1
return d
5.b. L’appel renvoie 2 (A et E ont C parmi leurs successeurs).
6. Matrice d’adjacences (2 possibilités selon ce qu’on décide pour la distance d’un village avec lui-même).
M = [
[None, 4, 7, None],
[4, None, 2, None],
[7, 2, None, 5],
[None, None, 5, None]
]
M = [
[0, 4, 7, None],
[4, 0, 2, None],
[7, 2, 0, 5],
[None, None, 5, 0]
]
Exercice 2 : parcours en largeur
1.a. Une file (FIFO : premier entré, premier sorti). On ajoute les sommets à la fin et on retire celui qui est au début.
1.b. Le marquage permet de ne pas redécouvrir ni réexplorer un sommet déjà rencontré. Sans lui, dès qu’il y a un cycle (ou simplement une arête dans un graphe non orienté), l’algorithme reviendrait indéfiniment sur les mêmes sommets : boucle infinie.
2. Déroulé manuel du parcours
| Sommet défilé | Sommets découverts | Contenu de la file |
|---|---|---|
| (initialisation) | 1 | [1] |
| 1 | 2, 3 | [2, 3] |
| 2 | 4, 5 | [3, 4, 5] |
| 3 | – | [4, 5] |
| 4 | 6 | [5, 6] |
| 5 | – | [6] |
| 6 | – | [] |
3.a. Arbre de parcours en largeur.
3.b. Le sommet 6 est à la profondeur 3 : la distance entre 1 et 6 est 3. Le chemin lu dans l’arbre est 1 → 2 → 4 → 6.
3.c. Les voisins sont traités par ordre croissant : le sommet 2 est défilé avant le sommet 3. Quand on explore 2, le sommet 5 est blanc : il est découvert et prend 2 pour père. Quand on explore 3, il est déjà marqué.
4.a. Les sommets 7 et 8.
4.b. Il n’existe aucun chemin entre 1 et 7 (ni 8) : le graphe n’est pas connexe.
4.c. Deux arbres (une forêt) : il faudrait relancer le parcours depuis un sommet resté blanc (7 ou 8) ; cet arbre contiendrait seulement 7 et 8.
5.a. Code à compléter :
ligne 10 : sommet_visite = file.pop(0)
ligne 11 : for voisin in graphe[sommet_visite]:
ligne 15 : parcours[voisin]["distance"] = parcours[sommet_visite]["distance"] + 1
ligne 17 : parcours[sommet_visite]["couleur"] = "noir"
5.b. p[6] vaut {"couleur": "noir", "parent": 4, "distance": 3}. p[7] vaut {"couleur": "blanc", "parent": None, "distance": None} (le sommet 7 n’a jamais été découvert).
6. Fonction est_accessible
return parcours[s2]["couleur"] == "noir"
est_accessible(graphe, 1, 8) renvoie False : le sommet 8 n’est pas accessible depuis le sommet 1.