web-dev-qa-db-fra.com

Meilleur algorithme pour détecter les cycles dans un graphe dirigé

Quel est l'algorithme le plus efficace pour détecter tous les cycles dans un graphe dirigé?

J'ai un graphe orienté représentant un programme de travaux à exécuter, un travail étant un nœud et une dépendance étant un Edge. J'ai besoin de détecter le cas d'erreur d'un cycle dans ce graphique menant à des dépendances cycliques.

363
Peauters

algorithme des composants fortement connectés de Tarjan a O(|E| + |V|) complexité temporelle.

Pour d'autres algorithmes, voir Composants fortement connectés sur Wikipedia.

183
aku

Étant donné qu’il s’agit d’un calendrier de travaux, je suppose qu’à un moment donné, vous allez les trier dans un ordre d’exécution proposé.

Si tel est le cas, alors une implémentation topologique peut dans tous les cas détecter des cycles. UNIX tsort certainement. Je pense qu'il est donc probablement plus efficace de détecter les cycles en même temps que le transfert, plutôt que dans une étape séparée.

La question pourrait donc devenir: "Comment puis-je gérer le plus efficacement possible", plutôt que "Comment détecter le plus efficacement les boucles". Pour lequel la réponse est probablement "utiliser une bibliothèque", mais à défaut l'article suivant de Wikipedia:

http://en.wikipedia.org/wiki/Topological_sorting

a le pseudo-code pour un algorithme et une brève description d'un autre de Tarjan. Les deux ont O(|V| + |E|) complexité temporelle.

68
Steve Jessop

Commencez avec un DFS: un cycle existe si et seulement si un back-Edge est découvert lors du DFS. Ceci est prouvé à la suite du théorème du chemin blanc.

29
Ajay Garg

La manière la plus simple de le faire est de faire une profondeur d'abord travers (DFT) du graphe.

Si le graphique a n sommets, il s'agit d'un algorithme de complexité temporelle O(n). Puisque vous devrez éventuellement faire une DFT à partir de chaque sommet, la complexité totale devient O(n^2).

Vous devez conserver un pile contenant tous les sommets de la première traversée en profondeur actuelle, son premier élément étant le nœud racine. Si vous rencontrez un élément qui est déjà dans la pile pendant la TFD, vous avez un cycle.

27
phys wizard

À mon avis, l'algorithme le plus compréhensible pour détecter le cycle dans un graphe dirigé est l'algorithme de coloration de graphe.

Fondamentalement, l’algorithme de coloration du graphique parcourt le graphique d’une manière DFS (recherche approfondie en premier, ce qui signifie qu’il explore complètement un chemin avant d’en explorer un autre). Lorsqu'il trouve un bord arrière, il marque le graphique comme contenant une boucle.

Pour une explication détaillée de l’algorithme de coloration des graphes, veuillez lire cet article: http://www.geeksforgeeks.org/detect-cycle-direct-graph-using-colors/

De plus, je fournis une implémentation de la coloration des graphes en JavaScript https://github.com/dexcodeinc/graph_algorithm.js/blob/master/graph_algorithm.js

24
Armin Primadi

Si vous ne pouvez pas ajouter une propriété "visité" aux nœuds, utilisez un ensemble (ou une carte) et ajoutez simplement tous les nœuds visités à l'ensemble, à moins qu'ils ne soient déjà dans l'ensemble. Utilisez une clé unique ou l'adresse des objets comme "clé".

Cela vous donne également des informations sur le nœud "racine" de la dépendance cyclique, ce qui sera utile lorsqu'un utilisateur doit résoudre le problème.

Une autre solution consiste à essayer de trouver la prochaine dépendance à exécuter. Pour cela, vous devez avoir une pile où vous pouvez vous rappeler où vous êtes maintenant et ce que vous devez faire ensuite. Vérifiez si une dépendance est déjà sur cette pile avant de l'exécuter. Si c'est le cas, vous avez trouvé un cycle.

Bien que cela puisse sembler avoir une complexité de O (N * M), vous devez vous rappeler que la profondeur de la pile est très limitée (N est donc petit) et que M devient plus petit avec chaque dépendance que vous pouvez cocher comme "exécuté" plus vous pouvez arrêter la recherche lorsque vous avez trouvé une feuille (de sorte que vous ne devez jamais vérifier chaque nœud -> M sera également petit).

Dans MetaMake, j'ai créé le graphique sous forme de liste de listes, puis supprimé tous les nœuds au fur et à mesure de leur exécution, ce qui a naturellement réduit le volume de recherche. En réalité, je n'ai jamais eu à effectuer de contrôle indépendant, tout s'est passé automatiquement lors de l'exécution normale.

Si vous avez besoin d'un mode "test uniquement", ajoutez simplement un indicateur "exécution à sec" qui désactive l'exécution des travaux réels.

7
Aaron Digulla

