Gli esercizi presentati faranno riferimento alla UNIT "alberi" presentata nel corso di di Programmazione II.
Un albero binario si dice completo se:
Sviluppiamo l'algoritmo procedendo come illustrato la scorsa lezione.
{AI: t e' inizializzato e "contiene" l'albero T} {AF: restituisce true se T e' completo, false altrimenti} function c (t:tree):boolean;
Consideriamo la funzione "profondita' di un albero binario", con la seguente intestazione:
{AI: t e' inizializzato e "contiene" l'albero T} {AF: restituisce la profondita' di T} function p (t:tree):integer;e la funzione "massimo di due interi", con la seguente intestazione:
{AI: a, b inizializzati} {AF: restituisce il massimo tra a e b} function max (a,b:integer):integer;Possiamo ora scrivere l'algoritmo c:
c([]) = true c([n,L,R]) = c(L) and c(R) and (p(L)=p(R))e l'algoritmo p:
p([]) = 0 p([n,L,R]) = max(p(L),p(R))
Passiamo immediatamente alla fase successiva.
{AI: t e' inizializzato e "contiene" l'albero T} {AF: restituisce la profondita' di T} function p (t:tree):integer; {SPECIFICA DELL'ALGORITMO: p([]) = 0 p([n,L,R]) = max(p(L),p(R)) } begin if t=nil then p:=0 else p:= 1+max(p(t^.left), p(t^.right)); end; end;
PRIMA DI ESAMINARE IL CODICE Pascal, CHIDIAMOCI SE NON SIA POSSIBILE MIGLIORARE LA PROCEDURA INTERVENENDO SULLA SPECIFICA AD "alto livello". FORSE SIAMO PASSATI TROPPO PRESTO AL CODICE Pascal...
+_{0<=k<=h-1} (2^k * 2 * (2^(h-(k+1)+1)-1))
=
+_{0<=k<=h-1} (2^(k+1) * (2^(h-k)-1))
=
+_{0<=k<=h-1} (2^(h+1)-2^(k+1))
=
h*2^(h+1) - (+_{0<=k<=h-1} 2^(k+1))
=
h*2^(h+1) - (2+2^2+2^3+...+2^h)
=
h*2^(h+1) - (2^(h+1)-2)
=
(h-1)*2^(h+1) +2
=
2 * ((h-1)*2^h + 1)
= (siccome 2^h - 1 = m)
2 * ( ((log (m-1))-1) * (m+1) + 1 )
Che e' un:
O ( m * (log m) )
La soluzione precedente ha quindi complessita' temporale O ( m * (log m) ), dove m e' il numero di nodi dell'albero completo in input. Ora presentiamo una nuova soluzione che ha complessita' temporale lineare in m.
{AI: t e' inizializzato e "contiene" l'albero T} {AF: restituisce true se T e' completo, false altrimenti} function c (t:tree):boolean;
Consideriamo una funzione "ausiliaria", con la seguente intestazione:
{AI: t e' inizializzato e "contiene" l'albero T} {AF: restituisce la profondita' di T se T e' completo, altrimenti restituisce -1} function a (t:tree):integer;Possiamo ora scrivere l'algoritmo c:
c(T) = (a(T) <> -1)e l'algoritmo a:
a([]) = 0 a([n,L,R]) = sia l = a(L) r = a(R) in se (l=-1) allora -1 altrimenti se (l<>r) allora -1 altrimenti max(l,r)
Passiamo immediatamente alla fase successiva.
{AI: t e' inizializzato e "contiene" l'albero T} {AF: restituisce la profondita' di T se T e' completo, altrimenti restituisce -1} function a (t:tree):integer; {SPECIFICA DELL'ALGORITMO: a([]) = 0 a([n,L,R]) = sia l = a(L) r = a(R) in se (l=-1) allora -1 altrimenti se (l<>r) allora -1 altrimenti 1+max(l,r) } vat l,r:integer; begin if t=nil then a:=0 else begin l:=a(t^.left); r:=a(t^.right); if l=-1 then a:=-1 else if l<>r then a:=-1 else a:= 1+max(l,r); end; end; {AI: t e' inizializzato e "contiene" l'albero T} {AF: restituisce true se T e' completo, false altrimenti} function c (t:tree):boolean; begin c := (a(t)<>-1) end;
Esercizi 1. Realizzare una funzione che soddisfi la seguente specifica: {AI: t e' inizializzato e "contiene" l'albero T} {AF: restituisce true se T e' bilanciato, false altrimenti} function b (t:tree):boolean; 2. Realizzare una funzione che soddisfi la seguente specifica: {AI: t e' inizializzato e "contiene" l'albero T} {AF: restituisce true se T e' quasi perfettamente bilanciato, false altrimenti} function q (t:tree):boolean;