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.
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.
Risposte:
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 .
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.
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.
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:
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.
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:
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.
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).
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.
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.
Dovrai usare BFS
quando vuoi trovare il ciclo più breve contenente un dato nodo in un grafo diretto.
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.