¿Por qué DFS y no BFS para encontrar ciclos en gráficos?

85

Predominantemente se utiliza DFS para encontrar un ciclo en gráficos y no BFS. ¿Alguna razón? Ambos pueden encontrar si un nodo ya ha sido visitado mientras atraviesan el árbol / gráfico.

mala compañía
fuente
5
En gráficos dirigidos, solo se puede usar DFS para detectar un ciclo; pero en gráficos no dirigidos se pueden usar ambos.
Hengameh

Respuestas:

73

La primera búsqueda en profundidad es más eficiente en la memoria que la búsqueda en amplitud, ya que puede retroceder antes. También es más fácil de implementar si usa la pila de llamadas, pero esto se basa en que la ruta más larga no desborde la pila.

Además, si su gráfico está dirigido , no solo debe recordar si ha visitado un nodo o no, sino también cómo llegó allí. De lo contrario, podría pensar que ha encontrado un ciclo, pero en realidad todo lo que tiene son dos caminos separados A-> B, pero eso no significa que haya un camino B-> A. Por ejemplo,

Si hace BFS a partir de 0, detectará que el ciclo está presente pero en realidad no hay ciclo.

Con una primera búsqueda en profundidad, puede marcar los nodos como visitados a medida que desciende y desmarcarlos cuando retrocede. Consulte los comentarios para ver una mejora del rendimiento de este algoritmo.

Para obtener el mejor algoritmo para detectar ciclos en un gráfico dirigido , puede consultar el algoritmo de Tarjan .

Mark Byers
fuente
3
(Memoria eficiente porque puede retroceder antes y más fácil de implementar porque puede dejar que la pila se encargue de almacenar la lista abierta en lugar de tener que mantenerla explícitamente).
Amber
3
En mi opinión, solo es más fácil si puede confiar en la recursividad de cola.
Hank Gay
2
"desmarque cuando retroceda" - ¡bajo su propio riesgo! Esto puede conducir fácilmente a un comportamiento O (n ^ 2), específicamente, un DFS malinterpretaría los bordes transversales como bordes de "árbol" (los bordes de "árbol" también serían un nombre inapropiado ya que en realidad ya no formarían un árbol)
Dimitris Andreou
1
@Dimitris Andreo: puede usar tres estados visitados en lugar de dos para mejorar el rendimiento. Con los gráficos dirigidos hay una diferencia entre "He visto este nodo antes" y "Este nodo es parte de un bucle". Con gráficos no dirigidos son equivalentes.
Mark Byers
Exactamente, definitivamente necesita un tercer estado (para hacer que el algoritmo sea lineal), por lo que debería considerar revisar esa parte.
Dimitris Andreou
28
  1. DFS es más fácil de implementar
  2. Una vez que DFS encuentra un ciclo, la pila contendrá los nodos que forman el ciclo. No ocurre lo mismo con BFS, por lo que debe realizar un trabajo adicional si desea imprimir también el ciclo encontrado. Esto hace que DFS sea mucho más conveniente.
IVlad
fuente
10

Un BFS podría ser razonable si el gráfico no está dirigido (¡sea mi invitado a mostrar un algoritmo eficiente usando BFS que reportaría los ciclos en un gráfico dirigido!), Donde cada "borde transversal" define un ciclo. Si el borde transversal es {v1, v2}, y la raíz (en el árbol BFS) que contiene esos nodos es r, entonces el ciclo es r ~ v1 - v2 ~ r( ~es una ruta, -un solo borde), que se puede informar casi tan fácilmente como en DFS.

La única razón para usar un BFS sería si sabe que su gráfico (no dirigido) tendrá rutas largas y una cobertura de ruta pequeña (en otras palabras, profunda y estrecha). En ese caso, BFS requeriría proporcionalmente menos memoria para su cola que la pila de DFS (ambos siguen siendo lineales, por supuesto).

En todos los demás casos, DFS es claramente el ganador. Funciona tanto en gráficos dirigidos como no dirigidos, y es trivial informar los ciclos; simplemente concate cualquier borde posterior a la ruta del ancestro al descendiente, y obtendrás el ciclo. Considerándolo todo, mucho mejor y más práctico que BFS para este problema.

Dimitris Andreou
fuente
4

BFS no funcionará para un gráfico dirigido en la búsqueda de ciclos. Considere A-> B y A-> C-> B como caminos de A a B en un gráfico. BFS dirá que después de recorrer uno de los caminos que se visita B. Al continuar recorriendo el siguiente camino, dirá que el nodo marcado B se ha encontrado nuevamente, por lo tanto, hay un ciclo. Claramente, no hay ciclo aquí.

