Salta al contenuto
Note per Studenti Alberi

Alberi

In questa pagina 6

Un albero è una collezione di nodi con struttura gerarchica padre-figlio, con i collegamenti strettamente necessari a tenerli connessi (una lista è il caso estremo). Applicazioni: dizionari e code con priorità (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 →, Alberi binari di ricercaAlbero binario di ricerca come albero binario proprio con entry nei nodi interni e foglie vuote; inorder crescente; TreeSearch; get, put e remove (due casi, con predecessore inorder) in Theta(h); altezza fino a n-1; esempi di inserimenti; alberi aumentati con campi size e max e algoritmi di conteggio e interrogazione in O(h).Alberi binari di ricerca →), file system, albero della ricorsione (vedi Algoritmi ricorsiviAlgoritmi ricorsivi come induzione eseguita; albero della ricorsione e record di attivazione nello stack; esempi ReverseArray, LinearSum, Power in tempo logaritmico, Fibonacci ricorsivo (esponenziale) e con memoizzazione; analisi della complessità con l'albero della ricorsione e correttezza per induzione.Algoritmi ricorsivi → e RicorsioneFunzioni che chiamano se stesse: caso base e passo ricorsivo, stack delle chiamate, esempi su numeri, stringhe e liste, ricorsione multipla e suo costo, divide et impera, confronto con l'iterazione.Ricorsione →), alberi di decisione, codici di Huffman, alberi filogenetici.

Definizione

Definizione (albero radicato). Un albero radicato TT è una collezione di nodi che, se non vuota, soddisfa:

  • esiste un nodo speciale rr, la radice;
  • ogni v≠rv \ne r ha un unico padre uu (vv è figlio di uu);
  • risalendo di padre in padre da ogni nodo si arriva a rr.

Definizione ricorsiva: T={r}∪T1∪⋯∪TkT = \{r\} \cup T_1 \cup \dots \cup T_k con k≥0k \ge 0, dove T1,…,TkT_1, \dots, T_k sono alberi non vuoti le cui radici sono figlie di rr.

Esempio. Un albero con radice rr, figli aa e bb e un nodo cc figlio di aa ha n=4n=4 nodi; le foglie sono bb e cc.

La definizione ricorsiva è la stessa idea della ricorsione sui dati: un albero è una radice più alberi più piccoli, e per questo quasi tutte le dimostrazioni sugli alberi usano l'induzione (Principio di induzionePer dimostrare una proprietà per ogni n basta il passo base e il passo induttivo da n a n+1.Principio di induzione →, Dimostrazioni, induzione e invariantiTecniche di dimostrazione (esempio, controesempio, assurdo), induzione con casi base multipli, invarianti di ciclo (inizializzazione, conservazione, uso alla fine), schema generale per provare la correttezza; esempi svolti su arrayMax, sequenza di bit e numeri di Fibonacci.Dimostrazioni, induzione e invarianti →) e quasi tutti gli algoritmi sono ricorsivi. Un albero è anche un caso particolare di grafo connesso e senza cicli (vedi Grafi - definizioni e proprietàGrafo G=(V,E) diretto e non diretto, grafo semplice e pesato; incidenza, adiacenza, grado; cammini, cicli, sottografi, grafi connessi e componenti connesse; alberi liberi, foreste, spanning tree e spanning forest; proprietà con dimostrazioni (somma dei gradi = 2m, m <= n(n-1)/2, alberi m = n-1, connessi m >= n-1, foreste m <= n-1).Grafi - definizioni e proprietà →); la lista concatenata (Liste concatenateLista concatenata semplice: nodi allocati dinamicamente collegati da puntatori; inserimento e rimozione in testa, scorrimento, ricerca, inserimento ordinato, deallocazione; confronto dei costi con l'array; versione in Python.Liste concatenate →) è l'albero in cui ogni nodo ha al più un figlio.

Albero di riferimento per gli esempi di questa pagina e di 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 →: radice rr con figli a,ba, b; aa ha figli c,dc, d; dd ha figlio ee. Ha n=6n=6 nodi, foglie b,c,eb, c, e.

Terminologia

Definizione (terminologia degli alberi)

Termine Significato
antenato di yy x=yx = y oppure xx antenato del padre di yy
discendente di yy xx tale che yy è antenato di xx
nodo interno nodo con almeno un figlio
nodo esterno o foglia nodo senza figli
sottoalbero TvT_v vv con tutti i suoi discendenti
albero ordinato per ogni nodo interno è fissato un ordine lineare tra i figli

Esempio. Nello stesso albero rr è antenato di cc, il sottoalbero TaT_a è {a,c}\{a,c\}, aa è un nodo interno e bb, cc sono foglie.

Dato un albero ordinato, due nodi u,vu, v allo stesso livello: uu è a sinistra di vv se uu precede vv nella visita in preorder (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 →).

Profondità, livello, altezza

