Salta al contenuto
Note per Studenti Esercizio 8 · indici del level numbering

Esercizio 8indici del level numbering

Esame
In questa pagina 5

Testo (esempio di tema d'esame, seconda parte, esercizio 1, 5 punti). Dato un albero binario proprio TT, si definisce il level numbering dei nodi di TT come segue:

  • se vv è radice di TT, allora l'indice ℓ(v)\ell(v) di vv nel level numbering è 11;
  • se vv non è radice, sia uu il padre di vv: allora ℓ(v)=2 ℓ(u)\ell(v) = 2\,\ell(u) se vv è figlio sinistro di uu, e ℓ(v)=2 ℓ(u)+1\ell(v) = 2\,\ell(u) + 1 se vv è figlio destro di uu.

Si vuole progettare un algoritmo ricorsivo che salvi in un campo v.indexLN l'indice ℓ(v)\ell(v) di vv nel level numbering, per ogni nodo v∈Tv \in T.

(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 O(n)O(n), dove nn è il numero di nodi di TT.


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 Θ(depth)\Theta(\text{depth}) per nodo, quindi fino a Θ(n2)\Theta(n^2) 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 vv). Se vv è una foglia imposta il proprio indice e termina: corretto. Se vv è interno, imposta idx (corretto per ipotesi sul parametro) e per l'ipotesi induttiva le chiamate sui figli assegnano correttamente i sottoalberi con i parametri 2 idx2\,idx e 2 idx+12\,idx + 1, che sono per definizione gli indici dei figli.

(b) Complessità

L'albero della ricorsione ha un nodo per ogni nodo di TT (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

t(n)∈Θ(n).t(n) \in \Theta(n).

(Si assume che le operazioni su interi costino Θ(1)\Theta(1): con alberi molto profondi gli indici crescono come 2depth2^{\text{depth}} e superano la parola di macchina.)

Esempio

Albero con radice RR, figlio sinistro XX (interno, con foglie aa e bb) e figlio destro YY (foglia): ℓ(R)=1\ell(R) = 1, ℓ(X)=2\ell(X) = 2, ℓ(Y)=3\ell(Y) = 3, ℓ(a)=4\ell(a) = 4, ℓ(b)=5\ell(b) = 5. 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 ℓ\ell sono 2ℓ2\ell e 2ℓ+12\ell + 1. In un albero completo gli indici coincidono con le posizioni in P[1..n]P[1..n]; in un albero qualsiasi possono arrivare a 2h+1−12^{h+1} - 1 (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 ℓ(v)\ell(v) risalendo dai nodi al padre: Θ(n⋅h)\Theta(n \cdot h) invece di Θ(n)\Theta(n).
  • Passare il valore del padre senza il fattore 22 (o senza il +1+1 per il figlio destro).
  • Dimenticare la chiamata iniziale con indice 11 (non 00).
  • Chiamare ricorsivamente sui figli anche per una foglia (accede a figli inesistenti).

Versione ripasso

Testo. Albero binario proprio; ℓ(radice)=1\ell(\text{radice}) = 1, figlio sinistro 2ℓ(u)2\ell(u), destro 2ℓ(u)+12\ell(u) + 1. Algoritmo ricorsivo che salva v.indexLN in tutti i nodi, in O(n)O(n).

Teoria collegata