Il n’existe aucun algorithme permettant de trouver tous les cycles d’un graphe orienté en temps polynomial. Supposons que le graphe dirigé ait n nœuds et que chaque paire de nœuds ait des connexions les unes aux autres, ce qui signifie que vous avez un graphe complet. Ainsi, tout sous-ensemble non vide de ces n nœuds indique un cycle et il existe 2 ^ n-1 nombre de ces sous-ensembles. Donc, aucun algorithme de temps polynomial n'existe. Supposons donc que vous ayez un algorithme efficace (non stupide) qui puisse vous indiquer le nombre de cycles dirigés dans un graphique. Vous pouvez d’abord rechercher les composants connectés forts, puis appliquer votre algorithme sur ces composants connectés. Puisque les cycles n'existent que dans les composants et non entre eux.

5
Yuwen

D'après le lemme 22.11 de Cormen et al., Introduction aux algorithmes (CLRS):

Un graphe orienté G est acyclique si et seulement si une recherche en profondeur de G en premier ne produit pas de bords arrières.

Cela a été mentionné dans plusieurs réponses. Ici, je vais également fournir un exemple de code basé sur le chapitre 22 de CLRS. L'exemple de graphique est illustré ci-dessous.

enter image description here

Le pseudo-code du CLRS pour la recherche en profondeur d'abord est le suivant:

enter image description here

Dans l'exemple de la figure 22.4 de CLRS, le graphique est constitué de deux arbres DFS: l'un composé de noeuds u , v , x et y , et l'autre des noeuds w et z . Chaque arbre contient un bord arrière: un de x à v et un autre de z à z (une boucle automatique).

La réalisation clé est qu’un bord arrière est rencontré lorsque, dans la fonction DFS-VISIT, lors d’une itération sur les voisins v de u, un nœud est rencontré avec la couleur GRAY .

Le code Python suivant est une adaptation du pseudocode du CLRS avec l'ajout d'une clause if 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])
        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]:
        # Detect cycles
        if v in discovered:
            print(f"Cycle detected: found a back Edge from {u} to {v}.")

        # Recurse into DFS tree
        if v not in discovered and v not in finished:
            dfs_visit(G, v, discovered, finished)

    discovered.remove(u)
    finished.add(u)

    return discovered, finished


if __== "__main__":
    G = Graph([
        ('u', 'v'),
        ('u', 'x'),
        ('v', 'y'),
        ('w', 'y'),
        ('w', 'z'),
        ('x', 'v'),
        ('y', 'x'),
        ('z', 'z')])

    dfs(G)

Notez que dans cet exemple, le time du pseudocode de CLRS n'est pas capturé car nous ne sommes intéressés que par la détection de cycles. Il existe également un code standard pour la construction de la représentation de la liste de contiguïté d'un graphique à partir d'une liste d'arêtes.

Lorsque ce script est exécuté, il affiche le résultat suivant:

Cycle detected: found a back Edge from x to v.
Cycle detected: found a back Edge from z to z.

Ce sont exactement les bords arrière dans l'exemple de la Figure 22.4 du CLRS.

4
Kurt Peek

J'avais implémenté ce problème dans sml (programmation impérative). Voici le contour. Trouvez tous les nœuds qui ont un indegree ou outdegree 0. De tels nœuds ne peuvent pas faire partie d'un cycle (donc, supprimez-les). Ensuite, supprimez tous les bords entrants ou sortants de ces nœuds. Appliquez récursivement ce processus au graphique obtenu. Si, à la fin, il ne reste aucun nœud ou bord, le graphe ne comporte aucun cycle, sinon il en a.

3
Rpant

Si DFS trouve un bord qui pointe vers un sommet déjà visité, vous avez un cycle à cet endroit.

3
mafonya

Pour ce faire, je fais un tri topologique, en comptant le nombre de sommets visités. Si ce nombre est inférieur au nombre total de sommets dans le DAG, vous avez un cycle.

2
Steve

https://mathoverflow.net/questions/16393/finding-a-cycle-of-fixed-length J'aime cette solution, la meilleure, spécialement pour les 4 longueurs :)

De plus, l'assistant physique dit que vous devez faire O (V ^ 2). Je crois que nous avons seulement besoin de O (V)/O (V + E). Si le graphique est connecté, DFS visitera tous les nœuds. Si le graphe contient des sous-graphes connectés, chaque fois que nous exécutons un DFS sur un sommet de ce sous-graphe, nous trouverons les sommets connectés et ne les prendrons pas en compte lors de la prochaine exécution du DFS. Par conséquent, la possibilité de courir pour chaque sommet est incorrecte.

1
amitgoswami

Comme vous l'avez dit, vous avez un ensemble de tâches qui doivent être exécutées dans un certain ordre. Topological sort vous a donné l'ordre requis pour la planification des travaux (ou pour des problèmes de dépendance s'il s'agit d'un direct acyclic graph). Exécutez dfs et maintenez une liste, puis commencez à ajouter un nœud au début de la liste, et si vous avez rencontré un nœud déjà visité. Ensuite, vous avez trouvé un cycle dans un graphe donné.

0
Bhagwati Malav