Perché DFS e non BFS per trovare il ciclo nei grafici


86

Prevalentemente DFS viene utilizzato per trovare un ciclo nei grafici e non BFS. Qualche motivo? Entrambi possono trovare se un nodo è già stato visitato durante l'attraversamento dell'albero / grafico.


5
Nei grafici orientati, solo DFS può essere utilizzato per rilevare un ciclo; ma nei grafici non indirizzati possono essere usati entrambi.
Hengameh

Risposte:


74

La prima ricerca in profondità è più efficiente in termini di memoria rispetto alla prima ricerca in ampiezza poiché puoi tornare indietro prima. È anche più facile da implementare se si utilizza lo stack di chiamate, ma si basa sul percorso più lungo che non supera lo stack.

Inoltre, se il tuo grafico è diretto, non devi solo ricordare se hai visitato o meno un nodo, ma anche come ci sei arrivato. Altrimenti potresti pensare di aver trovato un ciclo ma in realtà tutto ciò che hai sono due percorsi separati A-> B ma questo non significa che ci sia un percorso B-> A. Per esempio,

Se esegui BFS a partire da 0, rileverà come il ciclo è presente ma in realtà non c'è ciclo.

Con una prima ricerca in profondità puoi contrassegnare i nodi come visitati mentre scendi e deselezionarli mentre torni indietro. Vedere i commenti per un miglioramento delle prestazioni su questo algoritmo.

Per il miglior algoritmo per rilevare i cicli in un grafico diretto, potresti guardare l'algoritmo di Tarjan .


