Les Graphes
Programme
Notions | Compétences | Remarques |
---|---|---|
Graphes : structures relationnelles. Sommets, arcs, arĂȘtes, graphes orientĂ©s ou non orientĂ©s. |
ModĂ©liser des situations sous forme de graphes. Ăcrire les implĂ©mentations correspondantes dâun graphe : matrice dâadjacence, liste de successeurs/de prĂ©dĂ©cesseurs. Passer dâune reprĂ©sentation Ă une autre. |
On sâappuie sur des exemples comme le rĂ©seau routier, le rĂ©seau Ă©lectrique, Internet, les rĂ©seaux sociaux. Le choix de la reprĂ©sentation dĂ©pend du traitement quâon veut mettre en place : on fait le lien avec la rubrique « algorithmique ». |
Algorithmes sur les graphes. | Parcourir un graphe en profondeur dâabord, en largeur dâabord. RepĂ©rer la prĂ©sence dâun cycle dans un graphe. Chercher un chemin dans un graphe. |
Le parcours dâun labyrinthe et le routage dans Internet sont des exemples dâalgorithme sur les graphes. Lâexemple des graphes permet dâillustrer lâutilisation des classes en programmation. |
Notion de graphe non orienté
Imaginez un rĂ©seau social ayant 6 abonnĂ©s (A, B, C, D, E et F) oĂč :
- A est ami avec B, C et D
- B est ami avec A et D
- C est ami avec A, E et D
- D est ami avec tous les autres abonnés
- E est ami avec C, D et F
- F est ami avec E et D
La description de ce rĂ©seau social, malgrĂ© son faible nombre d'abonnĂ©s, est dĂ©jĂ quelque peu rĂ©barbative, alors imaginez cette mĂȘme description avec un rĂ©seau social comportant des millions d'abonnĂ©s !
Il existe un moyen plus "visuel" pour reprĂ©senter ce rĂ©seau social : on peut reprĂ©senter chaque abonnĂ© par un cercle (avec le nom de l'abonnĂ© situĂ© dans le cercle) et chaque relation "X est ami avec Y" par un segment de droite reliant X et Y ("X est ami avec Y" et "Y est ami avec X" Ă©tant reprĂ©sentĂ© par le mĂȘme segment de droite).
Voici ce que cela donne avec le réseau social décrit ci-dessus :
Ce genre de figure s'appelle un graphe. Les graphes sont des objets mathématiques trÚs utilisés, notamment en informatique.
Les cercles sont appelĂ©s des sommets ( parfois des nĆuds ) et les segments de droites qui relient 2 sommets des arcs ( parfois des arĂȘtes).
Plus formellement on dira qu'un graphe G est un couple G = (V,E) avec V un ensemble de sommets et E un ensemble d'arĂȘtes ( V et E pour Vertice (sommet) et Edge (arĂȘte) en anglais bien sĂ»r !)
Activité 1 - Construction de graphe
Construisez un graphe de réseau social à partir des informations suivantes :
- A est ami avec B et E
- B est ami avec A et C
- C est ami avec B,F et D
- D est ami avec C,F et E
- E est ami avec A,D et F
- F est ami avec C, D et E
RĂ©ponse
graph
A((A)) --- B((B))
A --- E((E))
B --- C((C))
E --- F((F))
E --- D((D))
C --- F
C --- D
F --- D
Vocabulaire sur les graphes
Un graphe non orientĂ© est une structure de donnĂ©es constituĂ©es dâobjets, appelĂ©s sommets, et de relations entre ces sommets appelĂ©es arcs.
Vous trouverez aussi des ouvrages oĂč pour ces graphes on parle de noeuds ( sommets ) et dâarĂȘtes (arcs) .
Un chemin ( chaine ) est une suite d'arcs (arĂȘtes) consĂ©cutifs dans un graphe, un peu comme si on se promenait sur le graphe. On le dĂ©signe par les lettres des sommets qu'il comporte.
Un chemin ( une chaine ) est dit Ă©lĂ©mentaire si il ne comporte pas plusieurs fois le mĂȘme sommet.
Un chemin ( une chaine ) est dit simple si il ne comporte pas plusieurs fois le mĂȘme arc.
Un cycle est un chemin ( une chaine ) qui commence et se termine au mĂȘme sommet.
Un graphe est connexe sâil existe un chemin (une chaine) pour toute paire de sommets (intuitivement, un graphe connexe comporte un seul « morceau »)
Activité 2 - Analyse d'un graphe
Soit le graphe G ci-dessous :
graph
A((1)) --- B((2))
A --- E((5))
E --- C((3))
E --- D((4))
C --- D
E --- F((6))
- Combien G possĂšde-t-il de sommets ?
- Combien G possĂšde-t-il dâarcs ?
- G est-il connexe ?
- Le sommet 1 possĂšde combien de sommets adjacents ?
- Combien le sommet 4 possĂšde-t-il de sommets adjacents ?
- Combien existe-t-il de chemins simples entre le sommet 2 et le sommet 4 ?
- Combien existe-t-il de chemins simples entre le sommet 2 et le sommet 6 ?
- Combien existe-t-il de chemins élémentaires entre le sommet 2 et le sommet 4 ?
- Combien existe-t-il de chemins élémentaires entre le sommet 2 et le sommet 6 ?
RĂ©ponse
- G a 6 sommets.
- G a 6 arcs.
- G est connexe.
- 1 possĂšde 2 sommets adjacents (2 et 5).
- 4 possĂšde 2 sommets adjacents (3 et 5).
- Il existe 2 chemins simples entre 2 et 4.
- Il existe 3 chemins simples entre 2 et 6.
- Il existe 2 chemins élémentaires entre 2 et 4.
- Il existe 1 chemin élémentaire entre 2 et 6.
Notion de graphe orienté
Autre utilisation possible des graphes : les logiciels de cartographie (ces logiciels sont souvent utilisés couplés à des récepteurs GPS). Ces logiciels de cartographie permettent, connaissant votre position A grùce à un récepteur GPS, d'indiquer la route à suivre pour se rendre à endroit B. Comment modéliser l'ensemble des lieux et des routes ? Simplement à l'aide d'un graphe !
Chaque lieu est un sommet et les routes qui relient les lieux entre eux sont des arcs.
Soit les lieux suivants : A, B, C, D, E, F et G.
Les différents lieux sont reliés par les routes suivantes :
- il existe une route entre A et C
- il existe une route entre A et B
- il existe une route entre A et D
- il existe une route entre B et F
- il existe une route entre B et E
- il existe une route entre B et G
- il existe une route entre D et G
- il existe une route entre E et F
Ici aussi, la représentation sous forme de graphe s'impose :
graph
A((A)) --- B((B))
A --- C((C))
A --- D((D))
B --- G((G))
B --- E((E))
B --- F((F))
D --- G
F --- E
ProblÚme : avec cette représentation du réseau routier sous forme de graphe, il est impossible de prendre en compte des routes à sens unique. Pour que cela puisse se faire, il va falloir orienter les arcs.
Voici de nouvelles contraintes :
- il existe une route entre A et C (double sens)
- il existe une route entre A et B (sens unique B â A)
- il existe une route entre A et D (sens unique A â D)
- il existe une route entre B et F (sens unique B â F)
- il existe une route entre B et E (sens unique E â B)
- il existe une route entre B et G (double sens)
- il existe une route entre D et G (double sens)
- il existe une route entre E et F (double)
Pour tenir compte de ces nouvelles contraintes, on utilisera un graphe orienté :
graph LR
B((B)) --> A((A))
A --> C((C))
C --> A
A --> D((D))
B --> G((G))
G --> B
E((E)) --> B
B --> F((F))
D --> G
G --> D
F --> E
E --> F
Dans un graphe orienté, les arcs possÚdent une orientation. On dira qu'un graphe orienté G est un couple G = (V, A) avec V un ensemble de sommets et A un ensemble d'arcs orientés.
Vocabulaire des graphes orientés
Un graphe orientĂ© est une structure de donnĂ©es constituĂ©e dâobjets, appelĂ©s sommets, et de relations entre ces sommets appelĂ©es arcs.
Lorsquâil y a un arc du sommet 1 vers le sommet 2, on dit que le sommet 2 est adjacent au sommet 1 ( voir ci-dessous ).
Par contre sâil nâexiste pas dâarc du sommet 2 vers le sommet 1, le sommet 1 nâest pas adjacent au sommet 2 ( voir schĂ©ma ).
graph
A((1)) --> B((2))
A --> C((3))
C --> A
C --> D((4))
D --> C
On note 1 â 2 lâarc (1, 2) oĂč 1 est son extrĂ©mitĂ© initiale et 2 son extrĂ©mitĂ© finale.\ 2 est le successeur de 1 et 1 est le prĂ©dĂ©cesseur de 2.
Un chemin est une suite d'arcs consécutifs dans un graphe, un peu comme si on se promenait sur le graphe. On la désigne par les lettres des sommets qu'il comporte.
Un chemin est dit Ă©lĂ©mentaire sâil ne comporte pas plusieurs fois le mĂȘme sommet.
Un chemin est dit simple sâil ne comporte pas plusieurs fois le mĂȘme arc.
Un circuit est un chemin qui commence et se termine au mĂȘme sommet.
Un graphe orienté est connexe si le graphe non orienté obtenu en oubliant le sens des arcs est connexe.
Un graphe orienté est fortement connexe lorsque pour toute paire de sommets distincts ( u, v ) il existe un chemin de u vers v et un chemin de v vers u .
Activité 3 - Analyse d'un graphe orienté
Soit le graphe G ci-dessous :
graph LR
A((1)) --> B((2))
B --> C((3))
C --> D((4))
D --> A
D --> E((7))
E --> F((5))
F --> G((6))
F --> B
B --> D
G --> E
- Combien G possĂšde-t-il de sommets ?
- Combien G possĂšde-t-il dâarcs ?
- G est-il connexe ?
- G est-il fortement connexe ?
- Le sommet 1 possĂšde combien de sommets adjacents ?
- Combien le sommet 4 possĂšde-t-il de sommets adjacents ?
- Combien existe-t-il de chemins simples entre le sommet 2 et le sommet 4 ?
- Combien existe-t-il de chemins simples entre le sommet 2 et le sommet 7 ?
- Combien existe-t-il de chemins élémentaires entre le sommet 2 et le sommet 4 ?
- Combien existe-t-il de chemins élémentaires entre le sommet 2 et le sommet 7 ?
RĂ©ponse
- G possĂšde 7 sommets.
- G possĂšde 10 arcs.
- G est connexe.
- G est fortement connexe.
- 1 possĂšde 1 sommet adjacent (2).
- 4 possĂšde 2 sommets adjacents (1 et 7).
- Il existe 2 chemins simples entre 2 et 4.
- Il existe 4 chemins simples entre 2 et 7.
- Il existe 2 chemins élémentaires entre 2 et 4.
- Il existe 2 chemins élémentaires entre 2 et 7.
Activité 4 - Comparaison de graphes #1
Les deux graphes suivants sont-ils identiques?
RĂ©ponse
Oui, ils sont identiques.
Activité 5 - Comparaison de graphes #2
Les deux graphes suivants sont-ils identiques?
RĂ©ponse
Non, ils ne sont pas identiques (voir voisins de 3).
Graphes pondérés
Parfois il est intéressant d'associer aux arcs des valeurs, on parle alors de graphes pondérés. Si nous revenons à notre "graphe cartographie", il est possible d'associer à chaque arc la distance en km entre les 2 lieux :
Il est aussi possible d'associer à chaque arc la durée du trajet entre 2 points :
En fonction du choix fait par le conducteur (trajet le plus court "en distance" ou trajet le plus court "en temps"), l'algorithme permettant de déterminer le "chemin le plus court entre 2 points" travaillera sur le graphe "graphe pondéré (km) cartographie" ou sur le graphe "graphe pondéré (minutes) cartographie". à noter que le "graphe pondéré (minutes) cartographie" peut évoluer au cours du temps en fonction du trafic routier : une application comme Waze utilise les données en provenance des utilisateurs de l'application afin de mettre à jour en temps réel leur "graphe pondéré (minutes) cartographie".
Implémentation des graphes avec une matrice d'adjacence
Une matrice est un tableau à double entrée :
La matrice A ci-dessus est constitué de 5 lignes et 4 colonnes.
On appelle matrice carrĂ©e une matrice qui comporte le mĂȘme nombre de lignes et de colonnes.
Les matrices d'adjacences sont des matrices carrées.
Reprenons l'exemple du "graphe cartographie" précédent :
On peut observer que pour un graphe non orientĂ©, la matrice dâadjacence sera symĂ©trique.
Comment construire une matrice d'adjacence ?
Il faut savoir qu'à chaque ligne correspond un sommet du graphe et qu'à chaque colonne correspond aussi un sommet du graphe. à chaque intersection ligne i-colonne j (ligne i correspond au sommet i et colonne j correspond au sommet j), on place un 1 s'il existe un arc entre le sommet i et le sommet j, et un zéro s'il n'existe pas d'arc entre le sommet i et le sommet j.
Il existe une arĂȘte entre le sommet E et le sommet F, nous avons donc placĂ© un 1 Ă l'intersection de la ligne E et de la colonne F (il en est de mĂȘme Ă l'intersection de la ligne F et de la colonne E).
Il n'existe pas d'arĂȘte entre le sommet D et le sommet C, nous avons donc placĂ© un 0 Ă l'intersection de la ligne D et de la colonne C (il en est de mĂȘme Ă l'intersection de la ligne C et de la colonne D).
Vérifiez que la matrice d'adjacence proposée ci-dessous correspond bien au graphe "cartographie".
Il est aussi possible d'Ă©tablir une matrice d'adjacence pour un graphe orientĂ©. Le principe reste le mĂȘme : si le sommet i (ligne) est liĂ© au sommet j (colonne), nous avons un 1 Ă l'intersection (0 dans le cas contraire).
Il est aussi possible d'utiliser une matrice d'adjacence pour implémenter un graphe pondéré : on remplace les 1 par les valeurs liées à chaque arc.
Activité 6 - Matrices d'adjacence
Ătablissez la matrice d'adjacence du graphe ci-dessous :
RĂ©ponse
Ătablissez la matrice d'adjacence du graphe ci-dessous :
RĂ©ponse
Ătablissez la matrice d'adjacence du graphe ci-dessous :
RĂ©ponse
Ătablissez la matrice d'adjacence du graphe ci-dessous :
RĂ©ponse
Dessinez le graphe dont la matrice d'adjacences est la suivante :
RĂ©ponse
graph
A((A)) --- B((B))
A --- D((D))
A --- C((C))
B --- C
B --- D
D --- C
B --- E
D --- E
Expliquez la raison pour laquelle un graphe non orientĂ© aura toujours une matrice dâadjacences symĂ©trique par rapport Ă la diagonale qui part dâen haut Ă gauche pour arriver en bas Ă droite.
RĂ©ponse
Un graphe non orientĂ© aura une matrice dâadjacence symĂ©trique car lâarĂȘte (u,v) est Ă©quivalente Ă lâarĂȘte (v,u);
Soit la matrice dâadjacence suivante :
- Cette matrice dâadjacence permet-elle de savoir, sans le construire, si le graphe correspondant est orientĂ© ?
- Tracez le graphe correspondant Ă cette matrice dâadjacences.
- Que pouvez-vous déduire, concernant le sommet 3, du 1 entouré en vert qui est sur la diagonale ?
RĂ©ponse
- Le graphe est orientĂ© car la matrice nâest pas symĂ©trique.
-
graph A((A)) --> B((B)) A --- C((C)) C --> C D --> C B --> D
Activité 7 - Matrice d'adjacence et Python
Voici une classe Python de construction de graphe (orienté ou non, pondéré ou non) à l'aide d'une matrice d'adjacence :
class Graphe_Mat:
'''
Implantation d'un graphe orienté ou non, pondéré ou non, sous la forme d'une matrice
d'adjacence. Cette classe permet d'implanter aussi bien des graphes
orientés que non orientés selon les méthodes d'ajout et suppression
d'arrĂȘte appelĂ©es. Pour garantir la consistance, un boolĂ©en, par
défaut à faux, permet de préciser si on crée un graphe orienté ou pas.
Pour les poids, on n'autorisera que des entiers positifs ou nuls.
La matrice d'adjacence contient le poids si l'arrĂȘte existe, None si
l'arrĂȘte n'existe pas.
La méthode 'distance' est présente pour rester compatible avec une
implantation de l'algorithme de Dijkstra définie sur des graphes
pondérés.
'''
def __init__(self, noms_noeuds = None, oriente = False):
self.oriente = oriente
if noms_noeuds is None:
self.noms_noeuds = []
self.matrice = []
self.nb_noeuds = 0
else:
self.noms_noeuds = noms_noeuds
self.nb_noeuds = len(noms_noeuds)
self.matrice = [[None] * self.nb_noeuds for _ in range(self.nb_noeuds)]
def ajouter_noeud(self, noeud):
assert not(noeud in self.noms_noeuds)
self.noms_noeuds.append(noeud)
self.nb_noeuds += 1
nouvelle_matrice = [[None] * self.nb_noeuds for _ in range(self.nb_noeuds)]
for i in range(self.nb_noeuds-1):
for j in range(self.nb_noeuds-1):
nouvelle_matrice[i][j] = self.matrice[i][j]
self.matrice = nouvelle_matrice
# pour un graphe non orienté :
def ajouter_arrete_non_orientee(self, noeud1, noeud2, poids):
assert not(self.oriente)
assert noeud1 in self.noms_noeuds
assert noeud2 in self.noms_noeuds
assert type(poids) is int and poids >= 0
indice_noeud1 = self.noms_noeuds.index(noeud1)
indice_noeud2 = self.noms_noeuds.index(noeud2)
self.matrice[indice_noeud1][indice_noeud2] = poids
self.matrice[indice_noeud2][indice_noeud1] = poids
def supprimer_arrete_non_orientee(self, noeud1, noeud2):
assert not(self.oriente)
assert noeud1 in self.noms_noeuds
assert noeud2 in self.noms_noeuds
indice_noeud1 = self.noms_noeuds.index(noeud1)
indice_noeud2 = self.noms_noeuds.index(noeud2)
assert not(self.matrice[indice_noeud1][indice_noeud2] is None)
self.matrice[indice_noeud1][indice_noeud2] = None
self.matrice[indice_noeud2][indice_noeud1] = None
# pour un graphe orienté :
def ajouter_arrete_orientee(self, noeud1, noeud2, poids):
assert self.oriente
assert noeud1 in self.noms_noeuds
assert noeud2 in self.noms_noeuds
assert type(poids) is int and poids >= 0
indice_noeud1 = self.noms_noeuds.index(noeud1)
indice_noeud2 = self.noms_noeuds.index(noeud2)
self.matrice[indice_noeud1][indice_noeud2] = poids
def supprimer_arrete_orientee(self, noeud1, noeud2):
assert self.oriente
assert noeud1 in self.noms_noeuds
assert noeud2 in self.noms_noeuds
indice_noeud1 = self.noms_noeuds.index(noeud1)
indice_noeud2 = self.noms_noeuds.index(noeud2)
assert not(self.matrice[indice_noeud1][indice_noeud2] is None)
self.matrice[indice_noeud1][indice_noeud2] = None
# affichage
def affiche(self):
retour = f"Liste des noeuds : {self.noms_noeuds}\nMatrice d'adjacence :\n"
for ligne in self.matrice:
retour = retour + f"{ligne} \n"
retour = retour.replace("None", "0")
print(retour)
# méthodes sur un graphe
def liste_voisins(self, noeud):
assert noeud in self.noms_noeuds
indice_noeud = self.noms_noeuds.index(noeud)
voisins = []
for indice_voisin in range(self.nb_noeuds):
if not(self.matrice[indice_noeud][indice_voisin] is None):
voisins.append(self.noms_noeuds[indice_voisin])
return voisins
def taille(self):
return self.nb_noeuds
def liste_noeuds(self):
'''Cette méthode renvoie une copie de la liste des noeuds. Ainsi,
La valeur renvoyĂ©e peut ĂȘtre modifiĂ©e sans risque que cela modifie le graphe'''
return self.noms_noeuds[:]
def distance(self, depart, arrivee):
assert depart in self.noms_noeuds
assert arrivee in self.liste_voisins(depart)
indice_depart = self.noms_noeuds.index(depart)
indice_arrivee = self.noms_noeuds.index(arrivee)
return self.matrice[indice_depart][indice_arrivee]
A lâaide de la classe Graphe prĂ©sentĂ©e ci-dessus, Ă©crivez un programme qui permet dâimplĂ©menter le graphe ci-contre avec une matrice dâadjacence.
Appliquez la méthode liste_voisins
sur lâobjet crĂ©Ă© dans l'exercice prĂ©cĂ©dent, pour chaque noeud. VĂ©rifiez quâelle fonctionne correctement.
# Tests
(insensible Ă la casse)(Ctrl+I)
Appliquez la méthode taille
sur lâobjet crĂ©Ă© dans l'exercice prĂ©cĂ©dent. VĂ©rifiez quâelle fonctionne correctement.
# Tests
(insensible Ă la casse)(Ctrl+I)
Activité 8 - Tracé de graphe avec Turtle
Ouvrez le notebook suivant : Notebook
Vous y trouvez une cellule permettant de tracer un graphe Ă l'aide du module Turtle
.
Cette cellule contient une fonction dessiner_graphe(g)
.
Le paramĂštre g
de cette fonction est un objet de la classe Graphe_Mat
.
Cette fonction permet de dessiner le graphe orientĂ© correspondant Ă la matrice dâadjacences de lâobjet g
.
ExĂ©cutez cette fonction pour vĂ©rifier que vos matrices dâadjacences des exercices prĂ©cĂ©dents correspondent bien aux graphes dĂ©sirĂ©s.
Inconvénients d'une matrice d'adjacence
La matrice dâadjacence est indĂ©niablement simple Ă mettre en Ćuvre mais elle a nĂ©anmoins quelques dĂ©fauts.
Dâune part, elle occupe un espace mĂ©moire proportionnel Ă \(n \times n\) . Ainsi un graphe de mille sommets nĂ©cessite une matrice dâun million de boolĂ©ens, ce qui reprĂ©sente dĂ©jĂ quelques mĂ©gaoctets, et ce, mĂȘme si le graphe contient trĂšs peu dâarc.
Dâautre part, parcourir tous les voisins dâun sommet donnĂ© exige de parcourir toute une ligne de la matrice, câest-Ă -dire n boolĂ©ens, alors mĂȘme quâil peut y avoir trĂšs peu de voisins.
Enfin elle limite les sommets Ă des entiers, qui plus est consĂ©cutifs et dâun nombre connu Ă lâavance.
Heureusement, il existe une autre façon de reprĂ©senter les graphes qui sâaffranchit de tous ces dĂ©fauts.
Dictionnaire d'adjacence
Dans cette nouvelle reprĂ©sentation, un graphe est un dictionnaire qui associe Ă chaque sommet lâensemble de ses voisins. On appelle cela un dictionnaire dâadjacence.
Lâensemble des sommets du graphe est donc exactement lâensemble des clĂ©s du dictionnaire.
Exemple d'un graphe non orienté
Le dictionnaire dâadjacence du graphe ci-dessous est crĂ©Ă© de la maniĂšre suivante :
- chaque sommet du graphe définit une clé du dictionnaire
- la liste des voisins de chaque sommet définit la valeur associée à chaque sommet.
Voici les clĂ©s ( sommets ) et les valeurs ( listes ) associĂ©es Ă chacune de ces clĂ©s dans lâexemple Ă©tudiĂ© :
Activité 9 - Analyse d'un dictionnaire d'adjacence
Soit le graphe orienté ci-dessous :
Le dictionnaire dâadjacence du graphe ci-dessus est crĂ©Ă© de la maniĂšre suivante :
- chaque sommet du graphe définit une clé du dictionnaire
- la liste des successeurs de chaque sommet définit la valeur associée à chaque sommet.
Ăcrivez la liste des successeurs associĂ©s Ă chacun des sommets du graphe orientĂ© ci-dessus.
RĂ©ponse
A â C, D
B â A, F, G
C â A
D â G
E â B, F
F â E
G â B, D
dic = {A: [C, D], ...}
Activité 10 - Dictionnaire d'adjacence et Python
Voici une classe Python de construction de graphe Ă l'aide d'un dictionnaire d'adjacence :
class Graphe_Dico:
'''
Implantation d'un graphe orienté ou non, pondéré ou non, sous la forme de listes
d'adjacence. Cette classe permet d'implanter aussi bien des graphes
orientés que non orientés selon les méthodes d'ajout et suppression
d'arrĂȘte appelĂ©es. Pour garantir la consistance, un boolĂ©en, par
défaut à faux, permet de préciser si on crée un graphe orienté ou pas.
Les poids doivent ĂȘtre des nombres entiers positifs ou nuls.
Pour représenter les poids, on choisit un dictionnaire :
(noeud_depart, noeud_arrivée) -> poids
Mais on aurait auss pu stocker dans le dictionnaire 'voisins' en lui
donnant la structure : noeud_depart -> (noeud_arrivee, poids)
'''
def __init__(self, noms_noeuds = None, oriente = False):
self.oriente = oriente
self.voisins = dict()
if noms_noeuds is None:
self.nb_noeuds = 0
else:
self.nb_noeuds = len(noms_noeuds)
for noeud in noms_noeuds:
self.voisins[noeud] = []
self.poids = dict()
def ajouter_noeud(self, noeud):
assert not(noeud in self.voisins)
self.nb_noeuds += 1
self.voisins[noeud] = []
# pour un graphe non orienté :
def ajouter_arrete_non_orientee(self, noeud1, noeud2, poids):
assert not(self.oriente)
assert noeud1 in self.voisins
assert noeud2 in self.voisins
assert not(noeud2 in self.voisins[noeud1])
assert type(poids) is int and poids >= 0
self.voisins[noeud1].append(noeud2)
self.voisins[noeud2].append(noeud1)
self.poids[(noeud1, noeud2)] = poids
self.poids[(noeud2, noeud1)] = poids
def supprimer_arrete_non_orientee(self, noeud1, noeud2):
assert not(self.oriente)
assert noeud1 in self.voisins
assert noeud2 in self.voisins
assert noeud2 in self.voisins[noeud1]
self.voisins[noeud1].remove(noeud2)
self.voisins[noeud2].remove(noeud1)
del self.poids[(noeud1, noeud2)]
del self.poids[(noeud2, noeud1)]
# pour un graphe orienté :
def ajouter_arrete_orientee(self, noeud1, noeud2, poids):
assert self.oriente
assert noeud1 in self.voisins
assert noeud2 in self.voisins
assert not(noeud2 in self.voisins[noeud1])
assert type(poids) is int and poids >= 0
self.voisins[noeud1].append(noeud2)
self.poids[(noeud1, noeud2)] = poids
def supprimer_arrete_orientee(self, noeud1, noeud2):
assert self.oriente
assert noeud1 in self.voisins
assert noeud2 in self.voisins
assert noeud2 in self.voisins[noeud1]
del self.poids[(noeud1, noeud2)]
# méthodes sur un graphe
def liste_voisins(self, noeud):
assert noeud in self.voisins
# on renvoie une copie des voisins pour que les modifications
# éventuelles apportées à la valeur renvoyée soient sans risque
# sur le graphe.
return self.voisins[noeud][:
def taille(self):
return self.nb_noeuds
def liste_noeuds(self):
return list(self.voisins.keys())
def distance(self, depart, arrivee):
assert depart in self.voisins
assert arrivee in self.voisins[depart]
return self.poids[(depart, arrivee)]
Soit le graphe ci-dessous :
- Créez un objet
g1
qui reprĂ©sente le graphe ci-dessus Ă lâaide de la classeGraphe_Dico
. - Affichez la liste des sommets de lâobjet
g1
. - Ajoutez une méthode
affiche1
qui permet dâafficher pour chaque sommet la liste de ses voisins. Lâapplication de cette mĂ©thode sur lâobjetg1
vous permettra de vérifier si la création de votre objetg1
correspond bien Ă ce que vous souhaitiez.
# Tests
(insensible Ă la casse)(Ctrl+I)
Soit le graphe ci-dessous :
- Créez un objet
g2
qui reprĂ©sente le graphe ci-dessus Ă lâaide de la classeGraphe_Dico
. - Affichez la liste des sommets de lâobjet
g2
. - Ajoutez une méthode
affiche2
qui permet dâafficher pour chaque sommet la liste de ses successeurs. Lâapplication de cette mĂ©thode sur lâobjetg2
vous permettra de vérifier si la création de votre objetg2
correspond bien Ă ce que vous souhaitiez.
Algorithme de parcours d'un graphe
L'idée du "parcours" est de "visiter" tous les sommets d'un graphe en partant d'un sommet quelconque. Ces algorithmes de parcours d'un graphe sont à la base de nombreux algorithmes trÚs utilisés : routage des paquets de données dans un réseau, découverte du chemin le plus court pour aller d'une ville à une autre...
Il existe 2 méthodes pour parcourir un graphe :
- Le parcours en largeur d'abord
- Le parcours en profondeur d'abord
Activité 11 - Parcours en largeur d'abord
Nous allons travailler sur un graphe G(V, E) avec V l'ensemble des sommets de ce graphe et E l'ensemble des arĂȘtes de ce graphe. Un sommet u sera adjacent avec un sommet v si u et v sont reliĂ©s par une arĂȘte (on pourra aussi dire que u et v sont voisins) Ă chaque sommet u de ce graphe nous allons associer une couleur : blanc ou noir. Autrement dit, chaque sommet u possĂšde un attribut couleur que l'on notera u.couleur, nous aurons u.couleur = blanc ou u.couleur = noir. Quelle est la signification de ces couleurs ?
- si u.couleur = blanc â u n'a pas encore Ă©tĂ© "dĂ©couvert"
-
si u.couleur = noir â u a Ă©tĂ© "dĂ©couvert"
-
Etudiez cet algorithme :
VARIABLE G : un graphe s : noeud (origine) u : noeud v : noeud f : file (initialement vide) parcours : liste vide //On part du principe que pour tout sommet u du graphe G, u.couleur = blanc Ă l'origine DEBUT s.couleur â noir enfiler (s,f) tant que f non vide : u â defiler(f) Ajouter u Ă parcours pour chaque sommet v adjacent au sommet u : si v.couleur n'est pas noir : v.couleur â noir enfiler(v, f) fin si fin pour fin tant que FIN
- Appliquez l'algorithme du parcours en largeur d'abord au graphe ci-dessous. Le 'point de départ' de notre parcours (le sommet s dans l'algorithme), sera le sommet A. Vous noterez les sommets atteints à chaque étape ainsi que les sommets présents dans la file f. Vous pourrez aussi, à chaque étape, donner les changements de couleur des sommets.
RĂ©ponse
2) En partant de AÂ :
s = A(blanc)
f = []
A(noir)
f = [A]
Debut Tant que:
u = A
f = []
DĂ©but pour:
v = B(blanc) ou F(blanc)
B(noir)
f = [B]
F(noir)
f = [B, F]
u = B
f = [F]
DĂ©but pour:
v = A(noir), C(blanc), D(blanc), G(blanc)
C(noir)
f = [F, C]
D(noir)
f = [F, C, D]
G(noir)
f = [F, C, D, G]
u = F
f = [C, D, G]
DĂ©but pour:
v = A(noir), G(noir), h(blanc)
H(noir)
f = [C, D, G, H]
u = C
f = [D, G, H]
DĂ©but pour:
v = B(noir), E(blanc)
E(noir)
f = [D, G, H, E]
u = D
f = [G, H, E]
DĂ©but pour:
v = B(noir), I(blanc)
I(noir)
f = [G, H, E, I]
u = G
f = [H, E, I]
DĂ©but pour:
v = B(noir), F(noir), I(noir)
u = H
f = [E, I]
DĂ©but pour:
v = F(noir), I(noir)
u = E
f = [I]
DĂ©but pour:
v = C(noir), I(noir)
u = I
f = []
DĂ©but pour:
v = E(noir), D(noir), G(noir), H(noir)
Fin Tant que
Le parcours en largeur à partir de A est donc : A, B, F, C, D, G, H, E, I.
Vous avez sans doute remarquĂ© que dans le cas d'un parcours en largeur d'abord, on "dĂ©couvre" d'abord tous les sommets situĂ©s Ă une distance k du sommet "origine" (sommet s) avant de commencer la dĂ©couverte des sommets situĂ©s Ă une distance k+1 (on dĂ©finit la distance comme Ă©tant le nombre d'arĂȘtes Ă parcourir depuis A pour arriver Ă destination):
En effet, pour cet exercice, nous avons bien :
sommets | A | B | F | C | D | G | H | E | I |
---|---|---|---|---|---|---|---|---|---|
Distances depuis A | 0 | 1 | 1 | 2 | 2 | 2 | 2 | 3 | 3 |
Activité 12 - Parcours en profondeur d'abord
On va ici retrouver le mĂȘme systĂšme de couleur (blanc : sommet non visitĂ©, noir : sommet dĂ©jĂ visitĂ©)
Etudiez cet algorithme :
VARIABLE
G : un graphe
u : noeud
v : noeud
//On part du principe que pour tout sommet u du graphe G, u.couleur = blanc Ă l'origine
DEBUT
PARCOURS-PROFONDEUR(G,u,parcours = None, couleurs = None) :
Si parcours est None:
parcours â []
couleurs â g.couleur() # g.couleurs() est le dictionnaire composĂ©s des noeuds et de la couleur associĂ©e au noeud
Fin Si
Si u n'est pas dans parcours:
u.couleur â noir
Ajouter u Ă parcours
Fin Si
Pour chaque sommet v adjacent au sommet u :
Si v.couleur n'est pas noir :
PARCOURS-PROFONDEUR(G,v,parcours,couleurs)
Fin Si
Fin Pour
FIN
Appliquez l'algorithme du parcours en profondeur d'abord au graphe ci-dessous.
RĂ©ponse
2) En partant de AÂ :
u = A(noir)
v = B(blanc), F(blanc)
parcours(G, B)Â :
u = B(noir)
v = A(noir), C(blanc), D(blanc), G(blanc)
parcours(G, C)
u = C(noir)
v = B(noir), E(blanc)
parcours(G, E)
u = E(noir)
v = C(noir), I(blanc)
parcours(G, I)
u = I(noir)
v = E(noir), D(blanc), G(blanc), H(blanc)
parcours(G, D)
u = D(noir)
v = B(noir), I(noir)
parcours(G, G)
u = G(noir)
v = B(noir), I(noir), F(blanc)
parcours(G, F)
u = F(noir)
v = A(noir), G(noir), H(blanc)
parcours(G, H)
u = H(noir)
v = F(noir), I(noir)
Parcours en profondeur Ă partir de AÂ : A, B, C, E, I, D, G, F, H.
Activité 13 - Comparaison des parcours
Comparez le résultat obtenu avec le parcours en largeur (A, B, F, C, D, G, H, E et I) et le résultat obtenu avec le parcours en profondeur (A, B, C, E, I, D, G, F et H)
Dans le cas du parcours en largeur on "dĂ©couvrait" tous les sommets situĂ©s Ă une distance k de l'origine avant de s'intĂ©resser aux sommets situĂ©s Ă une distance \(k+1\) de l'origine. Dans le cas du parcours en profondeur, on va chercher Ă aller "le plus loin possible" dans le graphe : A â B â C â E â I â D, quand on tombe sur "un cul-de-sac" (dans notre exemple, D est un "cul-de-sac", car une fois en D, on peut uniquement aller en B, or, B a dĂ©jĂ Ă©tĂ© dĂ©couvert...), on revient "en arriĂšre" (dans notre exemple, on repart de B pour aller explorer une autre branche : G â F â H)
RĂ©ponse
Dans le cas du parcours en largeur on âdĂ©couvraitâ tous les sommets situĂ©s Ă une distance k de lâorigine avant de sâintĂ©resser aux sommets situĂ©s Ă une distance k+1 de lâorigine. Dans le cas du parcours en profondeur, on va chercher Ă aller âle plus loin possibleâ dans le graphe : A Bâ â C â E â I â D, quand on tombe sur âun cul-de-sacâ (dans notre exemple, D est un âcul-de-sacâ, car une fois en D, on peut uniquement aller en B, or, B a dĂ©jĂ Ă©tĂ© dĂ©couvertâŠ), on revient âen arriĂšreâ (dans notre exemple, on repart de B pour aller explorer une autre branche : G â F â H)
Activité 14 - Parcours en profondeur non récursif
L'utilisation d'un algorithme récursif n'est pas une obligation pour le parcours en profondeur.
Etudiez cet algorithme :
VARIABLE
s : noeud (origine)
G : un graphe
u : noeud
v : noeud
p : pile (pile vide au départ)
parcours : liste vide
//On part du principe que pour tout sommet u du graphe G, u.couleur = blanc Ă l'origine
DEBUT
s.couleur â noir
piler(s,p)
tant que p n'est pas vide :
u â depiler(p)
Ajouter u Ă parcours
pour chaque sommet v adjacent au sommet u :
si v.couleur n'est pas noir :
v.couleur â noir
piler(v,p)
fin si
fin pour
fin tant que
FIN
Appliquez cet algorithme au graphe ci-dessous. VĂ©rifiez que l'on obtient bien un parcours en profondeur.
RĂ©ponse
2) En partant de AÂ :
s = A(blanc)
p = []
parcours = []
A(noir)
p = [A]
Debut Tant que:
u = A
p = []
parcours = [A]
DĂ©but pour:
v = B(blanc) ou F(blanc)
B(noir)
p = [B]
F(noir)
p = [F, B]
u = F
p = [B]
DĂ©but pour:
v = A(noir), G(blanc), H(blanc)
G(noir)
p = [G, B]
H(noir)
p = [H, G, B]
u = H
p = [G, B]
DĂ©but pour:
v = F(noir), I(blanc)
I(noir)
p = [I, G, B]
u = I
p = [G, B]
DĂ©but pour:
v = E(blanc), D(blanc), G(noir), H(noir)
E(noir)
p = [E, G, B]
D(noir)
p = [D, E, G, B]
u = D
p = [E, G, B]
DĂ©but pour:
v = B(noir), I(noir)
u = E
p = [G, B]
DĂ©but pour:
v = C(blanc), I(noir)
C(noir)
p = [C, G, B]
u = C
p = [G, B]
DĂ©but pour:
v = B(noir), E(noir)
u = G
p = [B]
DĂ©but pour:
v = B(noir), I(noir), F(noir)
u = B
p = []
DĂ©but pour:
v = A(noir), C(noir), D(noir), G(noir)
Fin Tant que
Le parcours en largeur à partir de A est donc : A, F, H, I, D, E, C, G, B.
Activité 15 - Implémentation des algorithmes de parcours de graphes
- Créer une méthode
couleurs()
dans la classeGraph
(matrice ou dictionnaire, comme bon vous semble...), qui renvoie un dictionnaire composé des couples(noeuds,'blanc')
, de maniÚre à avoir la couleur blanche associée à chaque noeud par défaut. - Implémentez les trois algorithmes de parcours de graphe vu précédemment.
# Tests
(insensible Ă la casse)(Ctrl+I)
RĂ©ponse
Activité 16 - Recherche d'un cycle
DĂ©finitions
Voici un rappel de 2 définitions vues précédemment :
- Une chaine est une suite d'arĂȘtes consĂ©cutives dans un graphe, un peu comme si on se promenait sur le graphe. On la dĂ©signe par les lettres des sommets qu'elle comporte. On utilise le terme de chaine pour les graphes non orientĂ©s et le terme de chemin pour les graphes orientĂ©s.
- Un cycle est une chaine qui commence et se termine au mĂȘme sommet.
Pour diffĂ©rentes raisons, il peut ĂȘtre intĂ©ressant de dĂ©tecter la prĂ©sence d'un ou plusieurs cycles dans un graphe (par exemple pour savoir s'il est possible d'effectuer un parcours qui revient Ă son point de dĂ©part sans ĂȘtre obligĂ© de faire demi-tour).
Nous allons étudier un algorithme qui permet de "détecter" la présence d'au moins un cycle dans un graphe.
Ătudiez cet algorithme. Que se passe-t-il quand le graphe G contient au moins un cycle ? Que se passe-t-il quand le graphe G ne contient aucun cycle :
VARIABLE
s : noeud (noeud quelconque)
G : un graphe
u : noeud
v : noeud
p : pile (vide au départ)
//On part du principe que pour tout sommet u du graphe G, u.couleur = blanc Ă l'origine
DEBUT
CYCLE():
piler(s,p)
tant que p n'est pas vide :
u â depiler(p)
pour chaque sommet v adjacent au sommet u :
si v.couleur n'est pas noir :
piler(v,p)
fin si
fin pour
si u est noir :
renvoie Vrai
sinon :
u.couleur â noir
fin si
fin tant que
renvoie Faux
FIN
RĂ©ponse
Quand le graphe G contient au moins un cycle : lâalgo renvoie vrai
Quand le graphe G ne contient aucun cycle : lâalgo renvoie faux
Appliquez l'algorithme de détection d'un cycle au graphe ci-dessous (vous partirez du sommet de votre choix).
RĂ©ponse
A partir de AÂ :
p = [A]
u = A(blanc)
p = []
v = B(blanc), C(blanc)
p = [B]
p = [C,B]
A(noir)
u = C
p = [B]
v = E(blanc), D(blanc)
p = [E, B]
p = [D, E, B]
C(noir)
u = D
p = [E, B]
v = C(noir)
D(noir)
u = E
p = [B]
v = C(noir)
E(noir)
u = B
p = []
v = A(noir), F(blanc)
B(noir)
Renvoie Faux
Appliquez l'algorithme de détection d'un cycle au graphe ci-dessous (vous partirez du sommet de votre choix).
RĂ©ponse
A partir de AÂ :
p = [A]
u = A(blanc)
p = []
v = B(blanc), C(blanc)
p = [B]
p = [C, B]
A(noir)
u = C(blanc)
p = [B]
v = E(blanc), D(blanc)
p = [E, B]
p = [D, E, B]
C(noir)
u = D(blanc)
p = [E, B]
v = C(noir), F(blanc)
p = [F, E, B]
D(noir)
u = F(blanc)
p = [E, B]
v = D(noir), B(blanc)
p = [B, E, B]
F(noir)
u = B(blanc)
p = [E, B]
v = A(noir), F(noir)
B(noir)
u = E(blanc)
p = [B]
v = C(noir)
E(noir)
u = B(noir)
p = []
v = A(noir), F(noir)
Renvoie Vraie
Activité 17 - Implémentation de la recherche d'un cycle
Implémentez cet algorithme (pensez à recopier la classe Graph (matrice ou dictionnaire, comme bon vous semble...))
# Tests
(insensible Ă la casse)(Ctrl+I)
RĂ©ponse
Activité 18 - Recherche d'un chemin dans un graphe
Nous allons maintenant nous intéresser à un algorithme qui permet de trouver une chaine entre 2 sommets (sommet de départ et sommet d'arrivée). Les algorithmes de ce type ont une grande importance et sont trÚs souvent utilisés.
Etudiez cet algorithme :
DEBUT
cherche_chemin(graphe,depart,arrivee)
P â pile vide
empiler le couple (depart,[depart]) dans P
Tant que P n'est pas vide faire
(sommet,chemin) â tĂȘte de P
listes_nouveaux_sommets_voisins â liste des sommets adjacents Ă sommet qui ne sont pas dans chemin
Pour un_sommet dans listes_nouveaux_sommets_voisins
Si un_sommet = arrivee alors
retourner chemin + [un_sommet]
sinon
empiler (un_sommet,chemin + [un_sommet])
FinSi
FinPour
FinTantQue
FIN
Appliquez l'algorithme permettant de trouver une chaine entre un noeud de départ et un noeud d'arrivée au graphe ci-dessous (vous choisirez les noeuds de départ et d'arrivée de votre choix).
A noter
Il est important de noter que dans la plupart des cas, les algorithmes de recherche de chaine (ou de chemin), travaillent sur des graphes pondérés (par exemple pour rechercher la route entre un point de départ et un point d'arrivée dans un logiciel de cartographie). Ces algorithmes recherchent aussi souvent les chemins les plus courts (logiciels de cartographie). On peut citer l'algorithme de Dijkstra ou encore l'algorithme de Bellman-Ford qui recherchent le chemin le plus court entre un noeud de départ et un noeud d'arrivée dans un graphe pondéré.
RĂ©ponse
Départ A, Arrivée E
chaine = [A]
A diff de E
u = B, F
E pas dans chaine
nchemin = TROUVER_CHAINE(G, B, E, chaine)
chaine = [A, B]
B diff de E
u = C, D, G
E pas dans chaine
nchemin = TROUVER_CHAINE(G, C, E, chaine)
chaine = [A, B, C]
C diff de E
u = E, B
E pas dans chaine
nchemin = TROUVER(G, E, E, chaine)
chaine = [A, B, C, E]
E = E
On renvoie chaine [A, B, C, E]
Implémentez cet algorithme sous Python.
# Tests
(insensible Ă la casse)(Ctrl+I)
# Tests
(insensible Ă la casse)(Ctrl+I)