2009-10-23 11 views
8

Qual è il modo migliore per visitare tutti i nodi di un albero collegato (tutti i nodi hanno riferimenti a genitore e tutti i figli, i nodi radice hanno null come genitore), in modo che nessun nodo venga visitato prima di qualsiasi dei suoi antenati? Punti Brownie per non ricorsivi.Camminare un albero, genitore prima

+1

Correlati: http://stackoverflow.com/questions/754439/iterative-tree-walking –

risposta

6

Pseudo codice:

NodesToVisit = some stack or some list 
NodesToVisit.Push(RootNode) 

While NodesToVisit.Length > 0 
{ 
    CurNode = NodesToVisit.Pop() 
    For each Child C in CurNode 
     NodesToVisit.Push(C) 
    Visit(CurNode) (i.e. do whatever needs to be done) 
} 

Edit: ricorsivo o no?
Per essere tecnicamente corretta, e come indicato dalla AndreyT e altri in questo post, questo approccio è una forma di un algoritmo ricorsivo, per cui uno stack esplicito gestiti, in sostituzione della pila CPU e dove la ricorsione si svolge a livello del ciclo While. Detto questo, si differenzia da un'implementazione ricorsiva per sé in un paio di sottili modi ma significativi:

  • Solo "variabili" vengono inseriti nello stack; non c'è uno "stack frame" e l'indirizzo di ritorno associato nello stack, l'unico "indirizzo di ritorno" è implicito nel ciclo while, e c'è solo una sua istanza.
  • Lo "stack" poteva essere usato come una lista in cui il successivo "frame" poteva essere portato ovunque nella lista, senza frenare la logica in alcun modo.
+0

OK. Questa non era una domanda accademica. Era una domanda pratica. Questo risponde in modo soddisfacente senza farmi pensare o fare ulteriori letture. Grazie. (Probabilmente mi farà pensare più tardi quando avrò il tempo, ma va bene ... Utile, anche ...) Il lavoro è finito. Grazie ancora :) – George

0

Creare un elenco di nodi nella radice (livello 0), scorrere su ciascun nodo a sua volta e cercare i figli diretti (il cui nodo padre è il nodo da cui stiamo attualmente cercando) (livello 1), una volta terminato con il livello 0 passa al livello 1 di iterazione e così via fino a quando non ci sono nodi non visitati rimanenti.

1

Utilizzare un set di nodi. Metti la radice nel set per iniziare. Quindi, in un ciclo, estrai un nodo dal set, visitalo, quindi metti i suoi figli nel set. Quando il set è vuoto, hai finito.

+0

Si desidera che la struttura dei dati sia FIFO, non solo un vecchio contenitore, per garantire la condizione di preordine. –

+0

Non c'era questo requisito nella domanda. –

1

In pseudocodice:

currentList = list(root) 
nextList = list() 
while currentList.count > 0: 
    foreach node in currentList: 
     nextList.add(node.children) 
    currentList = nextList 
3

Siete alla ricerca di un attraversamento preordine. Penso che puoi farlo senza ricorsività con la una coda :. In pseudocode:

Creare una coda vuota, quindi premere il nodo radice.

while nonempty(q) 
    node = pop(q) 
    visit(node) 
    foreach child(node) 
    push(q,child) 
+0

Sarebbe una * implementazione non reclamativa * di un * algoritmo ricorsivo *. Sostituire lo stack implicito con lo stack esplicito non trasforma un algoritmo ricorsivo in uno non ricorsivo. In effetti, non cambia affatto l'algoritmo. Quello che hai sopra è ovviamente ricorsivo (per quanto riguarda l'algoritmo). – AnT

+5

In genere quando le persone dicono di non volere la ricorsione, si riferiscono alla ricorsione a livello di funzione. Questo soddisfa questa condizione. –

+0

Bene, a volte sì. Tuttavia, il problema che stiamo considerando qui è specificamente realizzato per consentire una soluzione veramente non ricorsiva (cioè un algoritmo non ricorsivo). L'omaggio morto è la presenza di indicatori genitore. La tua soluzione "non ricorsiva" non usa i puntatori genitore. Non ti chiedi perché sono anche lì? Sono lì specificamente per consentire una vera soluzione non ricorsiva, quella con il requisito di memoria O (1). – AnT