3
(Memoria efficiente perché puoi tornare indietro prima e più facile da implementare perché puoi lasciare che lo stack si occupi di archiviare l'elenco aperto invece di doverlo mantenere esplicitamente.)
Amber

3
IMO, è più facile solo se puoi fare affidamento sulla ricorsione della coda.
Hank Gay

2
"deselezionali mentre torni indietro" - a tuo rischio e pericolo! Questo può facilmente portare a un comportamento O (n ^ 2), in particolare un tale DFS fraintenderebbe i bordi incrociati come bordi "albero" (anche i bordi "albero" sarebbero un termine improprio poiché non formerebbero più un albero)
Dimitris Andreou

1
@Dimitris Andreo: puoi usare tre stati visitati invece di due per migliorare le prestazioni. Con i grafici diretti c'è una differenza tra "Ho già visto questo nodo" e "Questo nodo fa parte di un ciclo". Con i grafici non orientati sono equivalenti.
Mark Byers

Esatto, hai sicuramente bisogno di un terzo stato (per rendere lineare l'algoritmo), quindi dovresti considerare di rivedere quella parte.
Dimitris Andreou

28
  1. DFS è più facile da implementare
  2. Quando DFS trova un ciclo, lo stack conterrà i nodi che formano il ciclo. Lo stesso non è vero per BFS, quindi è necessario fare del lavoro extra se si desidera stampare anche il ciclo trovato. Ciò rende DFS molto più conveniente.

11

Un BFS potrebbe essere ragionevole se il grafico non è orientato (sii mio ospite nel mostrare un algoritmo efficiente utilizzando BFS che riporterebbe i cicli in un grafico orientato!), Dove ogni "margine trasversale" definisce un ciclo. Se il margine trasversale è {v1, v2}e la radice (nell'albero BFS) che contiene quei nodi è r, allora il ciclo è r ~ v1 - v2 ~ r( ~è un percorso,- un singolo bordo), che può essere segnalato quasi facilmente come in DFS.

L'unico motivo per usare un BFS sarebbe sapere che il tuo grafico (non orientato) avrà percorsi lunghi e piccoli percorsi di copertura (in altre parole, profondo e stretto). In tal caso, BFS richiederebbe una quantità di memoria proporzionalmente inferiore per la sua coda rispetto allo stack di DFS (entrambi ancora lineari ovviamente).

In tutti gli altri casi, DFS è chiaramente il vincitore. Funziona su grafici sia diretti che non orientati ed è banale riportare i cicli: basta concatenare qualsiasi bordo posteriore al percorso dall'antenato al discendente e si ottiene il ciclo. Tutto sommato, molto meglio e pratico di BFS per questo problema.


4

BFS non funzionerà per un grafo diretto nella ricerca dei cicli. Considera A-> B e A-> C-> B come percorsi da A a B in un grafico. BFS dirà che dopo aver percorso uno dei sentieri che B viene visitato. Continuando a percorrere il percorso successivo, dirà che il nodo B contrassegnato è stato nuovamente trovato, quindi c'è un ciclo. Chiaramente non c'è ciclo qui.


Puoi spiegare come DFS identificherà chiaramente che il ciclo non esiste nel tuo esempio Sono d'accordo sul fatto che il ciclo non esiste nell'esempio fornito Ma se andiamo da A-> B e poi A-> C-> B troveremo che B è già stato visitato e il suo genitore è A non C .. e ho letto che DFS rileverà il ciclo confrontando il genitore dell'elemento già visitato con il nodo corrente dalla direzione che stiamo controllando in questo momento. Sto sbagliando DFS o che cosa?
smasher

Tutto ciò che hai mostrato qui è che questa particolare implementazione non funziona, non che è impossibile con BFS. In effetti, è possibile, anche se richiede più lavoro e spazio.
Potatura

@ Prune: Tutti i thread (penso) qui stanno cercando di dimostrare che bfs non funzionerà per rilevare i cicli. Se sai come contrastare la prova dovresti dare la prova. Dire semplicemente che gli sforzi sono maggiori non sarà sufficiente
Aditya Raman

Poiché l'algoritmo è fornito nei post collegati, non ritengo opportuno ripetere lo schema qui.
Potatura

Non sono riuscito a trovare alcun messaggio collegato, quindi ho chiesto lo stesso. Sono d'accordo con il tuo punto di vista sulla capacità di bfs e ho appena pensato all'implementazione. Grazie per la punta :)
Aditya Raman

4

Non so perché una domanda così vecchia sia apparsa nel mio feed, ma tutte le risposte precedenti sono negative, quindi ...

DFS viene utilizzato per trovare i cicli nei grafici diretti, perché funziona .

In un DFS, ogni vertice è "visitato", dove visitare un vertice significa:

  1. Il vertice viene avviato
  2. Viene visitato il sottografo raggiungibile da quel vertice. Ciò include il tracciamento di tutti i bordi non tracciati che sono raggiungibili da quel vertice e la visita di tutti i vertici raggiungibili non visitati.

  3. Il vertice è finito.

La caratteristica fondamentale è che tutti i bordi raggiungibili da un vertice vengono tracciati prima che il vertice sia finito. Questa è una funzionalità di DFS, ma non di BFS. In effetti questa è la definizione di DFS.

A causa di questa caratteristica, sappiamo che quando viene avviato il primo vertice di un ciclo:

  1. Nessuno dei bordi del ciclo è stato tracciato. Lo sappiamo, perché puoi raggiungerli solo da un altro vertice del ciclo e stiamo parlando del primo vertice da avviare.
  2. Tutti i bordi non tracciati raggiungibili da quel vertice verranno tracciati prima che sia finito, e questo include tutti gli spigoli nel ciclo, perché nessuno di essi è stato ancora tracciato. Pertanto, se c'è un ciclo, troveremo un bordo che torna al primo vertice dopo che è stato avviato, ma prima che sia finito; e
  3. Poiché tutti i bordi tracciati sono raggiungibili da ogni vertice iniziato ma non finito, trovare un bordo a tale vertice indica sempre un ciclo.

Quindi, se c'è un ciclo, allora abbiamo la certezza di trovare un bordo per un vertice iniziato ma non finito (2), e se troviamo un tale arco, allora ci è garantito che c'è un ciclo (3).

Ecco perché DFS viene utilizzato per trovare i cicli nei grafici diretti.

BFS non fornisce tali garanzie, quindi semplicemente non funziona. (nonostante algoritmi di ricerca del ciclo perfettamente validi che includono BFS o simili come sottoprocedure)

Un grafo non orientato, d'altra parte, ha un ciclo ogni volta che ci sono due percorsi tra una qualsiasi coppia di vertici, cioè quando non è un albero. Questo è facile da rilevare durante BFS o DFS: i bordi tracciati su nuovi vertici formano un albero e qualsiasi altro bordo indica un ciclo.


In effetti, questa è la risposta più (forse l'unica) correlata qui, elaborando le ragioni reali.
plasmacel

2

Se si posiziona un ciclo in un punto casuale di un albero, DFS tenderà a colpire il ciclo quando è coperto per circa metà dell'albero, e metà delle volte avrà già attraversato dove va il ciclo, e metà del tempo non lo farà ( e lo troverà in media nella metà del resto dell'albero), quindi valuterà in media circa 0,5 * 0,5 + 0,5 * 0,75 = 0,625 dell'albero.

Se si posiziona un ciclo in un punto casuale di un albero, BFS tenderà a colpire il ciclo solo quando viene valutato il livello dell'albero a quella profondità. Pertanto, di solito si finisce per dover valutare le foglie di un albero binario bilanciato, che generalmente si traduce in una valutazione maggiore dell'albero. In particolare, 3/4 delle volte almeno uno dei due collegamenti compare nelle foglie dell'albero, e in quei casi bisogna valutare mediamente 3/4 dell'albero (se esiste un collegamento) oppure 7 / 8 dell'albero (se ce ne sono due), quindi sei già in grado di cercare 1/2 * 3/4 ​​+ 1/4 * 7/8 = (7 + 12) / 32 = 21/32 = 0,656 ... dell'albero senza nemmeno aggiungere il costo della ricerca di un albero con un ciclo aggiunto lontano dai nodi foglia.

Inoltre, DFS è più facile da implementare rispetto a BFS. Quindi è quello da usare a meno che tu non sappia qualcosa sui tuoi cicli (ad esempio, è probabile che i cicli siano vicini alla radice da cui cerchi, a quel punto BFS ti dà un vantaggio).


Molti numeri magici lì. Non sono d'accordo con gli argomenti "DFS è più veloce". Dipende interamente dall'input e nessun input è più comune di un altro in questo caso.
IVlad

@Vlad - I numeri non sono magici. Sono mezzi, sono dichiarati come tali e sono quasi banali da calcolare date le ipotesi che ho affermato. Se l'approssimazione con la media è una cattiva approssimazione, sarebbe una critica valida. (E ho dichiarato esplicitamente che se potessi fare supposizioni sulla struttura, la risposta potrebbe cambiare.)
Rex Kerr

i numeri sono magici perché non significano niente. Hai preso un caso in cui DFS funziona meglio e hai estrapolato quei risultati al caso generale. Le tue affermazioni sono infondate: "DFS tenderà a colpire il ciclo quando è coperto circa la metà dell'albero": provalo. Per non parlare del fatto che non si può parlare di cicli su un albero. Un albero non ha un ciclo per definizione. Semplicemente non vedo qual è il tuo punto. DFS andrà in un modo fino a quando non raggiungerà un vicolo cieco, quindi non hai modo di sapere quanto del GRAPH (NON albero) esplorerà in media. Hai appena scelto un caso casuale che non prova nulla.
IVlad

@Vlad - Tutti i grafici non orientati completamente connessi non ciclici sono alberi (non orientati senza radici). Intendevo "un grafo che sarebbe un albero salvo un collegamento spurio". Forse questa non è l'applicazione principale per l'algoritmo, forse vuoi trovare cicli in qualche grafo aggrovigliato che ha moltissimi collegamenti che lo rendono non un albero. Ma se è simile ad un albero, mediata su tutti i grafici, è altrettanto probabile che qualsiasi nodo sia l'origine di detto collegamento spurio, il che rende la copertura dell'albero prevista del 50% quando il collegamento viene raggiunto. Quindi accetto che l'esempio potrebbe non essere stato rappresentativo. Ma la matematica dovrebbe essere banale.
Rex Kerr

1

Per dimostrare che un grafico è ciclico è sufficiente dimostrare che ha un ciclo (bordo che punta verso se stesso direttamente o indirettamente).

In DFS prendiamo un vertice alla volta e controlliamo se ha ciclo. Non appena viene trovato un ciclo, possiamo omettere di controllare altri vertici.

In BFS dobbiamo tenere traccia di molti spigoli di vertice simultaneamente e il più delle volte alla fine scopri se ha ciclo. Man mano che la dimensione del grafico aumenta, BFS richiede più spazio, calcolo e tempo rispetto a DFS.


0

In un certo senso dipende se stai parlando di implementazioni ricorsive o iterative.

Recursive-DFS visita ogni nodo due volte. Iterative-BFS visita ogni nodo una volta.

Se vuoi rilevare un ciclo, devi investigare i nodi sia prima che dopo aver aggiunto le loro adiacenze, sia quando "inizi" su un nodo e quando "finisci" con un nodo.

Ciò richiede più lavoro in Iterative-BFS, quindi la maggior parte delle persone sceglie Recursive-DFS.

Si noti che una semplice implementazione di Iterative-DFS con, diciamo, std :: stack ha lo stesso problema di Iterative-BFS. In tal caso, è necessario posizionare elementi fittizi nello stack per tenere traccia quando "finisci" di lavorare su un nodo.

Vedi questa risposta per maggiori dettagli su come Iterative-DFS richiede un lavoro aggiuntivo per determinare quando "finisci" con un nodo (risposta nel contesto di TopoSort):

Ordinamento topologico utilizzando DFS senza ricorsione

Si spera che questo spieghi perché le persone preferiscono Recursive-DFS per problemi in cui è necessario determinare quando "finisci" l'elaborazione di un nodo.


Questo è totalmente sbagliato, poiché non importa se usi la ricorsione o se elimini la ricorsione per iterazione. Puoi implementare un DFS iterativo che visita ogni nodo due volte, proprio come puoi implementare una variante ricorsiva che visita ogni nodo solo una volta.
plasmacel

0

Dovrai usare BFSquando vuoi trovare il ciclo più breve contenente un dato nodo in un grafo diretto.

Per esempio:inserisci qui la descrizione dell'immagine

Se il nodo specificato è 2, ci sono tre cicli in cui fa parte di - [2,3,4],[2,3,4,5,6,7,8,9] & [2,5,6,7,8,9]. Il più breve è[2,3,4]

Per implementarlo utilizzando BFS, è necessario mantenere esplicitamente la cronologia dei nodi visitati utilizzando strutture di dati appropriate.

Ma per tutti gli altri scopi (es: trovare un percorso ciclico o verificare se un ciclo esiste o meno), DFSè la scelta chiara per motivi citati da altri.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.