Aditya Raman
fuente
¿Puede explicar cómo DFS identificará claramente que el ciclo no existe en su ejemplo? Estoy de acuerdo en que el ciclo no existe en el ejemplo proporcionado. Pero si vamos de A-> B y luego A-> C-> B encontraremos que B ya fue visitado y su padre es A, no C ... y leí que DFS detectará el ciclo comparando el padre del elemento ya visitado con el nodo actual desde qué dirección estamos verificando en este momento. ¿Me estoy equivocando con DFS o ¿Qué?
smasher
Todo lo que ha demostrado aquí es que esta implementación en particular no funciona, no que sea imposible con BFS. De hecho, es posible, aunque requiere más trabajo y espacio.
Poda el
@Prune: Todos los hilos (creo) aquí están tratando de probar que bfs no funcionará para detectar ciclos. Si sabe cómo contrarrestar la prueba, debe presentar la prueba. Simplemente decir que los esfuerzos son mayores no será suficiente
Aditya Raman
Dado que el algoritmo se proporciona en las publicaciones vinculadas, no creo que sea apropiado repetir el esquema aquí.
Poda el
No pude encontrar ninguna publicación vinculada, por lo tanto, solicité lo mismo. Estoy de acuerdo con su punto sobre la capacidad de bfs y acabo de pensar en la implementación. Gracias por el consejo :)
Aditya Raman
3

No sé por qué apareció una pregunta tan antigua en mi feed, pero todas las respuestas anteriores son malas, así que ...

DFS se usa para encontrar ciclos en gráficos dirigidos, porque funciona .

En un DFS, cada vértice se "visita", donde visitar un vértice significa:

  1. Se inicia el vértice
  2. Se visita el subgrafo accesible desde ese vértice. Esto incluye trazar todos los bordes no trazados que son accesibles desde ese vértice y visitar todos los vértices no visitados accesibles.

  3. El vértice está terminado.

La característica crítica es que todos los bordes accesibles desde un vértice se trazan antes de que el vértice esté terminado. Esta es una característica de DFS, pero no BFS. De hecho, esta es la definición de DFS.

Debido a esta característica, sabemos que cuando se inicia el primer vértice de un ciclo:

  1. No se ha trazado ninguno de los bordes del ciclo. Lo sabemos porque solo se puede llegar a ellos desde otro vértice del ciclo, y estamos hablando del primer vértice que se inicia.
  2. Todas las aristas sin trazar a las que se pueda llegar desde ese vértice se trazarán antes de que finalice, y eso incluye todas las aristas del ciclo, porque todavía no se ha trazado ninguna. Por lo tanto, si hay un ciclo, encontraremos una arista de regreso al primer vértice después de que se inicia, pero antes de que termine; y
  3. Dado que todos los bordes que se trazan son accesibles desde cada vértice iniciado pero no terminado, encontrar un borde en dicho vértice siempre indica un ciclo.

Entonces, si hay un ciclo, entonces tenemos la garantía de encontrar un borde a un vértice iniciado pero no terminado (2), y si encontramos dicho borde, entonces tenemos la garantía de que hay un ciclo (3).

Es por eso que DFS se usa para encontrar ciclos en gráficos dirigidos.

BFS no ofrece tales garantías, por lo que simplemente no funciona. (a pesar de los algoritmos de búsqueda de ciclos perfectamente buenos que incluyen BFS o similar como subprocedimiento)

Un gráfico no dirigido, por otro lado, tiene un ciclo siempre que hay dos caminos entre cualquier par de vértices, es decir, cuando no es un árbol. Esto es fácil de detectar durante BFS o DFS: los bordes trazados a nuevos vértices forman un árbol, y cualquier otro borde indica un ciclo.

Matt Timmermans
fuente
De hecho, esta es la respuesta más relacionada (quizás la única) aquí, y se explica por las razones reales.
plasmacel
2

Si coloca un ciclo en un lugar aleatorio de un árbol, DFS tenderá a golpear el ciclo cuando esté cubierto aproximadamente la mitad del árbol, y la mitad del tiempo ya habrá atravesado el lugar donde va el ciclo, y la mitad de las veces no lo hará ( y lo encontrará en promedio en la mitad del resto del árbol), por lo que evaluará en promedio alrededor de 0.5 * 0.5 + 0.5 * 0.75 = 0.625 del árbol.

Si coloca un ciclo en un lugar aleatorio de un árbol, BFS tenderá a golpear el ciclo solo cuando se evalúa la capa del árbol a esa profundidad. Por lo tanto, generalmente termina teniendo que evaluar las hojas de un árbol binario de equilibrio, lo que generalmente da como resultado evaluar más del árbol. En particular, 3/4 de las veces al menos uno de los dos enlaces aparece en las hojas del árbol, y en esos casos hay que evaluar en promedio 3/4 del árbol (si hay un enlace) o 7 / 8 del árbol (si hay dos), por lo que ya tiene la expectativa de buscar 1/2 * 3/4 ​​+ 1/4 * 7/8 = (7 + 12) / 32 = 21/32 = 0.656 ... del árbol sin siquiera agregar el costo de buscar un árbol con un ciclo agregado fuera de los nodos de las hojas.

