Sei sulla pagina 1di 59

ESERCIZI SVOLTI DI

ALGORITMI E STRUTTURE DATI


Livio Colussi
Dipartimento di Matematica Pura ed Applicata
Universit` a di Padova
7 marzo 2010
Esercizio 1 Descrivere un algoritmo che dato un array A[1 . . n] di interi ed un intero x
determina se esistono i e j tali che A[i] + A[j] = x. Lalgoritmo deve avere complessit` a
massima O(nlog n).
Soluzione.
CERCA-IJ(A, n)
1 for i = 1 to n do P[i] = i
2 Ordina larray P[1 . . n] usando HEAP-SORT(P, A, n) modicato in modo che i
confronti vengano effettuati tra A[P[i]] ed A[P[j]].
3 // P ordinato in modo che A[P[1]] A[P[2]] . . . A[P[n]]
4 i = 1, j = n
5 while i < j and A[P[i]] +A[P[j]] ,= x
6 if A[P[i]] + A[P[j]] < x then i = i + 1 else j = j 1
7 if i < j
8 return (P[i], P[j])
9 else return (0, 0)
Esercizio 2 Scrivere un algoritmo che calcola il numero di inversioni di un array A[1 . . n]
in tempo O(nlog n) nel caso pessimo. (Suggerimento: modicare MERGE-SORT.)
Soluzione. La versione modicata di MERGE-SORT ` e:
MERGE-SORT(A, n)
1 return MERGE-SORT-RIC(A, 1, n)
MERGE-SORT-RIC(A, p, r)
1 if p r
2 return 0
3 q = (p +r)/2|
4 inv1 = MERGE-SORT-RIC(A, p, q)
5 inv2 = MERGE-SORT-RIC(A, q + 1, r)
6 inv3 = MERGE(A, p, q, r)
7 return inv1 + inv2 + inv3
1
MERGE(A, p, q, r)
1 n1 = q p + 1
2 n2 = r q
3 for i = 1 to n1
4 L[i] = A[p +i 1]
5 for j = 1 to n2
6 R[j] = A[q +j]
7 L[n1 + 1] =
8 R[n2 + 1] =
9 inv = 0
10 i = j = 1
11 for k = p to r
12 if L[i] R[j]
13 A[k] = L[i], i = i + 1
14 else A[k] = R[j], j = j + 1
15 // Il numero di inversioni diminuisce di n1 i + 1
16 inv = inv + n1 i + 1
17 return inv
Esercizio 3 Usando la denizione di (g(n)) mostrare che:
a) se f(n) = (g(n)) allora anche g(n) = (f(n))
b) max(f(n), g(n)) = (f(n) +g(n))
c) (n +a)
b
= (n
b
) per ogni a e per ogni b > 0.
Soluzione.
a) se f(n) = (g(n)) esistono due costanti positive c
1
e c
2
ed un intero positivo N tali
che c
1
g(n) f(n) ed f(n) c
2
g(n) per ogni n N. Dividendo la prima disu-
guaglianza per c
1
si ottiene g(n)
1
c
1
f(n) e dividendo la seconda per c
2
si ottiene
1
c
2
f(n) g(n). Siccome
1
c
1
e
1
c
2
sono due costanti positive possiamo concludere
che g(n) = (f(n)).
b) Ricordiamo che consideriamo soltanto funzioni non negative. Dunque
max(f(n), g(n)) f(n) +g(n) 2 max(f(n), g(n)).
Possiamo quindi concludere che f(n) + g(n) = (max(f(n), g(n))) e quindi
max(f(n), g(n)) = (f(n) +g(n)) per il punto a).
c) Prendiamo N > 2[a[ in modo che n/2 n +a 2n per ogni n N. Se b < 1 la
funzione x
b
` e decrescente e quindi
2
b
n
b
= (2n)
b
(n +a)
b
(n/2)
b
= (1/2
b
)n
b
per ogni n N. Se b 1 la funzione x
b
` e crescente e quindi
(1/2
b
)n
b
= (n/2)
b
(n +a)
b
(2n)
b
= 2
b
n
b
per ogni n N. In entrambi i casi (n +a)
b
= (n
b
).
2
Esercizio 4 Ordinare le seguenti funzioni in modo che g
i
(n) = (g
i+1
) e poi raggrupparle
in classi (g
i
(n)) = (g
i+1
(n)) = . . . = (g
j
(n)) (log
2
n ` e il logaritmo in base 2 e ln n
` e il logaritmo naturale):
(

2)
log
2
n
n
2
n! (3/2)
n
n
3
log
2
2
n log
2
(n!) 2
2
n
n
1/ log
2
n
ln ln n n2
n
n
log
2
log
2
n
log
2
n 1 2
log
2
n
e
n
4
log
2
n
(n + 1)!

log
2
n 2

2 log
2
n
n 2
n
nlog
2
n 2
2
n+1
Soluzione. Una propriet` a utile per confrontare asintoticamente funzioni ` e la seguente:
1. Se lim
n
f(n)
g(n)
= allora f(n) = (g(n)) ma f(n) ,= (g(n));
2. Se lim
n
f(n)
g(n)
= 0 allora f(n) = O(g(n)) ma f(n) ,= (g(n));
3. Se lim
n
f(n)
g(n)
= c ,= 0 allora f(n) = (g(n)).
Attenzione: tale propriet` a ` e condizione sufciente ma non necessaria. Ad esempio il limite
lim
x
x(2 + sin x)
x
non esiste eppure x x(2 + sin x) 3x e quindi x(2 + sin x) = (x).
Useremo questa propriet` a per confrontare le funzioni dellesercizio.
1. Cominciamo confrontando 2
2
n+1
con 2
2
n
.
lim
n
2
2
n+1
2
2
n
= lim
n
2
2
n+1
2
n
= lim
n
2
2
n
=
e quindi 2
2
n+1
= (2
2
n
) ma 2
2
n+1
,= (2
2
n
).
2. Confrontiamo 2
2
n
con (n + 1)!. Intanto
(n + 1)! = (n + 1)n(n 1) . . . 3 2 2n n
n1
= 2n
n
= 2
nlog
2
n+1
e quindi 2
nlog
2
n+1
= ((n + 1)!). Poi
lim
n
2
2
n
2
nlog
2
n+1
= lim
n
2
2
n
nlog
2
n1
=
e quindi 2
2
n
= (2
nlog
2
n+1
) = ((n + 1)!) ma 2
2
n
,= ((n + 1)!).
3. Confrontiamo (n + 1)! con n!.
lim
n
(n + 1)!
n!
= lim
n
(n + 1) =
e quindi (n + 1)! = (n!) ma (n + 1)! ,= (n!).
3
4. Confrontiamo n! con e
n
. Intanto
n! (n/2)
(n/2)
= 2
(n/2) log
2
(n/2)
(perch e in n! ci sono n/2 fattori maggiori o uguali di n/2) ed e
n
= 2
nlog
2
e
. Inoltre
lim
n
2
(n/2) log
2
(n/2)
2
nlog
2
e
= lim
n
2
(n/2) log
2
(n/2)n log
2
e
=
e quindi n! = (2
(n/2) log
2
(n/2)
) = (e
n
) ma n! ,= (e
n
).
5. Confrontiamo e
n
con n2
n
.
lim
n
e
n
n2
n
= lim
n
2
nlog
2
e
2
n+log
2
n
= lim
n
2
(log
2
e1)nlog
2
n
=
e quindi e
n
= (n2
n
) ma e
n
,= (n2
n
).
6. Confrontiamo n2
n
con 2
n
.
lim
n
n2
n
2
n
= lim
n
n =
e quindi n2
n
= (2
n
) ma n2
n
,= (2
n
).
7. Confrontiamo 2
n
con (3/2)
n
.
lim
n
2
n
(3/2)
n
= lim
n
(4/3)
n
=
e quindi 2
n
= ((3/2)
n
) ma 2
n
,= ((3/2)
n
).
8. Confrontiamo (3/2)
n
con n
log
2
log
2
n
. Intanto
(3/2)
n
= 2
nlog
2
(3/2)
e n
log
2
log
2
n
= 2
log
2
nlog
2
log
2
n
Inoltre
lim
n
2
nlog
2
(3/2)
2
log
2
nlog
2
log
2
n
= lim
n
2
nlog
2
(3/2)log
2
nlog
2
log
2
n
=
e quindi (3/2)
n
= (n
log
2
log
2
n
) ma (3/2)
n
,= (n
log
2
log
2
n
).
9. Confrontiamo n
log
2
log
2
n
con n
3
.
lim
n
n
log
2
log
2
n
n
3
= lim
n
n
log
2
log
2
n3
=
e quindi n
log
2
log
2
n
= (n
3
) ma n
log
2
log
2
n
,= (n
3
).
10. Confrontiamo n
3
con n
2
.
lim
n
n
3
n
2
= lim
n
n =
e quindi n
3
= (n
2
) ma n
3
,= (n
2
).
4
11. Confrontiamo n
2
con 4
log
2
n
. Siccome
4
log
2
n
= 2
log
2
n
2
log
2
n
= n n = n
2
ovviamente n
2
= (4
log
2
n
).
12. Confrontiamo n
2
con nlog
2
n.
lim
n
n
2
nlog
2
n
=
e quindi nlog
2
n = (n
2
) ma nlog
2
n ,= (n
2
).
13. Confrontiamo nlog
2
n con log
2
(n!). Intanto (n/2)
(n/2)
n! n
n
e quindi
(n/2) log
2
(n/2) log
2
(n!) nlog
2
n
Inoltre (n/2) log
2
(n/2) =
1
2
n(log
2
n 1) e, per n > 4,
log
2
n
2
1. Dunque, per
n > 4,
1
4
nlog
2
n log
2
(n!) nlog
2
n
e quindi log(n!) = (nlog
2
n).
14. Confrontiamo nlog
2
n con n.
lim
n
nlog
2
n
n
= lim
n
log
2
n =
e quindi nlog
2
n = (n) ma nlog
2
n ,= (n).
15. Confrontiamo n con 2
log
2
n
. Siccome 2
log
2
n
= n ovviamente n = (2
log
2
n
).
16. Confrontiamo n con (

2)
log
2
n
. Siccome
(

2)
log
2
n
= 2
log
2
(

2) log
2
n
= 2
1/2 log
2
n
=

n
abbiamo
lim
n
n
(

2)
log
2
n
= lim
n

n =
e quindi n = ((

2)
log
2
n
) ma n ,= ((

2)
log
2
n
).
17. Confrontiamo (

2)
log
2
n
con 2

2 log
2
n
. Siccome (

2)
log
2
n
= 2
1/2 log
2
n
lim
n
(

2)
log
2
n
2

2 log
2
n
= lim
n
2
1/2 log
2
n

2 log
2
n
=
e quindi (

2)
log
2
n
= (2

2 log
2
n
) ma (

2)
log
2
n
,= (2

2 log
2
n
).
18. Confrontiamo 2

2 log
2
n
con log
2
2
n. Siccome log
2
2
n = 2
log
2
(log
2
2
n)
= 2
2 log
2
log
2
n
lim
n
2

2 log
2
n
log
2
2
n
= lim
n
2

2 log
2
n2 log
2
log
2
n
=
e quindi 2

2 log
2
n
= (log
2
2
n) ma 2

2 log
2
n
,= (log
2
2
n).
5
19. Confrontiamo log
2
2
n con log
2
n.
lim
n
log
2
2
n
log
2
n
= lim
n
log
2
n =
e quindi log
2
2
n = (log
2
n) ma log
2
2
n ,= (log
2
n).
20. Confrontiamo log
2
n con

log
2
n.
lim
n
log
2
n

log
2
n
= lim
n

log
2
n =
e quindi log
2
n = (

log
2
n) ma log
2
n ,= (

log
2
n).
21. Confrontiamo

log
2
n con log
2
log
2
n.
Usando la sostituzione di variabile x = log
2
n
lim
n

log
2
n
log
2
log
2
n
= lim
x

x
log
2
x
=
e quindi

log
2
n = (log
2
log
2
n) ma

log
2
n ,= (log
2
log
2
n).
22. Confrontiamo log
2
log
2
n con 1.
lim
n
log
2
log
2
n
1
= lim
x
log
2
log
2
n =
e quindi log
2
log
2
n = (1) ma log
2
log
2
n ,= (1).
23. Inne n
1/ log
2
n
= 2
log
2
n(1/ log
2
n)
= 2 e quindi n
1/ log
2
n
= (1).
Lordine delle classi di appartenenza delle funzioni ` e il seguente:
(2
2
n+1
) > (2
2
n
) > ((n + 1)!) > (n!) > (e
n
) > (n2
n
) >
(2
n
) > ((3/2)
n
) > (n
log
2
log
2
n
) > (n
3
) > (n
2
) = (4
log
2
n
) >
(nlog
2
n) = (log
2
(n!)) > (n) = (2
log
2
n
) > ((

2)
log
2
n
) > (2

2 log
2
n
) >
(log
2
2
n) > (log
2
n) > (

log
2
n) > (log
2
log
2
n) > (1) = (n
1/ log
2
n
)
Esercizio 5 Abbiamo visto che T(n) = bnlog
2
n+(c+a)na ` e soluzione della ricorrenza
T(x) =

c se x 1
bx +a + 2T(x/2) se x > 1
Dimostrare che la soluzione della ricorrenza
T
QS
min
(n) =

c se n 1
bn +a + 2T
QS
min
(n/2|) se n > 1
che esprime la complessit` a minima dellalgoritmo QUICK-SORT ` e limitata inferiormente
da T
QS
min
(n) = (T(n)) = (nlog n).
6
Soluzione. Osserviamo intanto che se n = 2
k
` e potenza di 2 allora n/2
i
| = n/2
i
per
ogni i = 1, . . . , k e quindi T
QS
min
(n) = T(n).
Proviamo ora, per induzione su n, che T
QS
min
(n + 1) > T
QS
min
(n). Intanto
T
QS
min
(2) = 2b +a + 2T
QS
min
(1) > T
QS
min
(1)
Per n 2 assumiamo induttivamente che T
QS
min
(k + 1) > T
QS
min
(k) per ogni k < n. Se n ` e
pari
T
QS
min
(n + 1) = b(n + 1) +a + 2T
QS
min
((n + 1)/2|)
= b(n + 1) +a + 2T
QS
min
(n/2|)
= b +T
QS
min
(n) > T
QS
min
(n)
Se n ` e dispari
T
QS
min
(n + 1) = b(n + 1) +a + 2T
QS
min
((n + 1)/2|)
= b(n + 1) +a + 2T
QS
min
(n/2| + 1)
> bn +a + 2T
QS
min
(n/2|) = T
QS
min
(n)
A questo punto siamo in grado di concludere
T
QS
min
(n) T
QS
min
(2
log
2
n
) = T(2
log
2
n
)
= b2
log
2
n
log
2
n| + (c +a)2
log
2
n
a
b2
log
2
n1
(log
2
n 1) + (c +a)2
log
2
n1
a
=
1
2
bnlog
2
n
1
2
bn +
1
2
(c +a)n a
= (nlog
2
n)
Esercizio 6 Usare il metodo dellesperto per risolvere le seguenti ricorrenze:
1. T(n) = 4T(n/2) +n
2. T(n) = 4T(n/2) +n
2
3. T(n) = 4T(n/2) +n
3
Soluzione.
In tutti e tre i casi abbiamo n
log
b
a
= n
log
2
4
= n
2
.
Per la prima, preso =
1
2
abbiamo f(n) = n = O(n
3
2
) = O(n
log
b
a
). Quindi
T(n) = (n
2
).
Per la seconda f(n) = n
2
= (n
log
b
a
) e pertanto T(n) = (n
2
log
2
n).
Per la terza, preso =
1
2
, abbiamo f(n) = n
3
= (n
5
2
) = (n
log
b
a+
). Inoltre,
af(
n
b
) = 4(
n
2
)
3
=
n
3
2
=
1
2
f(n). Quindi T(n) = (n
3
).
Esercizio 7 La ricorrenza T(n) = 4T(n/2) +n
2
log n si pu` o risolvere con il metodo del-
lesperto? Giusticare la risposta. Se la risposta ` e negativa usare il metodo di sostituzione
per dimostrare che T(n) = O(n
2
log
2
n).
7
Soluzione.
In questo caso n
log
b
a
= n
log
2
4
= n
2
. Siccome f(n) = n
2
log n = (n
2
) = (n
log
b
a
)
ed f(n) ,= (n
log
b
a
) non si possono applicare i primi due casi del metodo dellesperto.
Inoltre, per ogni > 0, f(n) = n
2
log n = O(n
2+
) = (n
log
b
a+
) e quindi non possiamo
applicare neppure il terzo caso.
Proviamo T(n) = O(n
2
log
2
n). Assumiamo che per una opportuna costante C > 1
e per ogni x < n sia vericata la disuguaglianza T(x) C(x
2
log
2
x) e dimostriamo che
essa vale anche per n.
T(n) = 4T(n/2) +n
2
log n
4C(n/2)
2
log
2
(n/2) +n
2
log n
= Cn
2
(log n 1)
2
+n
2
log n
= Cn
2
(log
2
n 2 log n + 1) +n
2
log n
= Cn
2
log
2
n 2Cn
2
log n +Cn
2
+n
2
log n
= Cn
2
log
2
n (C 1)n
2
log n Cn
2
(log n 1)
Cn
2
log
2
n
Dunque T(n) = O(n
2
log
2
n).
Esercizio 8 Trovare limiti asintotici superiori ed inferiori il pi` u possibile stretti per le
seguenti ricorrenze.
1. T(n) = 2T(n/2) +n
3
2. T(n) = T(9n/10) +n
3. T(n) = 16T(n/4) +n
2
4. T(n) = 7T(n/3) +n
2
5. T(n) = 7T(n/2) +n
2
6. T(n) = 2T(n/4) +