1

Se si avvia al nodo radice, e visitare solo i genitori/figli dei nodi che hai già visitato, non c'è alcun modo per attraversare l'albero in modo tale che si visita un nodo prima di visitare i suoi antenati.

Qualsiasi tipo di attraversamento, profondità prima (ricorsiva/basata su stack), larghezza prima (in base alla coda), profondità limitata o semplicemente estrazione da un set non ordinato, funzionerà.

Il metodo "migliore" dipende dall'albero. Larghezza prima funzionerebbe bene per un albero molto alto con pochi rami. La profondità prima funzionerebbe bene per gli alberi con molti rami.

Poiché i nodi hanno effettivamente dei puntatori ai loro genitori, esiste anche un algoritmo a memoria costante, ma è molto più lento.

+0

Teh op ha detto "* nessun * nodo è visitato prima dei suoi antenati". Quindi è il contrario. – AnT

+0

Qual è il contrario? Hai letto correttamente la mia risposta? – Artelius

+0

Forse no. Ho pensato che nella tua prima frase hai affermato che il problema non può essere risolto, dal momento che il riesame dell'ordine di visita (che, presumo, hai frainteso) è impossibile da soddisfare. – AnT

3

Se si hanno anche collegamenti a tutti i bambini e al genitore, l'algoritmo non ricorsivo è piuttosto banale. Dimentica che hai un albero. Pensa che è un labirinto in cui ogni collegamento genitore-figlio è un normale corridoio bidirezionale da una giunzione all'altra. Tutto quello che devi fare per percorrere l'intero labirinto è girare nel prossimo corridoio a sinistra ad ogni incrocio. (In alternativa, pensaci come se camminassi nel labirinto con la mano sinistra che tocca sempre il muro sul lato sinistro). Se parti dal nodo radice (e ti muovi in ​​qualsiasi direzione), percorrerai l'intero albero visitando sempre i genitori prima dei bambini. Ogni "corridoio" in questo caso sarà percorso due volte (in una direzione e nell'altra), e ogni "giunzione" (nodo) sarà visitata tante volte quante "corridoi" si uniranno a essa.

+0

Questo è l'algoritmo di memoria costante che ho citato. – Artelius

1

Non sarei d'accordo con l'ampiezza della prima ricerca poiché la complessità dello spazio è spesso la rovina di quell'algoritmo di ricerca specifico. Probabilmente l'uso dell'algoritmo di approfondimento iterativo è un'alternativa migliore per questo tipo di utilizzo e copre lo stesso tipo di attraversamento della ricerca per ampiezza. Ci sono alcune piccole differenze nell'affrontare il margine della ricerca in ampiezza, ma non dovrebbe essere troppo difficile da codificare (pseudo).

Riferimento: http://en.wikipedia.org/wiki/Iterative_deepening

+0

+1 a causa della tua complessità spaziale di considerazione - ma perché non usare solo una ricerca in profondità? – Artelius

+0

Nella pratica molti alberi tendono ad essere più profondi di quanto non siano "più larghi", esp. nei processi decisionali dell'IA. La domanda non dice se l'albero è finito, ma potresti correre in un loop. Uno dei motivi per cui l'approfondimento iterativo è piaciuto è che è completo (troverà una soluzione). – mduvall

1

Ecco un veramente approccio non ricorsivo: nessuna pila, spazio costante. Questo codice Python presuppone che ogni nodo contiene un elenco dei bambini, e che gli oggetti nodo non definiscono l'uguaglianza, in modo che la funzione di 'indice' è il confronto delle identità:

def walkTree(root, visit_func): 
    cur = root 
    nextChildIndex = 0 

    while True: 
     visit_func(cur) 

     while nextChildIndex >= len(cur.children) and cur is not root: 
      nextChildIndex = cur.parent.children.index(cur) + 1 
      cur = cur.parent 

     if nextChildIndex >= len(cur.children): 
      break 

     cur = cur.children[nextChildIndex] 
     nextChildIndex = 0 

sono sicuro che potrebbe essere ripulito un po ', reso più conciso e più facile da leggere, ma questo è il succo.