Implémentation des graphes
Il existe de multiples manières de représenter un graphe, selon la nature du graphe mais aussi selon la nature des opérations que l'on souhaite effectuer sur ce graphe. Quelque soit le choix retenu, on souhaite pouvoir :
- utiliser des opérations de construction du graphe. Il faut pouvoir ajouter/supprimer des sommets, des arcs…
- utiliser des opérations de parcours du graphe : parcourir la liste des sommets, des arcs, ou bien des arcs issus d'un sommet donné…
On s'intéresse dans ce TP aux implémentations des graphes orientés.
Matrices d'adjacence¶
Les sommets du graphe sont les nombres \(0, 1, \ldots, n - 1\), pour un certain entier \(n\in \mathbb{N}\). On représente le graphe à l'aide d'une matrice adj de taille \(n \times n\) (un tableau constitué de \(n\) lignes et \(n\) colonnes). Il existe un arc entre les sommets \(i\) et \(j\) du graphe si et seulement si adj[i][j] vaut True.


Matrice d'adjacence adj de \(G_1\).
On donne ci-dessous le code python du constructeur de la classe Graphe.
class Graphe:bksl-nl """ Un graphe représenté par une matrice d'adjacence.bksl-nl Les sommets sont les nombres 0, 1, ..., n - 1. """bksl-nl def py-undpy-undinitpy-undpy-und(self, n):bksl-nl self.n = nbksl-nl self.adj = [[False]py-strn for py-und in range(n)]bksl-nlbksl-nl def ajouterpy-undsommet(self):bksl-nl """ Graphe -> Nonetypebksl-nl Ajoute un sommet au graphe """bksl-nl passbksl-nlbksl-nl def ajouterpy-undarc(self, source, destination):bksl-nl """ Graphe, int, int -> intbksl-nl Ajoute l'arc source -> destination """bksl-nl passbksl-nlbksl-nl def supprimerpy-undsommet(self, i):bksl-nl """ Graphe, int -> Nonetypebksl-nl Supprime le sommet i du graphe. Les sommets j > i sont renommés en j - 1. """bksl-nl passbksl-nlbksl-nl def supprimerpy-undarc(self, source, destination):bksl-nl """ Graphe, int, int -> Nonetypebksl-nl Supprime l'arc source -> destination """bksl-nl passbksl-nlbksl-nl def estpy-undvoisin(self, source, destination):bksl-nl """ Graphe, int, int -> boolbksl-nl Détermine si source et destination sont voisins """bksl-nl passbksl-nlbksl-nl def sommets(self):bksl-nl """ Graphe -> [int]bksl-nl Renvoie la liste des sommets du graphe self """bksl-nl passbksl-nlbksl-nl def voisins(self, sommet):bksl-nl """ Graphe, int -> [int]bksl-nl Renvoie la liste des voisins de sommet """bksl-nl passbksl-nlbksl-nl def listepy-undarcs(self):bksl-nl """ Graphe -> [(int, int)]bksl-nl Renvoie la liste des arcs du graphe """bksl-nl passbksl-nlbksl-nl def ordre(self):bksl-nl """ Graphe -> intbksl-nl Renvoie l'ordre du graphe """bksl-nl passbksl-nlbksl-nl def taille(self):bksl-nl """ Graphe -> intbksl-nl Renvoie la taille du graphe """bksl-nl passbksl-nlbksl-nl def decrire(self):bksl-nl """ Graphe -> Nonetypebksl-nl Affiche une description du graphe self """bksl-nl passbksl-nlbksl-nl
-
-
Initialiser une variable
g1de typeGraphe, représentant un graphe constitué des 4 somments \(0\), \(1\), \(2\), et \(3\). Initialement, aucun arc n'est présent dans le grapheg1. -
Écrire une méthode
sommetsde la classeGraphequi étant donné un grapheselfrenvoie la liste des sommets qui le constituent.1 2 3 4
def sommets(self): """ Graphe -> [int] Renvoie la liste des sommets du graphe self """ pass1print(g1.sommets())[0, 1, 2, 3]
-
-
-
Écrire une méthode
ajouter_arcde la classeGraphequi étant donné deux sommetssourceetdestinationdu grapheselfajoute l'arcsource\(\to\)destinationau grapheself.1 2 3 4
def ajouter_arc(self, source, destination): """ Graphe, int, int -> None Ajoute l'arc source -> destination """ pass -
À l'aide de la méthode
ajouter_arcs, construire le grapheg1de l'énoncé.1 2
for row in g1.adj: print(row)[False, True, False, True] [False, False, False, True] [False, True, False, False] [False, False, True, False] -
Écrire une méthode
est_voisinde la classeGraphequi étant donné deux sommetssourceetdestinationdu grapheselfrenvoieTruesi et seulement si il existe un arc reliantsourceàdestinationdans le grapheself.1 2 3 4
def est_voisin(self, source, destination): """ Graphe, int, int -> bool Détermine si source et destination sont voisins """ pass1 2
print(g1.est_voisin(0, 1)) print(g1.est_voisin(1, 0))True False
-
-
-
Écrire une méthode
voisinsde la classeGraphequi étant donné unsommetdu grapheselfrenvoie la liste des voisins desommet.1 2 3 4
def voisins(self, sommet): """ Graphe, int -> [int] Renvoie la liste des voisins de sommet """ pass1 2
print(g1.voisins(0)) print(g1.voisins(1))[1, 3] [3] -
Écrire une méthode
liste_arcsde la classeGraphequi renvoie la liste des couples(source, destination)correspondants à des arcs du grapheself.1 2 3 4
def liste_arcs(self): """ Graphe -> [(int, int)] Renvoie la liste des arcs du graphe """ pass1print(g1.liste_arcs())[(0, 1), (0, 3), (1, 3), (2, 1), (3, 2)]
-
-
-
Écrire une méthode
ordrede la classeGraphequi renvoie l'ordre du graphe.Rappel. L'ordre d'un graphe est le nombre de sommets qui composent le graphe.
1 2 3 4
def ordre(self): """ Graphe -> int Renvoie l'ordre du graphe """ pass1print(g1.ordre())4 -
Écrire une méthode
taillede la classeGraphequi renvoie la taille du graphe.Rappel. La taille d'un graphe est le nombre d'arêtes qui composent le graphe.
1 2 3 4
def taille(self): """ Graphe -> int Renvoie la taille du graphe """ pass1print(g1.taille())5 -
Écrire une méthode
decrirequi affiche les informations du grapheselfsur le modèle donné ci-dessous.1 2 3 4
def decrire(self): """ Graphe -> None Affiche une description du graphe self """ pass1g1.decrire()Graphe d'ordre 4 et de taille 5 Sommet 0 -> 1 3 Sommet 1 -> 3 Sommet 2 -> 1 Sommet 3 -> 2
-
-
-
Écrire une méthode
ajouter_sommetde la classeGraphequi ajoute un sommet au graphe. Ainsi, si le graphegest constitué de \(n\) sommets, alors après l'application de la méthodeajouter_sommetle graphe est constitué de \(n + 1\) sommets (le sommet ajouté est \(n\)) : la méthodeajouter_sommetmodifiera à la fois l'attributnet l'attributadjdu grapheself. Aucun arc ne sera ajouté par cette méthode.1 2 3 4
def ajouter_sommet(self): """ Graphe -> None Ajoute un sommet au graphe """ pass1 2
g1.ajouter_sommet() g1.decrire()Graphe d'ordre 5 et de taille 5 Sommet 0 -> 1 3 Sommet 1 -> 3 Sommet 2 -> 1 Sommet 3 -> 2 Sommet 4 -> -
Écrire une méthode
supprimer_arcde la classeGraphequi supprime (si présent) l'arc reliantsourceàdestinationdans le grapheself. Cette méthode ne produira pas d'erreur dans le cas où le graphe ne contient pas l'arc(source)\(\to\)(destination).1 2 3 4
def supprimer_arc(self, source, destination): """ Graphe, int, int -> None Supprime l'arc source -> destination """ pass -
Écrire une méthode
supprimer_sommetde la classeGraphequi supprime le sommetidu grapheself. La méthodesupprimer_sommetmodifiera l'attributnet l'attributadj. Si \(j > i\) est un sommet du graphe, alors son numéro sera \(j - 1\) après application de la méthodesupprimer_sommet, les sommets \(j < i\) conserveront le même numéro.1 2 3 4
def supprimer_sommet(self, i): """ Graphe, int -> None Supprime le sommet i du graphe. Les sommets j > i sont renommés en j - 1. """ pass1 2
g1.supprimer_sommet(2) g1.decrire()Graphe d'ordre 4 et de taille 3 Sommet 0 -> 1 2 Sommet 1 -> 2 Sommet 2 -> Sommet 3 ->
-
Dictionnaire d'adjacence¶
Les sommets du graphe sont quelconques (des nombres, non forcément consécutifs, des lettres, des mots, etc). On représente un graphe à l'aide d'un dictionnaire d'adjacence, noté adj. Si \(G = (S, A)\), alors les clés du dictionnaire adj seront les éléments de \(S\). La valeur associée à la clé source sera la liste des sommets destination de telles que (source, destination) \(\in A\).
Par exemple si \(G_2\) est le graphe :