n
7. T(n) = nT(n 1)
8. T(n) = T(

n) + 1
Soluzione. Le prime 6 si risolvono con il metodo dellesperto, la settima si risolve con
una sommatoria e lultima con una sostituzione di variabile.
1. n
log
b
a
= n
log
2
2
= n. Preso =
1
2
f(n) = n
3
= (n
3
2
) = (n
log
b
a+
).
Inoltre, af(
n
b
) = 2(
n
2
)
3
=
n
3
4
=
1
4
f(n). Quindi T(n) = (n
3
).
2. n
log
b
a
= n
log
10/9
1
= 1. Preso =
1
2
f(n) = n = (n
1
2
) = (n
log
b
a+
).
Inoltre, af(
n
b
) =
9n
10
=
9
10
f(n). Quindi T(n) = (n).
8
3. n
log
b
a
= n
log
4
16
= n
2
= f(n). Quindi T(n) = (n
2
log n).
4. n
log
b
a
= n
log
3
7
. Preso = 2 log
3
7, f(n) = n
2
= (n
log
b
a+
). Inoltre, af(
n
b
) =
7(
n
3
)
2
=
7
9
f(n). Quindi T(n) = (n
2
).
5. n
log
b
a
= n
log
2
7
. Preso = log
2
7 2, f(n) = n
2
= O(n
log
b
a
). Quindi T(n) =
(n
log
2
7
).
6. n
log
b
a
= n
log
4
2
=

n = f(n). Quindi T(n) = (

nlog n).
7. T(n) = nT(n 1) = n(n 1)T(n 2) = n!T(1) = (n!).
8. Con la sostituzione di variabile x = log
2
n si ottiene T(2
x
) = T(2
x/2
) +1. Poniamo
T

(x) = T(2
x
). Allora T

(x) soddisfa la ricorrenza


T

(x) = T

(x/2) + 1
che si risolve con il metodo dellesperto. Infatti
x
log
b
a
= x
log
2
1
= 1 = (f(x))
e quindi T

(x) = (log x). Pertanto


T(n) = T(2
log
2
n
) = T

(log
2
n) = (log log n).
Esercizio 9 Mostrare che, se gli elementi dellarray sono tutti distinti, lalgoritmo HEAP-
SORT ha complessit` a minima (nlog n).
Soluzione.
Lalgoritmo HEAP-SORT ` e il seguente:
HEAP-SORT(A, n)
1 for i = n/2| downto 1
2 HEAPFY(A, i, n)
3 for i = n downto 2
4 t = A[i], A[i] = A[1], A[1] = t
5 HEAPFY(A, 1, i 1)
HEAPFY(A, j, n)
1 k = j
2 if 2j + 1 n and A[2j + 1] > A[k]
3 k = 2j + 1
4 if 2j n and A[2j] > A[k]
5 k = 2j
6 if k ,= j
7 t = A[i], A[i] = A[k], A[k] = t
8 HEAPFY(A, k, n)
9
Siccome i valori sono tutti distinti, possiamo suddividere gli n elementi dellarray A
in due gruppi: il gruppo A
1
che contiene gli ,n/2| elementi pi` u grandi (e che vengono
estratti dallo heap nei primi ,n/2| passi del secondo ciclo for di HEAP-SORT) e il gruppo
A
2
che contiene gli n/2| elementi pi` u piccoli (e che rimangono nello heap dopo i primi
,n/2| passi del secondo ciclo for di HEAP-SORT).
Un heap di n nodi ha altezza h = ,log
2
n|, ha ,n/2| foglie a profondit` a i log
2
n| ed
n/2| nodi interni. Sia B linsieme dei nodi interni che hanno soltanto foglie come gli. I
nodi in B sono le foglie dello heap di n/2| nodi che si ottiene dallo heap iniziale togliendo
tutte le sue ,n/2| foglie. B contiene ,n/2|/2| nodi e i suoi nodi sono a profondit` a
i log
2
n| 1. Al pi` u un solo nodo in B ha un solo glio mentre tutti gli altri ne hanno
due. Sia m = [B A
2
[ il numero di nodi in B che appartengono al gruppo A
2
. Al pi` u
uno di tali nodi ha un solo glio mentre tutti gli altri ne hanno due. Siccome i gli di
nodi in A
2
appartengono ad A
2
abbiamo che 3m 1 ,n/2| e dunque m
n/21
3
. Di
conseguenza i nodi in B A
1
sono ,n/2|/2| m (n 2)/12.
I nodi in BA
1
vengono tutti estratti durante i primi ,n/2| passi del secondo ciclo for
di HEAP-SORT. Siccome nessuno di essi ` e una foglia ciascuno di essi deve risalire no
alla radice di livello in livello per effetto di uno scambio con il padre. Siccome essi sono
a profondit` a i log
2
n| 1 occorrono almeno (log
2
n| 1)(n 2)/12 = (nlog n)
scambi.
Esercizio 10 Realizzare una operazione DELETE(S, i) che toglie lelemento S[i] dallo
heap S e che opera in tempo O(log n).
Soluzione.
DELETE(S, i)
1 x = S[i]
2 S[i] = S[n]
3 n = n 1
4 if x > S[i]
5 HEAPFY(S, i, n)
6 else HEAPFY-REV(S, i)
HEAPFY-REV(S, i)
1 while i > 1 and S[i] > S[i/2|]
2 t = S[i]
3 S[i] = S[i/2|]
4 S[i/2|] = t
5 i = i/2|
Esercizio 11 Sia dato un array bidimensionale A di m righe ed n colonne. Supponendo
che ciascuna riga sia ordinata in ordine crescente descrivere un algoritmo che riunisce le
m righe di A in ununico array ordinato B di nm elementi. Lalgoritmo deve richiedere
tempo O(nmlog m). (Suggerimento: mantenere il primo elemento non ancora copiato in
B di ciascuna riga in un heap H di m elementi).
Soluzione. Usiamo un min-heap H in cui la radice ` e il minimo elemento. Ogni elemen-
to in H ha per chiave il primo elemento non ancora ricopiato di una riga di A e come
informazione associata lindice della riga.
10
MERGE-MULTIPLO(A, m, n)
1 H =
2 for i = 1 to m
3 INSERT(H, A[i, 1], i)
4 k[i] = 2
5 // k[i] ` e lindice del prossimo elemento nella riga i-esima.
6 for j = 1 to nm
7 (B[j], i) = EXTRACT-MIN(H)
8 if k[i] n
// La riga i-esima non ` e ancora nita.
9 INSERT(H, A[i, k[i]], i)
10 k[i] = k[i] + 1
11 return B
Lo heap contiene al massimo m elementi e le operazioni INSERT ed EXTRACT-MIN
richiedono tempo O(log m). Lalgoritmo richiede quindi tempo
T(n, m) = mO(log m) +nmO(log m) = O(nmlog m)
Esercizio 12 Spiegare perch e per gli algoritmi randomizzati ` e importante analizzare la
complessit` a media mentre complessit` a minima e massima sono poco signicative.
Soluzione. Il tempo richiesto dagli algoritmi randomizzati non dipende dallinput e quindi
non ` e possibile caratterizzare il caso pessimo ed il caso ottimo. Ogni input pu` o dare origine
alle stesse computazioni con uguale probabilit` a.
Esercizio 13 Laltezza di un albero binario ` e la lunghezza massima di un cammino dalla
radice ad una foglia. Dimostrare che un albero binario con n foglie ha altezza maggiore o
uguale di log
2
n.
Soluzione. Per induzione su n. Se n = 1 lalbero ha altezza 0 = log
2
1. Se n > 1 lalbero
T ha una radice e due sottoalberi T
1
e T
2
ed almeno uno di essi, diciamo T
1
, ha un numero
di foglie maggiore o uguale di n/2. Per ipotesi induttiva T
1
ha altezza maggiore o uguale
di log
2
(n/2) = log
2
n 1. Di conseguenza T ha altezza maggiore o uguale di log
2
n.
Esercizio 14 (*) Laltezza media di un albero binario con n foglie ` e la lunghezza media
dei cammini dalla radice alle foglie. Dimostrare che laltezza media di un albero binario
con n foglie ` e maggiore o uguale di log
2
n.
Soluzione. Per induzione su n. Se n = 1 lalbero ha altezza 0 = log
2
1. Se n > 1 lalbero
T ha una radice e due sottoalberi T
1
e T
2
e le foglie di T sono quelle di T
1
pi` u quelle di
T
2
. I cammini dalla radice alle foglie in T sono i cammini dalla radice alle foglie in T
1
e
in T
2
allungati con un primo arco che connette la radice di T rispettivamente con le radici
di T
1
e T
2
. Siano n
1
ed n
2
il numero di foglie di T
1
e T
2
. Siano h
1
, . . . , h
n
1
le altezze delle
foglie di T
1
e k
1
, . . . , k
n
2
le altezze delle foglie di T
2
. Allora
H
med
(T) =

n
1
i=1
(1 +h
i
) +

n
2
i=1
(1 +k
i
)
n
=
n
1
(1 +H
med
(T
1
)) +n
2
(1 +H
med
(T
2
))
n
11
Per ipotesi induttiva possiamo quindi assumere che:
H
med
(T)
n
1
(1 + log
2
n
1
) +n
2
(1 + log
2
n
2
)
n
=
n
1
(1 + log
2
n
1
) + (n n
1
)(1 + log
2
(n n
1
))
n
Per 1 x n 1 la funzione
f(x) =
x(1 + log
2
x) + (n x)(1 + log
2
(n x))
n
ha un minimo per x = n/2 e tale minimo vale
f(n/2) =
(n/2)(1 + log
2
(n/2)) + (n/2)(1 + log
2
(n/2))
n
= 1 + log
2
(n/2) = log
2
n
Possiamo quindi concludere che H
med
(T) log
2
n.
Esercizio 15 Usare il risultato dellesercizio precedente per dimostrare il limite inferiore
(nlog n) per la complessit` a media degli algoritmi di ordinamento.
Soluzione. La complessit` a media di un algoritmo di ordinamento ` e proporzionale allal-
tezza media dellalbero delle decisioni. Siccome lalbero delle decisioni ha n! foglie, per
lesercizio precedente, la sua altezza media ` e maggiore o uguale di log
2
(n!) = (nlog n).
Esercizio 16 Mostrare come sia possibile trovare contemporaneamente i due pi` u piccoli
elementi di un array di n elementi con n + ,log
2
n| 2 confronti.
Soluzione. Se n = 2 un confronto ` e sufciente per decidere quale sia il primo e quale
il secondo. Altrimenti raggruppiamo gli n elementi dellarray in ,n/2| gruppi contenenti
ciascuno due elementi tranne un eventuale gruppo nale di un solo elemento nel caso in
cui n sia dispari. Troviamo quindi il minimo di ciascun gruppo (con n/2| confronti) e
applichiamo ricorsivamente lalgoritmo per trovare i due elementi pi` u piccoli tra gli ,n/2|
minimi dei gruppi. Siano x e y i due elementi trovati. Il minore x ` e minore di tutti i
minimi dei gruppi e quindi anche di tutti i massimi dei gruppi ed ` e quindi anche il minimo
dellarray. Il maggiore y ` e minore di tutti i minimi escluso x ed ` e quindi minore anche
di tutti i massimi dei gruppi escluso eventualmente il massimo del gruppo che contiene x.
Un confronto tra y e il massimo del gruppo che contiene x ` e quindi sufciente per trovare
il secondo elemento in ordine di grandezza dellarray.
12
DUE-MINIMI (A, n) // n 2
1 if n == 2
2 if A[1] A[2]
3 return 1,2
4 else return 2,1
5 m = n/2|
6 for i = 1 to m
7 if A[2 i 1] < A[2 i]
8 B[i] = A[2 i 1], H[i] = 2 i 1
9 else B[i] = A[2 i], H[i] = 2 i
10 if n dispari
11 m = m + 1, B[m] = A[n], H[m] = n
12 p1, p2 = DUE-MINIMI (B, m)
13 m1 = H[p1], m2 = H[p2]
14 // B[p1] e B[p2] sono minimo e secondo minimo di B[1 . . m] ed m1, m2
sono gli indici di tali due elementi in A[1 . . n]. Di conseguenza A[m1] ` e
il minimo in A[1 . . n]. Inoltre se m1 = n allora A[m2] ` e il secondo
minimo in A[1 . . n]. Se m1 < n allora A[m1] ` e in un gruppo di due
elementi e laltro elemento del suo gruppo ha indice i = m1 1 se m1
` e pari ed i = m1 + 1 se m1 ` e dispari. In questo caso il secondo minimo
in A[1 . . n] ` e il minore tra A[m2] e A[i].
15 if n pari or m1 < n
16 if m1 pari
17 i = m1 1
18 else i = m1 + 1
19 if A[m2] > A[i]
20 m2 = i
21 return m1, m2
Il numero di confronti totale richiesto soddisfa la seguente relazione di ricorrenza:
C(n) =