Definizione (profondità, livello e altezza)

  • Profondità depth(v)\text{depth}(v) = numero di antenati di vv meno 1; ricorsivamente: depth(r)=0\text{depth}(r) = 0, altrimenti 1+depth(padre(v))1 + \text{depth}(\text{padre}(v)).
  • Livello ii = insieme dei nodi a profondità ii.
  • Altezza di un nodo: height(v)=0\text{height}(v) = 0 se vv è foglia, altrimenti 1+max⁡w figlio di vheight(w)1 + \max_{w \text{ figlio di } v} \text{height}(w). Altezza dell'albero: height(T)=height(r)\text{height}(T) = \text{height}(r).

Esempio. Nello stesso albero depth(c)=2\text{depth}(c)=2, height(a)=1\text{height}(a)=1 e height(T)=height(r)=2\text{height}(T)=\text{height}(r)=2.

Sull'albero di riferimento (rr; a,ba,b figli di rr; c,dc,d figli di aa; ee figlio di dd):

nodo rr aa bb cc dd ee
padre — rr rr aa aa dd
profondità 0 1 1 2 2 3
altezza 3 2 0 0 1 0
figli cuc_u 2 2 0 0 1 0

La profondità si ottiene salendo (quanti archi fino alla radice), l'altezza scendendo (quanti archi fino alla foglia più lontana): height(d)=1+max⁡{height(e)}=1+0=1\text{height}(d)=1+\max\{\text{height}(e)\}=1+0=1, height(a)=1+max⁡{0,1}=2\text{height}(a)=1+\max\{0,1\}=2, height(r)=1+max⁡{2,0}=3\text{height}(r)=1+\max\{2,0\}=3. Livelli: livello 0={r}0=\{r\}, 1={a,b}1=\{a,b\}, 2={c,d}2=\{c,d\}, 3={e}3=\{e\}. La somma dei figli è 2+2+0+0+1+0=5=n−12+2+0+0+1+0=5=n-1.

Proposizione (altezza e profondità delle foglie). Se TT non è vuoto, height(T)=max⁡{depth(v):v foglia}\text{height}(T) = \max\{\text{depth}(v) : v \text{ foglia}\}.

Esempio. Le foglie bb e cc hanno profondità 11 e 22, quindi height(T)=max⁡{1,2}=2\text{height}(T)=\max\{1,2\}=2. Sull'albero di riferimento le foglie b,c,eb,c,e hanno profondità 1,2,31,2,3 e height(T)=3\text{height}(T)=3.

