Esercizio 10albero binario bilanciato
In questa pagina 5
Testo (scritto del 07/08/2026, seconda parte, esercizio 2, 6 punti). Sia dato un albero binario. Un albero si dice bilanciato se, per ogni nodo, le altezze dei sottoalberi sinistro e destro differiscono al più di . (Ad esempio, la radice con figli e , dove ha figli ed , è bilanciata; un albero in cui da scende per più livelli un solo ramo mentre l'altro è corto non lo è.) Progettare un algoritmo ricorsivo isBalanced(root) che restituisca true se l'albero è bilanciato e false altrimenti.
(a) Scrivere l'algoritmo in pseudocodice, esplicitando all'inizio i parametri di ingresso e i valori di uscita. (b) Determinare la complessità al caso pessimo esprimendola in notazione , ed eventualmente e giustificando adeguatamente il risultato.
Suggerimento del testo: una soluzione efficiente può calcolare e fornire in uscita, durante ogni visita ricorsiva, non solo se un sottoalbero è bilanciato, ma anche una seconda informazione utile al padre per verificare il bilanciamento dei suoi sottoalberi.
Idea
Per il nodo servono: (1) e bilanciati, (2) . L'altezza dei figli è l'informazione extra suggerita: se ogni chiamata la restituisce insieme al booleano, il padre non deve ricalcolarla. Si usa la convenzione: altezza di un sottoalbero vuoto , altezza di una foglia (vedi AlberiAlbero radicato (definizione per padre e ricorsiva), terminologia (antenati, discendenti, nodi interni ed esterni, sottoalbero, albero ordinato), profondità, livello, altezza; altezza = massima profondità delle foglie; algoritmi depth e height con costo; somma dei figli = n-1; esempio di algoritmo Omega(n^2) (heightBad).Alberi →). È un postorder.
(a) Pseudocodice
Algoritmo isBalanced(T)
Input: albero binario T (eventualmente vuoto)
Output: true se T è bilanciato, false altrimenti
return check(T.root())[0]
Algoritmo check(v)
Input: nodo v oppure null (sottoalbero vuoto)
Output: coppia (balanced, height): balanced = T_v bilanciato; height = altezza di T_v
(se balanced = false il valore di height è irrilevante)
if v = null then return (true, -1)
(bL, hL) <- check(left(v))
if bL = false then return (false, 0)
(bR, hR) <- check(right(v))
if bR = false then return (false, 0)
if |hL - hR| > 1 then return (false, 0)
return (true, 1 + max(hL, hR))Il controllo anticipato (return (false, 0) appena un sottoalbero è sbilanciato) non cambia la complessità asintotica: la risposta false si propaga fino alla radice senza ulteriori calcoli.
Correttezza (induzione sull'altezza). Sottoalbero vuoto: bilanciato per definizione, altezza ✓. Nodo : per ipotesi induttiva check sui figli restituisce correttamente (bilanciato, altezza). è bilanciato se e solo se lo sono entrambi i figli e le altezze differiscono al più di , che è esattamente ciò che si controlla; l'altezza di è per definizione.
(b) Complessità
Ogni nodo (e ogni puntatore nullo) genera al più una chiamata di check: nodi più al più sottoalberi vuoti. Il costo locale di una chiamata (confronti, un valore assoluto, un massimo, assegnazioni) è , escluse le chiamate figlie. Quindi:
- Limite superiore: , perché il numero di chiamate è e ognuna è .
- Limite inferiore: se l'albero è bilanciato (nessuna uscita anticipata: si visitano tutti i nodi, ognuno una volta), quindi nel caso pessimo la complessità è . (Non si può fare meglio di : per dire che un albero è bilanciato bisogna guardare tutti i nodi.)
Confronto con la soluzione ingenua (calcolare height ad ogni nodo e poi chiamarsi ricorsivamente sui figli): ogni height costa e il totale è (ogni nodo è contato una volta per ciascun antenato), cioè più di appena l'albero ha altezza non costante (in un albero bilanciato ).
Esempio
Albero : check(D) , check(E) ; in : , restituisce ; check(C) ; in : , restituisce bilanciato. Albero a catena (solo figli sinistri): in le altezze dei due vuoti sono : ; in : , , differenza : ; in : , , differenza : non bilanciato. (Controllato su 3000 alberi casuali confrontando con la definizione.)
Errori comuni
- Usare l'altezza per l'albero vuoto: confonde il vuoto con una foglia e fa dichiarare bilanciato un nodo con un solo figlio che a sua volta ha un figlio (altezze e , differenza ), mentre le altezze corrette sono e (differenza ).
- Controllare il bilanciamento solo alla radice: la definizione vale per ogni nodo.
- Calcolare
heightad ogni nodo con una funzione separata: . - Non specificare input e output della procedura ausiliaria (richiesto dal testo).
Versione ripasso
Testo. Albero binario bilanciato = per ogni nodo le altezze dei sottoalberi sinistro e destro differiscono al più di . Algoritmo ricorsivo isBalanced con input/output, e complessità in , , .
- Idea: postorder che restituisce (balanced, height) (altezza extra); vuoto , foglia (vedi AlberiAlbero radicato (definizione per padre e ricorsiva), terminologia (antenati, discendenti, nodi interni ed esterni, sottoalbero, albero ordinato), profondità, livello, altezza; altezza = massima profondità delle foglie; algoritmi depth e height con costo; somma dei figli = n-1; esempio di algoritmo Omega(n^2) (heightBad).Alberi →).
check(v):null⇒ ; e dai figli; se uno èfalse⇒ ; se ⇒ ; altrimenti .- Complessità: una chiamata per nodo e per sottoalbero vuoto, costo costante: ; se è bilanciato ⇒ . Soluzione ingenua con
heightper ogni nodo: . - Codice:
check(null) = (true, -1);(bL,hL) <- check(left); sebL = false⇒(false, 0);(bR,hR) <- check(right); sebR = false⇒(false, 0); se ⇒(false, 0); altrimenti(true, 1 + max(hL,hR)). - Esempio: : : ; : ⇒ bilanciato; catena a sinistra: in differenza ⇒ non bilanciato.
- Errori: vuoto ; controllo solo alla radice;
heightseparata ad ogni nodo; input/output non specificati.