Esercizio 8indici del level numbering
In questa pagina 5
Testo (esempio di tema d'esame, seconda parte, esercizio 1, 5 punti). Dato un albero binario proprio , si definisce il level numbering dei nodi di come segue:
- se è radice di , allora l'indice di nel level numbering è ;
- se non è radice, sia il padre di : allora se è figlio sinistro di , e se è figlio destro di .
Si vuole progettare un algoritmo ricorsivo che salvi in un campo v.indexLN l'indice di nel level numbering, per ogni nodo .
(a) Si fornisca lo pseudocodice per l'algoritmo richiesto. (b) Si analizzi la complessità dell'algoritmo.
Per ricevere il massimo dei punti l'algoritmo deve avere complessità in tempo , dove è il numero di nodi di .
Idea
L'indice di un nodo dipende solo dall'indice del padre e dal lato. Quindi il padre deve conoscere il proprio indice prima di passarlo ai figli: serve una visita in preorder in cui l'indice viene passato come parametro (non lo si ricalcola risalendo dai nodi: costerebbe per nodo, quindi fino a in totale; vedi Visite di alberiVisite in preorder e postorder come schemi generali (template) da adattare; complessità Theta(n + somma dei costi di visita) perché la somma dei figli è n-1; esempi (indice di un libro, spazio occupato in un file system); profondità con il preorder, altezza con il postorder; antenato comune più basso.Visite di alberi →).
(a) Pseudocodice
Algoritmo levelNumbering(T, v, idx)
Input: albero binario proprio T, nodo v di T, intero idx = indice che v deve avere
Output: nessuno; side effect: v.indexLN = idx e lo stesso per ogni discendente di v
v.indexLN <- idx
if T.isInternal(v) then
levelNumbering(T, T.left(v), 2 * idx)
levelNumbering(T, T.right(v), 2 * idx + 1)Chiamata iniziale: levelNumbering(T, T.root(), 1).
Correttezza (induzione sull'altezza di ). Se è una foglia imposta il proprio indice e termina: corretto. Se è interno, imposta idx (corretto per ipotesi sul parametro) e per l'ipotesi induttiva le chiamate sui figli assegnano correttamente i sottoalberi con i parametri e , che sono per definizione gli indici dei figli.
(b) Complessità
L'albero della ricorsione ha un nodo per ogni nodo di (una chiamata per nodo, esattamente una) e ogni chiamata esegue un numero costante di operazioni, escluse le chiamate figlie: due moltiplicazioni, un'addizione, un'assegnazione e il test. Quindi
(Si assume che le operazioni su interi costino : con alberi molto profondi gli indici crescono come e superano la parola di macchina.)
Esempio
Albero con radice , figlio sinistro (interno, con foglie e ) e figlio destro (foglia): , , , , . Si riconosce la numerazione per livelli usata per l'array di uno heap (vedi HeapAlbero binario completo e sua altezza floor(log2 n); heap = albero completo con heap-order property; proprietà (radice minima, cammini non decrescenti); rappresentazione su array con level numbering; insert con up-heap bubbling, removeMin con down-heap bubbling, rimozione di una entry qualsiasi; invarianti e costi Theta(log n); esempio svolto.Heap →): i figli di sono e . In un albero completo gli indici coincidono con le posizioni in ; in un albero qualsiasi possono arrivare a (cammino tutto a destra), il che mostra perché l'array è adatto solo agli alberi completi. (Verificato con alberi casuali: gli indici sono tutti distinti.)
Errori comuni
- Calcolare risalendo dai nodi al padre: invece di .
- Passare il valore del padre senza il fattore (o senza il per il figlio destro).
- Dimenticare la chiamata iniziale con indice (non ).
- Chiamare ricorsivamente sui figli anche per una foglia (accede a figli inesistenti).
Versione ripasso
Testo. Albero binario proprio; , figlio sinistro , destro . Algoritmo ricorsivo che salva v.indexLN in tutti i nodi, in .
- Idea: l'indice del figlio dipende solo da quello del padre ⇒ preorder con l'indice passato come parametro (vedi Visite di alberiVisite in preorder e postorder come schemi generali (template) da adattare; complessità Theta(n + somma dei costi di visita) perché la somma dei figli è n-1; esempi (indice di un libro, spazio occupato in un file system); profondità con il preorder, altezza con il postorder; antenato comune più basso.Visite di alberi →).
- Pseudocodice:
levelNumbering(T, v, idx):v.indexLN <- idx; se interno,levelNumbering(T, left(v), 2*idx)elevelNumbering(T, right(v), 2*idx+1); chiamatalevelNumbering(T, T.root(), 1). - Correttezza: induzione sull'altezza di .
- Complessità: una chiamata per nodo, costo costante: .
- Esempio: , , , , ; come l'array di uno heap (vedi HeapAlbero binario completo e sua altezza floor(log2 n); heap = albero completo con heap-order property; proprietà (radice minima, cammini non decrescenti); rappresentazione su array con level numbering; insert con up-heap bubbling, removeMin con down-heap bubbling, rimozione di una entry qualsiasi; invarianti e costi Theta(log n); esempio svolto.Heap →).
- Nota: gli indici crescono come (un cammino tutto a destra arriva a ), quindi servono interi grandi per alberi molto profondi.
- Errori: risalire al padre (); fattore o dimenticato; indice iniziale .