web-dev-qa-db-fra.com

Comment vérifier si un graphe dirigé est acyclique?

Comment vérifier si un graphe dirigé est acyclique? Et comment s'appelle l'algorithme? J'apprécierais une référence.

77
nes1983

J'essaierais de trier le graphique topologiquement , et si vous ne pouvez pas, alors il a des cycles.

88
FryGuy

Effectuer une simple recherche de profondeur d'abord non est suffisant pour trouver un cycle. Il est possible de visiter un nœud plusieurs fois dans un DFS sans cycle existant. En fonction de votre point de départ, il est également possible que vous ne visitiez pas tout le graphique.

Vous pouvez vérifier les cycles dans une composante connectée d'un graphique comme suit. Recherchez un nœud qui n'a que des bords sortants. S'il n'y a pas de tel noeud, alors il y a un cycle. Démarrer un DFS sur ce noeud. Lorsque vous traversez chaque Edge, vérifiez si celui-ci pointe vers un nœud déjà présent sur votre pile. Cela indique l'existence d'un cycle. Si vous ne trouvez pas un tel Edge, il n'y a pas de cycles dans ce composant connecté. 

Comme le souligne Rutger Prins, si votre graphique n'est pas connecté, vous devez répéter la recherche sur chaque composant connecté.

À titre de référence, L'algorithme de la composante fortement connectée de Tarjan est étroitement lié. Cela vous aidera également à trouver les cycles et à ne pas simplement signaler leur existence.

32
Jay Conrod

Le lemme 22.11 du livre Introduction to Algorithms (Deuxième édition) dit que:

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

12

Solution1 algorithme de Kahn pour vérifier le cycle . Idée principale: maintenez une file d'attente où le nœud avec zéro degré sera ajouté à la file d'attente. Retirez ensuite le noeud un à un jusqu'à ce que la file d'attente soit vide. Vérifiez s'il existe des noeuds dans les noeuds.

Solution2 : Algorithme de Tarjan pour vérifier le composant connecté fort.

Solution3 :DFS. Utilisez un tableau entier pour marquer l’état actuel du nœud: I.e. 0 - signifie que ce nœud n'a pas été visité auparavant . -1 - signifie que ce nœud a été visité et que ses nœuds enfants sont en cours de visite . 1 - signifie que ce nœud a été visité et que c'est fait ..__ Donc, si le statut d'un nœud est -1 en DFS, cela signifie qu'il doit exister un cycle. 

7
Chris Su

Il ne devrait y avoir aucun bord arrière lors de la réalisation de DFS. Gardez une trace des nœuds déjà visités lors de la réalisation de DFS. Si vous rencontrez un bord entre le nœud actuel et le nœud existant, le graphique a un cycle.

1
santhosh

voici un code Swift pour savoir si un graphique a des cycles:

func isCyclic(G : Dictionary<Int,Array<Int>>,root : Int , var visited : Array<Bool>,var breadCrumb : Array<Bool>)-> Bool
{

    if(breadCrumb[root] == true)
    {
        return true;
    }

    if(visited[root] == true)
    {
        return false;
    }

    visited[root] = true;

    breadCrumb[root] = true;

    if(G[root] != nil)
    {
        for child : Int in G[root]!
        {
            if(isCyclic(G,root : child,visited : visited,breadCrumb : breadCrumb))
            {
                return true;
            }
        }
    }

    breadCrumb[root] = false;
    return false;
}


let G = [0:[1,2,3],1:[4,5,6],2:[3,7,6],3:[5,7,8],5:[2]];

var visited = [false,false,false,false,false,false,false,false,false];
var breadCrumb = [false,false,false,false,false,false,false,false,false];




var isthereCycles = isCyclic(G,root : 0, visited : visited, breadCrumb : breadCrumb)