Además, DFS es más fácil de implementar que BFS. Por lo tanto, es el que debe usar a menos que sepa algo sobre sus ciclos (por ejemplo, es probable que los ciclos estén cerca de la raíz desde la que busca, momento en el que BFS le da una ventaja).

Rex Kerr
fuente
Hay muchos números mágicos. No estoy de acuerdo con los argumentos "DFS es más rápido". Depende completamente de la entrada, y ninguna entrada es más común que otra en este caso.
IVlad
@Vlad - Los números no son mágicos. Son medias, se expresan como tales y son casi triviales de calcular dadas las suposiciones que expuse. Si aproximar por la media es una mala aproximación, sería una crítica válida. (Y dije explícitamente que si pudiera hacer suposiciones sobre la estructura, la respuesta podría cambiar).
Rex Kerr
los números son mágicos porque no significan nada. Tomó un caso en el que DFS funciona mejor y extrapoló esos resultados al caso general. Sus afirmaciones son infundadas: "DFS tenderá a golpear el ciclo cuando esté cubierto aproximadamente la mitad del árbol": demuéstrelo. Sin mencionar que no se puede hablar de ciclos en un árbol. Un árbol no tiene un ciclo por definición. Simplemente no veo cuál es tu punto. DFS irá en un sentido hasta que llegue a un callejón sin salida, por lo que no tiene forma de saber cuánto del GRÁFICO (NO árbol) explorará en promedio. Acaba de elegir un caso aleatorio que no prueba nada.
IVlad
@Vlad: todos los gráficos no cíclicos totalmente conectados y no dirigidos son árboles (no enraizados y no dirigidos). Quise decir "un gráfico que sería un árbol salvo por un enlace falso". Tal vez esta no sea la aplicación principal del algoritmo; tal vez desee encontrar ciclos en algún gráfico enredado que tenga muchos enlaces que lo conviertan en un árbol. Pero si tiene forma de árbol, promediado sobre todos los gráficos, es igualmente probable que cualquier nodo sea la fuente de dicho enlace espurio, lo que hace que la cobertura del árbol esperada sea del 50% cuando se activa el enlace. Entonces acepto que el ejemplo puede no haber sido representativo. Pero las matemáticas deberían ser triviales.
Rex Kerr
1

Para demostrar que un gráfico es cíclico, solo necesita demostrar que tiene un ciclo (el borde apunta hacia sí mismo, ya sea directa o indirectamente).

En DFS tomamos un vértice a la vez y comprobamos si tiene ciclo. Tan pronto como se encuentra un ciclo, podemos omitir la comprobación de otros vértices.

En BFS, necesitamos realizar un seguimiento de muchos bordes de vértices simultáneamente y la mayoría de las veces al final se descubre si tiene un ciclo. A medida que aumenta el tamaño del gráfico, BFS requiere más espacio, cálculo y tiempo en comparación con DFS.

Atacante
fuente
0

Depende en cierto modo de si se trata de implementaciones recursivas o iterativas.

Recursive-DFS visita cada nodo dos veces. Iterative-BFS visita cada nodo una vez.

Si desea detectar un ciclo, debe investigar los nodos antes y después de agregar sus adyacencias, tanto cuando "comienza" en un nodo como cuando "termina" con un nodo.

Esto requiere más trabajo en Iterative-BFS, por lo que la mayoría de la gente elige Recursive-DFS.

Tenga en cuenta que una implementación simple de Iterative-DFS con, digamos, std :: stack tiene el mismo problema que Iterative-BFS. En ese caso, debe colocar elementos ficticios en la pila para realizar un seguimiento cuando "termine" de trabajar en un nodo.

Consulte esta respuesta para obtener más detalles sobre cómo Iterative-DFS requiere trabajo adicional para determinar cuándo "termina" con un nodo (respondido en el contexto de TopoSort):

Ordenación topológica usando DFS sin recursividad

Con suerte, eso explica por qué la gente prefiere Recursive-DFS para problemas en los que necesita determinar cuándo "termina" de procesar un nodo.

Comunidad
fuente
Esto es totalmente incorrecto, ya que no importa si usa la recursividad o si elimina la recursividad por iteración. Puede implementar un DFS iterativo que visita cada nodo dos veces, al igual que puede implementar una variante recursiva que visita cada nodo solo una vez.
plasmacel
0

Deberá utilizarlo BFScuando desee encontrar el ciclo más corto que contenga un nodo determinado en un gráfico dirigido.

P.ej:ingrese la descripción de la imagen aquí

Si el nodo dado es 2, hay tres ciclos en los que forma parte de - [2,3,4], [2,3,4,5,6,7,8,9]& [2,5,6,7,8,9]. El más corto es[2,3,4]

Para implementar esto usando BFS, debe mantener explícitamente el historial de los nodos visitados utilizando estructuras de datos adecuadas.

Pero para todos los demás propósitos (por ejemplo: encontrar algún camino cíclico o comprobar si existe un ciclo o no), DFSes la opción clara por las razones mencionadas por otros.

Sameer
fuente