Salta al contenuto
Note per Studenti Esercizio 10 · albero binario bilanciato

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 11. (Ad esempio, la radice AA con figli BB e CC, dove BB ha figli DD ed EE, è bilanciata; un albero in cui da AA 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 O(⋅)O(\cdot), Ω(⋅)\Omega(\cdot) ed eventualmente Θ(⋅)\Theta(\cdot) 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 vv servono: (1) TLT_L e TRT_R bilanciati, (2) ∣h(TL)−h(TR)∣≤1\lvert h(T_L) - h(T_R) \rvert \le 1. 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 =−1= -1, altezza di una foglia =0= 0 (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 −1-1 ✓. Nodo vv: per ipotesi induttiva check sui figli restituisce correttamente (bilanciato, altezza). TvT_v è bilanciato se e solo se lo sono entrambi i figli e le altezze differiscono al più di 11, che è esattamente ciò che si controlla; l'altezza di vv è 1+max⁡(hL,hR)1 + \max(h_L, h_R) per definizione.

(b) Complessità

Ogni nodo (e ogni puntatore nullo) genera al più una chiamata di check: nn nodi più al più n+1n + 1 sottoalberi vuoti. Il costo locale di una chiamata (confronti, un valore assoluto, un massimo, assegnazioni) è Θ(1)\Theta(1), escluse le chiamate figlie. Quindi:

  • Limite superiore: O(n)O(n), perché il numero di chiamate è ≤2n+1\le 2n + 1 e ognuna è O(1)O(1).
  • Limite inferiore: Ω(n)\Omega(n) se l'albero è bilanciato (nessuna uscita anticipata: si visitano tutti i nodi, ognuno una volta), quindi nel caso pessimo la complessità è Θ(n)\Theta(n). (Non si può fare meglio di Ω(n)\Omega(n): 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 Θ(∣Tv∣)\Theta(\lvert T_v \rvert) e il totale è O(∑v∣Tv∣)=O(n⋅h)O(\sum_v \lvert T_v \rvert) = O(n \cdot h) (ogni nodo è contato una volta per ciascun antenato), cioè più di Θ(n)\Theta(n) appena l'albero ha altezza non costante (in un albero bilanciato O(nlog⁡n)O(n \log n)).

Esempio

Albero A→(B→(D,E), C)A \to (B \to (D, E),\ C): check(D) =(true,0)= (\text{true}, 0), check(E) =(true,0)= (\text{true}, 0); in BB: ∣0−0∣=0≤1\lvert 0 - 0 \rvert = 0 \le 1, restituisce (true,1)(\text{true}, 1); check(C) =(true,0)= (\text{true}, 0); in AA: ∣1−0∣=1≤1\lvert 1 - 0 \rvert = 1 \le 1, restituisce (true,2)(\text{true}, 2) ⇒\Rightarrow bilanciato. Albero a catena A→B→CA \to B \to C (solo figli sinistri): in CC le altezze dei due vuoti sono −1,−1-1, -1: (true,0)(\text{true}, 0); in BB: hL=0h_L = 0, hR=−1h_R = -1, differenza 11: (true,1)(\text{true}, 1); in AA: hL=1h_L = 1, hR=−1h_R = -1, differenza 2>12 > 1: (false,0)(\text{false}, 0) ⇒\Rightarrow non bilanciato. (Controllato su 3000 alberi casuali confrontando con la definizione.)

Errori comuni

  • Usare l'altezza 00 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 11 e 00, differenza 11), mentre le altezze corrette sono 11 e −1-1 (differenza 22).
  • Controllare il bilanciamento solo alla radice: la definizione vale per ogni nodo.
  • Calcolare height ad ogni nodo con una funzione separata: O(nh)O(n h).
  • 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 11. Algoritmo ricorsivo isBalanced con input/output, e complessità in OO, Ω\Omega, Θ\Theta.

  • Idea: postorder che restituisce (balanced, height) (altezza extra); vuoto =−1= -1, foglia =0= 0 (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): v=v = null ⇒ (true,−1)(\text{true}, -1); (bL,hL)(b_L, h_L) e (bR,hR)(b_R, h_R) dai figli; se uno è false ⇒ (false,0)(\text{false}, 0); se ∣hL−hR∣>1\lvert h_L - h_R \rvert > 1 ⇒ (false,0)(\text{false}, 0); altrimenti (true,1+max⁡(hL,hR))(\text{true}, 1 + \max(h_L, h_R)).
  • Complessità: una chiamata per nodo e per sottoalbero vuoto, costo costante: O(n)O(n); Ω(n)\Omega(n) se è bilanciato ⇒ Θ(n)\Theta(n). Soluzione ingenua con height per ogni nodo: O(nh)O(n h).
  • Codice: check(null) = (true, -1); (bL,hL) <- check(left); se bL = false ⇒ (false, 0); (bR,hR) <- check(right); se bR = false ⇒ (false, 0); se ∣hL−hR∣>1\lvert h_L - h_R \rvert > 1 ⇒ (false, 0); altrimenti (true, 1 + max(hL,hR)).
  • Esempio: A→(B→(D,E),C)A \to (B \to (D, E), C): BB: (true,1)(\text{true}, 1); AA: ∣1−0∣=1\lvert 1 - 0 \rvert = 1 ⇒ (true,2)(\text{true}, 2) bilanciato; catena A→B→CA \to B \to C a sinistra: in AA differenza 22 ⇒ non bilanciato.
  • Errori: vuoto =0= 0; controllo solo alla radice; height separata ad ogni nodo; input/output non specificati.

Teoria collegata