Selon le Lemme 22.11 de Cormen et al., Introduction to Algorithms (CLRS) :
Un graphe dirigé G est acyclique si et seulement si une recherche en profondeur de G ne produit pas de arêtes de retour.
Cela a été mentionné dans plusieurs réponses ; ici, je vais également fournir un exemple de code basé sur le chapitre 22 de CLRS. Le graphe exemple est illustré ci-dessous.
Le pseudo-code de CLRS pour la recherche en profondeur est le suivant :
Dans l'exemple de la Figure 22.4 de CLRS, le graphe se compose de deux arbres DFS : l'un composé des nœuds u, v, x et y, et l'autre des nœuds w et z. Chaque arbre contient une arête de retour : une de x à v et une autre de z à z (une boucle locale).
La clé de la réalisation est qu'une arête de retour est rencontrée lorsque, dans la fonction DFS-VISIT
, en itérant sur les voisins v
de u
, un nœud avec la couleur GRAY
est rencontré.
Le code Python suivant est une adaptation du pseudo-code de CLRS avec une clause if
ajoutée qui détecte les cycles :
import collections
class Graph(object):
def __init__(self, edges):
self.edges = edges
self.adj = Graph._build_adjacency_list(edges)
@staticmethod
def _build_adjacency_list(edges):
adj = collections.defaultdict(list)
for edge in edges:
adj[edge[0]].append(edge[1])
adj[edge[1]] # effets secondaires uniquement
return adj
def dfs(G):
discovered = set()
finished = set()
for u in G.adj:
if u not in discovered and u not in finished:
discovered, finished = dfs_visit(G, u, discovered, finished)
def dfs_visit(G, u, discovered, finished):
discovered.add(u)
for v in G.adj[u]:
# Détecter les cycles
if v in discovered:
print(f"Cycle détecté : une arête de retour trouvée de {u} à {v}.")
break
# Récursivité dans l'arbre DFS
if v not in finished:
dfs_visit(G, v, discovered, finished)
discovered.remove(u)
finished.add(u)
return discovered, finished
if __name__ == "__main__":
G = Graph([
('u', 'v'),
('u', 'x'),
('v', 'y'),
('w', 'y'),
('w', 'z'),
('x', 'v'),
('y', 'x'),
('z', 'z')])
dfs(G)
Remarquez que dans cet exemple, le time
dans le pseudo-code de CLRS n'est pas capturé car nous nous intéressons uniquement à la détection des cycles. Il y a aussi un certain code de boilerplate pour construire la représentation de liste d'adjacence d'un graphe à partir d'une liste d'arêtes.
Lorsque ce script est exécuté, il imprime la sortie suivante :
Cycle détecté : une arête de retour trouvée de x à v.
Cycle détecté : une arête de retour trouvée de z à z.
Il s'agit exactement des arêtes de retour dans l'exemple de la Figure 22.4 de CLRS.
16 votes
Vous dites que vous voulez détecter tous les cycles, mais votre cas d'utilisation suggère qu'il serait suffisant de détecter s'il y a des cycles.
35 votes
Il serait préférable de détecter tous les cycles afin de les corriger en une seule fois, plutôt que de vérifier, corriger, vérifier, corriger, etc.
2 votes
Vous devriez lire l'article "Finding all the elementary circuits of a directed graph" de Donald B. Johnson. Il trouvera uniquement les circuits élémentaires, mais cela devrait être suffisant pour votre cas. Et voici mon implémentation en Java de cet algorithme prête à être utilisée : github.com/1123/johnson
0 votes
Exécutez DFS avec une modification supplémentaire pour l'algorithme: marquez chaque nœud visité. Si vous visitez un nœud qui est déjà visité, alors vous avez un cycle. Lorsque vous revenez en arrière depuis un chemin, démarquez les nœuds qui ont été visités.
4 votes
@HeshamYassin, si vous visitez un nœud que vous avez déjà visité, cela ne signifie pas nécessairement qu'il y a une boucle. Veuillez lire mon commentaire cs.stackexchange.com/questions/9676/….
0 votes
Votre première phrase contredit votre dernière phrase; veuillez corriger. Si vous voulez vraiment détecter tous les cycles (première phrase), la taille et le temps d'exécution de votre sortie dans le pire des cas seront exponentiels par rapport à la taille de votre entrée. Si vous voulez vraiment juste détecter le cas d'erreur de n'importe quel cycle (dernière phrase), vous pouvez le faire en un temps linéaire par rapport à la taille de l'entrée. Je recommande cette dernière option.
0 votes
@Pneauters non, il ne serait pas nécessairement mieux de détecter tous les cycles. Considérez le cas où il y en a un nombre exponentiel.
0 votes
Vous pouvez utiliser mon implémentation simple et efficace : stackoverflow.com/a/60196714/1763149