DFS est principalement utilisé pour trouver un cycle dans les graphiques et non BFS. Des raisons? Les deux peuvent trouver si un nœud a déjà été visité lors de la traversée de l'arbre/graphique.
La première recherche approfondie est plus efficace en mémoire que la première recherche étendue, car vous pouvez revenir en arrière plus tôt. Il est également plus facile à implémenter si vous utilisez la pile d'appels, mais cela dépend du chemin le plus long qui ne déborde pas de la pile.
De plus, si votre graphique est dirigé , vous devez non seulement vous rappeler si vous avez visité un nœud ou non, mais aussi comment vous y êtes arrivé. Sinon, vous pourriez penser que vous avez trouvé un cycle, mais en réalité tout ce que vous avez est deux chemins séparés A-> B mais cela ne signifie pas qu'il y a un chemin B-> A. Par exemple,
Si vous faites BFS à partir de 0
, il détectera que le cycle est présent mais en réalité il n'y a pas de cycle.
Avec une première recherche approfondie, vous pouvez marquer les nœuds comme visités au fur et à mesure que vous descendez et les décocher lorsque vous revenez en arrière. Voir les commentaires pour une amélioration des performances de cet algorithme.
Pour le meilleur algorithme pour détecter les cycles dans un graphique dirigé vous pouvez regarder algorithme de Tarjan .
Un BFS pourrait être raisonnable si le graphique n'est pas orienté (soyez mon invité à montrer un algorithme efficace utilisant BFS qui rapporterait les cycles dans un graphique dirigé!), Où chaque "cross edge" définit un cycle. Si le bord croisé est {v1, v2}
, et la racine (dans l'arborescence BFS) qui contient ces nœuds est r
, puis le cycle est r ~ v1 - v2 ~ r
(~
est un chemin, -
un seul Edge), qui peut être signalé presque aussi facilement que dans DFS.
La seule raison d'utiliser un BFS serait si vous savez que votre graphique (non orienté) va avoir de longs chemins et une petite couverture de chemin (en d'autres termes, profonds et étroits). Dans ce cas, BFS nécessiterait proportionnellement moins de mémoire pour sa file d'attente que la pile de DFS (tous deux toujours linéaires bien sûr).
Dans tous les autres cas, DFS est clairement le gagnant. Il fonctionne à la fois sur les graphiques orientés et non orientés, et il est trivial de signaler les cycles - il suffit de concaténer n'importe quel Edge arrière sur le chemin de l'ancêtre au descendant et vous obtenez le cycle. Dans l'ensemble, beaucoup mieux et pratique que BFS pour ce problème.
BFS ne fonctionnera pas pour un graphique dirigé dans la recherche de cycles. Considérez A-> B et A-> C-> B comme des chemins de A à B dans un graphique. BFS dira qu'après avoir parcouru l'un des chemins que B est visité. En continuant à parcourir le chemin suivant, il dira que le nœud marqué B a de nouveau été trouvé, donc un cycle est là. Il n'y a manifestement pas de cycle ici.
Si vous placez un cycle à un endroit aléatoire dans un arbre, DFS aura tendance à frapper le cycle lorsqu'il est couvert environ la moitié de l'arbre, et la moitié du temps qu'il aura déjà parcouru où va le cycle, et la moitié du temps il ne le sera pas ( et le trouvera en moyenne dans la moitié du reste de l'arbre), il évaluera donc en moyenne environ 0,5 * 0,5 + 0,5 * 0,75 = 0,625 de l'arbre.
Si vous placez un cycle à un endroit aléatoire dans un arbre, BFS aura tendance à frapper le cycle uniquement lorsqu'il aura évalué la couche de l'arbre à cette profondeur. Ainsi, vous finissez généralement par devoir évaluer les feuilles d'un arbre binaire d'équilibre, ce qui se traduit généralement par une évaluation plus importante de l'arbre. En particulier, 3/4 du temps au moins un des deux liens apparaît dans les feuilles de l'arbre, et dans ces cas, vous devez évaluer en moyenne 3/4 de l'arbre (s'il y a un lien) ou 7/8 de l'arbre (s'il y en a deux), vous êtes donc déjà à la recherche de 1/2 * 3/4 + 1/4 * 7/8 = (7 + 12)/32 = 21/32 = 0,656 ... de l'arbre sans même ajouter le coût de recherche d'un arbre avec un cycle ajouté loin des nœuds foliaires.
De plus, DFS est plus facile à implémenter que BFS. C'est donc celui à utiliser à moins que vous ne sachiez quelque chose sur vos cycles (par exemple, les cycles sont susceptibles d'être près de la racine à partir de laquelle vous recherchez, à quel moment BFS vous donne un avantage).
Pour prouver qu'un graphique est cyclique, il suffit de prouver qu'il a un cycle (Edge pointant vers lui directement ou indirectement).
Dans DFS, nous prenons un sommet à la fois et vérifions s'il a un cycle. Dès qu'un cycle est trouvé, nous pouvons omettre de vérifier d'autres sommets.
Dans BFS, nous devons garder une trace de nombreux bords de sommet simultanément et le plus souvent à la fin, vous découvrez s'il a un cycle. À mesure que la taille du graphique augmente, BFS nécessite plus d'espace, de calcul et de temps par rapport à DFS.
Cela dépend en quelque sorte si vous parlez d'implémentations récursives ou itératives.
Récursif-DFS visite deux fois chaque nœud. Iterative-BFS visite chaque nœud une fois.
Si vous souhaitez détecter un cycle, vous devez examiner les nœuds avant et après avoir ajouté leurs contiguïtés - à la fois lorsque vous "démarrez" sur un nœud et lorsque vous "terminez" avec un nœud.
Cela nécessite plus de travail dans Iterative-BFS, donc la plupart des gens choisissent Recursive-DFS.
Notez qu'une implémentation simple de Iterative-DFS avec, disons, std :: stack a le même problème que Iterative-BFS. Dans ce cas, vous devez placer des éléments factices dans la pile pour suivre lorsque vous "terminez" de travailler sur un nœud.
Voir cette réponse pour plus de détails sur la façon dont Iterative-DFS nécessite un travail supplémentaire pour déterminer quand vous "terminez" avec un nœud (répondu dans le contexte de TopoSort):
Tri topologique utilisant DFS sans récursivité
J'espère que cela explique pourquoi les gens préfèrent Recursive-DFS pour les problèmes où vous devez déterminer quand vous "terminez" le traitement d'un nœud.