Un albero può essere attraversato senza ricorsione, pila o coda e solo una manciata di puntatori?


15

Mezzo decennio fa ero seduto in una classe di strutture di dati in cui il professore offriva credito extra se qualcuno potesse attraversare un albero senza ricorrere a ricorsione, una pila, una coda, ecc. (O qualsiasi altra struttura di dati simile) e solo alcuni suggerimenti. Mi è venuta in mente quella che pensavo fosse una risposta ovvia a quella domanda che alla fine fu accettata dal professore. Ero seduto in una classe di matematica discreta con un altro professore nello stesso dipartimento - e ha affermato che era impossibile attraversare un albero senza ricorsione, una pila, una fila, ecc. E che la mia soluzione non era valida.

Quindi, è possibile o impossibile? Perché o perché no?

Modifica: per aggiungere alcuni chiarimenti, l'ho implementato su un albero binario che aveva tre elementi: i dati memorizzati su ciascun nodo e i puntatori a due figli. La mia soluzione potrebbe essere estesa agli alberi n-ary con poche modifiche.

Il mio insegnante di strutture dati non ha posto alcun vincolo contro la mutazione dell'albero, e in effetti ho scoperto in seguito che la sua soluzione era quella di utilizzare i puntatori figlio per indicare nuovamente l'albero verso il basso. Il mio discreto professore di matematica ha detto che qualsiasi mutazione di un albero significa che non è più un albero secondo la definizione matematica di un albero, la sua definizione precluderebbe anche qualsiasi indicazione ai genitori - che corrisponderebbe al caso in cui l'ho risolto sopra.


3
Devi specificare i vincoli. Posso mutare l'albero? Come viene rappresentato l'albero? (Ad esempio, ogni nodo ha un puntatore genitore sul suo genitore?) La risposta dipenderà dai vincoli specifici; senza specificare tali vincoli, questo non è un problema ben posto.
DW

2
Immagino che la contrapposizione che i professori volevano davvero esprimere era "con spazio aggiuntivo". Ma qual è stata la tua soluzione, comunque? O(1)
Raffaello

Risposte:


17

Molta ricerca in questo settore è stata condotta a cupola, motivata dal metodo di attraversamento "economico" di alberi e strutture di elenchi generali nel contesto della raccolta dei rifiuti.

Un albero binario filettato è una rappresentazione adattata di alberi binari in cui vengono utilizzati alcuni puntatori zero per collegarsi ai nodi successivi dell'albero. Queste informazioni extra possono essere utilizzate per attraversare un albero senza pila. Tuttavia, è necessario un bit in più per nodo per distinguere i thread dai puntatori figlio. Wikipedia: Tree_traversal

Per quanto ne so, gli alberi binari implementati usando i puntatori nel solito modo (puntatore sinistro e destro per nodo) possono essere attraversati usando il metodo dei thread, in un metodo attribuito a Morris . I puntatori NIL vengono temporaneamente riutilizzati per eseguire il thread di un percorso alla radice. La parte intelligente è che durante l'attraversamento si possono distinguere i bordi originali dai collegamenti a filo temporanei, usando il modo in cui formano i cicli nell'albero).

Buona parte: nessuna struttura di dati extra. Parte negativa: un po 'barare, la pila è all'interno dell'albero in modo intelligente. Molto intelligente.

Una prova dello stack nascosto è mostrata in P. Mateti e R. Manghirmalani: Morris's Tree Traversal Algorithm Reconsidered DOI: 10.1016 / 0167-6423 (88) 90063-9

JM Morris: attraversare alberi binari in modo semplice ed economico. IPL 9 (1979) 197-200 DOI: 10.1016 / 0020-0190 (79) 90068-1

Poi c'è anche la scansione di Lindstrom . Questo metodo "ruota" i tre puntatori coinvolti in ciascun nodo (padre e due figli). Se si desidera eseguire algoritmi di pre-ordine o post-ordine decenti, sono necessari bit aggiuntivi per nodo. Se vuoi solo visitare tutti i nodi (tre volte, ma non sai mai quale visita esegui), puoi farlo senza i bit.

G. Lindstrom: scansione delle strutture dell'elenco senza pile o bit di tag. IPL 2 (1973) 47-51. DOI: 10.1016 / 0020-0190 (73) 90012-4

Forse il modo più semplice è un metodo di Robson . Qui lo stack necessario per l'algoritmo classico viene infilato tra le foglie.

JM Robson: un algoritmo migliorato per attraversare alberi binari senza stack ausiliario IPL 1 (1973) 149-152. 10,1016 / 0020-0190 (73) 90018-5