1 per n = 2
n/2| +C(,n/2|) + 1 per n > 2
Dimostriamo per sostituzione che C(n) = n + ,log
2
n| 2. Per n = 2 abbiamo
C(2) = 1 = 2 + ,log
2
2| 2
Per n > 2 possiamo usare lipotesi induttiva per ottenere
C(n) = n/2| +C(,n/2|) + 1
= n/2| + (,n/2| + ,log
2
,n/2|| 2) + 1
= n + ,log
2
,n/2|| 1
= n + ,log
2
n| 2
Lultimo passaggio ` e giusticato dal fatto che, se n ` e pari allora
,log
2
,n/2|| = ,log
2
n
2
| = ,log
2
n 1| = ,log
2
n| 1
13
mentre se n ` e dispari
,log
2
,n/2|| = ,log
2
n + 1
2
| = ,log
2
(n + 1) 1| = ,log
2
(n + 1)| 1
detto k = ,log
2
(n + 1)| abbiamo che 2
k1
< n + 1 2
k
. Siccome n > 2 certamente
k 2 per cui 2
k1
` e pari ed essendo anche n +1 pari avremo che 2
k1
< n 2
k
e quindi
,log
2
(n + 1)| = k = ,log
2
n|.
Dunque in entrambi i casi
,log
2
,n/2|| = ,log
2
n| 1
Esercizio 17 Mostrare come siano necessari almeno ,3n/2| 2 confronti nel caso pes-
simo per trovare contemporaneamente minimo e massimo di un array di n elementi (Sug-
gerimento: Sia m
1
il numero di elementi che, sulla base dei confronti gi` a effettuati, non
possiamo ancora escludere che possano essere il minimo ed m
2
il numero di quelli che
non possiamo ancora escludere che possano essere il massimo. Calcolare quindi di quanto
pu` o diminuire la somma m
1
+m
2
per effetto di un confronto).
Soluzione. Sia A linsieme degli n elementi dellarray e supponiamo che essi siano tutti
distinti. Un confronto tra due elementi di A permette di escludere che il maggiore dei due
sia il minimo e che il minore dei due sia il massimo.
Sia S linsieme degli elementi che non sono ancora stati esclusi come possibili minimi
e T quelli che non sono ancora stati esclusi come possibili massimi e si m
1
il numero di
elementi in S ed m
2
il numero di elementi in T.
Allinizio S = T = A ed m
1
+ m
2
= 2n. Possiamo dire di aver trovato minimo e
massimo solo quando m
1
+ m
2
= 2. Finch e vi sono almeno due elementi x e y in S T
(elementi che possono essere sia massimo che minimo) un confronto tra x e y diminuisce
di 2 la somma m
1
+m
2
mettendo uno dei due in S e laltro in T. Questo puo essere fatto al
pi` u n/2| volte arrivando ad m
1
+m
2
= 2n2n/2|. Dopo di che nessun confronto pu` o
diminuire m
1
+m
2
per pi` u di una unit` a. Occorrono quindi almeno altri 2n 2n/2| 2
confronti. In totale servono quindi almeno
n/2| + 2n 2n/2| 2 = n + ,n/2| 2 = ,3n/2| 2
confronti.
Esercizio 18 Data la seguente funzione:
SCAMBIA(a, b)
1 if a > b
2 t = a, a = b, b = t
descrivere un algoritmo che riordina una array Adi 4 elementi con 5 chiamate alla funzione
SCAMBIA. Dimostrare che 5 ` e un limite inferiore.
Soluzione.
14
ORDINA(A)
1 SCAMBIA(A[1], A[2])
2 // A[1] A[2]
3 SCAMBIA(A[3], A[4])
4 // A[3] A[4], A[1] A[2]
5 SCAMBIA(A[1], A[3])
6 // A[1] A[3] A[4], A[1] A[2] oppure
A[1] A[3] A[2], A[1] A[4]
7 SCAMBIA(A[2], A[4])
8 // A[1] A[3] A[4], A[1] A[2] A[4]
9 SCAMBIA(A[2], A[3])
10 // A[1] A[2] A[3] A[4]
Lalbero delle decisioni ha 4! = 24 foglie. Quindi la sua altezza deve essere almeno
,log
2
24| = 5.
Esercizio 19 Una tavola ad indirizzamento diretto usa un array di dimensione pari al-
la cardinalit` a [U[ dellinsieme U di chiavi possibili. Le operazioni SEARCH, INSERT e
DELETE si eseguono in tempo costante O(1).
Essa richiede per` o una inizializzazione a NIL di tutti gli elementi dellarray. Se il nume-
ro massimo N di chiavi memorizzate nellarray ` e molto minore di [U[ tale inizializzazione
pu` o risultare particolarmente penalizzante in termini di tempo calcolo.
Trovare un modo per implementare una tavola ad indirizzamento diretto che richieda
tempo O(1) sia per linizializzazione che per le operazioni SEARCH, INSERT e DELETE.
Suggerimento: Aggiungere un array di dimensione N che permetta di determinare se
un elemento della tavola ` e utilizzato.
Soluzione. Aggiungiamo un array I di dimensione N in cui memorizzare gli indici delle
celle utilizzate nella tavola ad indirizzamento diretto (il valore NIL per le celle relative
ad elementi eliminati) ed un intero n che contiene il numero totale di celle utilizzate.
Aggiungiamo poi ad ogni cella della tavola un campo intero ind che serve a memorizzare
lindice dellelemento dellarray I che contiene lindice della cella stessa (o NIL se la cella
` e stata prima utilizzata e poi svuotata da una DELETE). La situazione ` e la seguente:
key
T
ind
I
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
1 2 3 4 5 6
9 4 NIL 15 7 13
2 5 1 3 6 4
Linizializzazione richiede soltanto di porre n a 0:
INIT()
1 n = 0
15
Le operazioni sono:
SEARCH(k)
1 if ind[k] 1 and ind[k] n and I[ind[k]] == k
2 return T[k]
3 else return NIL
INSERT(x)
1 k = key[x], T[k] = x
2 if ind[k] < 1 or ind[k] > n or (I[ind[k]] ,= k and I[ind[k]] ,= NIL)
3 n = n + 1, I[n] = k, ind[k] = n
4 else I[ind[k]] = k
DELETE(k)
1 I[ind[k]] = NIL
Esercizio 20 In una tavola hash con risoluzione delle collisioni mediante liste come cam-
bia la complessit` a delle operazioni SEARCH, INSERT e DELETE se le liste vengono man-
tenute ordinate?
Soluzione. DELETE non richiede modiche e continua ad essere eseguita in tempo
costante (1).
Lunica modica a SEARCH ` e che se la chiave cercata non ` e presente non occorre
arrivare alla ne della lista ma ci possiamo fermare non appena troviamo una chiave pi` u
grande. Questo in media dimezza il tempo per la ricerca di una chiave non presente nella
tavola che rimane comunque (1 +).
Il tempo medio richiesto per la ricerca di una chiave presente nella tavola non viene
modicato e rimane quindi (1 +).
Invece INSERT richiede che la chiave inserita venga aggiunta nella giusta posizione
nella lista e non in cima alla lista.
Dunque INSERT non si pu` o pi` u eseguire in tempo costante (1) ma richiede, in media,
un tempo proporzionale alla met` a della lunghezza della lista e dunque (1 +).
Mantenere le liste ordinate puo essere conveniente soltanto per applicazioni in cui
viene eseguito un numero di SEARCH di chiavi non presenti nella tavola molto maggiore
del numero di INSERT.
Esercizio 21 Considerare una tavola hash T di dimensione 2
8
1 = 255 che usa la
funzione hash h(k) = k mod 255.
Mostrare che se essa viene usata per memorizzare delle stringhe di caratteri ASCII
(interpretando una stringa di n caratteri s = c
n1
c
n2
. . . c
1
c
0
come il numero intero k =

n1
i=0
c
i
256
i
) allora tutti gli anagrammi (stringhe ottenute permutando i caratteri) di una
stessa stringa s vengono mappati nella stessa cella dalla funzione hash.
Soluzione. Basta calcolare la funzione hash tenendo conto delle propriet` a dellaritmetica
modulare.
16
In questo modo si ottiene
h(k) = k mod 255
= (
n1

i=0
c
i
256
i
) mod 255
= (
n1

i=0
c
i
(256 mod 255)
i
) mod 255
= (
n1

i=0
c
i
) mod 255
e siccome la somma ` e commutativa si vede che lordine dei caratteri ` e irrilevante.
Esercizio 22 Mostrare che in un albero binario di ricerca il successore di un nodo avente
glio destro non ha glio sinistro ed il predecessore di un nodo avente glio sinistro non
ha glio destro.
Soluzione. Sia k la chiave del nodo x. Se x ha glio destro y il successore di x ` e il nodo
z con chiave minima del sottoalbero radicato in y. Se z avesse glio sinistro w il nodo
w sarebbe un nodo del sottoalbero radicato in y avente chiave minore della chiave di z.
Assurdo.
Simmetricamente, se x ha glio sinistro y il predecessore di x ` e il nodo z con chiave
massima del sottoalbero radicato in y. Se z avesse glio destro w il nodo w sarebbe un
nodo del sottoalbero radicato in y avente chiave maggiore della chiave di z. Assurdo.
Esercizio 23 Mostrare che se partiamo da un nodo x qualsiasi di un albero binario di
ricerca T di altezza h ed eseguiamo k volte listruzione x = SUCCESSOR(x) il tempo
totale richiesto ` e T(k) = O(k +h).
Soluzione. Ricordiamo la funzione SUCCESSOR e la funzione MINIMUM:
SUCCESSOR(x)
1 if x.right ,= NIL
2 y = MINIMUM(x.right )
3 else y = x.p
4 while y ,= NIL and x == y.right
5 x = y, y = x.p
6 return y
MINIMUM(x)
1 y = x
2 while y.left ,= NIL
3 y = y.left
4 return y
Siano x
0
, x
1
, . . . , x
n
i nodi visitati nei passi eseguiti durante tutte le k esecuzioni
di SUCCESSOR di modo che la complessit` a dellintera sequenza di operazioni ` e O(n)
(proporzionale al numero di archi percorsi).
17
Dividiamo gli archi in archi sinistri (quelli che connettono un glio sinistro al padre) e
archi destri (quelli che connettono un glio destro al padre) e per ogni nodo x dellalbero
distinguiamo una profondita destra hd(x) (numero di archi destri nel cammino dalla radice
al nodo) ed una profondit` a sinistra hs(x) (numero di archi sinistri nel cammino dalla radice
al nodo). Naturalmente la profondit` a h(x) del nodo sar` a uguale alla somma hd(x)+hs(x).
Dividiamo inoltre le k operazioni o
1
, . . . , o
k
in due gruppi: il gruppo D delle opera-
zioni discendenti che percorrono in discesa un arco destro e poi un certo numero s
i
di
archi sinistri (con la chiamata a MINIMUM) e il gruppo A delle operazioni ascendenti che
percorrono in salita un certo numero d
i
di archi destri e poi un arco sinistro.
Il numero di archi percorsi in tutta la sequenza di operazioni ` e pertanto
n =

o
i
D
(1 +s
i
) +

o
i
A
(1 +d
i
)
Il numero totale di archi destri percorsi in discesa ` e pari al numero [D[ di operazio-
ni discendenti mentre il numero totale di archi destri percorsi in salita ` e

o
i
A
d
i
. La
differenza

o
i
A
d
i
[D[
` e uguale alla differenza hd(x
0
) hd(x
n
) tra la profondit` a destra del nodo iniziale e la
profondit` a destra del nodo nale. Quindi

o
i
A
(1 +d
i
) = [A[ +

o
i
A
d
i
= [A[ + [D[ + hd(x
0
) hd(x
n
)
= k + hd(x
0
) hd(x
n
)
Il numero totale di archi sinistri percorsi in salita ` e pari al numero [A[ di operazioni
ascendenti mentre il numero totale di archi sinistri percorsi in discesa ` e

o
i
D
s
i
. La
differenza

o
i
D
s
i
[A[
` e uguale alla differenza hs(x
n
) hs(x
0
) tra la profondit` a sinistra del nodo nale e la
profondit` a sinistra del nodo iniziale. Quindi

o
i
D
(1 +s
i
) = [D[ +

o
i
D
s
i
= [A[ + [D[ + hs(x
n
) hs(x
0
)
= k + hs(x
n
) hs(x
0
)
Sommando i risultati ottenuti:
n =

o
i
D
(1 +s
i
) +

o
i
A
(1 +d
i
)
= 2k + hd(x
0
) hd(x
n
) + hs(x
n
) hs(x
0
)
2k + 2h = O(k +h)
e la complessit` a della sequenza di operazioni ` e T(k) = O(n) = O(k +h).
18
Esercizio 24 Supponiamo che una applicazione usi un albero binario di ricerca T ed
unaltra struttura dati che contiene un puntatore ad un nodo y di T il cui predecessore z ha
due gli. Quale inconveniente sorge se dallalbero T viene rimosso il nodo z? Modicare
lalgoritmo DELETERB per evitare tale inconveniente.
Soluzione. La procedura DELETERB(T, z) toglie il nodo y dopo aver ricopiato la sua
chiave e le informazioni associate nel nodo z. Di conseguenza il puntatore continuer` a a
puntare al nodo y eliminato mentre dovrebbe puntare al nodo z che, nellalbero, ha sostitui-
to y. Per evitare linconveniente invece di ricopiare le informazioni da y a z occorre, dopo
aver rimosso il nodo y dallalbero, inserirlo nellalbero al posto del nodo z cambiando gli
opportuni puntatori e quindi eliminare il nodo z.
Esercizio 25 Calcolare il massimo ed il minimo numero di nodi interni in un albero rosso
nero di altezza nera k.
Soluzione. Il minimo numero di nodi si ha quando non ci sono nodi rossi. In questo caso
lalbero ` e un albero binario completo di altezza k. Esso ha quindi 2
k
foglie e 2
k
1 nodi
interni.
Il massimo numero di nodi si ha quando ogni nodo interno nero ha entrambi i gli
rossi. In questo caso lalbero ` e un albero binario completo di altezza 2k. Esso ha quindi
2
2k
foglie e 2
2k
1 nodi interni. Siccome ogni nodo interno nero ha due gli rossi i nodi
interni neri sono
1
3
(2
2k
1) e i nodi interni rossi sono
2
3
(2
2k
1) (
`
E vero che 2
2k
1 ` e
sempre divisibile per 3?)
Esercizio 26 Sia x un nodo nero di un albero rosso-nero T e supponiamo che esso abbia
un glio y pure nero.
`
E allora possibile aggiungere un nodo rosso z tra x ed y mettendo
y come glio di z e z al posto di y come glio di x. Questo non aumenta laltezza nera
bh(T) dellalbero. Naturalmente occorre anche aggiungere un sottoalbero di altezza nera
opportuna e la cui radice w deve diventare il secondo glio di z.
Assumendo che laltezza nera del nodo x sia k = bh(x) determinare in funzione di k:
1. Laltezza nera bh(w) che deve avere la radice w del sottoalbero aggiunto;
2. Il minimo numero di nodi interni del sottoalbero di radice w;
3. Il minimo numero totale m di nodi rossi e di nodi interni neri che debbono essere
aggiunti (il nodo z e i nodi interni del sottoalbero di radice w);
4. Il valore di k per cui m risulta minimo e quello per cui m risulta massimo.
Soluzione.
1. Siccome y ` e nero la sua latezza nera ` e bh(y) = bh(x) 1 = k 1. Il nodo w
essendo il fratello deve avere la stessa altezza nera bh(w) = k 1.
2. Il minimo numero di nodi interni del sottoalbero di radice w si ha quando tutti i suoi
nodi sono neri ed ` e pari a 2
k1
1.
3. Il minimo numero totale m di nodi rossi e di nodi interni neri che debbono essere
aggiunti ` e quindi 2
k1
dei quali uno rosso e tutti gli altri neri.
19
4. Il valore di m risulta minimo quando k = 0 (in questo caso viene aggiunto il solo
nodo rosso e una foglia). Il valore di mrisulta massimo quando k ` e uguale allaltezza
nera dellalbero, ossia quando x ` e la radice e il nodo rosso viene aggiunto come glio
della radice. In questo caso m = 2
bh(T)1
.
Esercizio 27 Calcolare il massimo ed il minimo rapporto tra nodi interni rossi e nodi
interni neri in un albero rosso nero di n nodi.
Soluzione. Minimo rapporto:
Sia h tale che 2
h
1 n < 2
h+1
1 e consideriamo lalbero binario completo con
2
h
1 nodi tutti neri (altezza nera k = h 1).
Sia m = n2
h
+1 il numero di nodi da aggiungere usando il minimo numero possibile
di nodi rossi.
Se m = 0 tutti i nodi sono neri e il rapporto minimo ` e 0.
Altrimenti sia m = b
t1
b
t2
. . . b
0
la rappresentazione binaria di m.
Se t = 0 possiamo solo aggiungere un nodo rosso alla ne di un cammino (ad esempio
il primo) ottenendo un rapporto
1
n1
.
Altrimenti consideriamo i primi 2
t1
nodi neri a livello h 1. Essi sono le foglie di
un sottoalbero di altezza t 1 con nodi tutti neri. Se coloriamo di rosso la radice di tale
sottoalbero e aggiungiamo un livello di nodi neri otteniamo un albero rosso nero con 1
solo nodo rosso e 2
h
1 + 2
t
nodi in totale.
Possiamo ripetere loperazione per ciascuno dei bit b
i
= 1 con i > 0 aggiungendo
altrettanti nodi rossi. Inne, se b
0
= 1 dobbiamo semplicemente aggiungere un nodo
rosso a livello h.
In denitiva, se s ` e il numero di bit 1 nella rappresentazione binaria di m il rapporto
minimo ` e
s
ns
= (
log n
n
).
Massimo rapporto:
Sia h tale che 2
h
1 n < 2
h+1
1 e consideriamo due casi:
h dispari Consideriamo lalbero binario completo con 2
h+1
1 nodi e altezza h. Colo-
riamo i livelli dispari di rosso e quelli pari di nero. Otteniamo un albero rosso nero
con radice nera, foglie rosse e altezza nera k = (h + 1)/2. Questo ` e certamente
lalbero di 2
h+1
1 nodi con il massimo numero di nodi rossi e siccome ogni nodo
nero ha due gli rossi il numero di nodi rossi ` e
2
3
(2
h+1
1) e il numero di nodi neri
` e
1
3
(2
h+1
1).
Sia m = 2
h+1
1 n il numero di nodi da togliere.
Se m = 0 non dobbiamo togliere nessun nodo e il rapporto massimo ` e 2.
Altrimenti sia m = c
t
c
t1
. . . c
0
la rappresentazione di m in base 4.
Consideriamo i primi c
t
4
t
nodi rossi a livello h. Essi sono le foglie di c
t
sottoalberi
di altezza 2t con foglie rosse e radice rossa.
Ciascuno di tali sottoalberi contiene
4
t+1
1
3
nodi rossi e
2
3
(4
t
1) nodi neri. Se cam-
biamo colore a tutti i nodi di tali sottoalberi ed eliminiamo lultimo livello otteniamo
un sottoalbero con
2
3
(4
t
1) nodi rossi e
4
t
1
3
nodi neri.
Lalbero rosso nero cos` ottenuto contiene c
t
24
t
+1
3
nodi rossi in meno e c
t
4
t
1
3
nodi
neri in meno.
20
Possiamo ripetere loperazione per ciascuno delle cifre c
i
ottenendo un albero rosso
nero con
2
3
(2
h+1
1)
t

i=0
c
i
2 4
i
+ 1
3
=
2n

t
i=0
c
i
3
nodi rossi e
1
3
(2
h+1
1)
t

i=0
c
i
4
i
1
3
=
n +

t
i=0
c
i
3
nodi neri.
Il rapporto massimo ` e quindi
2n

t
i=0
c
i
n +

t
i=0
c
i
h pari Consideriamo lalbero binario completo con 2
h+1
1 nodi e altezza h. Coloriamo
i livelli dispari di nero e quelli pari di rosso esclusa la radice che deve essere nera.
Otteniamo un albero rosso nero con radice nera, gli della radice neri, foglie rosse
e altezza nera k = h/2 + 1. Questo ` e certamente lalbero di 2
h+1
1 nodi con il
massimo numero di nodi rossi e siccome ogni nodo nero, esclusa la radice, ha due
gli rossi il numero di nodi rossi ` e
2
3
(2
h+1
2) e il numero di nodi neri ` e
1
3
(2
h+1
+1).
Sia m = 2
h+1
1 n il numero di nodi da togliere.
Se m = 0 non dobbiamo togliere nessun nodo e il rapporto massimo ` e
2(2
h+1
2)
2
h+1
+1
.
Altrimenti sia m = c
t
c
t1
. . . c
0
la rappresentazione di m in base 4.
Consideriamo i primi c
t
4
t
nodi rossi a livello h. Essi sono le foglie di c
t
sottoalberi
di altezza 2t con foglie rosse e radice rossa.
Ciascuno di tali sottoalberi contiene
4
t+1
1
3
nodi rossi e
2
3
(4
t
1) nodi neri. Se cam-
biamo colore a tutti i nodi di tali sottoalberi ed eliminiamo lultimo livello otteniamo
un sottoalbero con
2
3
(4
t
1) nodi rossi e
4
t
1
3
nodi neri.
Lalbero rosso nero cos` ottenuto contiene c
t
24
t
+1
3
nodi rossi in meno e c
t
4
t
1
3
nodi
neri in meno.
Possiamo ripetere loperazione per ciascuno delle cifre c
i
ottenendo un albero rosso
nero con
2
3
(2
h+1
2)
t

i=0
c
i
2 4
i
+ 1
3
=
2n 2

t
i=0
c
i
3
nodi rossi e
1
3
(2
h+1
+ 1)
t

i=0
c
i
4
i
1
3
=
n + 2 +

t
i=0
c
i
3
nodi neri.
Il rapporto massimo ` e quindi
2n 2

t
i=0
c
i
n + 2 +

t
i=0
c
i
Esercizio 28 Dire in quale caso laltezza nera di un albero rosso nero aumenta per effetto
di una INSERTRB ed in quale caso diminuisce per effetto di una DELETERB.
21
Soluzione. Laltezza nera dellalbero aumenta per effetto di una INSERTRB quando:
a) viene inserito il primo nodo: in questo caso il nodo inserito diventa radice e laltezza
nera aumenta da 0 a 1.
b) viene eseguito il Caso 1 con x.p radice dellalbero: in questo caso, prima della
trasformazione, la radice nera non veniva contata nellaltezza nera mentre, dopo
la trasformazione che scambia i colori della radice con quello dei gli, i gli neri
contano nellaltezza nera dellalbero. Quindi laltezza nera aumenta di 1.
Laltezza nera dellalbero diminuisce per effetto di una DELETERB quando viene eseguito
il Caso 0 con x radice dellalbero. Questo accade se:
a) viene tolto lultimo nodo: in questo caso laltezza nera diminuisce da 1 a 0.
b) dopo aver eseguito il Caso 2 con x glio della radice. In questo caso il nero aggiun-
tivo attribuito convenzionalmente al nodo x viene trasferito alla radice e quindi non
viene pi` u contato nellaltezza nera.
Esercizio 29 Mostrare come sia possibile usare un albero rosso nero aumentato con il
campo size per calcolare il numero di inversioni di un array di n elementi in tempo
O(nlog n).
Soluzione. Sia A[1 . . n] larray. Indichiamo con b
i
il numero di inversioni del presso
A[1 . . i] dellarray. Allora b
1
= 0 mentre per i > 1 il valore di b
i
` e uguale a b
i1
aumentato
del numero di elementi in A[1 . . i 1] che sono maggiori di A[i].
Se inseriamo ad uno ad uno gli elementi dellarray nellordine A[1], A[2], A[3],. . . ,
A[n] possiamo calcolare il numero di inversioni totale sommando, ogni volta che eseguia-
mo la INSERTRB(T, A[i]), il numero di elementi maggiori di A[i] presenti nellalbero.
Dopo aver inserito lelemento A[i] possiamo calcolare il numero di elementi maggiori di
A[i] presenti nellalbero con una chiamata RANK(x) sul nodo x appena inserito. Siccome
sia INSERTRB che RANK richiedono tempo O(log n) lintera operazione richiede tempo
O(nlog n).
Esercizio 30 (*) Consideriamo un insieme di n corde di un cerchio C i cui estremi sono
tutti distinti. Descrivere un algoritmo che, in tempo O(nlog n), calcola il numero di coppie
di corde che si intersecano.
Soluzione. Fissiamo un sistema di riferimento sulla circonferenza scegliendo unorigine
O che non coincida con nessuno degli estremi delle corde ed un verso (ad esempio quello
antiorario). Ogni corda sottende due archi di cerchio, uno solo dei quali contiene lorigine.
Orientiamo le corde nello stesso verso dellarco contenente lorigine per cui il primo estre-
mo ha coordinata negativa ed il secondo ha coordinata positiva. A questo punto ` e facile
vedere che due corde si intersecano se e solo se le coordinate dei primi estremi sono nello
stesso ordine delle coordinate dei secondi estremi.
22
b
1
a
1
b
2
a
2
b
3
a
3 b
4
a
4
O
Ordiniamo le corde rispetto alle coordinate del primo estremo. Questo richiede tempo
O(nlog n). Consideriamo quindi la sequenza delle coordinate dei secondi estremi. Ogni
inversione in tale sequenza rappresenta una coppia di corde che non si intersecano. Quindi
il numero di coppie di corde che si intersecano ` e pari ad n(n 1)/2 (numero totale di
coppie di corde) meno il numero di inversioni. Calcoliamo quindi il numero b di inversioni
nella sequenza delle coordinate dei secondi estremi (tempo O(nlog n), vedi Esercizi 2 e
29). A questo punto il numero di intersezioni ` e n(n 1)/2 b.
Esercizio 31 Aumentare un albero rosso nero T in modo tale che le operazioni MINI-
MUM, MAXIMUM, SUCCESSOR e PREDECESSOR si possano eseguire in tempo O(1).
Soluzione. Sovrapponiamo alla struttura dati albero rosso-nero una struttura di lista dop-
piamente concatenata. Per fare questo aggiungiamo ad ogni nodo x dellalbero due campi
puntatore x.succ e x.pred che puntano rispettivamente al successore ed al predecessore
di x. Aggiungiamo inoltre allalbero T due campi puntatore T.min e T.max che puntano
rispettivamente al nodo minimo e massimo.
`
E ovvio che in questo modo le operazioni
MINIMUM, MAXIMUM, SUCCESSOR e PREDECESSOR si eseguono in tempo O(1). Le
operazioni INSERT e DELETE devono essere modicate in modo tale da inserire e togliere
un elemento sia dallalbero che dalla lista.
Esercizio 32 Aumentare un albero rosso nero T avente chiavi di tipo numerico in modo
da poter eseguire in tempo costante loperazione MINGAP che ritorna la minima distanza
tra due chiavi. Suggerimento: aumentare lalbero aggiungendo ad ogni nodo i tre campi
min, max e mingap.
Soluzione. I due campi x.min, x.max memorizzano rispettivamente la chiave minima
e quella massima del sottoalbero radicato in x. Usando la sentinella T.nil e ponendo
T.nil .min = e T.nil .max = per ogni nodo interno valgono le relazioni:
x.min = min(x.key, x.left.min)
x.max = max(x.key, x.right .max)
Quindi, per il teorema dellaumento, i due campi x.min e x.max si possono mantenere
senza aumentare la complessit` a asintotica delle operazioni di inserimento e cancallazione.
Il campo x.mingap memorizza la minima distanza tra due chiavi del sottoalbero radi-
cato in x. Ponendo T.nil .mingap = per ogni nodo interno vale la relazione:
x.mingap = min(x.left.mingap, x.right.mingap,
x.key x.left .max, x.right.min x.key)
23
Quindi, per il teorema dellaumento, anche x.mingap si pu` o mantenere senza aumentare
la complessit` a asintotica delle operazioni di inserimento e cancallazione.
Esercizio 33 Descrivere un algoritmo che dato un albero di intervalli T ed un intervallo i
stampa tutti gli intervalli in T che intersecano lintervallo i. Lalgoritmo deve avere com-
plessit` a O(min(n, max(k, 1) log n)) dove k ` e il numero di intervalli trovati. Opzionale:
trovare una soluzione che non modica T.
Soluzione. Versione che modica T:
STAMPA-INTERVALLI (T, i) // T albero di intervalli, i intervallo.
1 n = T.root .size
2 kmax = n/ log
2
n
3 k = 0
4 x = SEARCH(T, i)
5 while x ,= NIL and k < kmax
6 k = k + 1
7 PRINT(x.int)
8 DELETE(T, x)
9 x = SEARCH(T, i)
10 if x ,= NIL
11 x = MINIMUM(T)
12 while x ,= NIL
13 if i interseca x.int
14 PRINT(x.int)
15 x = SUCCESSOR(T, x)
Calcolare la complessit` a!!
Versione che non modica T:
STAMPA-INTERVALLI (T, i) // T albero di intervalli, i intervallo.
1 STAMPA-RIC(T.root, i)
STAMPA-RIC(x, i) // x ,= NIL radice di un sottoalbero di intervalli, i intervallo.
1 if i.high < x.int.low
2 // Lintervallo in x e quelli nel sottoalbero destro non intersecano i.
3 if x.left ,= NIL
4 STAMPA-RIC(x.left , i)
5 else
6 if x.left ,= NIL and i.low x.left.max
7 // Gli intervalli nel sottoalbero sinistro possono intersecare i.
8 STAMPA-RIC(x.left , i)
9 if i.low x.int .high
10 // Lintervallo in x interseca i.
11 PRINT(x.int)
12 if x.right ,= NIL
13 STAMPA-RIC(x.right , i)
24
La complessit` a ` e proporzionale al numero m totale di chiamate alla funzione ricorsiva
STAMPA-RIC. Infatti, escludendo i tempi per eseguire le chiamate ricorsive, lesecuzione
di una chiamata richiede tempo costante. Sia n
x
il numero di nodi dellalbero radicato in
x, sia k
x
il numero di intervalli di tale sottoalbero che intersecano lintervallo i e sia m
x
il
numero di chiamate di funzione che vengono eseguite. Dimostriamo dapprima m
x
n
x
per induzione su n
x
.
Se n
x
= 1 non vengono eseguite chiamate ricorsive e quindi m
x
= 1 n
x
.
Se n
x
> 1, nel caso peggiore, vengono effettuate le chiamate ricorsive sui sottoalberi
non vuoti di x. Quindi
m
x
1 +m
x. left
+m
x. right
1 +n
x. left
+n
x. right
= n
x
per ipotesi induttiva.
Dimostriamo quindi che m
x
h
x
max(k
x
, 1) per induzione sullaltezza h
x
del sot-
toalbero radicato in x.
Se si esegue il ramo then della prima if i k
x
intervalli che intersecano i stanno tutti nel
sottoalbero sinistro.
x.left
x
i
In questo caso k
x
= k
x. left
e quindi, per ipotesi induttiva,
m
x
1 +h
x. left
max(k
x. left
, 1) 1 + (h
x
1) max(k
x
, 1) h
x
max(k
x
, 1).
Se si esegue il ramo else della prima if allora i.high x.int .low e ci sono le seguenti
possibilit` a:
i.low > x.int.high e viene eseguita soltanto la prima chiamata.
x.left
x
i
In questo caso x.right = NIL, k
x. left
= k
x
e, per ipotesi induttiva,
m
x
1 +h
x. left
max(k
x. left
, 1) 1 + (h
x
1) max(k
x
, 1)
h
x
max(k
x
, 1).
i.low x.int.high e viene eseguita soltanto la prima chiamata ricorsiva.
x.left
x
i
25
In questo caso x.right = NIL, k
x. left
= k
x
1 ed inoltre almeno un intervallo del
sottoalbero sinistro interseca lintervallo i e dunque k
x. left
1. Quindi, per ipotesi
induttiva
m
x
1 +h
x. left
k
x. left
1 + (h
x
1)(k
x
1) = h
x
k
x
k
x
h
x
+ 2
e siccome k
x
= k
x. left
+ 1 2 e k
x
max(k
x
, 1) possiamo concludere
m
x
h
x
k
x
k
x
h
x
+ 2 h
x
max(k
x
, 1).
i.low > x.int.high e viene eseguita soltanto la seconda chiamata ricorsiva.
x.left x.right
x
i
In questo caso k
x. right
= k
x
e, per ipotesi induttiva,
m
x
1 +h
x. right
max(k
x. right
, 1) 1 + (h
x
1) max(k
x
, 1)
h
x
max(k
x
, 1).
i.low x.int.high e viene eseguita soltanto la seconda chiamata ricorsiva.
x.left x.right
x
i
In questo caso k
x. right
= k
x
1. Quindi, per ipotesi induttiva
m
x
1 +h
x. right
max(k
x. right
, 1) 1 + (h
x
1) max(k
x
1, 1).
Se k
x. right
1 allora k
x
= k
x. right
+ 1 2 e k
x
max(k
x
, 1) e quindi
m
x
1 + (h
x
1)(k
x. right
1) = h
x
k
x
k
x
h
x
+ 2 h
x
max(k
x
, 1).
Se k
x. right
= 0 allora k
x
= 1 e quindi
m
x
1 +h
x. right
h
x
= h
x
max(k
x
, 1).
i.low > x.int.high e vengono eseguite entrambe le chiamate ricorsive.
x.left x.right
x
i
26
In questo caso k
x
= k
x. left
+ k
x. right
ed inoltre almeno un intervallo del sottoalbero
sinistro interseca lintervallo i e dunque k
x
k
x. left
1. Per ipotesi induttiva,
m
x
1 +h
x. left
k
x. left
+h
x. right
max(k
x. right
, 1).
Se k
x. right
1 allora
m
x
1 +h
x. left
k
x. left
+h
x. right
k
x. right
1 + (h
x
1)(k
x. left
+k
x. right
)
= 1 +h
x
k
x
k
x
h
x
k
x
h
x
max(k
x
, 1)
Se k
x. right
= 0 allora k
x
= k
x. left
e quindi
m
x
1 +h
x. left
k
x. left
= 1 +h
x
k
x
k
x
h
x
max(k
x
, 1).
i.low x.int.high e vengono eseguite entrambe le chiamate ricorsive.
x.left x.right
x
i
In questo caso k
x
= k
x. left
+k
x. right
+1 ed inoltre almeno un intervallo del sottoalbero
sinistro interseca lintervallo i e dunque k
x
k
x. left
1 e h
x
2. Per ipotesi
induttiva
m
x
1 +h
x. left
k
x. left
+h
x. right
max(k
x. right
, 1).
Se k
x. right
1 allora
m
x
1 +h
x. left
k
x. left
+h
x. right
k
x. right
1 + (h
x
1)(k
x. left
+ k
x. right
)
= 1 + (h
x
1)(k
x
1) = h
x
k
x
k
x
h
x
+ 2
h
x
k
x
= h
x
max(k
x
, 1).
Se k
x. right
= 0 allora k
x
= k
x. left
+ 1 e quindi
m
x
1 +h
x. left
k
x. left
+h
x. right
1 + (h
x
1)(k
x
1) +h
x
1
= h
x
k
x
k
x
+ 1 h
x
k
x
h
x
max(k
x
, 1).
Dunque m
x
min(n
x
, h
x
max(k
x
, 1)). Negli alberi rosso-neri h
x
= O(log n
x
) e quindi
m
x
= O(min(n
x
, log n
x
max(k
x
, 1))).
Esercizio 34 Dimostrare che nel problema delle catene di montaggio se p1[j] = 2 allora
anche p2[j] = 2 e viceversa se p2[j] = 1 allora anche p1[j] = 1. In altre parole non pu` o
succedere che la strada pi` u breve per arrivare alla stazione j-esima della prima catena passi
per la stazione precedente della seconda catena e contemporaneamente la strada pi` u breve
per arrivare alla stazione j-esima della seconda catena passi per la stazione precedente
della prima catena.
27
Soluzione. Supponiamo p1[j] = 2 e p2[j] = 1. Allora f2[j 1] + t
2,j1
< f1[j 1] ed
f1[j 1] + t1[j 1] < f2[j 1]. Sommando le due disequazioni si ottiene t2[j 1] +
t1[j 1] < 0, assurdo.
Esercizio 35 Dimostrare che una parentesizzazione completa di un prodotto di n matrici
contiene esattamente n 1 coppie di parentesi.
Soluzione. Sia p(n) il numero di coppie di parentesi. Se n = 1 allora non occorre nessuna
parentesi e p(1) = 0 = 1 1. Se n > 1 allora vi ` e una coppia di parentesi esterna che
racchiude una parentesizzazione delle prime k matrici e una parentesizzazione delle ultime
n k matrici per un k tale che 1 k < n. Dunque p(n) = 1 +p(k) +p(n k) e quindi,
per ipotesi induttiva, p(n) = 1 +k 1 +n k 1 = n 1.
Esercizio 36 Descrivere un algoritmo che trova la pi` u lunga sottosequenza crescente di
una sequenza di n numeri interi. Lalgoritmo deve richiedere tempo O(n
2
).
Soluzione. Sia A la sequenza. Facciamo una copia B di A e poi ordiniamo B in ordine
crescente (tempo O(nlog n)). Usiamo quindi la programmazione dinamica per calcolare
la pi` u lunga sottosequenza comune (LCS) tra A e B (tempo O(n
2
)). Tale sottosequenza ` e
anche la pi` u lunga sottosequenza crescente di A.
Esercizio 37 (*) Descrivere un algoritmo che risolve il problema dellesercizio precedente
in tempo O(nlog n).
Soluzione. Invece di trasformare il problema nel problema della ricerca della pi` u lunga
sottosequenza comune (LCS) applichiamo direttamente la programmazione dinamica al
problema originale della ricerca della pi` u lunga sottosequenza crescente (LIS).
Sottostruttura ottima: Sia X = x
1
, x
2
, . . . , x
n
la sequenza di n numeri interi, sia S =
s
1
, s
2
, . . . , s
m
una LIS e sia m la sua lunghezza.
Se s
m
,= x
n
la sequenza S ` e una LIS per il presso X
n1
.
Se s
m
= x
n
il presso S
m1
` e una LIS del presso X
n1
il cui ultimo elemento ` e
minore o uguale di x
n
. Infatti se esistesse una sottosequenza crescente S

del presso
X
n1
il cui ultimo elemento ` e minore o uguale di x
n
ed avente lunghezza maggiore di
S
m1
la sottosequenza S

x
n
sarebbe una soluzione migliore di S.
Possiamo inoltre supporre che tra tutte le sottosequenze crescenti di X
n1
di lunghezza
m1 il cui ultimo elemento ` e minore o uguale di x
n
la sottosequenza S
m1
sia quella con
ultimo elemento minimo.
Il ragionamento precedente ci porta a considerare il problema pi` u generale che consiste
nel calcolare la sottosequenza di lunghezza j il cui ultimo elemento ` e minimo per ogni
j = 1, . . . , m con m lunghezza di una LIS. Verichiamo quindi la sottostruttura ottima
del nuovo problema.
Sia dunque
S
1
= s
1,1
; S
2
= s
2,1
, s
2,2
; . . . ; S
m
= s
m,1
, s
m,2
, . . . , s
m,m
una soluzione del nuovo problema.
Notiamo che se j < k allora s
j,j
< s
k,k
in quanto il presso s
k,1
, s
k,2
, . . . , s
k,j
di S
k
di lunghezza j ha ultimo elemento minore di s
k,k
ed S
j
` e la sottosequenza crescente di
lunghezza j con ultimo elemento s
j,j
minimo.
28
Inoltre deve essere x
n
= s
j,j
per qualche j. Infatti non pu` o essere x
n
< s
1,1
perch e
(s
1
, 1) ` e la sottosequenza di lunghezza 1 con ultimo elemento minimo, non pu` o essere
s
j1,j1
< x
n
< s
j,j
altrimenti S
j1
, x
n
sarebbe una sottosequenza crescente di lunghezza
j con ultimo elemento minore di s
j,j
ed inne non pu` o essere s
m,m
< x
n
altrimenti S
m
, x
n
sarebbe una sottosequenza crescente di lunghezza m + 1 contro lipotesi che m sia la
lunghezza di una LIS.
Sia quindi x
n
= s
j,j
. Allora il presso s
j,1
, s
j,2
, . . . , s
j,j1
di S
j
` e la sottosequenza
crescente di X
n1
di lunghezza j 1 il cui ultimo elemento ` e minimo mentre le altre
sottosequenze rimangono le stesse della soluzione per il presso X
n1
.
Soluzione ricorsiva: Sia
S
1
= s
1,1
; S
2
= s
2,1
, s
2,2
; . . . ; S
m
= s
m,1
, s
m,2
, . . . , s
m,m
una soluzione per il presso X
i
ed
S

1
= s

1,1
; S

2
= s

2,1
, s

2,2
; . . . ; S

m
= s

,1
, s

,2
, . . . , s

,m

una soluzione per il presso X


i1
. Allora:
per i = 1 abbiamo m = 1 ed S
1
= x
1
.
per i > 1 ed x
i
< s

1,1
abbiamo m = m

, S
1
= x
i
ed S
j
= S

j
per j > 1.
per i > 1 ed s

j1,j1
x
i
< s

j,j
abbiamo m = m

, S
j
= S

j1
, x
i
ed S
k
= S

k
per
k ,= j.
per i > 1 ed x
i
s

,m
abbiamo m = m

+ 1, S
m
= S

m
, x
i
ed S
k
= S

k
per
k < m.
Calcolo della LIS:
LIS(X, n)
1 S[1] = X[1], L[1] = 1, P[1] = NIL, m = 1
2 // Per ogni j = 1, . . . , m, S[j] ` e lultimo elemento s
j,j
della lista S
j
,
L[j] ` e lindice dellultimo elemento s
j,j
della lista S
j
e
P[j] ` e il puntatore allelemento precedente della lista S
j
.
3 for i = 2 to n
4 Cerca lindice j tale che S[1 . . j 1] X[i] < S[j . . m]
5 // Usando la ricerca binaria
6 S[j] = X[i], L[j] = i
7 if j > 1
8 P[j] = L[j 1]
9 else P[j] = NIL
10 if j > m
11 m = m + 1
12 return m, P, L
29
i
X
P
L
S
1 2 3 4 5 6 7 8 9 10 11 12 13
11 12 10 1 9 3 16 5 21 13 14 7 8
NIL 1 NIL NIL 4 4 6 6 8 8 10 8 12
1 3 5 7 8
4 6 8 12 13
Complessit` a O(nlog n). La ricostruzione della soluzione si fa in tempo O(m) usando
gli array L e P.
Esercizio 38 Uno dei test per lassunzione di un dattilografo consiste nel copiare un testo
X di n caratteri. Il testo Y scritto dal dattilografo viene poi confrontato con il testo X
per controllare quanti errori egli ha commesso. Naturalmente, a causa degli errori, la
lunghezza m del testo Y pu` o non essere uguale alla lunghezza n del testo originario. I tipi
di errori che un dattilografo pu` o commettere sono i seguenti:
Sostituzione: legge un carattere e ne scrive un altro.
Cancellazione: non scrive il carattere letto.
Inserimento: scrive un carattere che non c` e nel testo originale.
Scambio: legge due caratteri consecutivi e li scrive in ordine inverso.
Con un sufciente numero di errori si puo trasformare il testo X in un testo Y qualsiasi
(ad esempio con n cancellazioni ed m inserimenti) e si puo ottenere lo stesso testo Y in
pi` u modi diversi (ad esempio lo stesso effetto di una sostituzione si puo ottenere con una
cancellazione ed un inserimento).
Scrivere un algoritmo che dati i due testi X ed Y calcola il minimo numero di errori
che la dattilografa ha dovuto commettere per trasformare X in Y .
Soluzione. Usiamo la programmazione dinamica. Indichiamo con
a) R
a,b
la sostituzione del carattere a con il carattere b.
b) D
a
la cancellazione del carattere a.
c) I
a
linserimento del carattere a.
d) S
a,b
lo scambio dei due caratteri consecutivi a e b.
ed inoltre con
e) C
a
il ricopiamento corretto del carattere a.
30
Una soluzione del problema ` e allora una sequenza S di tali cinque operazioni che
trasforma il testo X nel testo Y . Una soluzione ` e ottima se contiene il minimo numero
possibile delle prime quattro operazioni (gli errori).
Sottostruttura ottima: Sia S una soluzione ottima e sia e il numero di errori contenuto
in S. Allora
a) se S = S

R
a,b
la sequenza S

` e ottima per X
n1
ed Y
m1
ed e = 1 +e
n1,m1
.
b) se S = S

D
a
la sequenza S

` e ottima per X
n1
ed Y ed e = 1 +e
n1,m
.
c) se S = S

I
a
la sequenza S

` e ottima per X ed Y
m1
ed e = 1 +e
n,m1
.
d) se S = S

S
a,b
la sequenza S

` e ottima per X
n2
ed Y
m2
ed e = 1 +e
n2,m2
.
e) se S = S

C
a
la sequenza S

` e ottima per X
n1
ed Y
m1
ed e = e
n1,m1
.
Soluzione ricorsiva: Sia S
i,j
una soluzione ottima per i due pressi X
i
ed Y
j
e sia e
i,j
il numero di errori contenuto in S
i,j
.
Se i = 0 le uniche operazioni possibili sono gli inserimenti e quindi
e
0,j
= j.
Se j = 0 le uniche operazioni possibili sono le cancellazioni e quindi
e
i,0
= i.
Se i 1, j 1 ed x
i
= y
j
= a le operazioni possibili sono D
a
, I
a
e C
a
. Quindi
e
i,j
= min(1 +e
i1,j
, 1 +e
i,j1
, ei 1, j 1).
Se i 1, j 1, x
i
= a ,= y
j
= b e o i = 1 o j = 1 o x
i1
,= y
j
o x
i
,= y
j1
le operazioni
possibili sono R
a,b
, D
a
ed I
b
. Quindi
e
i,j
= min(1 +e
i1,j1
, 1 +e
i1,j
, 1 +e
i,j1
).
Se i 1, j 1, x
i
= a ,= y
j
= b ed i > 1, j > 1, x
i1
= y
j
e x
i
= y
j1
le operazioni
possibili sono R
a,b
, D
a
, I
b
ed S
b,a
. Quindi
e
i,j
= min(1 +e
i1,j1
, 1 +e
i1,j
, 1 +e
i,j1
, 1 +e
i2,j2
).
Calcolo del numero minimo di errori:
DATTILOGRAFA(X, n, Y, m)
1 for i = 0 to n
2 E[i, 0] = i
3 for j = 1 to m
4 E[0, j] = j
5 for i = 1 to n
6 for j = 1 to m
31
1 // Un errore di tipo I pu` o sempre esserci.
2 E[i, j] = 1 +E[i, j 1], O[i, j] = I
3 // Un errore di tipo D pu` o sempre esserci.
4 if E[i, j] > 1 +E[i 1, j]
5 E[i, j] = 1 +E[i 1, j], O[i, j] = D
6 // Un ricopiamento corretto pu` o esserci solo se X[i] == Y [j] e in tal
caso non possono esserci altri errori.
7 if X[i] == Y [j]
8 if E[i, j] > E[i 1, j 1]
9 E[i, j] = E[i 1, j 1], O[i, j] = C
10 else // X[i] ,= Y [j]. Pu` o esserci un errore di tipo R.
11 if E[i, j] > 1 +E[i 1, j 1]
12 E[i, j] = 1 +E[i 1, j 1], O[i, j] = R
13 // Pu` o essere avvenuto un errore di tipo S solo
se i > 1, j > 1, X[i 1] == Y [j] e X[i] == Y [j 1].
14 if i > 1 and j > 1 and X[i 1] == Y [j] and
X[i] == Y [j 1] and E[i, j] > 1 +E[i 2, j 2]
15 E[i, j] = 1 +E[i 2, j 2], O[i, j] = S
16 return E, O
Stampa della sequenza di operazioni:
STAMPA-SOLUZIONE(O, i, j)
1 if i == 0
2 for k = 1 to j
3 PRINT(I)
4 elseif j == 0
5 for k = 1 to i
6 PRINT(D)
7 elseif O[i, j] == C
8 STAMPA-SOLUZIONE(O, i 1, j 1), PRINT(C)
9 elseif O[i, j] == R
10 STAMPA-SOLUZIONE(O, i 1, j 1), PRINT(R)
11 elseif O[i, j] == I
12 STAMPA-SOLUZIONE(O, i, j 1), PRINT(I)
13 elseif O[i, j] == D
14 STAMPA-SOLUZIONE(O, i 1, j), PRINT(D)
15 else // O[i, j] == S
16 STAMPA-SOLUZIONE(O, i 2, j 2), PRINT(S)
Esercizio 39 Il problema dello zaino frazionario ` e il seguente:
Dati n tipi di merce M
1
, . . . , M
n
in quantit` a rispettive q
1
, . . . , q
n
e con valori unitari
c
1
, . . . , c
n
si vuole riempire uno zaino di capacit` a Q in modo che il contenuto abbia valore
massimo.
Mostrare che il seguente algoritmo risolve il problema:
32
RIEMPI-ZAINO(q, c, n, Q)
1 // Precondizione: c
1
c
2
. . . c
n
2 Sp = Q, i = 1
3 while i n
4 if Sp q
i
5 z
i
= q
i
6 else z
i
= Sp
7 Sp = Sp z
i
8 i = i + 1
9 return z
Soluzione.
Sottostruttura ottima
Sia y
1
, . . . , y
n
una soluzione ottima. Allora y
2
, . . . , y
n
` e soluzione del sottoproblema con
le merci M
2
, . . . , M
n
con zaino di capacit` a Qy
1
, ed ` e ottima altrimenti potremmo trovare
una soluzione del problema originale migliore di y
1
, . . . , y
n
.
Propriet` a della scelta golosa
Sia z
1
, . . . , z
k1
, y
k
, . . . , y
n
una soluzione ottima che contiene le scelte z
1
, . . . , z
k1
fatte
precedentemente. Lalgoritmo sceglie
z
k
= min

q
k
, Q
k1

i=1
z
i

che ` e la massima quantit` a possibile. Quindi y


k
z
k
.
Se nella soluzione ottima z
1
, . . . , z
k1
, y
k
, . . . , y
n
sostituisco y
k
con z
k
e diminuisco
corrispondentemente le quantit` a successive trovo una soluzione di costo maggiore o ugua-
le (poich e le merci successive hanno costo unitario minore o uguale). Ma la soluzione
z
1
, . . . , z
k1
, y
k
, . . . , y
n
` e ottima e quindi la soluzione ottenuta z
1
, . . . , z
k
, y

k+1
, . . . , y

n
ha
lo stesso costo ed ` e quindi una soluzione ottima che contiene z
k
.
Prova diretta
Lalgoritmo pone z
i
= q
i
per ogni indice i no ad un certo indice k per il quale pone
z
k
= Sp = Q
k1

i=1
z
i
e pone quindi z
i
= 0 per tutti gli indici i successivi. Sia y
1
, y
2
, . . . , y
n
una qualsiasi altra
soluzione. La differenza tra il costo C della soluzione calcolata dallalgoritmo e il costo
C

dellaltra soluzione ` e:
C C

=
n

i=1
(z
i
y
i
)c
i
Per i < k abbiamo c
i
c
k
e z
i
y
i
= q
i
y
i
0 e quindi
(z
i
y
i
)c
i
(z
i
y
i
)c
k
33
Per i = k abbiamo c
i
= c
k
e z
k
= Q

k1
i=1
z
i
e quindi
(z
k
y
k
)c
k
= (Q
k1

i=1
z
i
y
i
)c
k
Per i > k abbiamo c
i
c
k
e z
i
= 0 e quindi
(z
i
y
i
)c
i
y
i
c
k
Mettendo tutto assieme:
C C

=
k1

i=1
(z
i
y
i
)c
i
+ (z
k
y
k
)c
k

n

i=k+1
(z
i
y
i
)c
i

k1

i=1
(z
i
y
i
)c
k
+

Q
k1

i=1
(z
i
y
i
)

c
k

n

i=k+1
y
i
c
k
c
k
(Q
n

i=1
y
i
) = 0
Quindi la soluzione trovata dallalgoritmo ` e ottima.
Esercizio 40 Il problema dello zaino 0-1 il seguente:
Dati n tipi di oggetti O
1
, . . . , O
n
di volumi v
1
, . . . , v
n
e valori c
1
, . . . , c
n
, si vuole
riempire uno zaino di capacit` a V con tali oggetti in modo che il contenuto abbia valore
massimo. Si suppone di avere a disposizione un numero illimitato di oggetti di ciascun
tipo.
Mostrare che il seguente algoritmo non risolve il problema:
RIEMPI-ZAINO(v, c, n, V )
1 // Precondizione: c
1
/v
1
c
2
/v
2
. . . c
n
/v
n
2 Sp = V , i = 1
3 while i n
4 z
i
= Sp/v
i
|, Sp = Sp z
i
v
i
5 i = i + 1
6 return z
Soluzione. Basta trovare un controesempio. Supponiamo di avere tre tipi di oggetti di
volumi v
1
= 7, v
2
= 6, v
3
= 4 e valori c
1
= 35, c
2
= 24, c
3
= 12 e zaino di capacit` a
V = 10.
Siccome c
1
/v
1
= 5 c
2
/v
2
= 4 c
3
/v
3
= 3, lalgoritmo prende solamente un
oggetto di valore 35. La soluzione ottima ` e invece prendere un oggetto di valore 24 ed uno
di valore 12.
Esercizio 41 Nel problema della scelta delle attivit` a abbiamo visto come si arrivi ad una
soluzione globalmente ottima scegliendo ad ogni passo, tra le attivit` a compatibili con
quelle gi` a scelte, quella con tempo di ne minimo. Mostrare che questo non ` e vero se
scegliamo quella di durata minima. Mostrare che non ` e vero neppure scegliendo quella
incompatibile con il minimo numero di attivit` a rimanenti.
34
Soluzione. Consideriamo le tre attivit` a a
1
= (0, 3), a
2
= (2, 4) e a
3
= (3, 6). Se
scegliamo quella di durata minima otteniamo una soluzione costituita soltanto dallattivit` a
a
2
mentre una soluzione ottima ` e costituita dalle altre due attivit` a.
Consideriamo le attivit` a a
1
= (0, 4), a
2
= (3, 5), a
3
= (1, 5), a
4
= (1, 6), a
5
= (4, 7),
a
6
= (6, 10), a
7
= (8, 11), a
8
= (10, 12), a
9
= (10, 13), a
10
= (10, 14) e a
11
= (11, 14).
Se scegliamo quella incompatibile con il minimo numero di attivit` a rimanenti otteniamo
una soluzione costituita dalle attivit` a a
2
, a
6
, a
8
mentre una soluzione ottima ` e costituita
dalle attivit` a a
1
, a
5
, a
7
, a
11
.
Esercizio 42 Siano a
1
, . . . , a
n
delle attivit` a didattiche aventi tempi di inizio s
1
, . . . , s
n
e
tempi di ne f
1
, . . . , f
n
e supponiamo di avere un insieme sufcientemente grande di aule
A
1
, A
2
, . . . in cui svolgerle. Trovare un algoritmo goloso per programmare tutte le attivit` a
nel minimo numero possibile m di aule.
Soluzione. Lalgoritmo goloso ` e il seguente:
PROGRAMMA-LEZIONI (s, f, n) // Precondizione: s
1
s
2
. . . s
n
1 m = 0
2 for i = 1 to n
3 h = m + 1
4 for j = 1 to m
5 if s
i
t
j
then h = j
6 // La lezione a
i
si pu` o eseguire nellaula A
h
. Se h == m + 1
in nessuna delle m aule usate nora si pu` o effettuare a
i
.
7 J[i] = h
8 t
h
= f
i
9 if h == m+ 1 then m = m+ 1
10 // Postcondizione: J[1 . . n] ` e una programmazione ottima delle n
attivit` a didattiche che usa il minimo numero m di aule.
J[i] == j signica che la lezione a
i
si terr` a nellaula A
j
.
11 return J, m
Che la soluzione J[1, n] usi il minimo numero possibile m di aule ` e evidente. Sia a
i
la prima attivit` a assegnata allultima aula A
m
ed s
i
il suo tempo di inizio. Al tempo s
i
le
precedenti m 1 aule erano tutte occupate e quindi tra le n attivit` a ce ne sono almeno m
che si sovrappongono nello stesso istante. Dunque sono necessarie almeno m aule.
Esercizio 43 Siano a
1
, . . . , a
n
delle attivit` a didattiche aventi tempi di inizio s
1
, . . . , s
n
e
tempi di ne f
1
, . . . , f
n
e supponiamo di avere a disposizione m aule A
1
, . . . , A
m
in cui
svolgerle. Trovare un algoritmo goloso per programmare il massimo numero possibile di
attivit` a nelle m aule disponibili.
Soluzione. Lalgoritmo goloso ` e il seguente:
35
PROGRAMMA-LEZIONI (s, f, n, m) // Precondizione: f
1
f
2
. . . f
n
1 for j = 1 to m
2 t
j
= 0
3 t
0
= 1
4 for i = 1 to n
5 h = 0
6 for j = 1 to m
7 if s
i
t
j
and t
j
t
h
then h = j
8 // A
h
` e laula che si libera per ultima tra quelle in cui ` e possibile
effettuare la lezione a
i
.
Se h == 0 in nessuna delle m aule si pu` o effettuare a
i
.
9 J[i] = h
10 if h ,= 0 then t
h
= f
i
11 // Postcondizione: J[1 . . n] ` e una programmazione ottima del massimo
numero di attivit` a didattiche che si possono svolgere nelle m aule.
J[i] == j ,= 0 signica che la lezione a
i
si terr` a nellaula A
j
.
J[i] == 0 signica che la lezione a
i
non si terr` a in nessuna delle m aule.
12 return J
Supponiamo che lassegnazione J[1, i 1] delle prime i 1 attivit` a didattiche sia
estensibile ad una programmazione ottima B[1, n] di tutte le attivit` a.
Lalgoritmo, dopo aver assegnato le prime i 1 attivit` a didattiche alle aule J[1, i 1],
con lassegnazione J[i] = h, assegna la i-esima attivit` a a
i
allaula A
h
che si libera per
ultima tra tutte quelle che si liberano prima dellinizio s
i
di a
i
. Se nessuna delle aule si
libera prima di s
i
lattivit` a a
i
non viene assegnata e J[i] = 0.
Supponiamo che esista una programmazione ottima B[1, n] che assegna le prime i 1
lezioni come lalgoritmo, ossia B[1, i 1] = J[1, i 1], e dimostriamo che esiste una
programmazione ottima B

[1, n] tale che B

[1, i] = J[1, i].


Se B[i] = J[i] siamo a posto. Se J[i] = 0 lattivit` a i-esima ` e incompatibile con le
attivit` a J[1, i 1] e quindi B[i] = J[i] = 0. Supponiamo quindi J[i] ,= 0 e B[i] ,= J[i].
Se B[i] = 0, la programmazione ottima B[1, n] non assegna nessuna aula allattivit` a
i-esima. B[1, n] deve assegnare allaula A
h
a cui lalgoritmo assegna la i-esima attivit` a
qualche altra attivit` a successiva (altrimenti non sarebbe ottima). Consideriamo la prima
attivit` a di indice j > i assegnata da B[1, n] allaula A
h
. Sia B

[1, n] la programmazione
che assegna le attivit` a come B[1, n] tranne che sostituisce la j-esima con la i-esima (ossia
B

[i] = J[i] e B

[j] = 0). Siccome f


j
f
i
questo non comporta sovrapposizioni nellaula
A
h
e quindi B

[1, n] ` e una programmazione ottima tale che B

[1, i] = J[1, i].


Se B[i] ,= 0, la programmazione ottima B[1, n] assegna lattivit` a i-esima ad una
diversa aula A
h
con h

= B[i] diverso da h = J[i].


Quindi, dopo aver assegnato le prime i1 attivit` a entrambe le aule A
h
ed A
h
dovevano
essere libere. In B possiamo quindi scambiare tra le due aule A
h
ed A
h
le attivit` a di indice
maggiore o uguale ad i ottenendo anche in questo caso una programmazione ottima tale
che B

[1, i] = J[1, i].


Quando lalgoritmo termina J[1, n] = B[1, n] ` e una programmazione ottima.
36
Esercizio 44 Dimostrare che ogni algoritmo di compressione che accorcia qualche se-
quenza di bit deve necessariamente allungarne qualche altra. (Suggerimento: confrontare
il numero di sequenze con il numero di codiche.)
Soluzione.
Supponiamo, per assurdo, che esista un algoritmo di compressione che non allunga
nessuna sequenza di bit ma ne accorcia qualcuna.
Sia f la pi` u corta sequenza che viene accorciata dallalgoritmo e sia n la sua lunghezza.
Il numero di sequenze di lunghezza minore di n ` e 2
n
1.
Siccome codiche di sequenze distinte devono essere necessariaente distinte (altrimen-
ti la decodica sarebbe ambigua) il numero di sequenze che sono codiche di sequenze di
lunghezza minore di n ` e pure 2
n
1.
Siccome lalgoritmo non allunga nessuna sequenza ognuna delle sequenza di bit di
lunghezza minore di n ` e codica di qualche le di lunghezza minore di n.
Pertanto la codica di f che ha anchessa lunghezza minore di n deve coincidere con
la codica di una delle sequenze pi` u corte di n: assurdo perch e sequenze diverse devono
avere codiche diverse.
Esercizio 45 Sia C = c
1
, . . . , c
n
un insieme di caratteri ed f
1
, . . . , f
n
le loro frequenze
in un le. Mostrare come si possa rappresentare ogni codice presso ottimo per C con una
sequenza di 2n 1 +n,log
2
n| bits. (Suggerimento: usare 2n 1 bit per rappresentare la
struttura dellalbero ed n,log
2
n| bits per elencare i caratteri nellordine in cui compaiono
nelle foglie dellalbero del codice.)
Soluzione. Rappresentiamo la struttura dellalbero con la sequenza in preordine dei bit
che etichettano gli archi dellalbero a cui aggiungiamo un bit 1 alla ne.
120
a:57
0
63
1
25
0
c:12
0
b:13
1
38
1
14
0
f:5
0
e:9
1
d:24
1
Per ricostruire la strutture dellalbero si parte dalla radice e quindi:
1. ogni volta che si incontra un bit 0 si aggiunge un glio sinistro e si scende sul glio
appena aggiunto;
2. quando si incontra un bit 1 si risale no a che si incontra un antenato privo di glio
destro. Se lo si trova si aggiunge un glio destro a tale antenato e ci si sposta su tale
glio. Altrimenti abbiamo nito.
37
Rappresentiamo la sequenza dei caratteri associati alle foglie elencando i loro codici a
lunghezza ssa (servono ,log
2
n| bits per ogni carattere).
Per ricostruire lassociazione tra caratteri e foglie basta associare i caratteri alle foglie
nellordine da sinistra a destra.
Esercizio 46 Un cassiere vuole dare un resto di n centesimi di euro usando il minimo
numero di monete.
a) Descrivere un algoritmo goloso per fare ci` o con tagli da 1/c, 2/c, 5/c, 10/c, 20/c, 50/c,
1e e 2e.
b) Dimostrare che lalgoritmo goloso funziona anche con monete di tagli
c
0
= 1, c
1
= c, . . . , c
k
dove c ` e un intero maggiore di 1 e k 0.
c) Trovare un insieme di tagli di monete per i quali lalgoritmo goloso non funziona.
Soluzione. La scelta golosa consiste nello scegliere il massimo numero possibile di
monete di taglio massimo.
RESTO(R, t, n)
1 // Precondizione: t
1
> t
2
> . . . > t
n
= 1 sono i tagli delle monete. R ` e il
resto da dare.
2 for i = 1 to n
3 A[i] = R/t
i
|
4 R = R mod t
i
5 // Postcondizione: A[1], A[2], . . . , A[n] sono il numero di monete del resto
di tagli rispettivi t
1
, t
2
, . . . , t
n
.
a) I tagli sono t
1
= 2e, t
2
= 1e, t
3
= 50/c, t
4
= 20/c, t
5
= 10/c, t
6
= 5/c, t
7
= 2/c e
t
8
= 1/c.
Sia A
1
, A
2
, . . . , A
8
una qualsiasi soluzione ottima per un certo resto R.
Quindi R =

8
i=1
A
i
t
i
con n =

8
i=1
A
i
minimo.
Sia R
k
=

8
i=k+1
A
i
t
i
la somma delle monete di taglio minore di t
k
.
Se riusciamo a dimostrare che R
k
< t
k
per ogni k allora la soluzione che vie-
ne calcolata dallalgoritmo ` e proprio la soluzione ottima A
1
, A
2
, . . . , A
8
e dunque
lalgoritmo calcola una soluzione ottima.
R
8
: R
8
= 0/c < t
8
.
R
7
: R
7
= A
8
/c. Per lottimalit` a A
8
< 2 (altrimenti potrei sostituire due monete da
1/c con una da 2/c) e quindi R
7
1/c < t
7
.
R
6
: R
6
= 2A
7
+ A
8
/c. Per lottimalit` a A7 < 3 (altrimenti potrei sostituire tre
monete da 2/c con una da 5/c ed una da 1/c) ed inoltre se A
7
= 2 allora A
8
= 0
(altrimenti potrei sostituire due monete da 2/c ed una da 1/c con una da 5/c).
Quindi R
6
4/c < t
6
.
38
R
5
: R
5
= 5A
6
+ R
6
/c. Per lottimalit` a A
6
< 2 (altrimenti potrei sostituire due
monete da 5/c con una da 10/c). Quindi R
5
5/c +R
6
< 5/c +t
6
= t
5
.
R
4
: R
4
= 10A
5
+ R
5
/c. Per lottimalit` a A
5
< 2 (altrimenti potrei sostituire due
monete da 10/c con una da 20/c). Quindi R
4
10/c +R
5
< 10/c +t
5
= t
4
.
R
3
: R
3
= 20A
4
+ 10A
5
+R
5
/c. Per lottimalit` a A
4
< 3 (altrimenti potrei sostituire
tre monete da 20/c con una da 50/c ed una da 10/c) ed inoltre se A
4
= 2 allora
A
5
= 0 (altrimenti potrei sostituire due monete da 20/c ed una da 10/c con una
da 50/c). Quindi R
3
40/c +R5 < 40/c +t5 < t3.
R
2
: R
2
= 50A
3
+ R
3
/c. Per lottimalit` a A
3
< 2 (altrimenti potrei sostituire due
monete da 50/c con una da 1e). Quindi R
2
50/c +R
3
< 50/c +t3 = t2.
R
1
: Inne R
1
= 100A
2
+ R2/c. Per lottimalit` a A
2
< 2 (altrimenti potrei sostituire
due monete da 1e con una da 2e). Quindi R
1
1e +R
2
< 1e +t
2
= t1.
b) I tagli sono t
1
= c
k1
, t
2
= c
k2
, . . . , t
k1
= c
1
= c e t
k
= c
0
= 1 con c > 1 e
k 1.
Sia A
1
, A
2
, . . . , A
k
una soluzione ottima per un certo resto R.
Quindi R =

k
i=1
A
i
t
i
con n =

k
i=1
A
i
minimo.
Sia R
j
=

8
i=j+1
A
i
t
i
la somma delle monete di taglio minore di t
j
.
Se riusciamo a provare che R
j
< t
j
per ogni j allora la soluzione calcolata dallal-
goritmo ` e proprio la soluzione ottima A
1
, A
2
, . . . , A
k
e dunque lalgoritmo calcola
una soluzione ottima.
Intanto R
k
= 0 < t
k
.
Per ogni j < k abbiamo R
j
= c
kj1
A
j+1
+ R
j+1
. Per lottimalit` a A
j+1
< c
(altrimenti potrei sostituire c monete di taglio t
j+1
= c
kj1
con una di taglio t
j
=
c
kj
). Quindi R
j
(c 1)t
j+1
+R
j+1
< ct
j+1
= t
j
.
c) Scegliamo i tagli t
1
= 7/c, t2 = 5/c, e t
3
= 1/c. Con R = 10/c lalgoritmo goloso
sceglie una moneta da 7/c e tre da 1/c. La soluzione ottima ` e invece costituita da due
sole monete da 5/c.
Esercizio 47 Mostrare che se al contatore binario A di k bit aggiungiamo anche una ope-
razione DECREMENT(A) che decrementa di una unit` a il valore del contatore allora una
sequenza di n operazioni pu` o costare O(nk).
Soluzione. Consideriamo una successione di n = 2
k
1 operazioni (con k > 2) di cui le
prime 2
k1
1 sono INCREMENT (dopo di che i bit del contatore sono tutti 1) mentre le
altre 2
k1
sono una ripetizione di 2
k2
gruppi di due operazioni: una INCREMENT seguita
da una DECREMENT.
Il numero di bit che modicati dalle due operazioni di un gruppo ` e k + k = 2k e il
numero totale di bit modicati ` e maggiore di 2k2
k2
= O(kn).
Esercizio 48 Su di una certa struttura dati viene eseguita una sequenza di n operazioni.
Loperazione i-esima costa i quando i ` e una potenza di 2 mentre ha costo 1 negli altri casi.
Mostrare che tali operazioni hanno costo ammortizzato costante.
39
Soluzione. Il costo delli-esima operazione ` e:
c
i
=

1 se i non ` e potenza di 2
i se i ` e potenza di 2
Il numero di potenze di 2 comprese tra 0 ed n ` e log
2
n| + 1 e quindi il costo totale della
sequenza di n operazioni ` e:
C(n) =
n

i=1
c
i
= n log
2
n| 1 +
log
2
n

j=0
2
j
= n log
2
n| + 2
log
2
n+1
2
3n
Quindi il costo ammortizzato di una operazione ` e O(3n)/n = O(1).
Esercizio 49 Realizzare un contatore binario di k + 1 bit A[0 . . k] che prevede, oltre al-
loperazione INCREMENT, anche una operazione RESET che azzera il contatore. Fare in
modo che la complessit` a ammortizzata delle operazioni risulti costante. Suggerimento:
memorizzare la posizione m del bit 0 successivo al bit 1 pi` u signicativo (ossia m ` e il
minimo tale che tutti i bit in A[m. . k] sono 0).
Soluzione.
RESET(A) // Tutti i bit in A[m, k] sono 0.
1 for i = 0 to m 1
2 A[i] = 0
3 m = 0
INCREMENT(A)
1 i = 0
2 while i k and A[i] = 1
3 A[i] = 0
4 i = i + 1
5 if i == k + 1
6 m = 0
7 else A[i] = 1
8 if i == m
9 m = m + 1
Il costo effettivo di una operazione INCREMENT ` e t+1 pari al numero di bit modicati.
Tra questi vi ` e un certo numero t 0 di 1 trasformati in 0 e al pi` u un solo 0 trasformato
in 1.
Il costo effettivo di una operazione RESET ` e m + 1.
Attribuiamo un costo ammortizzato 3 alloperazione INCREMENT ed un costo ammor-
tizzato 1 a RESET.
Quando eseguiamo una INCREMENT usiamo una unit` a di costo per pagare leventuale
bit 0 trasformato in 1, una unit` a di costo la attribuiamo come credito prepagato a tale bit 1
40
e laltra unit` a di costo la attribuiamo alla variabile m se essa viene incrementata. Quindi
ogni bit 1 ha sempre un suo credito prepagato e la variabile m ha sempre esattamente m
crediti prepagati.
Quando eseguiamo una INCREMENT paghiamo la trasformazione dei t bit 1 in 0
usando i crediti prepagati attribuiti a tali bit.
Quando eseguiamo una RESET usiamo gli m crediti attribuiti alla variabile m per
pagare lazzeramento dei primi m bit del registro.
Alternativamente possiamo usare la funzione potenziale
= m+
k

i=0
A[i]
Esercizio 50 Realizzare una pila P con operazioni di costo ammortizzato costante avendo
a disposizione memoria per al pi` u M elementi. Se la memoria ` e piena quando si esegue
una PUSH, prima di eseguire loperazione vengono scaricati su disco alcuni elementi. Se
una operazione POP toglie lultimo elemento in memoria e ci sono degli elementi registrati
su disco, dopo loperazione se ne ricaricano alcuni in memoria.
Soluzione. Una scelta che funzione ` e scaricare e ricaricare dalla memoria secondaria
blocchi di M/2 elementi.
Sia n il numero di elementi in memoria principale e sia b una variabile booleana avente
valore TRUE se vi ` e qualche gruppo scaricato in memoria di massa e FALSE altrimenti.
Prendiamo come funzione potenziale:
(n, b) =

n se b = FALSE
[n M/2[ +M/2 se b = TRUE
Se b = FALSE una PUSH senza scaricamenti in memoria secondaria ha costo ammor-
tizzato:
c = c + (n + 1) (n)
= 1 + (n + 1) (n) = 2
mentre se vi scaricamento in memoria secondaria (n = M) allora
c = c + (M/2 + 1) (M)
= 1 +M/2 + ([M/2 + 1 M/2[ +M/2) (M) = 2
Invece una POP ha costo ammortizzato
c = c + (n 1) (n)
= 1 + (n 1) (n) = 0
Se b = TRUE una PUSH senza scaricamenti in memoria secondaria ha costo ammor-
tizzato:
c = c + (n + 1) (n)
= 1 + ([n + 1 M/2[ +M/2) ([n M/2[ +M/2) 2
41
mentre se vi scaricamento in memoria secondaria (n = M) allora
c = c + (M/2 + 1) (M)
= 1 +M/2 + ([M/2 + 1 M/2[ +M/2) ([M M/2[ +M/2) = 2
Invece una POP senza recupero da memoria secondaria ha costo ammortizzato
c = c + (n 1) (n)
= 1 + ([n 1 M/2[ +M/2) ([n M/2[ +M/2) 2
mentre se vi ` e recupero da memoria secondaria (n = 0) ma la memoria secondaria non si
svuota (b rimane TRUE) allora
c = c + (m/2 1) (0)
= 1 +M/2 + ([M/2 1 M/2[ +M/2) ([0 M/2[ +M/2) = 2
Inne se vi ` e recupero da memoria secondaria (n = 0) e la memoria secondaria si svuota
(b diventa FALSE) allora
c = c + (m/2 1) (0)
= 1 +M/2 + (M/2 1) ([0 M/2[ +M/2) = 0
Esercizio 51 Realizzare una coda Q utilizzando due normali pile P
1
e P
2
in modo che le
operazioni ENQUEUE(Q, x) e DEQUEUE(Q) richiedano tempo ammortizzato costante.
Soluzione.
ENQUEUE(Q, x)
1 PUSH(P
1
, x)
DEQUEUE(Q, x)
1 if EMPTY(P
2
)
2 while not EMPTY(P
1
)
3 x = POP(P
1
)
4 PUSH(P
2
, x)
5 return POP(P
2
)
Il costo effettivo di una ENQUEUE ` e 1, quello di una DEQUEUE senza travaso ` e 1
mentre quello di una DEQUEUE con travaso ` e 1 + [P
1
[.
Usiamo la funzione potenziale = [P
1
[. ENQUEUE ha costo ammortizzato c = c +
= 1 + 1 = 2. Il costo ammortizzato di DEQUEUE senza travaso ` e c = c + =
1 + 0 = 1. Inne il costo ammortizzato di DEQUEUE con travaso ` e c = c + =
1 + [P
1
[ [P
1
[ = 1.
42
Esercizio 52 Assumere che la contrazione della tavola dinamica venga effettuata quando
=
1
3
invece che quando =
1
4
e che invece di ridurre la sua dimensione ad
1
2
size essa
venga ridotta a
2
3
size. Calcolare il costo ammortizzato delle operazioni usando la funzione
potenziale:
= [2 num size[
Soluzione. Il costo effettivo di una INSERT senza espansione e di una DELETE senza con-
trazione ` e 1, quello di una INSERT con espansione e quello di una DELETE con contrazione
` e 1 +num.
Se 1/2 il costo ammortizzato di una INSERT senza espansione ` e
c
i
= c
i
+
i

i1
= 1 + (2 num
i
size
i
) (2 num
i1
size
i1
)
= 1 + 2(num
i1
+ 1) size
i1
2 num
i1
+size
i1
)
= 3
mentre con espansione ` e:
c
i
= c
i
+
i

i1
= 1 +num
i1
+ (2 num
i
size
i
) (2 num
i1
size
i1
)
= 1 +num
i1
+ 2(num
i1
+ 1) 2 num
i1
2 num
i1
+num
i1
)
= 3
(Per i = 1 abbiamo size
i
= num
i1
+ 1 invece di size
i
= 2 num
i1
. In questo caso
c
i
= 2.)
Se < 1/2 non vi ` e sicuramente espansione e il costo ammortizzato di una INSERT ` e:
c
i
= c
i
+
i

i1
= 1 + (size
i
2 num
i
) (size
i1
2 num
i1
)
= 1 +size
i1
2(num
i1
+ 1) size
i1
+ 2 num
i1
)
= 1
Se 1/2 il costo ammortizzato di una DELETE senza contrazione ` e:
c
i
= c
i
+
i

i1
= 1 + (size
i
2 num
i
) (size
i1
2 num
i1
)
= 1 +size
i1
2(num
i1
1) size
i1
+ 2 num
i1
)
= 3
mentre con contrazione ` e:
c
i
= c
i
+
i

i1
= 1 +num
i1
+ (size
i
2 num
i
) (size
i1
2 num
i1
)
= 1 +num
i1
+ 2 num
i1
2(num
i1
1) 3 num
i1
+ 2 num
i1
)
= 3
43
Se > 1/2 non vi ` e sicuramente contrazione e il costo ammortizzato di una DELETE ` e:
c
i
= c
i
+
i

i1
= 1 + (2 num
i
size
i
) (2 num
i1
size
i1
)
= 1 + 2(num
i1
1) size
i1
2 num
i1
+size
i1
)
= 1
Esercizio 53 Perch e in un B-albero non possiamo avere grado minimo t = 1?
Soluzione. I nodi di un B-albero con grado minimo t = 1 contengono 0 o 1 chiavi ed
hanno 1 o 2 gli. Un nodo contenente 1 chiave ` e pieno. Siccome, se lalbero non ` e vuoto,
la radice contiene almeno una chiave essa ` e sempre piena. Di conseguenza ogni INSERT
spezza la radice in due nodi vuoti ed aggiunge una nuova radice. Dopo n inserimenti
lalbero ha altezza h = n.
Esercizio 54 Calcolare il numero massimo di chiavi che pu` o contenere un B-albero in
funzione del suo grado minimo t e della sua altezza h.
Soluzione. Il numero massimo M di chiavi si ha quando tutti i nodi sono pieni:
M = (2t 1)
h

i=0
(2t)
i
= (2t 1)
(2t)
h+1
1
2t 1
= (2t)
h+1
1
Esercizio 55 Dire quale struttura dati si ottiene se in ogni nodo nero di un albero rosso-
nero conglobiamo i suoi eventuali gli rossi.
Soluzione. Siccome ogni nodo rosso ha padre nero tutti i nodi rossi vengono assorbiti dai
rispettivi padri.
Ogni nodo nero assorbe 0, 1 o 2 nodi rossi e quindi alla ne avr` a 1, 2 o 3 chiavi e, se
non ` e foglia avr` a 2, 3 o 4 gli.
Le foglie dellalbero ottenuto saranno tutte alla stessa altezza: laltezza nera che ave-
vano nellalbero rosso nero.
Otteniamo quindi un B-albero con grado minimo t = 2, ossia un 2-3-4-albero.
Esercizio 56 Scrivere una funzione che cerca la chiave minima contenuta in un B-albero
ed una che data una chiave k cerca la chiave successiva, ossia la minima chiave k

> k
presente nel B-albero.
Soluzione. La chiave minima ` e la prima chiave della prima foglia del B-albero:
MINIMO(T)
1 x = T.root
2 if x == NIL
3 return NIL
4 while not x.leaf
5 x = x.c
1
6 DISKREAD(x)
7 return x.key
1
44
La chiave successiva alla chiave k si trova con una versione modicata della funzione
SEARCH che usa un diverso invariante per la ricerca binaria.
SUCCESSIVORIC(x, k)
1 i = 1
2 j = x.n + 1
3 // INVARIANTE: x.key
1..i1
k < x.key
j..x. n
4 while i < j
5 m = (i +j)/2|
6 if x.key
m
k
7 i = m + 1
8 else j = m
9 // x.key
1..i1
k < x.key
i..x. n
10 if i x.n
11 ks = x.key
i
12 else ks = NIL
13 // ks ` e la minima chiave maggiore di k nel nodo x.
14 ks

= NIL
15 if not x.leaf
16 DISKREAD(x.c
i
)
17 ks

= SUCCESSIVORIC(x.c
i
, k)
18 // ks

` e la minima chiave maggiore di k nel sottoalbero di radice x.c


i
.
19 if ks

,= NIL
20 ks = ks

21 // ks ` e la minima chiave maggiore di k nel sottoalbero di radice x.


22 return ks
Esercizio 57 In un B-albero con grado minimo t = 2 vengono inserite le chiavi 1, 2, . . . , n
nellordine. Valutare il numero m di nodi del B-albero risultante in funzione di n.
Soluzione. Linserzione avviene sempre nellultima foglia a destra e quindi si percorre
sempre lultimo ramo a destra del B-albero. Le eventuali operazioni SPLITCHILD vengono
eseguite quando un nodo contiene 3 chiavi. In tal caso una chiave viene spostata nel padre
(che si trova sempre sul ramo di destra) e vengono creati du nodi gli con una sola chiave.
A quello dei due gli che non si trova sul ramo di destra non verranno mai aggiunte altre
chiavi. Pertanto i nodi che non stanno sullultimo ramo hanno sempre una sola chiave
mentre i nodi sullultimo ramo possono avere 1, 2 o 3 chiavi.
Indichiamo con c
i
il numero di chiavi contenute nel nodo di altezza i sullultimo ramo.
Ad ognuna delle c
i
chiavi ad altezza i contenute nel nodo di altezza i dellultimo ramo
rimane associato un sottoalbero di altezza i 1 costituito da nodi che non appartengono
allultimo ramo.
Tali sottoalberi hanno 2
i
1 nodi e altrettante chiavi. Il numero totale di chiavi ` e
quindi:
n =
h

i=0
[c
i
+c
i
(2
i
1)] =
h

i=0
c
i
2
i
45
mentre il numero totale di nodi ` e:
m =
h

i=0
(1 +c
i
(2
i
1)) =
h

i=0
c
i
2
i

i=0
(c
i
1)
Questo fornisce i limiti:
n 2h m n
Esercizio 58 Supponiamo che la dimensione di una pagina di disco si possa scegliere
arbitrariamente e che il tempo di accesso sia a + bt dove t ` e il grado minimo di un B-
albero i cui nodi occupano una pagina della dimensione scelta ed a e b sono due costanti.
Suggerire un valore ottimo di t nel caso a = 30ms e b = 40s.
Soluzione. Basta calcolare il minimo della funzione:
f(t) = (a +bt) log
t
n = lnn
a +bt
ln t
la cui derivata ` e:
f

(t) = lnn
bt(ln t 1) a
t ln
2
t
che si annulla per t(ln t 1) = a/b. Nel nostro caso a/b = 30000/40 = 750. Poich e
179(ln179 1) = 749.54
180(ln180 1) = 754.73
la derivata si annulla in un punto t
0
compreso tra 179 e 180. Siccome la derivata ` e negativa
per t < t
0
e positiva per t > t
0
il punto t
0
` e un punto di minimo. Per t intero il punto di
minimo ` e o 179 o 180. Siccome
f(179) = 7163.53 lnn
f(180) = 7163.55 lnn
il punto di minimo ` e 179.
Esercizio 59 Mostrare come sia possibile aggiungere ad ogni nodo interno x di un B-
albero i campi x.size
i
che contengono il numero di chiavi presenti nei sottoalbero di radici
x.c
i
. Dire quali sono le modiche da apportare a INSERT e DELETE. Assicurarsi che la
complessit` a asintotica non aumenti.
Soluzione. Sia x un nodo interno e sia y = x.c
i
glio di x. Vale la seguente formula:
x.size
i
=

y.n se y ` e foglia
y.n +

y. n+1
j=1
y.size
j
altrimenti
INSERT percorre un cammino dalla radice no alla foglia in cui si deve inserire la
nuova chiave. Prima di scendere da un nodo x al glio x.c
i
aumentiamo x.size
i
di 1.
Quando eseguiamo una SPLITCHILD(x, i, x.c
i
) dobbiamo ricalcolare sia x.size
i
che
x.size
i+1
usando la formula precedente.
DELETE percorre un cammino dalla radice no alla foglia da cui viene rimossa o la
chiave data o la successiva. Prima di scendere da un nodo x al glio x.c
i
diminuiamo
x.size
i
di 1.
Quando eseguiamo una AUGMENTCHILD(x, i, x.c
i
) dobbiamo usare la formula pre-
cedente per ricalcolare i campi x.size
i
relativi ai gli che vengono modicati.
46
Esercizio 60 Mostrare come sia possibile aggiungere ad ogni nodo x di un B-albero un
campo height che contiene laltezza del sottoalbero di radice x. Dire quali sono le mo-
diche da apportare a INSERT e DELETE. Assicurarsi che la complessit` a asintotica non
aumenti.
Soluzione. Laltezza di un nodo di un B-albero non pu` o cambiare.
`
E sufciente quin-
di assegnare laltezza corretta ai nodi quando essi vengono creati da una INSERT. In
particolare:
1. quando si inserisce il primo nodo in un albero vuoto gli viene assegnata altezza 0;
2. quando si divide un nodo in due parti si assegna al nuovo nodo laltezza del nodo
iniziale;
3. quando si crea una nuova radice si assegna ad essa laltezza della vecchia radice
aumentata di 1.
Esercizio 61 Siano dati due B-alberi T1 e T2 ed una chiave k tale che ogni chiave in T1
sia minore di k ed ogni chiave in T2 sia maggiore di k. Scrivere un algoritmo che riunisce
T1, T2 e k in ununico B-albero T (operazione JOIN(T1, k, T2)). Se h1 ed h2 sono le
altezze rispettive di T1 e T2 lalgoritmo deve avere complessit` a O(1 + [h1 h2[).
Soluzione. Assumiamo che i B-alberi siano aumentati con il campo height come visto
nel precedente esercizio.
JOIN(r1, k, r2)
1 // r1, r2 radici di T1 e T2, alla ne r ` e la radice di T.
2 if r1.height == r2.height
3 return JOINH1EQH2(r1, k, r2)
4 elseif r1.height > r2.height
5 return JOINH1GTH2(r1, k, r2)
6 else return JOINH1LTH2(r1, k, r2)
47
JOINH1EQH2(r1, k, r2)
1 if r1.n + r2.n + 1 2t 1
2 Aggiungi alla ne di r1 la chiave k, le chiavi di r2 e
3 i puntatori ai discendenti di r2.
4 r = r1, DISKWRITE(r)
5 else if r1.n < t 1
6 i = t 1 r1.n
7 // Ad r1 mancano i chiavi ed r2 ne ha almeno i pi` u
8 del necessario.
9 Aggiungi alla ne di r1 la chiave k, le prime i 1
10 chiavi di r2 e i primi i puntatori di r2.
11 k = r2.key
i
12 Togli da r2 le prime i chiavi ed i primi i puntatori.
13 elseif r2.n < t 1
14 i = t 1 r2.n
15 // Ad r2 mancano i chiavi ed r1 ne ha almeno i pi` u
16 del necessario.
17 Aggiungi allinizio di r2 la chiave k, le ultime i 1
18 chiavi di r1 e gli ultimi i puntatori di r1.
19 k = r1.key
r1. ni+1
20 Togli da r1 le ultime i chiavi e gli ultimi i
21 puntatori.
22 // Sia r1 che r2 hanno almeno t 1 chiavi.
23 Crea un nuovo nodo r con la sola chiave k e gli r1 ed r2.
24 DISKWRITE(r), DISKWRITE(r1), DISKWRITE(r2)
25 return r
48
JOINH1GTH2(r1, k, r2)
1 if r1.n < 2t 1
2 r = r1
3 else Crea un nodo r vuoto e con unico glio r1.
4 SPLITCHILD(r, 1, r1)
5 x = r, y = x.c
x. n+1
, DISKREAD(y)
6 while y.height > r2.height
7 if y.n == 2t 1
8 SPLITCHILD(x, x.n + 1, y)
9 y = x.c
x. n+1
10 x = y, y = x.c
x. n+1
, DISKREAD(y)
11 if y.n + r2.n + 1 2t 1
12 Aggiungi alla ne di y la chiave k, le chiavi di r2 e
13 i puntatori ai discendenti di r2.
14 DISKWRITE(y)
15 else if r2.n < t 1
16 i = t 1 r2.n
17 // Ad r2 mancano i chiavi ed y ne ha almeno i pi` u
18 del necessario.
19 Aggiungi allinizio di r2 la chiave k, le ultime i 1
20 chiavi di y e gli ultimi i puntatori di y.
21 k = y.key
y. ni+1
22 Togli da y le ultime i chiavi e gli ultimi i puntatori.
23 // Sia y che r2 hanno almeno t 1 chiavi.
24 Aggiungi alla ne di x la chiave k ed un puntatore ad r2.
25 DISKWRITE(x), DISKWRITE(y), DISKWRITE(r2),
26 return r
49
JOINH1LTH2(r1, k, r2)
1 if r2.n < 2t 1
2 r = r2
3 else Crea un nodo r vuoto e con unico glio r2.
4 SPLITCHILD(r, 1, r2)
5 x = r, y = x.c
1
, DISKREAD(y)
6 while y.height > r1.height
7 if y.n = 2t 1
8 SPLITCHILD(x, 1, y)
9 y = x.c
1
10 x = y, y = x.c
1
, DISKREAD(y)
11 if r1.n +y.n + 1 2t 1
12 Aggiungi allinizio di y le chiavi di r1 e la chiave k e
13 i puntatori di r1.
14 DISKWRITE(y)
15 else if r1.n < t 1
16 i = t 1 r1.n
17 // Ad r1 mancano i chiavi ed y ne ha almeno i pi` u
18 del necessario.
19 Aggiungi alla ne di r1 la chiave k, le prime i 1
20 chiavi di y e i primi i puntatori di y.
21 k = y.key
i
22 Togli da y le prime i chiavi e i primi i puntatori.
23 // Sia r1 che y hanno almeno t 1 chiavi.
24 Aggiungi allinizio di x la chiave k ed un puntatore ad r1.
25 DISKWRITE(x), DISKWRITE(y), DISKWRITE(r1),
26 return r
Complessit` a. Se h1 = h2 lalgoritmo richiede un tempo proporzionale a t. Siccome
t ` e una costante lalgoritmo ha complessit` a O(1) = O(1 + [h1 h2[).
Se h1 > h2 lalgoritmo richiede un tempo proporzionale a t pi` u un tempo proporzio-
nale ad (h1 h2)t per il ciclo while. Siccome t ` e una costante lalgoritmo ha complessit` a
O(1 + [h1 h2[). Il caso h1 < h2 ` e simmetrico.
50
Esercizio 62 Siano dati un B-albero T ed una chiave k di T. Trovare un algoritmo che
divide T in un B-albero T1 contenente tutte le chiavi di T minori di k, un B-albero T2
contenente tutte le chiavi di T maggiori di k, e la chiave k (operazione SPLIT). Lalgoritmo
deve avere complessit` a O(h) in cui h ` e laltezza di T.
Soluzione.
SPLIT(r, k) // r radice di T. Ritorna le radici r1 e r2 di T1 e T2.
1 Con la ricerca binaria trova lindice i tale che r.key
1..i
k < r.key
i+1
.
2 if i > 0 and r.key
i
== k
3 // La chiave k si trove nella radice.
4 // Costruisci T1
5 if i == 1
6 r1 = r.c
1
// La chiave k ` e la prima.
7 else Crea un nuovo nodo r1.
8 Copia da r in r1 le chiavi r.key
1
, . . . , r.key
i1
ed i
puntatori r.c
1
, . . . , r.c
i
.
9 DISKWRITE(r1)
10 // Costruisci T2
11 if i == r.n
12 r2 = r.c
r. n+1
// La chiave k ` e lultima.
13 else Crea un nuovo nodo r2.
14 Copia da r in r2 le chiavi r.key
i+1
, . . . , r.key
r. n
ed i
puntatori r.c
i+1
, . . . , r.c
r. n+1
.
15 DISKWRITE(r2)
16 else // La chiave k si trove nel sottoalbero r.c
i+1
.
17 DISKREAD(r.c
i+1
)
18 x1, x2 = SPLIT(r.c
i+1
, k)
19 // Costruisci T1
20 if i == 0
21 r1 = x1 // r.c
i+1
` e il primo glio.
22 else Crea un nuovo nodo y1.
23 Copia da r in y1 le chiavi r.key
1
, . . . , r.key
i1
ed i
puntatori r.c
1
, . . . , r.c
i
.
24 DISKWRITE(y1)
25 r1 = JOIN(y1, r.key
i
, x1)
26 // Costruisci T2
27 if i == r.n
28 r2 = x2 // r.c
i+1
` e lultimo glio.
29 else Crea un nuovo nodo y2.
30 Copia da r in y2 le chiavi r.key
i+2
, . . . , r.key
r. n
ed i
puntatori r.c
i+2
, . . . , r.c
r. n+1
.
31 DISKWRITE(y2)
32 r2 = JOIN(x2, r.key
i+1
, y2)
33 return r1, r2
Complessit` a. Calcoliamo la complessit` a in funzione di h = r.height. Per dimostrare
che lalgoritmo richiede tempo O(h) = O(log n) calcoliamo separatamente il tempo T
1
(h)
51
richiesto per eseguire le chiamate alla funzione JOIN necessarie a costruire r1, il tempo
T
2
(h) richiesto per eseguire le chiamate alla funzione JOIN necessarie a costruire r2 ed il
tempo T
3
(h) richiesto per eseguire tutte le altre operazioni.
Il tempo richiesto nel caso pessimo per eseguire tutte le altre operazioni ` e dato dalla
relazione di ricorrenza T
3
(h) = t + T
3
(h 1) e quindi T
3
(h) = O(ht) che ` e O(h) dato
che t ` e una costante.
Il tempo richiesto nel caso pessimo per eseguire le JOIN necessarie a costruire r1 ` e
il tempo necessario ad eseguire la JOIN(y1, r.key
i
, x1) pi` u il tempo per eseguire le JOIN
necessarie a costruire x1.
Siccome x1 ` e la radice del primo sottoalbero ritornato da SPLIT(r.c
i+1
, k) la sua
altezza h1 = x1.height ` e sicuramente minore di h.
Il tempo necessario ad eseguire JOIN(y1, r.key
i
, x1) ` e
O(1 + [y1.height x1.height[) = O(h h1)
dato che y1.height = h 1.
Dunque il tempo totale T
1
(h) per le JOIN necessarie a costruire r1 ` e dato dalla rela-
zione di ricorrenza T
1
(h) = O(h h1) + T
1
(h1) ed ` e quindi O(h). Simmetricamente si
dimostra che il tempo totale T
2
(h) per le JOIN necessarie a costruire r2 ` e O(h).
Quindi lintero algoritmo richiede tempo O(h).
Esercizio 63 Sia T = 1, 2, . . . , n ed S = o
1
, o
2
, . . . , o
n+m
una sequenza di opera-
zioni contenente n operazioni INSERT che inseriscono ogni intero x T una e una sola
volta ed m operazioni EXTRACTMIN che estraggono m degli interi inseriti. Le operazioni
possono essere in un ordine qualsiasi con il solo vincolo che se o
i
` e una EXTRACTMIN la
sequenza o
1
, o
2
, . . . , o
i1
deve contenere pi` u INSERT che EXTRACTMIN. Una possibile
sequenza con n = 9 ed m = 6 ` e:
S = 4, 8, 0, 3, 0, 9, 2, 6, 0, 0, 0, 1, 7, 0, 5
in cui le operazioni INSERT sono indicate con il numero x T che viene inserito men-
tre le operazioni EXTRACTMIN sono indicate con uno 0. Si chiede un algoritmo ef-
ciente che data la sequenza di operazioni S = o
1
, o
2
, . . . , o
n+m
calcoli la sequenza
X = x
1
, x
2
, . . . , x
m
degli m interi estratti. Ad esempio, con la sequenza
S = 4, 8, 0, 3, 0, 9, 2, 6, 0, 0, 0, 1, 7, 0, 5
il risultato dovrebbe essere
X = 4, 3, 2, 6, 8, 1
Soluzione. Indichiamo con e
1
, . . . , e
m
le m operazioni EXTRACTMIN nellordine in cui
compaiono in S.
Raggruppiamo gli interi 1, 2, . . . , n in m + 1 insiemi disgiunti I
1
, . . . , I
m+1
mettendo
in I
1
gli interi inseriti prima della prima EXTRACTMIN e
1
, in I
m+1
quelli inseriti dopo
lultima EXTRACTMIN e
m
e, per 2 i m, mettendo in I
i
quelli inseriti tra e
i1
ed e
i
.
Siccome alcuni degli insiemi possono essere vuoti aggiungiamo a ciascun insieme
I
i
un elemento ttizio di valore n + i. Inoltre, nel rappresentante di ciascun insieme
memorizziamo lindice dellinsieme stesso. La versione astratta dellalgoritmo ` e:
52
MINIMOOFFLINE(S, X, m, n)
1 // S ` e larray di interi che rappresenta la sequenza di n INSERT (denotate
dallintero x inserito) e di m EXTRACTMIN (denotate con uno 0).
2 Raggruppa gli interi inseriti e quelli ttizi negli insiemi I
1
, I
2
, . . . , I
m+1

3 for i = 1 to n
4 Trova lindice j dellinsieme I
j
che contiene i.
5 if j m
6 X
j
= i
7 Trova il primo h > j con X
h
non ancora calcolato.
8 I
h
= I
j
I
h
9 // X
1
, . . . , X
m
sono gli interi estratti.
Dettagli implementativi:
Gli interi da raggruppare (quelli inseriti e quelli ttizzi) sono gli m + n + 1 interi
1, 2, . . . , n, n + 1, . . . , n +m+ 1.
Per ragrupparli negli insiemi disgiunti I
1
, I
2
, . . . , I
m+1
useremo le foreste di insiemi
disgiunti.
Per ogni intero 1, 2, . . . , n, n + 1, . . . , n + m + 1 dobbiamo quindi prevedere i campi
rank e p ed un campo indice in cui memorizzare lindice dellinsieme di appartenenza
(aggiungiamo tale campo a tutti ma ci preoccuperemo di mantenere aggiornato soltanto
quello del rappresentante).
Come struttura dati di supporto per memorizzare gli interi da raggruppare usiamo tre
array rank, p e indice di m+n+1 elementi ciascuno. Per ogni intero x = 1, . . . , m+n+1,
rank[x], p[x] e indice[x] sono i valori dei campi rank, p e indice relativi a tale intero x.
Correttezza dellalgoritmo: Aggiungiamo allalgoritmo le asserzioni per la prova di
correttezza.
53
MINIMOOFFLINE(S, X, m, n)
1 // S ` e larray di interi che rappresenta la sequenza di n INSERT (denotate
dallintero x inserito) e di m EXTRACTMIN (denotate con uno 0).
2 Raggruppa gli interi inseriti e quelli ttizi negli insiemi I
1
, I
2
, . . . , I
m+1

3 for i = 1 to n
4 // Ogni x < i estratto ` e stato memorizzato al posto giusto in X.
Se i x n e x I
k
allora:
a) x ` e stato inserito prima di e
k
,
b) x non pu` o essere estratto prima di e
k
,
c) e
k
estrae un intero y i.
5 Trova lindice j dellinsieme I
j
che contiene i.
6 // Ogni x < i estratto ` e stato memorizzato al posto giusto in X.
Se i x n e x I
k
allora:
a) x ` e stato inserito prima di e
k
,
b) x non pu` o essere estratto prima di e
k
,
c) e
k
estrae un intero y i,
d) i I
j
.
Inoltre, se j = m + 1 allora i non pu` o essere estratto mentre se
j m allora i ` e stato inserito prima di e
j
ed ` e il pi` u piccolo
inserito prima di e
j
che possa essere estratto da e
j
.
Quindi e
j
estrae i.
7 if j m
8 X
j
= i
9 // Ogni x < i + 1 estratto ` e stato memorizzato al posto
giusto in X. Se i x n e x I
k
allora:
a) x ` e stato inserito prima di e
k
,
b) x non pu` o essere estratto prima di e
k
,
c) e
k
estrae un intero y i,
d) i I
j
e j m.
10 Trova il primo h > j con X
h
non ancora calcolato.
11 I
h
= I
j
I
h
12 // Ogni x < i + 1 estratto ` e stato memorizzato al posto giusto in X.
Se i + 1 x n e x I
k
allora:
a) x ` e stato inserito prima di e
k
,
b) x non pu` o essere estratto prima di e
k
,
c) e
k
estrae un intero y i + 1,
13 // X
1
, . . . , X
m
sono gli interi estratti.
Esercizio 64 Il minimo antenato comune di due nodi u e v in un albero T ` e il nodo w di
massima profondit` a che ` e antenato sia di u che di v. Nel problema dei minimi comuni
antenati off-line sono dati un albero T con n nodi ed una lista
L = (u
1
, v
1
), (u
2
, v
2
), . . . , (u
m
, v
m
)
di m coppie di nodi. Si chiede di calcolare la lista
W = w
1
, w
2
, . . . , w
m

54
dei minimi comuni antenati di ogni coppia.
Esiste un algoritmo, dovuto a Robert E. Tarjan, che risolve il problema in tempo (quasi)
lineare con una sola percorrenza dellalbero. Assumiamo che ogni nodo dellalbero abbia
i seguenti campi:
- child puntatore al primo glio;
- sibling puntatore al fratello;
- ancestor puntatore ad un suo antenato nellalbero;
- color che allinizio ` e white per tutti i nodi e alla ne ` e black.
ed inoltre i seguenti campi necessari per raggruppare i nodi dellalbero in foreste di insiemi
disgiunti:
- rank rango del nodo nella foresta di insiemi disgiunti;
- p puntatore al padre in una foresta di insiemi disgiunti (non al padre nellalbero T).
Lalgoritmo di Tarjan ` e descritto dalla seguente funzione ricorsiva che deve essere
chiamata sulla radice dellalbero.
MINCOMANT(u)
1 MAKESET(u)
2 u.ancestor = u
3 v = u.child
4 while v ,= NIL
5 MINCOMANT(v)
6 UNION(u, v)
7 FINDSET(u).ancestor = u
8 v = v.sibling
9 u.color = BLACK
10 for ogni (u
i
, v
i
) L con u
i
== u
11 if v
i
.color == BLACK
12 w
i
= FINDSET(v
i
).ancestor
dimostrare che esso ` e corretto e valutarne la complessit` a.
Soluzione.
Correttezza. Consideriamo la generica chiamata MINCOMANT(u) e sia
r = x
0
, . . . , x
k
= u
il cammino dalla radice r dellalbero T al nodo u su cui viene richiamata la funzione
MINCOMANT.
Sia Inv(u) la seguente asserzione sugli antenati x
0
, . . . , x
k1
di u:
55
Per ogni j = 0, . . . , k 1 il nodo x
j
e tutti i nodi che stanno nei sottoalberi
radicati nei gli di x
j
che precedono il glio x
j+1
costituiscono un insieme
disgiunto S
j
il cui rappresentante ha il puntatore ancestor che punta ad x
j
.
Inoltre z.color = BLACK per tutti i nodi z S
j
escluso il nodo x
j
per il quale
x
j
.color = WHITE.
Sia Pre(u) la seguente asserzione:
Per ogni coppia (u
i
, v
i
) in L tale che u
i
, v
i

k1
j=1
S
j
e che
u
i
.color = v
i
.color = BLACK
` e stato assegnato correttamente a w
i
il puntatore al minimo comune antenato
di u
i
e v
i
. Inoltre z.color = WHITE per ogni z ,
k1
j=1
S
j
.
e Post(u) lasserzione:
Il nodo x
k
= u e tutti i suoi discendenti costituiscono un insieme disgiunto
S
k
il cui rappresentante ha il puntatore ancestor che punta ad x
k
e z.color =
BLACK per tutti i nodi z S
k
compreso il nodo x
k
= u. Per ogni coppia
(u
i
, v
i
) in L tale che u
i
, v
i

k
j=1
S
j
e u
i
.color = v
i
.color = BLACK ` e stato
assegnato correttamente a w
i
il puntatore al minimo comune antenato di u
i
e
v
i
. Inoltre z.color = WHITE per ogni z ,
k
j=1
S
j
.
Quando viene effettuata la chiamata principale MINCOMANT(r) sulla radice del-
lalbero sono banalmente vere sia Pre(r) che Inv(r). Se quando termina la chiamata
principale MINCOMANT(r) risultano vere sia Post(r) che Inv(r) allora ogni nodo z ha
z.color = BLACK e quindi per ogni coppia in L ` e stato assegnato correttamente a w
i
il
puntatore al minimo comune antenato di u
i
e v
i
.
Dobbiamo dimostrare che se sono vere Pre(u) e Inv(u) quando viene effettuata una
generica chiamata MINCOMANT(u) allora sono vere Post(u) e Inv(u) quando tale chia-
mata termina. Per fare questa dimostrazione possiamo assumere induttivamente che le
chiamate ricorsive soddisno la medesima condizione.
Il seguente ` e la funzione MinComAnt annotata con le asserzioni che servono per tale
verica.
56
MINCOMANT(u)
1 // Pre(u) e Inv(u).
2 MAKESET(u)
3 u.ancestor = u
4 v = u.child
5 while v ,= NIL
6 // Pre(v) e Inv(v).
7 MINCOMANT(v)
8 // Post(v) e Inv(v).
9 UNION(u, v)
10 FINDSET(u).ancestor = u
11 v = v.sibling
12 // Inv(v).
Il nodo x
k
= u e i suoi discendenti costituiscono un insieme disgiunto
S
k
il cui rappresentante ha il puntatore ancestor che punta ad x
k
e
z.color = BLACK per tutti i nodi z S
k
escluso il nodo x
k
= u per
cui u.color = WHITE.
Per ogni coppia (u
i
, v
i
) in L tale che u
i
, v
i

k
j=1
S
j
e
u
i
.color = v
i
.color = BLACK e stato assegnato correttamente a w
i
il
puntatore al minimo comune antenato di u
i
e v
i
.
Inoltre z.color = WHITE per ogni z ,
k
j=1
S
j
.
13 u.color = BLACK
14 for ogni (u
i
, v
i
) L con u
i
== u
15 if v
i
.color = BLACK
16 w
i
= FINDSET(v
i
).ancestor
17 // Post(u) e Inv(u).
Complessit` a. Possiamo assumere che le operazioni sugli insiemi disgiunti richiedano
tempo costante.
Sia n il numero di nodi di T ed m il numero di coppie in L.
La funzione MINCOMANT viene richiamata una e una sola volta su ogni vertice u
dellalbero. Infatti essa viene richiamata soltanto su vertici di colore WHITE, prima di ter-
minare li colora BLACK e alla ne di tutto lalgoritmo i nodi sono tutti di colore BLACK.
Quindi il numero totale di chiamate ` e n e dunque le istruzioni interne al ciclo while
vengono eseguite al pi` u n volte.
Data la lista L possiamo costruire in tempo O(m) una lista L
u
per ogni vertice u che
contiene tutti i vertici v
i
tali che (u
i
, v
i
) L con u
i
= u e tutti gli u
i
tali che (u
i
, v
i
) L
con v
i
= u.
Il ciclo for esplora ciascuna lista L
u
al pi` u una sola volta. Siccome la somma delle
lunghezze delle liste L
u
` e 2m lesecuzione di tutti i cicli for di tutte le chiamate richiede
tempo O(m).
Pertanto lintero algoritmo richiede tempo O(m+n).
Esercizio 65 Dimostrare che non esiste nessuna struttura dati S che permetta di eseguire
in tempo costante tutte e tre le operazioni MAKE(S), INSERT(S, x) ed EXTRACTMIN(S)
(sia caso pessimo che ammortizzato).
Soluzione. Possiamo usare una tale struttura dati per ordinare un array nel seguente modo:
57
SORT(A, n)
1 MAKE(S)
2 for i = 1 to n do INSERT(S, A[i])
3 for i = 1 to n do A[i] = EXTRACTMIN(S)
Se MAKE(S), INSERT(S, A[i]) ed EXTRACTMIN(S) richiedessero tempo costante
lalgoritmo SORT richiederebbe tempo O(n). Impossibile perch e O(nlog n) ` e un limite
inferiore per lordinamento.
Esercizio 66 Sono dati un insieme p
1
, p
2
, . . . , p
n
di n punti ed un insieme di m connes-
sioni dirette c
1
= (x
1
, y
1
), c
2
= (x
2
, y
2
), . . . , c
m
= (x
m
, y
m
). Descrivere un algoritmo
efciente che utilizza una struttura dati per insiemi disgiunti per determinare se tutti i
punti sono connessi tra loro.
Soluzione.
CONNESSI (p, n, c, m)
1 for i = 1 to n
2 MAKESET(p
i
)
3 nsets = n
4 for j = 1 to m
5 p = FINDSET(x
j
)
6 q = FINDSET(y
j
)
7 if p ,= q
8 UNION(p, q)
9 nsets = nsets 1
10 return nsets = 1
Esercizio 67 Supponiamo che non esista una rappresentazione della chiave . Riscri-
vere DELETE(H, x) per un mucchio binomiale H in modo che essa non usi la chiave .
Assicurarsi che la complessit` a rimanga O(log n).
Soluzione. La funzione DELETE(H, x) usa la funzione DECREASEKEY(H, ) per
far diventare il nodo x una radice e quindi la funzione EXTRACTMIN(H) per rimuovere
la radice minima che a questo punto ` e proprio il nodo x. Occorre effettuare le stesse
operazioni senza cambiare chiave ad x.
58
DELETE(H, x)
1 y = x.parent
2 while y ,= NIL
3 k = x.key, x.key = y.key, y.key = k
4 x = y, y = x.parent
// Ora x ` e una radice. La tolgo dalla lista delle radici.
5 if x == H.cima
6 H.cima = x.sibling
7 else z = H.cima
8 while z.sibling ,= x do z = z.sibling
9 z.sibling = x.sibling
// Costruisco un heap H1 con i gli di x.
10 H1.cima = NIL
11 while x.child ,= NIL
12 y = x.child, x.child = x.sibling
13 y.parent = NIL
14 y.sibling = H1.cima, H1.cima = y
15 UNION(H, H1)
16 return x
Esercizio 68 Nella funzione EXTRACTMIN(H) abbiamo dovuto percorrere tutta la lista
dei gli del nodo estratto per invertirne lordine. Questo perch e la lista delle radici ` e
ordinata per grado crescente mentre le liste dei gli sono ordinate per grado decrescente.
Cosa succede se ordiniamo le due liste in modo concorde?
Soluzione. Dobbiamo comunque percorrere la lista dei gli per porre a NIL tutti i
puntatori parent.
59

Potrebbero piacerti anche