Dimostrazione (induzione sull'altezza). Se TT è la sola radice, l'altezza è 00 e l'unica foglia è rr, di profondità 00. Altrimenti T={r}∪T1∪⋯∪TkT = \{r\} \cup T_1 \cup \dots \cup T_k: ogni foglia di TT sta in un TiT_i e ha profondità in TT pari a 11 più la profondità in TiT_i. Per ipotesi induttiva la massima profondità delle foglie di TiT_i è height(Ti)\text{height}(T_i), quindi la massima profondità delle foglie di TT è 1+max⁡iheight(Ti)=height(r)1 + \max_i \text{height}(T_i) = \text{height}(r). □\square

Interfaccia e implementazione

Metodi tipici: root(), parent(v), children(v), isRoot(v), isInternal(v), isExternal(v), size(). L'implementazione a struttura collegata dà a ogni nodo un riferimento al padre e alla collezione dei figli; spazio Θ(n)\Theta(n) e children(v) in tempo proporzionale al numero di figli.

Algoritmi su profondità e altezza

Profondità (ricorsivo, risalendo):

Algoritmo depth(T, v)
Input: nodo v di T          Output: profondità di v
if T.isRoot(v) then return 0
else return 1 + depth(T, T.parent(v))

Costo Θ(depth(v)+1)\Theta(\text{depth}(v) + 1), al caso pessimo Θ(n)\Theta(n) (albero ridotto a un cammino). Versione iterativa: si sale con un ciclo contando i passi.

Altezza di un nodo (dal basso, usando le altezze dei figli):

Algoritmo height(T, v)
Input: nodo v di T          Output: altezza di v
h <- 0
foreach w in T.children(v) do h <- max(h, 1 + height(T, w))
return h

Costo: height\text{height} è chiamato una volta per nodo; la chiamata su uu costa Θ(1+cu)\Theta(1 + c_u) con cuc_u numero di figli. Poiché in un albero con nn nodi ∑ucu=n−1\sum_{u} c_u = n - 1 (ogni nodo non radice ha un solo padre), il totale è ∑u(1+cu)=2n−1∈Θ(n)\sum_u (1 + c_u) = 2n - 1 \in \Theta(n). È una visita in postorder (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 →). L'uguaglianza ∑ucu=n−1\sum_u c_u = n-1 si vede contando gli archi in due modi: ogni arco collega un padre a un figlio, quindi il numero di archi è ∑ucu\sum_u c_u; ma ogni nodo tranne la radice ha esattamente un padre, cioè esattamente un arco che lo collega verso l'alto, quindi gli archi sono n−1n-1. Sull'albero di riferimento: ∑u(1+cu)=6+5=11=2⋅6−1\sum_u(1+c_u)=6+5=11=2\cdot6-1 chiamate-passi.

Una strategia lenta (heightBad\text{heightBad}). Calcolare l'altezza come massima depth tra le foglie, invocando depth da ciascuna foglia, è corretto per la proposizione ma costa Ω(n2)\Omega(n^2). Istanza cattiva: un cammino di n/2n/2 nodi con n/2n/2 foglie attaccate al nodo più basso; ognuna delle n/2n/2 foglie ha profondità ≥n/2\ge n/2, e ogni chiamata a depth costa almeno questo: n2⋅n2=n24\frac n2 \cdot \frac n2 = \frac{n^2}{4} operazioni (per n=40n=40: 2020 foglie a profondità ≥20\ge 20, quindi ≥400\ge 400 passi contro i 2n−1=792n-1=79 di height). Il grafico confronta le due crescite: la prima è quadratica (si veda Notazione asintoticaDefinizioni di O, Omega, Theta e o piccolo con le costanti c ed n0; esempi con costanti esplicite; proprietà (polinomi, esponenziali, logaritmi, somme, implicazioni tra notazioni); sommatorie notevoli; terminologia (logaritmica, lineare, polinomiale, esponenziale).Notazione asintotica →), la seconda lineare.

Grafico interattivo: Costo di heightBad sull'istanza cattiva (almeno n²/4 passi) contro height (2n − 1 passi): a n = 40 sono 400 contro 79, e il rapporto cresce con n

Errori comuni

  • Confondere profondità e altezza di un nodo: la prima si conta dalla radice in giù, la seconda dalla foglia più lontana in su. In una foglia l'altezza è 00, la profondità no.
  • Dire che l'altezza dell'albero è il numero di nodi o di livelli: è il numero di archi del cammino più lungo (un albero con la sola radice ha altezza 00).
  • Stimare height come Θ(n⋅figli)\Theta(n \cdot \text{figli}): il costo totale è Θ(n)\Theta(n) perché ∑cu=n−1\sum c_u = n-1.

Versione ripasso

  • Albero radicato: radice rr; ogni v≠rv \ne r ha un unico padre; risalendo da ogni nodo si arriva a rr. Definizione ricorsiva: T={r}∪T1∪⋯∪TkT = \{r\} \cup T_1 \cup \dots \cup T_k, con k≥0k \ge 0.
  • Terminologia: antenato di yy (x=yx = y oppure antenato del padre di yy); discendente; nodo interno (almeno un figlio); foglia (nessun figlio); sottoalbero TvT_v; albero ordinato (ordine fisso tra i figli).
  • Profondità: depth(r)=0\text{depth}(r) = 0, depth(v)=1+depth(padre(v))\text{depth}(v) = 1 + \text{depth}(\text{padre}(v)). Livello ii: nodi a profondità ii.
  • Altezza: height(v)=0\text{height}(v) = 0 se vv è foglia, altrimenti 1+max⁡w figlio di vheight(w)1 + \max_{w \text{ figlio di } v} \text{height}(w). Altezza dell'albero: height(r)\text{height}(r), cioè il numero di archi del cammino più lungo.
  • Proposizione: height(T)=max⁡{depth(v):v foglia}\text{height}(T) = \max\{\text{depth}(v) : v \text{ foglia}\}, per induzione sull'altezza.
  • Somma dei figli: ∑ucu=n−1\sum_u c_u = n - 1, perché ogni nodo non radice ha un solo padre.
  • Interfaccia: root, parent, children, isRoot, isInternal, isExternal, size. Struttura collegata: spazio Θ(n)\Theta(n).
  • Algoritmi (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 →):
    • depth(T,v): radice →0\to 0, altrimenti 1+depth(parent)1 + \text{depth}(\text{parent}); costo Θ(depth(v)+1)\Theta(\text{depth}(v)+1), al caso pessimo Θ(n)\Theta(n);
    • height(T,v): h←0h \leftarrow 0, per ogni figlio ww: h←max⁡(h,1+height(w))h \leftarrow \max(h, 1 + \text{height}(w)); postorder con costo ∑u(1+cu)=2n−1∈Θ(n)\sum_u (1 + c_u) = 2n - 1 \in \Theta(n);
    • heightBad (depth chiamato da ogni foglia): Ω(n2)\Omega(n^2) sul cammino di n/2n/2 nodi con n/2n/2 foglie in fondo; per n=40n = 40 almeno 400400 passi contro 7979.
  • Albero di riferimento: radice rr con figli a,ba, b; aa ha figli c,dc, d; dd ha figlio ee. Ha n=6n = 6 nodi, foglie b,c,eb, c, e, altezza 33; profondità 0,1,1,2,2,30,1,1,2,2,3.
  • Somma dei figli sull'albero di riferimento: 2+2+0+0+1+0=5=n−12+2+0+0+1+0 = 5 = n-1. Costo di height sullo stesso albero: ∑u(1+cu)=11=2n−1\sum_u (1 + c_u) = 11 = 2n-1.
  • Errori tipici: profondità (dalla radice in giù) contro altezza (dalla foglia in su); altezza uguale al numero di nodi o di livelli (è il numero di archi, radice sola =0= 0); stimare height come Θ(n⋅figli)\Theta(n \cdot \text{figli}).

Esercizi su questo argomento

Teoria collegata