IPL = Lettere per l'elaborazione delle informazioni


Mi piace anche questa soluzione, anche se non è nulla che mi sarei inventato durante il mio primo anno di lezioni di informatica. Sì, probabilmente barare secondo le regole del mio professore.
NL - Chiedere scusa a Monica l'

2
Puoi fornire link / riferimenti per le strategie?
Raffaello

1
La vera parte negativa di quel metodo è che non puoi avere più di un attraversamento in corso contemporaneamente.
Gilles 'SO- smetti di essere malvagio'

6

v


Questo è simile alla soluzione che il professore di strutture dati che ha proposto il problema ha usato per risolverlo. Il discreto professore di matematica obiettò che "questo è diventato un grafico piuttosto che un albero" se ci sono indicazioni per i genitori.
NL - Chiedere scusa a Monica l'

@NathanLiddle: dipende dalla definizione dell'albero utilizzata (che non hai fornito). Nel "mondo reale", la rappresentazione dell'albero di Yuval è ragionevole anche se la teoria dei grafi direbbe che le cose che definisce non sono alberi, ovviamente.
Raffaello

@Raphael Sì, e soddisfa i requisiti del professore originale, quindi è una risposta accettabile per me.
NL - Chiedere scusa a Monica il

0

La mia soluzione era una traversata in primis usando archi forati nidificati per forzare la forza dell'albero. Questo non è efficiente in alcun modo, e in effetti una struttura di dati ricorsiva come un albero sta implorando un attraversamento ricorsivo, ma la domanda non era se un albero potesse essere attraversato in modo efficiente, è se fosse persino possibile.

Pseudocode:
root = pointer root 
depth = integer 0
finished = bool false
//If we n-ary tree also track how many children have been found 
//on the node with the most children for the purposes of this psuedocode 
//we'll assume a binary tree and insert a magic number of 2 so that we 
//can use bitwise operators instead of integer division 
while(!finished)
    ++depth
    treePosition = pointer root
    finished = true;
    for i := 0..2**depth
        for j := 0..depth
            if (i & j) //bitwise operator explained below
                // if right child doesn't exist break the loop
                treePosition = treePosition.rightChild
            else
                // if left child doesn't exist break the loop
                treePosition = treePosition.leftChild
        if j has any children
            finished = false
            do anything else you want when visiting the node

Per i primi livelli sembrerebbe così, come puoi vedere, l'operatore bit a bit nello pseudocodice decide semplicemente una svolta a sinistra oa destra su un albero binario:

2**1       0               1
2**2   00      01      10      11
2**3 000 001 010 011 100 101 110 111

Per n-ary, dovresti prendere i% (maxChildren ** j) / j per determinare quale percorso prendere tra 0 e maxChildren.

Ad ogni nodo su n-ary dovresti anche controllare per vedere se il numero dei figli è maggiore di maxChildren e aggiornarlo in modo appropriato.


Se si desidera utilizzare più di un binario, è necessario sostituire il numero magico 2 con una variabile che sarebbe incrementata per corrispondere al numero massimo di figli che ha visto, e invece di operatori bit a bit sarebbe necessario dividere per quella stessa variabile elevata a il potere della profondità dell'albero dove eri.
NL - Chiedere scusa a Monica il

O(1)O(lgn)O(1)O(n)O(1)spazio aggiuntivo "). Ad esempio, cosa fai se depthsupera la larghezza di int?
DW

DW, il professore che ha posto il problema non ha posto quel limite al problema, e ciò che mi ha infastidito così tanto nella mia discussione con il discreto professore di matematica è che non ha MAI riconosciuto che era persino possibile attraversare un albero senza ricorsione, impilare, o in coda, qualunque sia il costo. L'unica cosa che la mia soluzione dimostra è che è possibile fare qualsiasi cosa in modo ripetitivo che può essere fatto in modo ricorsivo, anche se si rimuovono le opzioni per stack, coda, ecc.
NL - Chiedere scusa a Monica il

Una cosa è dire che è irrisolvibile senza O (1) spazio aggiuntivo, un'altra è dichiarare irrisolvibile il problema senza ricorsione, stack o coda. E in realtà, dopo aver visto il mio codice, il discreto professore di matematica non ha ancora ammesso il punto perché ha detto che "i" nel primo ciclo stava prendendo il posto di una coda. Come va per la testa dura?
NL - Chiedere scusa a Monica il

1
idepthdepthΘ(n)Θ(n)iO(1)
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.