Alors le dictionnaire correspondant est :
1 2 3 4 5 6 | |
On donne ci-dessous le code python du constructeur de la classe Graphe.
class Graphe:bksl-nl """ Un graphe représenté par un dictionnaire d'adjacence. """bksl-nl def py-undpy-undinitpy-undpy-und(self, n):bksl-nl self.n = nbksl-nl self.adj = [[False]py-strn for py-und in range(n)]bksl-nlbksl-nl def ajouterpy-undsommet(self, s):bksl-nl """ Graphe, str -> Nonetypebksl-nl Ajoute le sommet s au graphe self """bksl-nl passbksl-nlbksl-nl def ajouterpy-undarc(self, source, destination):bksl-nl """ Graphe, str, str -> Nonetypebksl-nl Ajoute l'arc source -> destination au graphe self """bksl-nl passbksl-nlbksl-nl
Toutes les questions de cette partie concernent cette classe Graphe, qui sera écrite dans un nouveau fichier. Les sommets seront ici des chaînes de caractère.
- Donner la représentation sous forme de dictionnaire des graphes orientés de la fiche "Exercices de compréhension".
-
-
Initialiser une variable
g2de typeGraphereprésentant un graphe initialement vide. -
Écrire une méthode
ajouter_sommetqui ajoute un sommetsau grapheself. Initialement, aucun arc ne sort du sommet ajouté. Si le sommet est déjà présent dans le graphe, alors cette méthode ne fait rien.1 2 3 4 5
def ajouter_sommet(self, s): """ Graphe, str -> None Ajoute le sommet s au graphe self Ne fait rien si le sommet est déjà présent dans le graphe """ pass -
Écrire une méthode
ajouter_arcde la classeGraphequi étant donné deux sommetssourceetdestinationdu grapheselfajoute l'arcsource\(\to\)destinationau grapheself.1 2 3 4
def ajouter_arc(self, source, destination): """ Graphe, str, str -> None Ajoute l'arc source -> destination au graphe self """ pass -
À l'aide des méthodes
ajouter_sommetetajouter_arc, construire le grapheg2de l'énoncé.1print(g2.adj){'A': ['B', 'C', 'D'], 'B': ['D'], 'C': ['A', 'D'], 'D': ['E'], 'E': ['F'], 'F': ['E']}
-
-
Écrire les méthodes
est_voisin,sommets,voisins,liste_arcs,ordre,taille,decrire,supprimer_arc,supprimer_sommet(dans cet ordre) de la classeGraphe(ainsi que leur signature et leur documentation). VOUS TESTEREZ VOS MÉTHODES.1g2.decrire()Graphe d'ordre 6 et de taille 9 Sommet A -> B C D Sommet B -> D Sommet C -> A D Sommet D -> E Sommet E -> F Sommet F -> E1 2
g2.supprimer_sommet('C') g2.decrire()Graphe d'ordre 5 et de taille 6 Sommet A -> B D Sommet B -> D Sommet D -> E Sommet E -> F Sommet F -> E -
Écrire la méthode
predecesseursde la classeGraphequi étant donné un grapheselfet un sommetdestinationrenvoie la liste des prédécesseurs desource.Attention. Dans l'exemple, le sommet
'C'a été supprimé lors des exemples de la question précédente.1 2 3 4
def predecesseurs(self, destination): """ Graphe, str -> [str] Renvoie la liste des prédécesseurs de u """ pass1print(g2.predecesseurs('D'))['A', 'B']
Chemins dans les graphes acycliques¶
Un graphe orienté est dit acyclique lorsqu'il n'existe aucun cycle dans le graphe. On parle de DAG pour Directed Acyclic Graph. Par exemple, dans la feuille "Exercices de compréhension", \(G_3\) est un graphe orienté acyclique. \(G_4\) n'est pas un graphe orienté acyclique : par exemple \(c \rightarrow e \rightarrow c\) est un cycle.
Écrire une fonction récursive chemin qui prend en argument un graphe g acyclique, deux sommets u et v du graphe et qui renvoie True s'il existe un chemin de u à v, False sinon.
- Pourquoi est-il important de supposer que
gest acyclique ? Que risque-t-il de se passer si on exécutechemin(g, u v)sigpossède un cycle ? - Pouvez-vous modifier votre fonction pour que la fonction
cheminrenvoie :- la liste des sommets du chemin dans le cas où il existe un chemin entre
uetv. Le premier élément de la liste doit êtreu, le dernier élément de la liste doit êtrev. - la liste vide si il n'existe pas de chemin reliant
uàv.
- la liste des sommets du chemin dans le cas où il existe un chemin entre
Rappels¶
Si l est un objet de type list, i un indice compatible avec la taille de la liste, et e1 un élément présent dans la liste l, e2 un élément non présent dans la liste l :
l.copy()renvoie une copie indépendante dele1 in ls'évalue àTrue;e2 in ls'évalue àFalsel.pop()supprime le dernier élément de la liste,l.pop(i)supprime l'élément d'indiceide la liste.l.remove(e1)supprime la première occurrence dee1dansl;l.remove(e2)soulève une erreurl.append(e2)ajoute l'élémente2à la fin de la liste ;l.insert(i, e2)insère l'élémente2à l'indicei.enumerate(l)renvoie la liste[(i, l[i]) for i in range(len(l))]
Si d est un objet de type dict, k1 une clé présente dans le dictionnaire, v1 la valeur associée à la clé k1, k2 une clé non présente dans le dictionnaire :
d[k1]renvoiev1;d[k2]soulève une erreur ;d.get(k1)renvoiev1,d.get(k2)renvoieNone;k1 in ds'évalue àTrue;k2 in ds'évalue àFalsed.pop(k1)supprimek1dedet renvoiev1;d.pop(k2)soulève une erreur ;d.setdefault(k1, "valeur par defaut")ne fait rien cark1est déjà présent dans le dictionnaire,d.setdefault(k2, "valeur par défaut")associe àk2la valeur"valeur par défaut"d.keys()renvoie la liste des clés du dictionnaire ;d.values()renvoie la liste des valeurs du dictionnaire ;d.items()renvoie la liste des couples(clé, valeur)du dictionnaire ;