L'idée est la suivante: un algorithme dfs normal avec un tableau pour garder la trace des nœuds visités, et un tableau supplémentaire servant de marqueur pour les nœuds conduisant au nœud actuel, de sorte que, chaque fois que nous exécutons un dfs pour un nœud nous définissons son élément correspondant dans le tableau de marqueurs comme vrai, de sorte que, chaque fois qu'un nœud déjà visité est rencontré, nous vérifions si l'élément correspondant dans le tableau de marqueurs est vrai, si c'est vrai, c'est l'un des nœuds qui laissent à lui-même (d'où un cycle), et l'astuce est que chaque fois qu'un dfs d'un nœud retourne, nous redéfinissons le marqueur correspondant sur false, de sorte que, si nous le revoyons depuis une autre route, nous ne soyons pas dupes.

1
m.eldehairy

Je sais que c’est un sujet ancien, mais pour les futurs chercheurs, voici une implémentation C # que j’ai créée (je ne prétends pas que c’est le plus efficace!). Ceci est conçu pour utiliser un entier simple pour identifier chaque nœud. Vous pouvez décorer comme bon vous semble si votre objet de nœud est correctement haché et égal.

Pour les graphiques très profonds, cela peut entraîner une surcharge importante, car cela crée un hachage en profondeur sur chaque nœud (ils sont détruits sur toute leur largeur).

Vous entrez le nœud dans lequel vous souhaitez effectuer la recherche et le chemin d'accès à ce nœud.

  • Pour un graphique avec un seul noeud racine, vous envoyez ce noeud et un hashset vide
  • Pour un graphique comportant plusieurs nœuds racine, vous le placez dans un foreach sur ces nœuds et transmettez un nouveau hachage vide à chaque itération.
  • Lorsque vous recherchez des cycles sous un nœud donné, transmettez-le simplement avec un hashset vide

    private bool FindCycle(int node, HashSet<int> path)
    {
    
        if (path.Contains(node))
            return true;
    
        var extendedPath = new HashSet<int>(path) {node};
    
        foreach (var child in GetChildren(node))
        {
            if (FindCycle(child, extendedPath))
                return true;
        }
    
        return false;
    }
    
1
Matthew

La solution donnée par ShuggyCoUk est incomplète car il pourrait ne pas vérifier tous les nœuds.


def isDAG(nodes V):
    while there is an unvisited node v in V:
        bool cycleFound = dfs(v)
        if cyclefound:
            return false
    return true

Cela a une complexité temporelle O (n + m) ou O (n ^ 2)

1
Rutger Prins

Je viens d'avoir cette question dans une interview de Google.

Tri topologique

Vous pouvez essayer de trier topologiquement, qui est O (V + E), où V représente le nombre de sommets et E le nombre d'arêtes. Un graphe orienté est acyclique si et seulement si cela peut être fait.

Enlèvement récursif des feuilles

Supprimez récursivement les nœuds feuilles jusqu'à ce qu'il n'en reste plus, et s'il reste plus d'un nœud, vous avez un cycle. Sauf erreur, c'est O (V ^ 2 + VE).

DFS-style ~ O (n + m)

Cependant, un algorithme DFS-esque efficace, le pire des cas O (V + E), est:

function isAcyclic (root) {
    const previous = new Set();

    function DFS (node) {
        previous.add(node);

        let isAcyclic = true;
        for (let child of children) {
            if (previous.has(node) || DFS(child)) {
                isAcyclic = false;
                break;
            }
        }

        previous.delete(node);

        return isAcyclic;
    }

    return DFS(root);
}
0
Tom Golden

Voici mon implémentation Ruby de l’algorithme peel off leaf node .

def detect_cycles(initial_graph, number_of_iterations=-1)
    # If we keep peeling off leaf nodes, one of two things will happen
    # A) We will eventually peel off all nodes: The graph is acyclic.
    # B) We will get to a point where there is no leaf, yet the graph is not empty: The graph is cyclic.
    graph = initial_graph
    iteration = 0
    loop do
        iteration += 1
        if number_of_iterations > 0 && iteration > number_of_iterations
            raise "prevented infinite loop"
        end

        if graph.nodes.empty?
            #puts "the graph is without cycles"
            return false
        end

        leaf_nodes = graph.nodes.select { |node| node.leaving_edges.empty? }

        if leaf_nodes.empty?
            #puts "the graph contain cycles"
            return true
        end

        nodes2 = graph.nodes.reject { |node| leaf_nodes.member?(node) }
        edges2 = graph.edges.reject { |Edge| leaf_nodes.member?(Edge.destination) }
        graph = Graph.new(nodes2, edges2)
    end
    raise "should not happen"
end
0
neoneye