Application des parcours de graphe
On s'intéresse dans ce tp à différentes applications directes des parcours (en profondeur, en largeur) de graphes.
class Pile:bksl-nl def py-undpy-undinitpy-undpy-und(self, maxpy-undelt = float('inf')):bksl-nl self.contenu = []bksl-nl self.maxpy-undelt = maxpy-undeltbksl-nl self.logpy-undlist = []bksl-nlbksl-nl def py-undpy-undreprpy-undpy-und(self):bksl-nl return "[" + ", ".join(b.py-undpy-undreprpy-undpy-und() for b in self.contenu) + "] (Sommet pile)" bksl-nlbksl-nl def py-undpy-undlenpy-undpy-und(self):bksl-nl return len(self.contenu)bksl-nlbksl-nl def log(self):bksl-nl print("\n".join(self.logpy-undlist))bksl-nlbksl-nl def affiche(self):bksl-nl print(self)bksl-nlbksl-nl def estpy-undvide(self):bksl-nl return self.contenu == []bksl-nlbksl-nl def estpy-undpleine(self):bksl-nl return self.maxpy-undelt == len(self)bksl-nlbksl-nl def empiler(self, c):bksl-nl self.logpy-undlist.append(f"Empile {c}")bksl-nl if self.estpy-undpleine():bksl-nl raise IndexError("Capacité maximale de la pile atteinte")bksl-nl self.contenu.append(c)bksl-nlbksl-nl def depiler(self):bksl-nl if self.estpy-undvide():bksl-nl raise IndexError("Impossible de dépiler : la pile est vide.")bksl-nl c = self.contenu.pop()bksl-nl self.logpy-undlist.append(f"Dépile {c}")bksl-nl return cbksl-nlbksl-nlclass File:bksl-nl def py-undpy-undinitpy-undpy-und(self, maxpy-undelt = float('inf')):bksl-nl self.contenu = []bksl-nl self.maxpy-undelt = maxpy-undeltbksl-nl self.logpy-undlist = []bksl-nlbksl-nl def py-undpy-undreprpy-undpy-und(self):bksl-nl return "(Début file) [" + ", ".join(b.py-undpy-undreprpy-undpy-und() for b in self.contenu) + "]"bksl-nlbksl-nl def py-undpy-undlenpy-undpy-und(self):bksl-nl return len(self.contenu)bksl-nlbksl-nl def log(self):bksl-nl print("\n".join(self.logpy-undlist))bksl-nlbksl-nl def affiche(self):bksl-nl print(self)bksl-nlbksl-nl def estpy-undvide(self):bksl-nl return self.contenu == []bksl-nlbksl-nl def estpy-undpleine(self):bksl-nl return len(self) == self.maxpy-undelt bksl-nlbksl-nl def enfiler(self, b):bksl-nl self.logpy-undlist.append(f"Enfile {b}")bksl-nl if self.estpy-undpleine():bksl-nl raise IndexError("Capacité maximale de la file atteinte")bksl-nl self.contenu.append(b)bksl-nlbksl-nl def defiler(self):bksl-nl if self.estpy-undvide():bksl-nl raise IndexError("Impossible de défiler : la file est vide.")bksl-nl c = self.contenu.pop(0)bksl-nl self.logpy-undlist.append(f"Défile {c}")bksl-nl return cbksl-nlbksl-nlclass Graphe:bksl-nl """ Un graphe représenté par un dictionnaire d'adjacence. """bksl-nl def py-undpy-undinitpy-undpy-und(self):bksl-nl self.adj = dict()bksl-nlbksl-nl def ajouterpy-undsommet(self, s):bksl-nl """ Graphe, str -> Nonetypebksl-nl Ajoute le sommet s au graphe self """bksl-nl # self.adj.setdefault(source, [])bksl-nl if s not in self.adj:bksl-nl self.adj[s] = []bksl-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 self.adj[source].append(destination)bksl-nlbksl-nl def estpy-undvoisin(self, source, destination):bksl-nl """ Graphe, str, str -> boolbksl-nl Détermine si source et destination sont voisins """bksl-nl for v in self.adj[source]:bksl-nl if v == destination:bksl-nl return Truebksl-nl return Falsebksl-nl bksl-nl def sommets(self):bksl-nl """ Graphe -> [str]bksl-nl Renvoie la liste des sommets du graphe self """bksl-nl return sorted(list(self.adj.keys()))bksl-nl bksl-nl def voisins(self, s):bksl-nl """ Graphe, stsr -> [str]bksl-nl Renvoie la liste des voisins de sommet """bksl-nl return sorted(self.adj[s])bksl-nl bksl-nl def listepy-undarcs(self):bksl-nl """ Graphe -> [(str, str)]bksl-nl Renvoie la liste des arcs du graphe """bksl-nl arcs = []bksl-nl for u in self.sommets():bksl-nl for v in self.adj[u]:bksl-nl arcs.append((u, v))bksl-nl return arcsbksl-nl bksl-nl def supprimerpy-undarc(self, source, destination):bksl-nl """ Graphe, int, int -> Nonetypebksl-nl Supprime le ou les arcs source -> destination """bksl-nl for i, v in enumerate(self.adj[source]):bksl-nl if v == destination:bksl-nl self.adj[source].pop(i)bksl-nl bksl-nl def supprimerpy-undsommet(self, sommet):bksl-nl """ Graphe, str -> Nonetypebksl-nl Supprime le sommet i du graphe. """bksl-nl for s in self.sommets():bksl-nl self.supprimerpy-undarc(s, sommet)bksl-nl self.adj.pop(sommet)bksl-nl bksl-nl def ordre(self):bksl-nl """ Graphe -> intbksl-nl Renvoie l'ordre du graphe """bksl-nl return len(self.adj)bksl-nl bksl-nl def taille(self):bksl-nl """ Graphe -> intbksl-nl Renvoie la taille du graphe """bksl-nl return len(self.listepy-undarcs())bksl-nl bksl-nl def decrire(self):bksl-nl """ Graphe -> Nonetypebksl-nl Affiche une description du graphe self """bksl-nl print(f"Graphe d'ordre {self.ordre()} et de taille {self.taille()}")bksl-nl for s in self.sommets():bksl-nl print(f"Sommet {s} -> {' '.join(str(i) for i in self.voisins(s))}")bksl-nlbksl-nlclass Ensemble:bksl-nl def py-undpy-undinitpy-undpy-und(self):bksl-nl """ Ensemble -> Nonetype """bksl-nl self.contenu = dict()bksl-nlbksl-nl def ajouter(self, elt):bksl-nl """ Ensemble, elt -> Nonetype """bksl-nl self.contenu[elt] = Truebksl-nlbksl-nl def supprimer(self, elt):bksl-nl """ Ensemble, elt -> Nonetype """bksl-nl self.contenu.pop(elt)bksl-nlbksl-nl def py-undpy-undcontainspy-undpy-und(self, elt):bksl-nl """ Ensemble, elt -> bool """bksl-nl if self.contenu.get(elt):bksl-nl return Truebksl-nl return Falsebksl-nlbksl-nldef arcspy-undverspy-undgraphepy-undo(arcs):bksl-nl """ [(Sommet, Sommet)] -> Graphebksl-nl Sommet peut être de type int ou strbksl-nl Construit le graphe non orienté dont on donne la liste des arêtes. """bksl-nl g = Graphe()bksl-nl for (source, destination) in arcs:bksl-nl g.ajouterpy-undsommet(source)bksl-nl g.ajouterpy-undsommet(destination)bksl-nl g.ajouterpy-undarc(source, destination)bksl-nl return gbksl-nlbksl-nldef arcspy-undverspy-undgraphepy-undno(arcs):bksl-nl """ [(Sommet, Sommet)] -> Graphebksl-nl Sommet peut être de type int ou strbksl-nl Construit le graphe non orienté dont on donne la liste des arêtes. """bksl-nl g = Graphe()bksl-nl for (source, destination) in arcs:bksl-nl g.ajouterpy-undsommet(source)bksl-nl g.ajouterpy-undsommet(destination)bksl-nl g.ajouterpy-undarc(source, destination)bksl-nl g.ajouterpy-undarc(destination, source)bksl-nl return gbksl-nlbksl-nlbksl-nldef parcourspy-undprofondeur(G, depart):bksl-nl """ Graphe, Sommet -> [Sommet]bksl-nl Parcours le graphe G depuis le sommet depart en profondeur """bksl-nl vus = Ensemble()bksl-nl ordrepy-undvisites = []bksl-nl àpy-undexplorer = Pile()bksl-nl àpy-undexplorer.empiler(depart)bksl-nl while not àpy-undexplorer.estpy-undvide():bksl-nl # définition du sommet courant bksl-nl sommet = àpy-undexplorer.depiler()bksl-nl if sommet in vus:bksl-nl continuebksl-nl # opération de traitementbksl-nl vus.ajouter(sommet)bksl-nl ordrepy-undvisites.append(sommet)bksl-nl # ajout des voisins non visitésbksl-nl for destination in reversed(G.voisins(sommet)):bksl-nl if not destination in vus:bksl-nl àpy-undexplorer.empiler(destination)bksl-nl return ordrepy-undvisitesbksl-nlbksl-nldef parcourspy-undlargeur(G, depart):bksl-nl """ Graphe, Sommet -> [Sommet]bksl-nl Parcours le graphe G depuis le sommet depart en largeur """bksl-nl vus = Ensemble()bksl-nl ordrepy-undvisites = []bksl-nl àpy-undexplorer = File()bksl-nl àpy-undexplorer.enfiler(depart)bksl-nl while not àpy-undexplorer.estpy-undvide():bksl-nl # définition du sommet courant bksl-nl sommet = àpy-undexplorer.defiler()bksl-nl if sommet in vus:bksl-nl continuebksl-nl # opération de traitementbksl-nl vus.ajouter(sommet)bksl-nl ordrepy-undvisites.append(sommet)bksl-nl # ajout des voisins non visitésbksl-nl for destination in G.voisins(sommet):bksl-nl if not destination in vus:bksl-nl àpy-undexplorer.enfiler(destination)bksl-nl return ordrepy-undvisitesbksl-nlbksl-nldef oubliepy-undorientation(G):bksl-nl """ Graphe -> Graphebksl-nl Renvoie le graphe non orienté G' dans lequel on a "oublié"bksl-nl le sens des arcs de G """bksl-nl Gp = Graphe()bksl-nl for s in G.sommets():bksl-nl Gp.ajouterpy-undsommet(s)bksl-nl for (source, destination) in G.listepy-undarcs():bksl-nl Gp.ajouterpy-undarc(source, destination)bksl-nl Gp.ajouterpy-undarc(destination, source)bksl-nl return Gpbksl-nlbksl-nlg1 = arcspy-undverspy-undgraphepy-undo([(0, 2), (1, 0), (1, 3),bksl-nl (3, 2), (4, 5), (5, 6), (6, 4)]) bksl-nlg2 = arcspy-undverspy-undgraphepy-undno([(0, 1), (0, 5), (0, 8),bksl-nl (1, 2), (2, 3), (3, 4), (3, 7),bksl-nl (3, 8), (4, 5), (4, 7)])bksl-nlg3 = arcspy-undverspy-undgraphepy-undo([(1, 2), (4, 5), (4, 3), (5, 3)])bksl-nlg3.ajouterpy-undsommet(0)bksl-nlg4 = arcspy-undverspy-undgraphepy-undo([(0, 1), (1, 2), (2, 3), (2, 4),bksl-nl (3, 5), (5, 4), (4, 1)])bksl-nlbksl-nl
Existence de chemin entre deux sommets distincts¶
Écrire une fonction existe_chemin qui étant donné un graphe G (orienté ou non) et deux sommets source et destination distincts détermine s'il est possible de trouver un chemin dans le graphe G entre le sommet source et le sommet destination. Vous utiliserez pour cela une des fonctions de parcours de graphe écrite précédemment.
def existepy-undchemin(G, sommet, destination):bksl-nl """ Graphe, Sommet, Sommet -> boolbksl-nl Renvoie True si et seulement si il existe un chemin entre source et destination dans G. """bksl-nl passbksl-nlbksl-nl
Composantes connexes d'un graphe non orienté¶
Écrire une fonction composantes_connexes qui étant donné un graphe G non orienté en détermine les composantes connexes. Cette fonction renverra un dictionnaire définit de la manière suivante :
kest une clé du dictionnaire si et seulement sikest un sommet deG- on associera à la clé
kun entier représentant le numéro de la composante connexe associée àk. L'entier choisit peut-être arbitraire, mais deux clésk1etk2doivent être associé au même entier si et seulement si les sommetsk1etk2appartiennent à la même composante connexe.
Indication. On pourra utiliser l'algorithme suivant :
- on initialise un dictionnaire
composantesainsi qu'un entiercomposante_numqui permettront respectivement de mémoriser le numéro de la composante connexe de chaque sommet et le numéro de la dernière composante connexe découverte. - pour chaque
sommetdu graphe, on détermine la liste des sommetsatteignablesdepuissommetà l'aide d'un parcours du graphe depuissommet.- pour chaque sommet
atteignableprésent dans la listeatteignables, on attribue àatteignabledans le dictionnairecomposantesle numéro de la composante connexe desommet. - on prendra soin de mettre à jour convenablement le numéro de la dernière composante connexe découverte ainsi que de ne pas explorer inutilement le graphe depuis les sommets dont on a déjà trouvé la composante connexe.
- pour chaque sommet
def composantespy-undconnexes(G):bksl-nl """ Graphe -> {Sommet:int}bksl-nl Détermine les composantes connexes du graphe G sous la forme d'un dictionnaire """bksl-nl passbksl-nlbksl-nl
Repérer la présence d'un cycle dans un graphe orienté¶
On cherche dans cette question à détecter si un cycle est présent dans un graphe orienté G. On rappelle qu'un cycle (ou circuit) dans un graphe \(G = (S, A)\) est une suite d'arcs consécutifs \(a_0, \ldots, a_{n - 1}\) de telle sorte que le sommet source de l'arc \(a_0\) soit le sommet destination de l'arc \(a_{n - 1}\) (\(n \geq 1\) est la longueur du cycle).
L'algorithme de détection de cycle dans un graphe se base sur l'algorithme de parcours en profondeur récursif, dont on rappelle une version ci-dessous.
1 2 3 4 5 6 7 8 9 10 | |
Commenter les différences entre les deux algorithmes de parcours en profondeur.
Afin de détecter les éventuels cycles dans le graphe, on va marquer les sommets avec un code couleur :
- les sommets en
BLANCsont les sommets non atteints par le parcours en profondeur ; - les sommets en
GRISsont les sommets atteints dont le parcours en profondeur est en cours ; - les sommets en
NOIRsont les sommets atteints dont le parcours en profondeur est terminé.
Détection d'un cycle depuis un sommet donné¶
On remplace l'ensemble vus de l'algorithme récursif du parcours en profondeur par un dictionnaire couleurs définit de la manière suivante :
- les clés
ksont les sommets du graphe ; - la valeur associée à la clé
kdans le dictionnairecouleursest un entier représentant la couleur actuelle du sommetkdans le parcours en profondeur du graphe. On prendra comme convention que l'entier0représente la couleur blanche, l'entier1représente la couleur grise, et l'entier2la couleur noire.
Lors de l'appel récursif parcours_cycle(G, depart, couleurs) le sommet depart (dont on suppose qu'il n'a pas encore été exploré, il est donc en BLANC) :
- on commence par marquer en
GRISle sommet départ ; - pour chaque
destinationvoisine dedepart:- si la
destinationn'a pas encore été explorée, alors on l'explore récursivement et on renvoieTruedans le cas où cette exploration détecte un cycle,Falsesinon. - sinon, si la
destinationest déjà en cours d'exploration, alors cela veut dire l'on vient de détecter un cycle. On renvoie doncTrue.
- si la
- on marque le sommet en
NOIRpour indiquer qu'il a été complètement exploré et que l'on n'a pas trouvé de cycle. On renvoie doncFalse.
BLANC, GRIS, NOIR = 0, 1, 2bksl-nlbksl-nldef parcourspy-undcycle(G, depart, couleurs):bksl-nl """ Graphe, Sommet, {Sommet:Couleur} -> boolbksl-nl Renvoie True si et seulement si il existe un cycle dans le graphe G dans la composante connexe de depart. bksl-nl On suppose que depart est en BLANC. """bksl-nl couleurs[...] = GRISbksl-nl for destination in G.voisins(depart):bksl-nl if ...:bksl-nl return ...bksl-nl elif ...bksl-nl return ...bksl-nl couleurs[...] = ...bksl-nl return ...bksl-nlbksl-nl
Détection d'un cycle dans un graphe¶
En déduire une fonction contient_cycle qui étant donné un graphe orienté G détermine si celui-ci contient un cycle. On fera pour cela appel à la fonction précédente, en initialisant un dictionnaire couleurs dans lequel tous les sommets du graphe G sont coloriés en blanc. On parcourra le graphe à l'aide de la fonction parcours_cycle depuis les sommets du graphe, et on renverra True si on détecte un cycle depuis un sommet de cette façon. On prendra garde à ne pas explorer de manière inutile le graphe à partir de sommets déjà parcourus en utilisant astucieusement les couleurs des sommets.
def contientpy-undcycle(G):bksl-nl """ Graphe -> boolbksl-nl Renvoie True ssi le graphe orienté G contient un cycle """bksl-nl passbksl-nlbksl-nl
Distance entre deux sommets¶
Écrire une fonction distance qui étant donné un graphe G ainsi que deux sommets source et arrivee détermine la plus petite longueur d'un chemin de source vers arrivee. Si arrivee n'est pas accessible depuis source, la fonction renverra -1.
Indication. On reprendra l'algorithme du parcours en largueur d'un graphe en le modifiant. On enfilera dans la file à_explorer le couple (sommet, distance) où distance correspond à la longueur d'un plus petit chemin entre source et sommet. Ainsi, si l'on défile le couple (sommet, distance) et que sommet est égal à arrivee, alors on pourra renvoyer la valeur distance.
En particulier :
- on commencera par enfiler le couple
(depart, 0)dans la fileà_explorer; - si le
sommetcourant se trouve àdistancedu sommetsource, alors ses voisins non encore explorés se trouvent àdistance + 1du sommetsource, lorsque le graphe est parcouru en largeur.
def distance(G, depart, arrivee):bksl-nl """ Graphe, Sommet, Sommet -> int bksl-nl Renvoie la longueur du plus petit chemin entre depart et arrivee dans G """bksl-nl passbksl-nlbksl-nl
Diamètre d'un graphe¶
Écrire une fonction diametre qui étant donné un graphe G en détermine le diamètre d ainsi que les deux sommets (source, destination) les plus éloignés du graphe. On rappelle que le diamètre d'un graphe est la plus grande distance possible entre deux sommets source et destination quelconques du graphe.
def diametre(G):bksl-nl """ Graphe -> intbksl-nl Renvoie la distance maximale entre deux sommets quelconques du graphe """bksl-nl passbksl-nlbksl-nl