Larghezza prima contro profondità prima


172

Quando si attraversa un albero / grafico qual è la differenza tra Breadth First e Depth first? Qualsiasi esempio di codifica o pseudocodice sarebbe fantastico.


6
Hai controllato Wikipedia ( prima la profondità , prima l'ampiezza )? Ci sono esempi di codice su quelle pagine, insieme a molte belle immagini.
dal

Ho pensato anche a questo, ma poi gli esempi forniti sono leggermente più belli di quelli trovati su Wikipedia ....
jonnybazookatone,

Risposte:


292

Questi due termini distinguono tra due diversi modi di camminare su un albero.

Probabilmente è più semplice esibire la differenza. Considera l'albero:

    A
   / \
  B   C
 /   / \
D   E   F

Una prima traversata approfondita visiterebbe i nodi in questo ordine

A, B, D, C, E, F

Si noti che si scende fino in fondo una gamba prima di proseguire .

Una prima traversata ampia visiterebbe il nodo in questo ordine

A, B, C, D, E, F

Qui lavoriamo fino in fondo ogni livello prima di scendere.

(Nota che c'è qualche ambiguità negli ordini trasversali, e ho barato per mantenere l'ordine di "lettura" ad ogni livello dell'albero. In entrambi i casi potrei arrivare a B prima o dopo C, e allo stesso modo potrei arrivare a E prima o dopo F. Questo può o non può importare, dipende dalla tua applicazione ...)


Entrambi i tipi di attraversamento possono essere raggiunti con lo pseudocodice:

Store the root node in Container
While (there are nodes in Container)
   N = Get the "next" node from Container
   Store all the children of N in Container
   Do some work on N

La differenza tra i due ordini trasversali sta nella scelta di Container.

  • Per profondità usa prima una pila. (L'implementazione ricorsiva utilizza lo stack di chiamate ...)
  • Per prima cosa usa una coda.

Sembra l'implementazione ricorsiva

ProcessNode(Node)
   Work on the payload Node
   Foreach child of Node
      ProcessNode(child)
   /* Alternate time to work on the payload Node (see below) */

La ricorsione termina quando si raggiunge un nodo che non ha figli, quindi si garantisce che finisca per grafici finiti e aciclici.


A questo punto, ho ancora tradito un po '. Con un po 'di intelligenza puoi anche lavorare sui nodi in questo ordine:

D, B, E, F, C, A

che è una variazione di profondità in primo luogo, in cui non faccio il lavoro su ciascun nodo fino a quando non risalgo l'albero. Ho comunque visitato i nodi superiori sulla strada per trovare i loro figli.

Questo attraversamento è abbastanza naturale nell'implementazione ricorsiva (usa la riga "Tempo alternativo" sopra invece della prima riga "Lavoro"), e non troppo difficile se usi uno stack esplicito, ma lo lascerò come un esercizio.


@dmckee Grazie! Credo che volevi dire "Lavora sul payload su Node", giusto?
Batbrat,

4
Vale la pena notare che è possibile modificare la profondità prima versione per ottenere il prefisso ( A, B, D, C, E, F- il primo presentato), infisso ( D, B, A, E, C, F- usato per l'ordinamento: aggiungere come un albero AVL quindi leggere infisso) o postfisso ( D, B, E, F, C, Al'alternativa presentata) traversal. I nomi sono dati dalla posizione in cui si elabora la radice. Va notato che infix ha davvero senso solo per gli alberi binari. @batbrat quelli sono i nomi ... visto il tempo da quando l'hai chiesto, probabilmente lo sai già.
Theraot,

@Theraot grazie per averlo aggiunto! Sì, conosco questo tipo di attraversamenti e perché Infix ha senso solo per gli alberi binari.
Batbrat,

Come decidere quale soluzione ha una migliore complessità spaziale o temporale?
IgorGanapolsky

1
@IgorGanapolsky Dovrebbe essere lo stesso per entrambi in linea di principio (dopo tutto, usano essenzialmente lo stesso codice). Una domanda più interessante sarebbe come hanno un impatto sulla cache e sul working set, ma penso che dipenderà dalla morfologia dell'albero.
dmckee --- ex-moderatore gattino

95

Comprensione dei termini:

Questa immagine dovrebbe darti l'idea del contesto in cui vengono usate le parole ampiezza e profondità .

Comprensione di ampiezza e profondità


Ricerca approfondita:

Ricerca approfondita

  • L'algoritmo di ricerca in profondità agisce come se volesse allontanarsi il più rapidamente possibile dal punto di partenza.

  • Di solito usa a Stackper ricordare dove dovrebbe andare quando raggiunge un vicolo cieco.

  • Regole da seguire: spingere il primo vertice A sul Stack

    1. Se possibile, visita un vertice non visitato adiacente, contrassegnalo come visitato e spingilo sulla pila.
    2. Se non riesci a seguire la Regola 1, allora, se possibile, fai uscire un vertice dallo stack.
    3. Se non riesci a seguire la Regola 1 o la Regola 2, il gioco è fatto.
  • Codice Java:

    public void searchDepthFirst() {
        // Begin at vertex 0 (A)
        vertexList[0].wasVisited = true;
        displayVertex(0);
        stack.push(0);
        while (!stack.isEmpty()) {
            int adjacentVertex = getAdjacentUnvisitedVertex(stack.peek());
            // If no such vertex
            if (adjacentVertex == -1) {
                stack.pop();
            } else {
                vertexList[adjacentVertex].wasVisited = true;
                // Do something
                stack.push(adjacentVertex);
            }
        }
        // Stack is empty, so we're done, reset flags
        for (int j = 0; j < nVerts; j++)
            vertexList[j].wasVisited = false;
    }
    
  • Applicazioni : le ricerche in base alla profondità vengono spesso utilizzate nelle simulazioni di giochi (e situazioni simili al gioco nel mondo reale). In un gioco tipico puoi scegliere una delle diverse azioni possibili. Ogni scelta porta a ulteriori scelte, ognuna delle quali porta a ulteriori scelte, e così via in un grafico delle possibilità a forma di albero in continua espansione.


Prima ricerca:

Ampia ricerca

  • L'algoritmo di ricerca in ampiezza ama stare il più vicino possibile al punto di partenza.
  • Questo tipo di ricerca viene generalmente implementato usando a Queue.
  • Regole da seguire: Trasforma il vertice A iniziale nel vertice corrente
    1. Visita il prossimo vertice non visitato (se presente) adiacente al vertice corrente, contrassegnalo e inseriscilo nella coda.
    2. Se non è possibile eseguire la Regola 1 perché non ci sono più vertici non visitati, rimuovere un vertice dalla coda (se possibile) e renderlo il vertice corrente.
    3. Se non riesci a rispettare la Regola 2 perché la coda è vuota, il gioco è fatto.
  • Codice Java:

    public void searchBreadthFirst() {
        vertexList[0].wasVisited = true;
        displayVertex(0);
        queue.insert(0);
        int v2;
        while (!queue.isEmpty()) {
            int v1 = queue.remove();
            // Until it has no unvisited neighbors, get one
            while ((v2 = getAdjUnvisitedVertex(v1)) != -1) {
                vertexList[v2].wasVisited = true;
                // Do something
                queue.insert(v2);
            }
        }
        // Queue is empty, so we're done, reset flags
        for (int j = 0; j < nVerts; j++) 
            vertexList[j].wasVisited = false;
    }
    
  • Applicazioni : la ricerca in ampiezza prima trova prima tutti i vertici che si trovano a un bordo dal punto iniziale, quindi tutti i vertici che si trovano a due bordi di distanza e così via. Questo è utile se stai cercando di trovare il percorso più breve dal vertice iniziale a un dato vertice.

Speriamo che ciò sia sufficiente per comprendere le ricerche Breadth-First e Depth-First. Per ulteriori letture consiglierei il capitolo Graphs da un eccellente libro sulle strutture di dati di Robert Lafore.


6
Se avessi avuto dieci voti in più, lo farei.
Snr

@snr potresti assegnare una taglia;)
Snow

@Se se dici le sue direttive, posso. Non so come fare.
Snr

Grazie @snr, sono così felice di ricevere la mia prima taglia. Apprezzo molto
Yogesh Umesh Vaity,

1
Grazie @Snow, sono felice che abbiate trovato utile la mia risposta.
Yogesh Umesh Vaity

4

Dato questo albero binario:

inserisci qui la descrizione dell'immagine

Larghezza prima traversata:
attraversa ogni livello da sinistra a destra.

"Sono G, i miei figli sono D e io, i miei nipoti sono B, E, H e K, i loro nipoti sono A, C, F"

- Level 1: G 
- Level 2: D, I 
- Level 3: B, E, H, K 
- Level 4: A, C, F

Order Searched: G, D, I, B, E, H, K, A, C, F

Profondità prima traversata:
traversata non viene eseguita ATTRAVERSO interi livelli alla volta. Invece, l'attraversamento si tuffa per primo nella PROFONDITÀ (dalla radice alla foglia) dell'albero. Tuttavia, è un po 'più complesso che semplicemente su e giù.

Esistono tre metodi:

1) PREORDER: ROOT, LEFT, RIGHT.
You need to think of this as a recursive process:  
Grab the Root. (G)  
Then Check the Left. (It's a tree)  
Grab the Root of the Left. (D)  
Then Check the Left of D. (It's a tree)  
Grab the Root of the Left (B)  
Then Check the Left of B. (A)  
Check the Right of B. (C, and it's a leaf node. Finish B tree. Continue D tree)  
Check the Right of D. (It's a tree)  
Grab the Root. (E)  
Check the Left of E. (Nothing)  
Check the Right of E. (F, Finish D Tree. Move back to G Tree)  
Check the Right of G. (It's a tree)  
Grab the Root of I Tree. (I)  
Check the Left. (H, it's a leaf.)  
Check the Right. (K, it's a leaf. Finish G tree)  
DONE: G, D, B, A, C, E, F, I, H, K  

2) INORDER: LEFT, ROOT, RIGHT
Where the root is "in" or between the left and right child node.  
Check the Left of the G Tree. (It's a D Tree)  
Check the Left of the D Tree. (It's a B Tree)  
Check the Left of the B Tree. (A)  
Check the Root of the B Tree (B)  
Check the Right of the B Tree (C, finished B Tree!)  
Check the Right of the D Tree (It's a E Tree)  
Check the Left of the E Tree. (Nothing)  
Check the Right of the E Tree. (F, it's a leaf. Finish E Tree. Finish D Tree)...  
Onwards until...   
DONE: A, B, C, D, E, F, G, H, I, K  

3) POSTORDER: 
LEFT, RIGHT, ROOT  
DONE: A, C, B, F, E, D, H, K, I, G

Uso (alias, perché ci interessa):
mi è davvero piaciuta questa semplice spiegazione di Quora dei metodi Depth First Traversal e di come vengono comunemente utilizzati:
"In-Order Traversal stamperà i valori [in ordine per il BST (albero binario di ricerca)] "
" L'attraversamento pre-ordine viene utilizzato per creare una copia dell'albero [albero di ricerca binario]. "
"Traversal postorder viene utilizzato per eliminare [albero di ricerca binario]."
https://www.quora.com/What-is-the-use-of-pre-order-and-post-order-traversal-of-binary-trees-in-computing


2

Penso che sarebbe interessante scrivere entrambi in un modo che solo cambiando alcune righe di codice ti darebbe un algoritmo o l'altro, in modo che vedrai che il tuo dillema non è così forte come sembra all'inizio .

Personalmente mi piace l'interpretazione di BFS come inondazione di un paesaggio: le aree a bassa quota verranno inondate per prime, e solo dopo le aree ad alta quota seguiranno. Se immagini le altitudini del paesaggio come isoline come vediamo nei libri di geografia, è facile vedere che BFS riempie tutta l'area sotto lo stesso isoline allo stesso tempo, proprio come questo sarebbe con la fisica. Pertanto, interpretare le altitudini come distanza o costo in scala dà un'idea abbastanza intuitiva dell'algoritmo.

Con questo in mente, puoi facilmente adattare l'idea alla base della prima ricerca per trovare facilmente l'albero di spanning minimo, il percorso più breve e anche molti altri algoritmi di minimizzazione.

Non ho ancora visto alcuna interpretazione intuitiva di DFS (solo quella standard sul labirinto, ma non è potente come quella BFS e inondazioni), quindi per me sembra che BFS sembra correlare meglio con i fenomeni fisici come descritto sopra, mentre Il DFS si correla meglio con le scelte dillema sui sistemi razionali (cioè persone o computer che decidono quale mossa fare una partita a scacchi o uscire da un labirinto).

Quindi, per me la differenza tra bugie su quale fenomeno naturale si adatta meglio al loro modello di propagazione (trasversale) nella vita reale.


1
Puoi implementarli con un algoritmo simile, basta usare lo stack per DFS e la coda per BFS. Il problema con BFS è che è necessario tenere traccia di tutti i nodi visti finora. DFS in fisica .. Immagino universi alternativi e tu ne vuoi uno con la vita, tutti i figli della radice, sono diversi big bang e vai fino alla morte dell'universo, niente vita? torni all'ultima biforcazione e provi un altro giro, fino a quando tutti sono esauriti e vai al prossimo big bang, stabilendo nuove leggi fisiche per il nuovo universo. super intuitivo. un buon problema è trovare un modo con il cavallo in una scacchiera.
juanmf,
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.