You are on page 1of 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. L’algoritmo 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 l’array P[1 . . n] usando HEAP-SORT(P, A, n) modificato 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: modificare MERGE-SORT.)
Soluzione. La versione modificata 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 definizione 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 sufficiente 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 dell’esercizio.
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
n−1
= 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
n−1
= ∞
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
e−1)n−log
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
n−3
= ∞
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
n−2 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. Infine n
1/ log
2
n
= 2
log
2
n(1/ log
2
n)
= 2 e quindi n
1/ log
2
n
= Θ(1).
L’ordine 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)n−a ` 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 dell’algoritmo 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
n−1
(log
2
n − 1) + (c +a)2
log
2
n−1
− a
=
1
2
bnlog
2
n −
1
2
bn +
1
2
(c +a)n −a
= Ω(nlog
2
n)
Esercizio 6 Usare il metodo dell’esperto 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-
l’esperto? Giustificare 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 dell’esperto.
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 verificata 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 dell’esperto, la settima si risolve con
una sommatoria e l’ultima 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 dell’esperto. 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 dell’array sono tutti distinti, l’algoritmo HEAP-
SORT ha complessit` a minima Ω(nlog n).
Soluzione.
L’algoritmo 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 dell’array 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 l’insieme dei nodi interni che hanno soltanto foglie come figli. 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 figlio 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 figlio mentre tutti gli altri ne hanno due. Siccome i figli di
nodi in A
2
appartengono ad A
2
abbiamo che 3m − 1 ≤ ,n/2| e dunque m ≤
n/2−1
3
. Di
conseguenza i nodi in B ∩ A
1
sono ,¸n/2|/2| − m ≥ (n − 2)/12.
I nodi in B∩A
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 fino
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 l’elemento 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 un’unico array ordinato B di nm elementi. L’algoritmo 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 l’indice 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 l’indice 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 finita.
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). L’algoritmo 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 significative.
Soluzione. Il tempo richiesto dagli algoritmi randomizzati non dipende dall’input 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 L’altezza 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 l’albero ha altezza 0 = log
2
1. Se n > 1 l’albero
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 (*) L’altezza media di un albero binario con n foglie ` e la lunghezza media
dei cammini dalla radice alle foglie. Dimostrare che l’altezza media di un albero binario
con n foglie ` e maggiore o uguale di log
2
n.
Soluzione. Per induzione su n. Se n = 1 l’albero ha altezza 0 = log
2
1. Se n > 1 l’albero
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 dell’esercizio 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 all’al-
tezza media dell’albero delle decisioni. Siccome l’albero delle decisioni ha n! foglie, per
l’esercizio 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 sufficiente per decidere quale sia il primo e quale
il secondo. Altrimenti raggruppiamo gli n elementi dell’array in ,n/2| gruppi contenenti
ciascuno due elementi tranne un eventuale gruppo finale 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 l’algoritmo 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
dell’array. 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 sufficiente per trovare
il secondo elemento in ordine di grandezza dell’array.
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 l’altro 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 l’ipotesi 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
L’ultimo passaggio ` e giustificato 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
k−1
< n + 1 ≤ 2
k
. Siccome n > 2 certamente
k ≥ 2 per cui 2
k−1
` e pari ed essendo anche n +1 pari avremo che 2
k−1
< 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 l’insieme degli n elementi dell’array 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 l’insieme 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.
All’inizio 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 l’altro in T. Questo puo essere fatto al
pi` u ¸n/2| volte arrivando ad m
1
+m
2
= 2n−2¸n/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 − 2¸n/2| − 2
confronti. In totale servono quindi almeno
¸n/2| + 2n − 2¸n/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]
L’albero 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[ dell’insieme 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 dell’array. Se il nume-
ro massimo N di chiavi memorizzate nell’array ` 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 l’inizializzazione 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
l’indice dell’elemento dell’array I che contiene l’indice 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
L’inizializzazione 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 modifiche e continua ad essere eseguita in tempo
costante Θ(1).
L’unica modifica a SEARCH ` e che se la chiave cercata non ` e presente non occorre
arrivare alla fine 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
modificato 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
n−1
c
n−2
. . . c
1
c
0
come il numero intero k =

n−1
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 dell’aritmetica
modulare.
16
In questo modo si ottiene
h(k) = k mod 255
= (
n−1

i=0
c
i
256
i
) mod 255
= (
n−1

i=0
c
i
(256 mod 255)
i
) mod 255
= (
n−1

i=0
c
i
) mod 255
e siccome la somma ` e commutativa si vede che l’ordine dei caratteri ` e irrilevante.
Esercizio 22 Mostrare che in un albero binario di ricerca il successore di un nodo avente
figlio destro non ha figlio sinistro ed il predecessore di un nodo avente figlio sinistro non
ha figlio destro.
Soluzione. Sia k la chiave del nodo x. Se x ha figlio destro y il successore di x ` e il nodo
z con chiave minima del sottoalbero radicato in y. Se z avesse figlio 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 figlio sinistro y il predecessore di x ` e il nodo z con chiave
massima del sottoalbero radicato in y. Se z avesse figlio 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 l’istruzione 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 dell’intera sequenza di operazioni ` e O(n)
(proporzionale al numero di archi percorsi).
17
Dividiamo gli archi in archi sinistri (quelli che connettono un figlio sinistro al padre) e
archi destri (quelli che connettono un figlio destro al padre) e per ogni nodo x dell’albero
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 finale. 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 finale 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
un’altra struttura dati che contiene un puntatore ad un nodo y di T il cui predecessore z ha
due figli. Quale inconveniente sorge se dall’albero T viene rimosso il nodo z? Modificare
l’algoritmo 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, nell’albero, ha sostitui-
to y. Per evitare l’inconveniente invece di ricopiare le informazioni da y a z occorre, dopo
aver rimosso il nodo y dall’albero, inserirlo nell’albero 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
l’albero ` 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 figli
rossi. In questo caso l’albero ` 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 figli 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 figlio y pure nero.
`
E allora possibile aggiungere un nodo rosso z tra x ed y mettendo
y come figlio di z e z al posto di y come figlio di x. Questo non aumenta l’altezza nera
bh(T) dell’albero. Naturalmente occorre anche aggiungere un sottoalbero di altezza nera
opportuna e la cui radice w deve diventare il secondo figlio di z.
Assumendo che l’altezza nera del nodo x sia k = bh(x) determinare in funzione di k:
1. L’altezza 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
k−1
− 1.
3. Il minimo numero totale m di nodi rossi e di nodi interni neri che debbono essere
aggiunti ` e quindi 2
k−1
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 all’altezza
nera dell’albero, ossia quando x ` e la radice e il nodo rosso viene aggiunto come figlio
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 l’albero binario completo con
2
h
− 1 nodi tutti neri (altezza nera k = h − 1).
Sia m = n−2
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
t−1
b
t−2
. . . b
0
la rappresentazione binaria di m.
Se t = 0 possiamo solo aggiungere un nodo rosso alla fine di un cammino (ad esempio
il primo) ottenendo un rapporto
1
n−1
.
Altrimenti consideriamo i primi 2
t−1
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 l’operazione per ciascuno dei bit b
i
= 1 con i > 0 aggiungendo
altrettanti nodi rossi. Infine, se b
0
= 1 dobbiamo semplicemente aggiungere un nodo
rosso a livello h.
In definitiva, se s ` e il numero di bit 1 nella rappresentazione binaria di m il rapporto
minimo ` e
s
n−s
= Θ(
log n
n
).
Massimo rapporto:
Sia h tale che 2
h
− 1 ≤ n < 2
h+1
− 1 e consideriamo due casi:
h dispari Consideriamo l’albero 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
l’albero di 2
h+1
− 1 nodi con il massimo numero di nodi rossi e siccome ogni nodo
nero ha due figli 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
t−1
. . . 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 l’ultimo livello otteniamo
un sottoalbero con
2
3
(4
t
− 1) nodi rossi e
4
t
−1
3
nodi neri.
L’albero rosso nero cos`ı ottenuto contiene c
t
2·4
t
+1
3
nodi rossi in meno e c
t
4
t
−1
3
nodi
neri in meno.
20
Possiamo ripetere l’operazione 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 l’albero 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, figli della radice neri, foglie rosse
e altezza nera k = h/2 + 1. Questo ` e certamente l’albero di 2
h+1
− 1 nodi con il
massimo numero di nodi rossi e siccome ogni nodo nero, esclusa la radice, ha due
figli 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
t−1
. . . 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 l’ultimo livello otteniamo
un sottoalbero con
2
3
(4
t
− 1) nodi rossi e
4
t
−1
3
nodi neri.
L’albero rosso nero cos`ı ottenuto contiene c
t
2·4
t
+1
3
nodi rossi in meno e c
t
4
t
−1
3
nodi
neri in meno.
Possiamo ripetere l’operazione 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 l’altezza nera di un albero rosso nero aumenta per effetto
di una INSERTRB ed in quale caso diminuisce per effetto di una DELETERB.
21
Soluzione. L’altezza nera dell’albero aumenta per effetto di una INSERTRB quando:
a) viene inserito il primo nodo: in questo caso il nodo inserito diventa radice e l’altezza
nera aumenta da 0 a 1.
b) viene eseguito il Caso 1 con x.p radice dell’albero: in questo caso, prima della
trasformazione, la radice nera non veniva contata nell’altezza nera mentre, dopo
la trasformazione che scambia i colori della radice con quello dei figli, i figli neri
contano nell’altezza nera dell’albero. Quindi l’altezza nera aumenta di 1.
L’altezza nera dell’albero diminuisce per effetto di una DELETERB quando viene eseguito
il Caso 0 con x radice dell’albero. Questo accade se:
a) viene tolto l’ultimo nodo: in questo caso l’altezza nera diminuisce da 1 a 0.
b) dopo aver eseguito il Caso 2 con x figlio 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 nell’altezza 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] l’array. Indichiamo con b
i
il numero di inversioni del prefisso
A[1 . . i] dell’array. Allora b
1
= 0 mentre per i > 1 il valore di b
i
` e uguale a b
i−1
aumentato
del numero di elementi in A[1 . . i − 1] che sono maggiori di A[i].
Se inseriamo ad uno ad uno gli elementi dell’array nell’ordine 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 nell’albero.
Dopo aver inserito l’elemento A[i] possiamo calcolare il numero di elementi maggiori di
A[i] presenti nell’albero con una chiamata RANK(x) sul nodo x appena inserito. Siccome
sia INSERTRB che RANK richiedono tempo O(log n) l’intera 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 un’origine
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 l’origine.
Orientiamo le corde nello stesso verso dell’arco contenente l’origine 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 dell’albero due campi
puntatore x.succ e x.pred che puntano rispettivamente al successore ed al predecessore
di x. Aggiungiamo inoltre all’albero 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 modificate in modo tale da inserire e togliere
un elemento sia dall’albero che dalla lista.
Esercizio 32 Aumentare un albero rosso nero T avente chiavi di tipo numerico in modo
da poter eseguire in tempo costante l’operazione MINGAP che ritorna la minima distanza
tra due chiavi. Suggerimento: aumentare l’albero 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 dell’aumento, 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 dell’aumento, 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 l’intervallo i. L’algoritmo 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 modifica T.
Soluzione. Versione che modifica 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 modifica 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 // L’intervallo 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 // L’intervallo 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, l’esecuzione
di una chiamata richiede tempo costante. Sia n
x
il numero di nodi dell’albero radicato in
x, sia k
x
il numero di intervalli di tale sottoalbero che intersecano l’intervallo 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 sull’altezza 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 l’intervallo 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 l’intervallo 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 l’intervallo 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,j−1
< 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. L’algoritmo 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 dell’esercizio 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 prefisso X
n−1
.
Se s
m
= x
n
il prefisso S
m−1
` e una LIS del prefisso X
n−1
il cui ultimo elemento ` e
minore o uguale di x
n
. Infatti se esistesse una sottosequenza crescente S

del prefisso
X
n−1
il cui ultimo elemento ` e minore o uguale di x
n
ed avente lunghezza maggiore di
S
m−1
la sottosequenza S

x
n
sarebbe una soluzione migliore di S.
Possiamo inoltre supporre che tra tutte le sottosequenze crescenti di X
n−1
di lunghezza
m−1 il cui ultimo elemento ` e minore o uguale di x
n
la sottosequenza S
m−1
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. Verifichiamo 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 prefisso 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
j−1,j−1
< x
n
< s
j,j
altrimenti S
j−1
, x
n
sarebbe una sottosequenza crescente di lunghezza
j con ultimo elemento minore di s
j,j
ed infine non pu` o essere s
m,m
< x
n
altrimenti S
m
, x
n
sarebbe una sottosequenza crescente di lunghezza m + 1 contro l’ipotesi che m sia la
lunghezza di una LIS.
Sia quindi x
n
= s
j,j
. Allora il prefisso s
j,1
, s
j,2
, . . . , s
j,j−1
di S
j
` e la sottosequenza
crescente di X
n−1
di lunghezza j − 1 il cui ultimo elemento ` e minimo mentre le altre
sottosequenze rimangono le stesse della soluzione per il prefisso X
n−1
.
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 prefisso X
i
ed
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 prefisso X
i−1
. 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

j−1,j−1
≤ x
i
< s

j,j
abbiamo m = m

, S
j
= S

j−1
, x
i
ed S
k
= S

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

m

,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 l’ultimo elemento s
j,j
della lista S
j
,
L[j] ` e l’indice dell’ultimo elemento s
j,j
della lista S
j
e
P[j] ` e il puntatore all’elemento precedente della lista S
j
.
3 for i = 2 to n
4 “Cerca l’indice 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 l’assunzione 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 sufficiente 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
l’inserimento 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
n−1
ed Y
m−1
ed e = 1 +e
n−1,m−1
.
b) se S = S

D
a
la sequenza S

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

I
a
la sequenza S

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

S
a,b
la sequenza S

` e ottima per X
n−2
ed Y
m−2
ed e = 1 +e
n−2,m−2
.
e) se S = S

C
a
la sequenza S

` e ottima per X
n−1
ed Y
m−1
ed e = e
n−1,m−1
.
Soluzione ricorsiva: Sia S
i,j
una soluzione ottima per i due prefissi 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
i−1,j
, 1 +e
i,j−1
, ei − 1, j − 1).
Se i ≥ 1, j ≥ 1, x
i
= a ,= y
j
= b e o i = 1 o j = 1 o x
i−1
,= y
j
o x
i
,= y
j−1
le operazioni
possibili sono R
a,b
, D
a
ed I
b
. Quindi
e
i,j
= min(1 +e
i−1,j−1
, 1 +e
i−1,j
, 1 +e
i,j−1
).
Se i ≥ 1, j ≥ 1, x
i
= a ,= y
j
= b ed i > 1, j > 1, x
i−1
= y
j
e x
i
= y
j−1
le operazioni
possibili sono R
a,b
, D
a
, I
b
ed S
b,a
. Quindi
e
i,j
= min(1 +e
i−1,j−1
, 1 +e
i−1,j
, 1 +e
i,j−1
, 1 +e
i−2,j−2
).
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 Q−y
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
k−1
, y
k
, . . . , y
n
una soluzione ottima che contiene le scelte z
1
, . . . , z
k−1
fatte
precedentemente. L’algoritmo sceglie
z
k
= min

q
k
, Q−
k−1

i=1
z
i

che ` e la massima quantit` a possibile. Quindi y
k
≤ z
k
.
Se nella soluzione ottima z
1
, . . . , z
k−1
, 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
k−1
, 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
L’algoritmo pone z
i
= q
i
per ogni indice i fino ad un certo indice k per il quale pone
z
k
= Sp = Q−
k−1

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 dall’algoritmo e il costo
C

dell’altra 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−

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

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

=
k−1

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

n

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

k−1

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

Q −
k−1

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 dall’algoritmo ` 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, l’algoritmo 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 fine 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 dall’attivit` 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 fine f
1
, . . . , f
n
e supponiamo di avere un insieme sufficientemente 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. L’algoritmo 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 nell’aula A
h
. Se h == m + 1
in nessuna delle m aule usate finora 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 significa che la lezione a
i
si terr` a nell’aula 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 all’ultima 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 fine 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. L’algoritmo 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 l’aula 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 significa che la lezione a
i
si terr` a nell’aula A
j
.
J[i] == 0 significa che la lezione a
i
non si terr` a in nessuna delle m aule.
12 return J
Supponiamo che l’assegnazione 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.
L’algoritmo, dopo aver assegnato le prime i −1 attivit` a didattiche alle aule J[1, i −1],
con l’assegnazione J[i] = h, assegna la i-esima attivit` a a
i
all’aula A
h
che si libera per
ultima tra tutte quelle che si liberano prima dell’inizio s
i
di a
i
. Se nessuna delle aule si
libera prima di s
i
l’attivit` 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 l’algoritmo, 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 l’attivit` 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 all’attivit` a
i-esima. B[1, n] deve assegnare all’aula A
h
a cui l’algoritmo 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] all’aula 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 nell’aula
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 l’attivit` a i-esima ad una
diversa aula A
h
con h

= B[i] diverso da h = J[i].
Quindi, dopo aver assegnato le prime i−1 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 l’algoritmo 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 codifiche.)
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 dall’algoritmo e sia n la sua lunghezza.
Il numero di sequenze di lunghezza minore di n ` e 2
n
− 1.
Siccome codifiche di sequenze distinte devono essere necessariaente distinte (altrimen-
ti la decodifica sarebbe ambigua) il numero di sequenze che sono codifiche di sequenze di
lunghezza minore di n ` e pure 2
n
− 1.
Siccome l’algoritmo non allunga nessuna sequenza ognuna delle sequenza di bit di
lunghezza minore di n ` e codifica di qualche file di lunghezza minore di n.
Pertanto la codifica di f che ha anch’essa lunghezza minore di n deve coincidere con
la codifica di una delle sequenze pi` u corte di n: assurdo perch´ e sequenze diverse devono
avere codifiche diverse.
Esercizio 45 Sia C = ¦c
1
, . . . , c
n
¦ un insieme di caratteri ed f
1
, . . . , f
n
le loro frequenze
in un file. Mostrare come si possa rappresentare ogni codice prefisso ottimo per C con una
sequenza di 2n −1 +n,log
2
n| bits. (Suggerimento: usare 2n −1 bit per rappresentare la
struttura dell’albero ed n,log
2
n| bits per elencare i caratteri nell’ordine in cui compaiono
nelle foglie dell’albero del codice.)
Soluzione. Rappresentiamo la struttura dell’albero con la sequenza in preordine dei bit
che etichettano gli archi dell’albero a cui aggiungiamo un bit 1 alla fine.
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 dell’albero si parte dalla radice e quindi:
1. ogni volta che si incontra un bit 0 si aggiunge un figlio sinistro e si scende sul figlio
appena aggiunto;
2. quando si incontra un bit 1 si risale fino a che si incontra un antenato privo di figlio
destro. Se lo si trova si aggiunge un figlio destro a tale antenato e ci si sposta su tale
figlio. Altrimenti abbiamo finito.
37
Rappresentiamo la sequenza dei caratteri associati alle foglie elencando i loro codici a
lunghezza fissa (servono ,log
2
n| bits per ogni carattere).
Per ricostruire l’associazione tra caratteri e foglie basta associare i caratteri alle foglie
nell’ordine 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 l’algoritmo 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 l’algoritmo 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 dall’algoritmo ` e proprio la soluzione ottima A
1
, A
2
, . . . , A
8
e dunque
l’algoritmo calcola una soluzione ottima.
R
8
: R
8
= 0/c < t
8
.
R
7
: R
7
= A
8
/c. Per l’ottimalit` 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 l’ottimalit` 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 l’ottimalit` 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 l’ottimalit` 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 l’ottimalit` 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 l’ottimalit` 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
: Infine R
1
= 100A
2
+ R2/c. Per l’ottimalit` 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
k−1
, t
2
= c
k−2
, . . . , t
k−1
= 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 dall’al-
goritmo ` e proprio la soluzione ottima A
1
, A
2
, . . . , A
k
e dunque l’algoritmo calcola
una soluzione ottima.
Intanto R
k
= 0 < t
k
.
Per ogni j < k abbiamo R
j
= c
k−j−1
A
j+1
+ R
j+1
. Per l’ottimalit` a A
j+1
< c
(altrimenti potrei sostituire c monete di taglio t
j+1
= c
k−j−1
con una di taglio t
j
=
c
k−j
). 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 l’algoritmo 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
k−1
− 1 sono INCREMENT (dopo di che i bit del contatore sono tutti 1) mentre le
altre 2
k−1
sono una ripetizione di 2
k−2
gruppi di due operazioni: una INCREMENT seguita
da una DECREMENT.
Il numero di bit che modificati dalle due operazioni di un gruppo ` e k + k = 2k e il
numero totale di bit modificati ` e maggiore di 2k2
k−2
= O(kn).
Esercizio 48 Su di una certa struttura dati viene eseguita una sequenza di n operazioni.
L’operazione 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 dell’i-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-
l’operazione 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 significativo (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 modificati.
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 all’operazione INCREMENT ed un costo ammor-
tizzato 1 a RESET.
Quando eseguiamo una INCREMENT usiamo una unit` a di costo per pagare l’eventuale
bit 0 trasformato in 1, una unit` a di costo la attribuiamo come credito prepagato a tale bit 1
40
e l’altra 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 l’azzeramento 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 l’operazione vengono scaricati su disco alcuni elementi. Se
una operazione POP toglie l’ultimo elemento in memoria e ci sono degli elementi registrati
su disco, dopo l’operazione 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
Infine 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. Infine 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
− Φ
i−1
= 1 + (2 num
i
− size
i
) − (2 num
i−1
− size
i−1
)
= 1 + 2(num
i−1
+ 1) − size
i−1
− 2 num
i−1
+size
i−1
)
= 3
mentre con espansione ` e:
ˆ c
i
= c
i
+ Φ
i
− Φ
i−1
= 1 +num
i−1
+ (2 num
i
− size
i
) − (2 num
i−1
− size
i−1
)
= 1 +num
i−1
+ 2(num
i−1
+ 1) − 2 num
i−1
− 2 num
i−1
+num
i−1
)
= 3
(Per i = 1 abbiamo size
i
= num
i−1
+ 1 invece di size
i
= 2 num
i−1
. 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
− Φ
i−1
= 1 + (size
i
− 2 num
i
) − (size
i−1
− 2 num
i−1
)
= 1 +size
i−1
− 2(num
i−1
+ 1) −size
i−1
+ 2 num
i−1
)
= −1
Se α ≤ 1/2 il costo ammortizzato di una DELETE senza contrazione ` e:
ˆ c
i
= c
i
+ Φ
i
− Φ
i−1
= 1 + (size
i
− 2 num
i
) − (size
i−1
− 2 num
i−1
)
= 1 +size
i−1
− 2(num
i−1
− 1) − size
i−1
+ 2 num
i−1
)
= 3
mentre con contrazione ` e:
ˆ c
i
= c
i
+ Φ
i
− Φ
i−1
= 1 +num
i−1
+ (size
i
− 2 num
i
) − (size
i−1
− 2 num
i−1
)
= 1 +num
i−1
+ 2 num
i−1
− 2(num
i−1
− 1) − 3 num
i−1
+ 2 num
i−1
)
= 3
43
Se α > 1/2 non vi ` e sicuramente contrazione e il costo ammortizzato di una DELETE ` e:
ˆ c
i
= c
i
+ Φ
i
− Φ
i−1
= 1 + (2 num
i
− size
i
) − (2 num
i−1
− size
i−1
)
= 1 + 2(num
i−1
− 1) − size
i−1
− 2 num
i−1
+size
i−1
)
= −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 figli. Un nodo contenente 1 chiave ` e pieno. Siccome, se l’albero 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
l’albero 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 figli 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 fine avr` a 1, 2 o 3 chiavi e, se
non ` e foglia avr` a 2, 3 o 4 figli.
Le foglie dell’albero ottenuto saranno tutte alla stessa altezza: l’altezza nera che ave-
vano nell’albero 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 modificata 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..i−1
≤ 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..i−1
≤ 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
nell’ordine. Valutare il numero m di nodi del B-albero risultante in funzione di n.
Soluzione. L’inserzione avviene sempre nell’ultima foglia a destra e quindi si percorre
sempre l’ultimo 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 figli con una sola chiave.
A quello dei due figli che non si trova sul ramo di destra non verranno mai aggiunte altre
chiavi. Pertanto i nodi che non stanno sull’ultimo ramo hanno sempre una sola chiave
mentre i nodi sull’ultimo ramo possono avere 1, 2 o 3 chiavi.
Indichiamo con c
i
il numero di chiavi contenute nel nodo di altezza i sull’ultimo ramo.
Ad ognuna delle c
i
chiavi ad altezza i contenute nel nodo di altezza i dell’ultimo ramo
rimane associato un sottoalbero di altezza i − 1 costituito da nodi che non appartengono
all’ultimo 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

h

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 = 40µs.
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 modifiche 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
figlio 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 fino alla foglia in cui si deve inserire la
nuova chiave. Prima di scendere da un nodo x al figlio 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 fino alla foglia da cui viene rimossa o la
chiave data o la successiva. Prima di scendere da un nodo x al figlio 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 figli che vengono modificati.
46
Esercizio 60 Mostrare come sia possibile aggiungere ad ogni nodo x di un B-albero un
campo height che contiene l’altezza del sottoalbero di radice x. Dire quali sono le mo-
difiche da apportare a INSERT e DELETE. Assicurarsi che la complessit` a asintotica non
aumenti.
Soluzione. L’altezza di un nodo di un B-albero non pu` o cambiare.
`
E sufficiente quin-
di assegnare l’altezza 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 l’altezza del nodo
iniziale;
3. quando si crea una nuova radice si assegna ad essa l’altezza 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 un’unico B-albero T (operazione JOIN(T1, k, T2)). Se h1 ed h2 sono le
altezze rispettive di T1 e T2 l’algoritmo 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 fine 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 fine 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 fine 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 all’inizio 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. n−i+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 figli 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 figlio 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 fine 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 all’inizio 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. n−i+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 fine 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 figlio 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 all’inizio 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 fine 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 all’inizio 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 l’algoritmo richiede un tempo proporzionale a t. Siccome
t ` e una costante l’algoritmo ha complessit` a O(1) = O(1 + [h1 − h2[).
Se h1 > h2 l’algoritmo 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 l’algoritmo 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). L’algoritmo
deve avere complessit` a O(h) in cui h ` e l’altezza 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 l’indice 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
i−1
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 l’ultima.
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 figlio.
22 else “Crea un nuovo nodo y1.”
23 “Copia da r in y1 le chiavi r.key
1
, . . . , r.key
i−1
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 l’ultimo figlio.
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 l’algoritmo 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 l’intero 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
i−1
¦ 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 effi-
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 nell’ordine 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
l’ultima EXTRACTMIN e
m
e, per 2 ≤ i ≤ m, mettendo in I
i
quelli inseriti tra e
i−1
ed e
i
.
Siccome alcuni degli insiemi possono essere vuoti aggiungiamo a ciascun insieme
I
i
un elemento fittizio di valore n + i. Inoltre, nel rappresentante di ciascun insieme
memorizziamo l’indice dell’insieme stesso. La versione astratta dell’algoritmo ` e:
52
MINIMOOFFLINE(S, X, m, n)
1 // S ` e l’array di interi che rappresenta la sequenza di n INSERT (denotate
dall’intero x inserito) e di m EXTRACTMIN (denotate con uno 0).
2 “Raggruppa gli interi inseriti e quelli fittizi negli insiemi I
1
, I
2
, . . . , I
m+1

3 for i = 1 to n
4 “Trova l’indice j dell’insieme 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 fittizzi) 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 l’indice dell’insieme 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 dell’algoritmo: Aggiungiamo all’algoritmo le asserzioni per la prova di
correttezza.
53
MINIMOOFFLINE(S, X, m, n)
1 // S ` e l’array di interi che rappresenta la sequenza di n INSERT (denotate
dall’intero x inserito) e di m EXTRACTMIN (denotate con uno 0).
2 “Raggruppa gli interi inseriti e quelli fittizi 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 l’indice j dell’insieme 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 dell’albero. Assumiamo che ogni nodo dell’albero abbia
i seguenti campi:
- child puntatore al primo figlio;
- sibling puntatore al fratello;
- ancestor puntatore ad un suo antenato nell’albero;
- color che all’inizio ` e white per tutti i nodi e alla fine ` e black.
ed inoltre i seguenti campi necessari per raggruppare i nodi dell’albero 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 nell’albero T).
L’algoritmo di Tarjan ` e descritto dalla seguente funzione ricorsiva che deve essere
chiamata sulla radice dell’albero.
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 dell’albero T al nodo u su cui viene richiamata la funzione
MINCOMANT.
Sia Inv(u) la seguente asserzione sugli antenati x
0
, . . . , x
k−1
di u:
55
“Per ogni j = 0, . . . , k − 1 il nodo x
j
e tutti i nodi che stanno nei sottoalberi
radicati nei figli di x
j
che precedono il figlio 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
∈ ∪
k−1
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 ,∈ ∪
k−1
j=1
S
j
.”
e Post(u) l’asserzione:
“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-
l’albero 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 soddisfino la medesima condizione.
Il seguente ` e la funzione MinComAnt annotata con le asserzioni che servono per tale
verifica.
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
dell’albero. Infatti essa viene richiamata soltanto su vertici di colore WHITE, prima di ter-
minare li colora BLACK e alla fine di tutto l’algoritmo 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 l’esecuzione di tutti i cicli for di tutte le chiamate richiede
tempo O(m).
Pertanto l’intero 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
l’algoritmo SORT richiederebbe tempo O(n). Impossibile perch´ e O(nlog n) ` e un limite
inferiore per l’ordinamento.
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
efficiente 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 figli 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 figli del nodo estratto per invertirne l’ordine. Questo perch´ e la lista delle radici ` e
ordinata per grado crescente mentre le liste dei figli sono ordinate per grado decrescente.
Cosa succede se ordiniamo le due liste in modo concorde?
Soluzione. Dobbiamo comunque percorrere la lista dei figli per porre a NIL tutti i
puntatori parent.
59

M ERGE (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 definizione 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 = Θ(nb ) per ogni a e per ogni b > 0. Soluzione. a) se f (n) = Θ(g(n)) esistono due costanti positive c1 e c2 ed un intero positivo N tali che c1 g(n) ≤ f (n) ed f (n) ≤ c2 g(n) per ogni n ≥ N. Dividendo la prima disuguaglianza per c1 si ottiene g(n) ≤ c11 f (n) e dividendo la seconda per c2 si ottiene 1 f (n) ≤ g(n). Siccome c11 e c12 sono due costanti positive possiamo concludere c2 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 xb e decrescente e quindi ` 2b nb = (2n)b ≤ (n + a)b ≤ (n/2)b = (1/2b)nb per ogni n ≥ N. Se b ≥ 1 la funzione xb e crescente e quindi ` (1/2b )nb = (n/2)b ≤ (n + a)b ≤ (2n)b = 2b nb per ogni n ≥ N. In entrambi i casi (n + a)b = Θ(nb ). 2

Esercizio 4 Ordinare le seguenti funzioni in modo che gi (n) = Ω(gi+1 ) e poi raggrupparle in classi Θ(gi (n)) = Θ(gi+1 (n)) = . . . = Θ(gj (n)) (log2 n e il logaritmo in base 2 e ln n ` e il logaritmo naturale): ` √ ( 2)log2 n n1/ log2 n 4log2 n n2 ln ln n (n + 1)! n! n2n (3/2)n n3 log2 n log2 (n!) 2 log n√ 2 log2 n log2 n 1 2log2 n
2 log2 n

22 en

n

log2 n 2

n

2n

n log2 n 22

n+1

Soluzione. Una propriet` utile per confrontare asintoticamente funzioni e la seguente: a ` 1. Se limn→∞ 2. Se limn→∞ 3. Se limn→∞
f (n) g(n) f (n) g(n) f (n) g(n)

= ∞ allora f (n) = Ω(g(n)) ma f (n) = Θ(g(n)); = 0 allora f (n) = O(g(n)) ma f (n) = Θ(g(n)); = c = 0 allora f (n) = Θ(g(n)).

Attenzione: tale propriet` e condizione sufficiente ma non necessaria. Ad esempio il limite a` x(2 + sin x) x→∞ x lim non esiste eppure x ≤ x(2 + sin x) ≤ 3x e quindi x(2 + sin x) = Θ(x). Useremo questa propriet` per confrontare le funzioni dell’esercizio. a 1. Cominciamo confrontando 22
n+1 n+1

con 22 .

n

22 n+1 n n lim = lim 22 −2 = lim 22 = ∞ n→∞ 22n n→∞ n→∞ e quindi 22
n+1

= Ω(22 ) ma 22
n

n

n+1

= Θ(22 ).

n

2. Confrontiamo 22 con (n + 1)!. Intanto (n + 1)! = (n + 1)n(n − 1) . . . 3 · 2 ≤ 2n · nn−1 = 2nn = 2n log2 n+1 e quindi 2n log2 n+1 = Ω((n + 1)!). Poi lim 22
n

n→∞ 2n log2 n+1
n

= lim 22
n→∞

n −n log

2

n−1

=∞

e quindi 22 = Ω(2n log2 n+1 ) = Ω((n + 1)!) ma 22 = Θ((n + 1)!). 3. Confrontiamo (n + 1)! con n!. lim (n + 1)! = lim (n + 1) = ∞ n→∞ n!

n

n→∞

e quindi (n + 1)! = Ω(n!) ma (n + 1)! = Θ(n!). 3

4. Confrontiamo n! con en . Intanto n! ≥ (n/2)(n/2) = 2(n/2) log2 (n/2) (perch´ in n! ci sono n/2 fattori maggiori o uguali di n/2) ed en = 2n log2 e . Inoltre e 2(n/2) log2 (n/2) = lim 2(n/2) log2 (n/2)−n log2 e = ∞ n→∞ n→∞ 2n log2 e lim e quindi n! = Ω(2(n/2) log2 (n/2) ) = Ω(en ) ma n! = Θ(en ). 5. Confrontiamo en con n2n . 2n log2 e en = n→∞ n+log n = n→∞ 2(log2 e−1)n−log2 n = ∞ lim lim lim n→∞ n2n 2 2 e quindi en = Ω(n2n ) ma en = Θ(n2n ). 6. Confrontiamo n2n con 2n . n2n = lim n = ∞ n→∞ n→∞ 2n lim e quindi n2n = Ω(2n ) ma n2n = Θ(2n ). 7. Confrontiamo 2n con (3/2)n . 2n = lim (4/3)n = ∞ n→∞ (3/2)n n→∞ lim e quindi 2n = Ω((3/2)n ) ma 2n = Θ((3/2)n ). 8. Confrontiamo (3/2)n con nlog2 log2 n . Intanto (3/2)n = 2n log2 (3/2) Inoltre
n→∞

e

nlog2 log2 n = 2log2 n log2 log2 n

= lim 2n log2 (3/2)−log2 n log2 log2 n = ∞ 2log2 n log2 log2 n n→∞ e quindi (3/2)n = Ω(nlog2 log2 n ) ma (3/2)n = Θ(nlog2 log2 n ). lim 9. Confrontiamo nlog2 log2 n con n3 . nlog2 log2 n = lim nlog2 log2 n−3 = ∞ n→∞ n→∞ n3 lim e quindi nlog2 log2 n = Ω(n3 ) ma nlog2 log2 n = Θ(n3 ). 10. Confrontiamo n3 con n2 . n3 = lim n = ∞ n→∞ n2 n→∞ lim e quindi n3 = Ω(n2 ) ma n3 = Θ(n2 ). 4

2n log2 (3/2)

11. Confrontiamo n2 con 4log2 n . Siccome 4log2 n = 2log2 n 2log2 n = n · n = n2 ovviamente n2 = Θ(4log2 n ). 12. Confrontiamo n2 con n log2 n. n2 =∞ lim n→∞ n log n 2 e quindi n log2 n = Ω(n2 ) ma n log2 n = Θ(n2 ). 13. Confrontiamo n log2 n con log2 (n!). Intanto (n/2)(n/2) ≤ n! ≤ nn e quindi (n/2) log2 (n/2) ≤ log2 (n!) ≤ n log2 n
1 Inoltre (n/2) log2 (n/2) = 2 n(log2 n − 1) e, per n > 4, n > 4, 1 n log2 n ≤ log2 (n!) ≤ n log2 n 4 e quindi log(n!) = Θ(n log2 n). log2 n 2

≥ 1. Dunque, per

14. Confrontiamo n log2 n con n.
n→∞

lim

n log2 n = lim log2 n = ∞ n→∞ n

e quindi n log2 n = Ω(n) ma n log2 n = Θ(n). 15. Confrontiamo n con 2log2 n . Siccome 2log2 n = n ovviamente n = Θ(2log2 n ). √ 16. Confrontiamo n con ( 2)log2 n . Siccome √ √ √ ( 2)log2 n = 2log2 ( 2) log2 n = 21/2 log2 n = n √ n lim √ log n = lim n = ∞ n→∞ ( 2) 2 √ log n √ e quindi n = Ω(( 2) 2 ) ma n = Θ(( 2)log2 n ). √ √ √ 17. Confrontiamo ( 2)log2 n con 2 2 log2 n . Siccome ( 2)log2 n = 21/2 log2 n √ √ ( 2)log2 n = n→∞ 21/2 log2 n− 2 log2 n = ∞ lim lim √ n→∞ 2 2 log2 n √ √ √ √ e quindi ( 2)log2 n = Ω(2 2 log2 n ) ma ( 2)log2 n = Θ(2 2 log2 n ). √ 2 18. Confrontiamo 2 2 log2 n con log2 n. Siccome log2 n = 2log2 (log2 n) = 22 log2 log2 n 2 2 √ √ 2 2 log2 n = n→∞ 2 2 log2 n−2 log2 log2 n = ∞ lim lim n→∞ log2 n 2 √ √ e quindi 2 2 log2 n = Ω(log2 n) ma 2 2 log2 n = Θ(log2 n). 2 2
n→∞

abbiamo

5

Confrontiamo log2 log2 n con 1. L’ordine delle classi di appartenenza delle funzioni e il seguente: ` Θ(22 ) > Θ(22 ) > Θ((n + 1)!) > Θ(n!) > Θ(en ) > Θ(n2n ) > Θ(2n ) > Θ((3/2)n ) > Θ(nlog2 log2 n ) > Θ(n3 ) > Θ(n2 ) = Θ(4log2 n ) > √ √ Θ(n log2 n) = Θ(log2 (n!)) > Θ(n) = Θ(2log2 n ) > Θ(( 2)log2 n ) > Θ(2 2 log2 n ) > Θ(log2 n) > Θ(log2 n) > Θ( log2 n) > Θ(log2 log2 n) > Θ(1) = Θ(n1/ log2 n ) 2 n+1 n Esercizio 5 Abbiamo visto che T (n) = bn log2 n+(c+a)n−a e soluzione della ricorrenza ` T (x) = c se x ≤ 1 bx + a + 2T (x/2) se x > 1 Dimostrare che la soluzione della ricorrenza QS Tmin (n) = c se n ≤ 1 QS bn + a + 2Tmin ( n/2 ) se n > 1 che esprime la complessit` minima dell’algoritmo Q UICK -S ORT e limitata inferiormente a ` QS da Tmin (n) = Ω(T (n)) = Ω(n log n). √ Usando la sostituzione di variabile x = log2 n lim log2 n log2 log2 n = lim x =∞ x→∞ log x 2 n→∞ e quindi log2 n = Ω(log2 log2 n) ma log2 n = Θ(log2 log2 n). Confrontiamo log2 n con log2 log2 n.19. Confrontiamo log2 n con log2 n. 21. log2 n log2 n = lim n→∞ n→∞ lim log2 n = ∞ e quindi log2 n = Ω( log2 n) ma log2 n = Θ( log2 n). 22. 6 . Infine n1/ log2 n = 2log2 n(1/ log2 n) = 2 e quindi n1/ log2 n = Θ(1). Confrontiamo log2 n con log2 n. n→∞ lim log2 log2 n = x→∞ log2 log2 n = ∞ lim 1 e quindi log2 log2 n = Ω(1) ma log2 log2 n = Θ(1). 2 2 20. 23. 2 log2 n 2 = lim log2 n = ∞ n→∞ n→∞ log n 2 lim e quindi log2 n = Ω(log2 n) ma log2 n = Θ(log2 n).

preso = 2 abbiamo f (n) = n = O(n 2 ) = O(nlogb a− ). . . b 2 2 2 Esercizio 7 La ricorrenza T (n) = 4T (n/2) + n2 log n si pu` risolvere con il metodo delo l’esperto? Giustificare la risposta. T (n) = 4T (n/2) + n2 3. 7 . k e quindi Tmin (n) = T (n). Inoltre. Se n e ` pari QS QS Tmin (n + 1) = b(n + 1) + a + 2Tmin( (n + 1)/2 ) QS = b(n + 1) + a + 2Tmin( n/2 ) QS QS = b + Tmin (n) > Tmin (n) Se n e dispari ` QS QS Tmin (n + 1) = b(n + 1) + a + 2Tmin( (n + 1)/2 ) QS = b(n + 1) + a + 2Tmin( n/2 + 1) QS QS > bn + a + 2Tmin ( n/2 ) = Tmin (n) A questo punto siamo in grado di concludere QS QS Tmin (n) ≥ Tmin (2 log2 n ) = T (2 log2 n ) = b2 log2 n log2 n + (c + a)2 log2 n − a ≥ b2log2 n−1 (log2 n − 1) + (c + a)2log2 n−1 − a 1 1 1 bn log2 n − bn + (c + a)n − a = 2 2 2 = Ω(n log2 n) Esercizio 6 Usare il metodo dell’esperto per risolvere le seguenti ricorrenze: 1.Soluzione. Osserviamo intanto che se n = 2k e potenza di 2 allora n/2i = n/2i per ` QS ogni i = 1. Per la seconda f (n) = n2 = Θ(nlogb a ) e pertanto T (n) = Θ(n2 log2 n). 3 1 Per la prima. che Tmin (n + 1) > Tmin (n). 5 Per la terza. . preso = 1 . In tutti e tre i casi abbiamo nlogb a = nlog2 4 = n2 . Se la risposta e negativa usare il metodo di sostituzione ` per dimostrare che T (n) = O(n2 log2 n). Quindi T (n) = Θ(n2 ). per induzione su n. QS QS Proviamo ora. T (n) = 4T (n/2) + n 2. 2 3 af ( n ) = 4( n )3 = n = 1 f (n). T (n) = 4T (n/2) + n3 Soluzione. Quindi T (n) = Θ(n3 ). Intanto QS QS QS Tmin (2) = 2b + a + 2Tmin (1) > Tmin (1) QS QS Per n ≥ 2 assumiamo induttivamente che Tmin (k + 1) > Tmin (k) per ogni k < n. abbiamo f (n) = n3 = Ω(n 2 ) = Ω(nlogb a+ ). .

T (n) = ≤ = = = = ≤ 4T (n/2) + n2 log n 4C(n/2)2 log2 (n/2) + n2 log n Cn2 (log n − 1)2 + n2 log n Cn2 (log2 n − 2 log n + 1) + n2 log n Cn2 log2 n − 2Cn2 log n + Cn2 + n2 log n Cn2 log2 n − (C − 1)n2 log n − Cn2 (log n − 1) Cn2 log2 n Dunque T (n) = O(n2 log2 n). T (n) = 2T (n/2) + n3 2. T (n) = 2T (n/4) + n 7. Preso = 1 2 3 f (n) = n3 = Ω(n 2 ) = Ω(nlogb a+ ). f (n) = n2 log n = O(n2+ ) = Ω(nlogb a+ ) e quindi non possiamo applicare neppure il terzo caso. Inoltre. T (n) = 7T (n/2) + n2 √ 6. 4 1 2 1 2. Esercizio 8 Trovare limiti asintotici superiori ed inferiori il pi` possibile stretti per le u seguenti ricorrenze. In questo caso nlogb a = nlog2 4 = n2 . Siccome f (n) = n2 log n = Ω(n2 ) = Ω(nlogb a ) ed f (n) = Θ(nlogb a ) non si possono applicare i primi due casi del metodo dell’esperto. 1. Assumiamo che per una opportuna costante C > 1 e per ogni x < n sia verificata la disuguaglianza T (x) ≤ C(x2 log2 x) e dimostriamo che essa vale anche per n. Le prime 6 si risolvono con il metodo dell’esperto. 1. T (n) = 16T (n/4) + n2 4. Proviamo T (n) = O(n2 log2 n). nlogb a = nlog2 2 = n. Preso = f (n) = n = Ω(n 2 ) = Ω(nlogb a+ ). 10 Quindi T (n) = Θ(n). T (n) = nT (n − 1) √ 8. Inoltre. af ( n ) = 2( n )3 = b 2 n3 4 = 1 f (n). nlogb a = nlog10/9 1 = 1. Quindi T (n) = Θ(n3 ). per ogni > 0. Inoltre. T (n) = T (9n/10) + n 3. af ( n ) = b 9n 10 = 9 f (n). 8 . T (n) = T ( n) + 1 Soluzione.Soluzione. T (n) = 7T (n/3) + n2 5. la settima si risolve con una sommatoria e l’ultima con una sostituzione di variabile.

Con la sostituzione di variabile x = log2 n si ottiene T (2x ) = T (2x/2 ) + 1. n) 9 .3. Inoltre. i − 1) H EAPFY (A. A[1] = t 5 H EAPFY (A. a Soluzione. 8. √ √ 6. 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]. 1. i. Allora T (x) soddisfa la ricorrenza T (x) = T (x/2) + 1 che si risolve con il metodo dell’esperto. Quindi T (n) = Θ( n log n). af ( n ) = b 7 7( n )2 = 9 f (n). Preso = log2 7 − 2. Poniamo T (x) = T (2x ). L’algoritmo H EAP -S ORT e il seguente: ` H EAP -S ORT (A. Infatti xlogb a = xlog2 1 = 1 = Θ(f (x)) e quindi T (x) = Θ(log x). A[k] = t 8 H EAPFY (A. A[i] = A[k]. nlogb a = nlog3 7 . n) 3 for i = n downto 2 4 t = A[i]. l’algoritmo H EAP S ORT ha complessit` minima Ω(n log n). nlogb a = nlog4 2 = n = f (n). n) 1 for i = n/2 downto 1 2 H EAPFY (A. 7. T (n) = nT (n − 1) = n(n − 1)T (n − 2) = n!T (1) = Θ(n!). k. Pertanto T (n) = T (2log2 n ) = T (log2 n) = Θ(log log n). j. f (n) = n2 = O(nlogb a− ). nlogb a = nlog4 16 = n2 = f (n). se gli elementi dell’array sono tutti distinti. 3 5. A[i] = A[1]. 4. Quindi T (n) = Θ(n2 log n). nlogb a = nlog2 7 . Quindi T (n) = Θ(nlog2 7 ). Preso = 2 − log3 7. f (n) = n2 = Ω(nlogb a+ ). Quindi T (n) = Θ(n2 ). Esercizio 9 Mostrare che.

Sia B l’insieme dei nodi interni che hanno soltanto foglie come figli. Ogni elemento in H ha per chiave il primo elemento non ancora ricopiato di una riga di A e come informazione associata l’indice della riga. Siccome i figli di nodi in A2 appartengono ad A2 abbiamo che 3m − 1 ≤ n/2 e dunque m ≤ n/2 −1 . (Suggerimento: mantenere il primo elemento non ancora copiato in B di ciascuna riga in un heap H di m elementi). Siccome essi sono a profondit` i ≥ log2 n − 1 occorrono almeno ( log2 n − 1)(n − 2)/12 = Ω(n log n) a scambi. Esercizio 10 Realizzare una operazione D ELETE (S. i) che toglie l’elemento S[i] dallo heap S e che opera in tempo O(log n). Di 3 conseguenza i nodi in B ∩ A1 sono n/2 /2 − m ≥ (n − 2)/12. i. 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. Siccome nessuno di essi e una foglia ciascuno di essi deve risalire fino ` alla radice di livello in livello per effetto di uno scambio con il padre. Al pi` un solo nodo in B ha un solo figlio mentre tutti gli altri ne hanno u due. L’algoritmo deve richiedere tempo O(nm log m). D ELETE (S. ` Soluzione. n) 6 else H EAPFY-R EV (S. 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.Siccome i valori sono tutti distinti. Al pi` u uno di tali nodi ha un solo figlio mentre tutti gli altri ne hanno due. Soluzione. Un heap di n nodi ha altezza h = log2 n . Supponendo che ciascuna riga sia ordinata in ordine crescente descrivere un algoritmo che riunisce le m righe di A in un’unico array ordinato B di nm elementi. Usiamo un min-heap H in cui la radice e il minimo elemento. B contiene n/2 /2 nodi e i suoi nodi sono a profondit` a i ≥ log2 n − 1. i) 1 x = S[i] 2 S[i] = S[n] 3 n = n−1 4 if x > S[i] 5 H EAPFY (S. 10 . ha n/2 foglie a profondit` i ≥ log2 n ed a n/2 nodi interni. I nodi in B ∩ A1 vengono tutti estratti durante i primi n/2 passi del secondo ciclo for di H EAP -S ORT. i) H EAPFY-R EV (S. possiamo suddividere gli n elementi dell’array A in due gruppi: il gruppo A1 che contiene gli n/2 elementi pi` grandi (e che vengono u estratti dallo heap nei primi n/2 passi del secondo ciclo for di H EAP -S ORT) e il gruppo A2 che contiene gli n/2 elementi pi` piccoli (e che rimangono nello heap dopo i primi u n/2 passi del secondo ciclo for di H EAP -S ORT ). Sia m = |B ∩ A2 | il numero di nodi in B che appartengono al gruppo A2 .

kn2 le altezze delle foglie di T2 . Se n > 1 l’albero u T ha una radice e due sottoalberi T1 e T2 e le foglie di T sono quelle di T1 pi` quelle di T2 . m. Ogni input pu` dare origine ` o alle stesse computazioni con uguale probabilit` . diciamo T1 . a Esercizio 13 L’altezza di un albero binario e la lunghezza massima di un cammino dalla ` radice ad una foglia. Dimostrare che l’altezza media di un albero binario con n foglie e maggiore o uguale di log2 n. ` Soluzione. Allora Hmed (T ) = + hi ) + n2 (1 + ki ) i=1 n n1 (1 + Hmed (T1 )) + n2 (1 + Hmed (T2 )) = n 11 n1 i=1 (1 . . Di conseguenza T ha altezza maggiore o uguale di log2 n. a a Soluzione. I cammini dalla radice alle foglie in T sono i cammini dalla radice alle foglie in T1 e in T2 allungati con un primo arco che connette la radice di T rispettivamente con le radici di T1 e T2 . Il tempo richiesto dagli algoritmi randomizzati non dipende dall’input e quindi non e possibile caratterizzare il caso pessimo ed il caso ottimo. Esercizio 14 (*) L’altezza media di un albero binario con n foglie e la lunghezza media ` dei cammini dalla radice alle foglie. Se n = 1 l’albero ha altezza 0 = log2 1. Dimostrare che un albero binario con n foglie ha altezza maggiore o uguale di log2 n. Per induzione su n. Per ipotesi induttiva T1 ha altezza maggiore o uguale di log2 (n/2) = log2 n − 1. Soluzione. A[i. . . Se n = 1 l’albero ha altezza 0 = log2 1. k[i]]. / ` 6 for j = 1 to nm 7 (B[j]. m) = m O(log m) + nm O(log m) = O(nm log m) Esercizio 12 Spiegare perch´ per gli algoritmi randomizzati e importante analizzare la e ` complessit` media mentre complessit` minima e massima sono poco significative. hn1 le altezze delle foglie di T1 e k1 . 1]. L’algoritmo richiede quindi tempo T (n. Siano h1 . . . i) 10 k[i] = k[i] + 1 11 return B Lo heap contiene al massimo m elementi e le operazioni I NSERT ed E XTRACT-M IN richiedono tempo O(log m). i) 4 k[i] = 2 5 / k[i] e l’indice del prossimo elemento nella riga i-esima.M ERGE -M ULTIPLO (A. . n) 1 H =∅ 2 for i = 1 to m 3 I NSERT (H. Per induzione su n. / ` 9 I NSERT (H. Siano n1 ed n2 il numero di foglie di T1 e T2 . Se n > 1 l’albero T ha una radice e due sottoalberi T1 e T2 ed almeno uno di essi. . i) = E XTRACT-M IN (H) 8 if k[i] ≤ n / La riga i-esima non e ancora finita. A[i. . ha un numero di foglie maggiore o uguale di n/2.

Altrimenti raggruppiamo gli n elementi dell’array in n/2 gruppi contenenti ciascuno due elementi tranne un eventuale gruppo finale di un solo elemento nel caso in cui n sia dispari. Soluzione. per l’esercizio precedente. La complessit` media di un algoritmo di ordinamento e proporzionale all’ala ` tezza media dell’albero delle decisioni. a Soluzione. Siano x e y i due elementi trovati. Se n = 2 un confronto e sufficiente per decidere quale sia il primo e quale ` il secondo. 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. la sua altezza media e maggiore o uguale di log2 (n!) = Ω(n log n). 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 ` ` ` dell’array. ` Esercizio 16 Mostrare come sia possibile trovare contemporaneamente i due pi` piccoli u elementi di un array di n elementi con n + log2 n − 2 confronti. 12 . Esercizio 15 Usare il risultato dell’esercizio precedente per dimostrare il limite inferiore Ω(n log n) per la complessit` media degli algoritmi di ordinamento. Un confronto tra y e il massimo del gruppo che contiene x e quindi sufficiente per trovare ` il secondo elemento in ordine di grandezza dell’array.Per ipotesi induttiva possiamo quindi assumere che: Hmed (T ) ≥ n1 (1 + log2 n1 ) + n2 (1 + log2 n2 ) n n1 (1 + log2 n1 ) + (n − n1 )(1 + log2 (n − n1 )) = n Per 1 ≤ x ≤ n − 1 la funzione f (x) = x(1 + log2 x) + (n − x)(1 + log2 (n − x)) n ha un minimo per x = n/2 e tale minimo vale f (n/2) = (n/2)(1 + log2 (n/2)) + (n/2)(1 + log2 (n/2)) n = 1 + log2 (n/2) = log2 n Possiamo quindi concludere che Hmed (T ) ≥ log2 n. Troviamo quindi il minimo di ciascun gruppo (con n/2 confronti) e applichiamo ricorsivamente l’algoritmo per trovare i due elementi pi` piccoli tra gli n/2 u minimi dei gruppi. Siccome l’albero delle decisioni ha n! foglie.

B[m] = A[n]. m) 13 m1 = H[p1 ]. In questo caso il secondo minimo ` ` in A[1 . m2 = H[p2 ] 14 / B[p1 ] e B[p2 ] sono minimo e secondo minimo di B[1 . 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 + log2 n − 2. p2 = D UE -M INIMI (B. . Se m1 < n allora A[m1 ] e in un gruppo di due ` elementi e l’altro elemento del suo gruppo ha indice i = m1 − 1 se m1 e pari ed i = m1 + 1 se m1 e dispari. m] ed m1 . n) / n ≥ 2 / 1 if n == 2 2 if A[1] ≤ A[2] 3 return 1. H[i] = 2 i 10 if n dispari 11 m = m + 1.D UE -M INIMI (A. ` 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 . . H[m] = n 12 p1 . Inoltre se m1 = n allora A[m2 ] e il secondo ` minimo in A[1 . m2 / sono gli indici di tali due elementi in A[1 .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].2 4 else return 2. . se n e pari allora ` ` log2 n/2 = log2 n = log2 n − 1 = log2 n − 1 2 13 . n] e il minore tra A[m2 ] e A[i]. Di conseguenza A[m1 ] e ` il minimo in A[1 . n]. . Per n = 2 abbiamo C(2) = 1 = 2 + log2 2 − 2 Per n > 2 possiamo usare l’ipotesi induttiva per ottenere C(n) = n/2 + C( n/2 ) + 1 = n/2 + ( n/2 + log2 n/2 = n + log2 n/2 − 1 = n + log2 n − 2 − 2) + 1 L’ultimo passaggio e giustificato dal fatto che. n]. . n].

Dunque in entrambi i casi log2 n/2 = log2 n − 1 Esercizio 17 Mostrare come siano necessari almeno 3n/2 − 2 confronti nel caso pessimo per trovare contemporaneamente minimo e massimo di un array di n elementi (Suggerimento: Sia m1 il numero di elementi che. All’inizio S = T = A ed m1 + m2 = 2n. b) 1 if a > b 2 t = a. Calcolare quindi di quanto pu` diminuire la somma m1 + m2 per effetto di un confronto). non a possiamo ancora escludere che possano essere il minimo ed m2 il numero di quelli che non possiamo ancora escludere che possano essere il massimo. Dopo di che nessun confronto pu` u o diminuire m1 + m2 per pi` di una unit` . Occorrono quindi almeno altri 2n − 2 n/2 − 2 u a confronti. o Soluzione. 14 . Sia S l’insieme 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 m1 il numero di elementi in S ed m2 il numero di elementi in T . Sia A l’insieme degli n elementi dell’array e supponiamo che essi siano tutti distinti. Finch´ vi sono almeno due elementi x e y in S ∩ T e (elementi che possono essere sia massimo che minimo) un confronto tra x e y diminuisce di 2 la somma m1 + m2 mettendo uno dei due in S e l’altro in T . 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. Esercizio 18 Data la seguente funzione: S CAMBIA (a. ` Soluzione.mentre se n e dispari ` log2 n/2 = log2 n+1 = log2 (n + 1) − 1 = log2 (n + 1) − 1 2 detto k = log2 (n + 1) abbiamo che 2k−1 < n + 1 ≤ 2k . Siccome n > 2 certamente k ≥ 2 per cui 2k−1 e pari ed essendo anche n + 1 pari avremo che 2k−1 < n ≤ 2k e quindi ` log2 (n + 1) = k = log2 n . a = b. b = t descrivere un algoritmo che riordina una array A di 4 elementi con 5 chiamate alla funzione S CAMBIA. In totale servono quindi almeno n/2 + 2n − 2 n/2 − 2 = n + n/2 − 2 = 3n/2 − 2 confronti. Dimostrare che 5 e un limite inferiore. sulla base dei confronti gi` effettuati. Possiamo dire di aver trovato minimo e massimo solo quando m1 + m2 = 2. Questo puo essere fatto al pi` n/2 volte arrivando ad m1 + m2 = 2n − 2 n/2 .

Le operazioni S EARCH. Aggiungiamo poi ad ogni cella della tavola un campo intero ind che serve a memorizzare l’indice dell’elemento dell’array I che contiene l’indice della cella stessa (o NIL se la cella e stata prima utilizzata e poi svuotata da una D ELETE). 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. A[4]) 8 / A[1] ≤ A[3] ≤ A[4]. A[2]) 2 / A[1] ≤ A[2] / 3 S CAMBIA (A[3]. Quindi la sua altezza deve essere almeno log2 24 = 5. A[1] ≤ A[2] oppure / A[1] ≤ A[3] ≤ A[2]. A[1] ≤ A[2] ≤ A[4] / 9 S CAMBIA (A[2]. La situazione e la seguente: ` ` key T ind 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 2 5 1 3 6 4 I 9 1 4 2 NIL 3 15 4 7 5 13 6 L’inizializzazione richiede soltanto di porre n a 0: I NIT () 1 n=0 15 . ` Soluzione. I NSERT e a D ELETE si eseguono in tempo costante O(1). A[3]) 6 / A[1] ≤ A[3] ≤ A[4]. A[3]) 10 / A[1] ≤ A[2] ≤ A[3] ≤ A[4] / L’albero delle decisioni ha 4! = 24 foglie. Essa richiede per` una inizializzazione a NIL di tutti gli elementi dell’array. Esercizio 19 Una tavola ad indirizzamento diretto usa un array di dimensione pari alla cardinalit` |U| dell’insieme U di chiavi possibili. I NSERT e D ELETE. A[1] ≤ A[4] 7 S CAMBIA (A[2]. Se il numeo ro massimo N di chiavi memorizzate nell’array e molto minore di |U| tale inizializzazione ` pu` risultare particolarmente penalizzante in termini di tempo calcolo. Suggerimento: Aggiungere un array di dimensione N che permetta di determinare se un elemento della tavola e utilizzato.O RDINA (A) 1 S CAMBIA (A[1]. A[1] ≤ A[2] / 5 S CAMBIA (A[1]. o Trovare un modo per implementare una tavola ad indirizzamento diretto che richieda tempo O(1) sia per l’inizializzazione che per le operazioni S EARCH. A[4]) 4 / A[3] ≤ A[4].

Soluzione. o u un tempo proporzionale alla met` della lunghezza della lista e dunque Θ(1 + α). c1 c0 come il numero intero k = n−1 i i=0 ci 256 ) allora tutti gli anagrammi (stringhe ottenute permutando i caratteri) di una stessa stringa s vengono mappati nella stessa cella dalla funzione hash. I[n] = k. . T [k] = x 2 if ind [k] < 1 or ind [k] > n or (I[ind [k]] = k and I[ind [k]] = 3 n = n + 1.Le operazioni sono: S EARCH (k) 1 if ind [k] ≥ 1 and ind [k] ≤ n and I[ind [k]] == k 2 return T [k] 3 else return NIL I NSERT (x) 1 k = key[x]. Basta calcolare la funzione hash tenendo conto delle propriet` dell’aritmetica a modulare. Esercizio 21 Considerare una tavola hash T di dimensione 28 − 1 = 255 che usa la funzione hash h(k) = k mod 255. a Mantenere le liste ordinate puo essere conveniente soltanto per applicazioni in cui viene eseguito un numero di S EARCH di chiavi non presenti nella tavola molto maggiore del numero di I NSERT. L’unica modifica a S EARCH e che se la chiave cercata non e presente non occorre ` ` arrivare alla fine della lista ma ci possiamo fermare non appena troviamo una chiave pi` u grande. Invece I NSERT richiede che la chiave inserita venga aggiunta nella giusta posizione nella lista e non in cima alla lista. D ELETE non richiede modifiche e continua ad essere eseguita in tempo costante Θ(1). . ind [k] = n 4 else I[ind [k]] = k D ELETE (k) 1 I[ind [k]] = NIL ) NIL Esercizio 20 In una tavola hash con risoluzione delle collisioni mediante liste come cambia la complessit` delle operazioni S EARCH. Dunque I NSERT non si pu` pi` eseguire in tempo costante Θ(1) ma richiede. Questo in media dimezza il tempo per la ricerca di una chiave non presente nella tavola che rimane comunque Θ(1 + α). I NSERT e D ELETE se le liste vengono mana tenute ordinate? Soluzione. Il tempo medio richiesto per la ricerca di una chiave presente nella tavola non viene modificato e rimane quindi Θ(1 + α). Mostrare che se essa viene usata per memorizzare delle stringhe di caratteri ASCII (interpretando una stringa di n caratteri s = cn−1 cn−2 . in media. 16 .

Assurdo. . Ricordiamo la funzione S UCCESSOR e la funzione M INIMUM: S UCCESSOR (x) 1 if x. y = x. x1 .In questo modo si ottiene h(k) = k mod 255 n−1 = ( i=0 n−1 ci 256i ) mod 255 ci (256 mod 255)i ) mod 255 i=0 n−1 = ( = ( i=0 ci ) mod 255 e siccome la somma e commutativa si vede che l’ordine dei caratteri e irrilevante.right 5 x = y.left 4 return y Siano x0 . 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 l’istruzione x = S UCCESSOR (x) il tempo totale richiesto e T (k) = O(k + h). . Se z avesse figlio destro w il nodo w sarebbe un nodo del sottoalbero radicato in y avente chiave maggiore della chiave di z. se x ha figlio sinistro y il predecessore di x e il nodo z con chiave ` massima del sottoalbero radicato in y. Assurdo. . Sia k la chiave del nodo x. ` Soluzione. ` ` Esercizio 22 Mostrare che in un albero binario di ricerca il successore di un nodo avente figlio destro non ha figlio sinistro ed il predecessore di un nodo avente figlio sinistro non ha figlio destro. xn i nodi visitati nei passi eseguiti durante tutte le k esecuzioni di S UCCESSOR di modo che la complessit` dell’intera sequenza di operazioni e O(n) a ` (proporzionale al numero di archi percorsi). Soluzione. Simmetricamente.p 6 return y M INIMUM (x) 1 y=x 2 while y.right = NIL 2 y = M INIMUM (x.left = NIL 3 y = y. .right ) 3 else y = x. Se x ha figlio destro y il successore di x e il nodo ` z con chiave minima del sottoalbero radicato in y. 17 . Se z avesse figlio sinistro w il nodo w sarebbe un nodo del sottoalbero radicato in y avente chiave minore della chiave di z.p 4 while y = NIL and x == y.

. Quindi a (1 + si ) = |D| + si oi ∈D oi ∈D = |A| + |D| + hs(xn ) − hs(x0 ) = k + hs(xn ) − hs(x0 ) Sommando i risultati ottenuti: n = oi ∈D (1 + si ) + (1 + di ) oi ∈A = 2k + hd (x0 ) − hd (xn ) + hs(xn ) − hs(x0 ) ≤ 2k + 2h = O(k + h) e la complessit` della sequenza di operazioni e T (k) = O(n) = O(k + h). . La ` differenza si − |A| oi ∈D e uguale alla differenza hs(xn ) − hs(x0 ) tra la profondit` sinistra del nodo finale e la ` a profondit` sinistra del nodo iniziale. Naturalmente la profondit` h(x) del nodo sar` uguale alla somma hd (x)+hs(x). . ok in due gruppi: il gruppo D delle operazioni discendenti che percorrono in discesa un arco destro e poi un certo numero si di archi sinistri (con la chiamata a M INIMUM) e il gruppo A delle operazioni ascendenti che percorrono in salita un certo numero di di archi destri e poi un arco sinistro. a a Dividiamo inoltre le k operazioni o1 . Quindi a (1 + di ) = |A| + di oi ∈A oi ∈A = |A| + |D| + hd(x0 ) − hd (xn ) = k + hd (x0 ) − hd (xn ) 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 oi ∈D si . La ` differenza di − |D| oi ∈A e uguale alla differenza hd (x0 ) − hd (xn ) tra la profondit` destra del nodo iniziale e la ` a profondit` destra del nodo finale. Il numero di archi percorsi in tutta la sequenza di operazioni e pertanto ` n= oi ∈D (1 + si ) + oi ∈A (1 + di ) 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 oi ∈A di .Dividiamo gli archi in archi sinistri (quelli che connettono un figlio sinistro al padre) e archi destri (quelli che connettono un figlio destro al padre) e per ogni nodo x dell’albero distinguiamo una profondita destra hd (x) (numero di archi destri nel cammino dalla radice al nodo) ed una profondit` sinistra hs(x) (numero di archi sinistri nel cammino dalla radice a al nodo). a ` 18 . .

Quale inconveniente sorge se dall’albero T viene rimosso il nodo z? Modificare l’algoritmo D ELETE RB per evitare tale inconveniente. Il minimo numero totale m di nodi rossi e di nodi interni neri che debbono essere aggiunti e quindi 2k−1 dei quali uno rosso e tutti gli altri neri. Soluzione. Per evitare l’inconveniente invece di ricopiare le informazioni da y a z occorre. Esso ha quindi ` 22k foglie e 22k − 1 nodi interni. L’altezza nera bh(w) che deve avere la radice w del sottoalbero aggiunto. inserirlo nell’albero al posto del nodo z cambiando gli opportuni puntatori e quindi eliminare il nodo z. z) toglie il nodo y dopo aver ricopiato la sua chiave e le informazioni associate nel nodo z. ` 19 . Esercizio 25 Calcolare il massimo ed il minimo numero di nodi interni in un albero rosso nero di altezza nera k. Il massimo numero di nodi si ha quando ogni nodo interno nero ha entrambi i figli rossi. Il valore di k per cui m risulta minimo e quello per cui m risulta massimo. dopo aver rimosso il nodo y dall’albero. Soluzione. Naturalmente occorre anche aggiungere un sottoalbero di altezza nera opportuna e la cui radice w deve diventare il secondo figlio di z.Esercizio 24 Supponiamo che una applicazione usi un albero binario di ricerca T ed un’altra struttura dati che contiene un puntatore ad un nodo y di T il cui predecessore z ha due figli. 1. 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). In questo caso l’albero e un albero binario completo di altezza 2k. Esso ha quindi 2k foglie e 2k − 1 nodi ` interni. ha sostituito y. 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. Il minimo numero di nodi si ha quando non ci sono nodi rossi. Soluzione. E allora possibile aggiungere un nodo rosso z tra x ed y mettendo y come figlio di z e z al posto di y come figlio di x. 4. 2. Il minimo numero di nodi interni del sottoalbero di radice w. Di conseguenza il puntatore continuer` a a puntare al nodo y eliminato mentre dovrebbe puntare al nodo z che. nell’albero. ` 3. 2. 3. Questo non aumenta l’altezza nera bh(T ) dell’albero. Siccome ogni nodo interno nero ha due figli rossi i nodi 1 ` ` interni neri sono 3 (22k − 1) e i nodi interni rossi sono 2 (22k − 1) (E vero che 22k − 1 e 3 sempre divisibile per 3?) Esercizio 26 Sia x un nodo nero di un albero rosso-nero T e supponiamo che esso abbia ` un figlio y pure nero. La procedura D ELETE RB(T. In questo caso l’albero e un albero binario completo di altezza k. Assumendo che l’altezza nera del nodo x sia k = bh(x) determinare in funzione di k: 1. Il minimo numero di nodi interni del sottoalbero di radice w si ha quando tutti i suoi nodi sono neri ed e pari a 2k−1 − 1.

In definitiva. c0 la rappresentazione di m in base 4. b0 la rappresentazione binaria di m. Se t = 0 possiamo solo aggiungere un nodo rosso alla fine di un cammino (ad esempio 1 il primo) ottenendo un rapporto n−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. Se cam3 biamo colore a tutti i nodi di tali sottoalberi ed eliminiamo l’ultimo livello otteniamo t −1 2 un sottoalbero con 3 (4t − 1) nodi rossi e 4 3 nodi neri. Coloriamo i livelli dispari di rosso e quelli pari di nero. Otteniamo un albero rosso nero con radice nera. t t t+1 −1 L’albero rosso nero cos` ottenuto contiene ct 2·43+1 nodi rossi in meno e ct 4 3 nodi ı neri in meno. ` Altrimenti sia m = bt−1 bt−2 . . Possiamo ripetere l’operazione per ciascuno dei bit bi = 1 con i > 0 aggiungendo altrettanti nodi rossi. ` Altrimenti sia m = ct ct−1 . Il valore di m risulta massimo quando k e uguale all’altezza ` nera dell’albero. Se m = 0 non dobbiamo togliere nessun nodo e il rapporto massimo e 2. Sia m = n−2h +1 il numero di nodi da aggiungere usando il minimo numero possibile di nodi rossi. Soluzione. 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 2h − 1 + 2t nodi in totale. Ciascuno di tali sottoalberi contiene 4 3 −1 nodi rossi e 2 (4t − 1) nodi neri. Minimo rapporto: Sia h tale che 2h − 1 ≤ n < 2h+1 − 1 e consideriamo l’albero binario completo con h 2 − 1 nodi tutti neri (altezza nera k = h − 1). Se m = 0 tutti i nodi sono neri e il rapporto minimo e 0. Essi sono le foglie di un sottoalbero di altezza t − 1 con nodi tutti neri. Altrimenti consideriamo i primi 2t−1 nodi neri a livello h − 1. . . Infine. Sia m = 2h+1 − 1 − n il numero di nodi da togliere. foglie rosse e altezza nera k = (h + 1)/2.4. In questo caso m = 2bh(T )−1 . se s e il numero di bit 1 nella rappresentazione binaria di m il rapporto ` log n s minimo e n−s = Θ( n ). 20 . Questo e certamente ` l’albero di 2h+1 − 1 nodi con il massimo numero di nodi rossi e siccome ogni nodo nero ha due figli rossi il numero di nodi rossi e 3 (2h+1 − 1) e il numero di nodi neri `2 1 h+1 e 3 (2 ` − 1). ` Massimo rapporto: Sia h tale che 2h − 1 ≤ n < 2h+1 − 1 e consideriamo due casi: h dispari Consideriamo l’albero binario completo con 2h+1 − 1 nodi e altezza h. se b0 = 1 dobbiamo semplicemente aggiungere un nodo rosso a livello h. ossia quando x e la radice e il nodo rosso viene aggiunto come figlio ` della radice. Essi sono le foglie di ct sottoalberi di altezza 2t con foglie rosse e radice rossa. Consideriamo i primi ct 4t nodi rossi a livello h. Il valore di m risulta minimo quando k = 0 (in questo caso viene aggiunto il solo nodo rosso e una foglia). .

c0 la rappresentazione di m in base 4. esclusa la radice. figli della radice neri. Il rapporto massimo e quindi ` 2n − n+ t i=0 ci t i=0 ci h pari Consideriamo l’albero binario completo con 2h+1 − 1 nodi e altezza h. Coloriamo i livelli dispari di nero e quelli pari di rosso esclusa la radice che deve essere nera. Consideriamo i primi ct 4t nodi rossi a livello h.Possiamo ripetere l’operazione per ciascuno delle cifre ci ottenendo un albero rosso nero con t 2 · 4i + 1 2n − t ci 2 h+1 i=0 ci (2 − 1) − = 3 3 3 i=0 nodi rossi e t 4i − 1 1 h+1 n + t ci i=0 ci (2 − 1) − = 3 3 3 i=0 nodi neri. 21 . foglie rosse e altezza nera k = h/2 + 1. t t t+1 2·(2h+1 −2) . 3 −1 L’albero rosso nero cos` ottenuto contiene ct 2·43+1 nodi rossi in meno e ct 4 3 nodi ı neri in meno. Il rapporto massimo e quindi ` 2n − 2 − n+2+ t i=0 ci t i=0 ci Esercizio 28 Dire in quale caso l’altezza nera di un albero rosso nero aumenta per effetto di una I NSERT RB ed in quale caso diminuisce per effetto di una D ELETE RB. ha due figli rossi il numero di nodi rossi e 2 (2h+1 −2) e il numero di nodi neri e 3 (2h+1 +1). Se cambiamo colore a tutti i nodi di tali sottoalberi ed eliminiamo l’ultimo livello otteniamo t −1 un sottoalbero con 2 (4t − 1) nodi rossi e 4 3 nodi neri. Questo e certamente l’albero di 2h+1 − 1 nodi con il ` massimo numero di nodi rossi e siccome ogni nodo nero. 2 Ciascuno di tali sottoalberi contiene 4 3 −1 nodi rossi e 3 (4t − 1) nodi neri. . . 2h+1 +1 Possiamo ripetere l’operazione per ciascuno delle cifre ci ottenendo un albero rosso nero con t 2 h+1 2 · 4i + 1 2n − 2 − t ci i=0 ci (2 − 2) − = 3 3 3 i=0 nodi rossi e t 4i − 1 1 h+1 n+2+ ci (2 + 1) − = 3 3 3 i=0 t i=0 ci nodi neri. Otteniamo un albero rosso nero con radice nera. ` Se m = 0 non dobbiamo togliere nessun nodo e il rapporto massimo e Altrimenti sia m = ct ct−1 . `3 `1 Sia m = 2h+1 − 1 − n il numero di nodi da togliere. Essi sono le foglie di ct sottoalberi di altezza 2t con foglie rosse e radice rossa.

Ogni corda sottende due archi di cerchio. Se inseriamo ad uno ad uno gli elementi dell’array nell’ordine A[1]. calcola il numero di coppie di corde che si intersecano. . . In questo caso il nero aggiuntivo attribuito convenzionalmente al nodo x viene trasferito alla radice e quindi non viene pi` contato nell’altezza nera. Allora b1 = 0 mentre per i > 1 il valore di bi e uguale a bi−1 aumentato ` del numero di elementi in A[1 . L’altezza nera dell’albero aumenta per effetto di una I NSERT RB quando: a) viene inserito il primo nodo: in questo caso il nodo inserito diventa radice e l’altezza nera aumenta da 0 a 1. i figli neri contano nell’altezza nera dell’albero. Sia A[1 . . . il numero di elementi maggiori di A[i] presenti nell’albero. ogni volta che eseguiamo la I NSERT RB(T. i] dell’array. la radice nera non veniva contata nell’altezza nera mentre. . Dopo aver inserito l’elemento A[i] possiamo calcolare il numero di elementi maggiori di A[i] presenti nell’albero con una chiamata R ANK (x) sul nodo x appena inserito. i − 1] che sono maggiori di A[i]. A[i]). Indichiamo con bi il numero di inversioni del prefisso A[1 .p radice dell’albero: in questo caso. A[2]. Fissiamo un sistema di riferimento sulla circonferenza scegliendo un’origine O che non coincida con nessuno degli estremi delle corde ed un verso (ad esempio quello antiorario). A[3]. Soluzione. b) dopo aver eseguito il Caso 2 con x figlio della radice. . A[n] possiamo calcolare il numero di inversioni totale sommando.. b) viene eseguito il Caso 1 con x. 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. Descrivere un algoritmo che. 22 . L’altezza nera dell’albero diminuisce per effetto di una D ELETE RB quando viene eseguito il Caso 0 con x radice dell’albero. uno solo dei quali contiene l’origine. u 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(n log n). n] l’array. Quindi l’altezza nera aumenta di 1. Esercizio 30 (*) Consideriamo un insieme di n corde di un cerchio C i cui estremi sono tutti distinti. Questo accade se: a) viene tolto l’ultimo nodo: in questo caso l’altezza nera diminuisce da 1 a 0. Siccome sia I NSERT RB che R ANK richiedono tempo O(log n) l’intera operazione richiede tempo O(n log n). Orientiamo le corde nello stesso verso dell’arco contenente l’origine per cui il primo estremo ha coordinata negativa ed il secondo ha coordinata positiva. Soluzione.Soluzione. dopo la trasformazione che scambia i colori della radice con quello dei figli. in tempo O(n log n). prima della trasformazione.

key. S UCCESSOR e P REDECESSOR si eseguono in tempo O(1).left.max = −∞ per ogni nodo interno valgono le relazioni: x. M AXIMUM.nil .right.max memorizzano rispettivamente la chiave minima e quella massima del sottoalbero radicato in x. Le operazioni I NSERT e D ELETE devono essere modificate in modo tale da inserire e togliere un elemento sia dall’albero che dalla lista.max ) Quindi. Soluzione. I due campi x. a Il campo x.min.left.min = min(x.mingap memorizza la minima distanza tra due chiavi del sottoalbero radicato in x. Questo richiede tempo O(n log n). S UCCESSOR e P REDECESSOR si possano eseguire in tempo O(1). x.max si possono mantenere senza aumentare la complessit` asintotica delle operazioni di inserimento e cancallazione. x. Per fare questo aggiungiamo ad ogni nodo x dell’albero due campi puntatore x.pred che puntano rispettivamente al successore ed al predecessore di x.min = ∞ e T.right . M AXIMUM.max . vedi Esercizi 2 e 29). Sovrapponiamo alla struttura dati albero rosso-nero una struttura di lista doppiamente concatenata. ` Esercizio 31 Aumentare un albero rosso nero T in modo tale che le operazioni M INI MUM. Aggiungiamo inoltre all’albero T due campi puntatore T. Suggerimento: aumentare l’albero aggiungendo ad ogni nodo i tre campi min. max e mingap. Consideriamo quindi la sequenza delle coordinate dei secondi estremi. Soluzione.nil . E ovvio che in questo modo le operazioni M INIMUM.min − x.key − x.mingap = min(x.mingap.key) 23 b2 b1 b3 b4 .min e T. x.succ e x.nil . Calcoliamo quindi il numero b di inversioni nella sequenza delle coordinate dei secondi estremi (tempo O(n log n). Ponendo T. per il teorema dell’aumento.max = max(x.left .max che puntano ` rispettivamente al nodo minimo e massimo.mingap.a1 a2 a3 a4 O Ordiniamo le corde rispetto alle coordinate del primo estremo.min e x. 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.min) x. x. x. Ogni inversione in tale sequenza rappresenta una coppia di corde che non si intersecano.key. i due campi x. Esercizio 32 Aumentare un albero rosso nero T avente chiavi di tipo numerico in modo da poter eseguire in tempo costante l’operazione M IN G AP che ritorna la minima distanza tra due chiavi. A questo punto il numero di intersezioni e n(n − 1)/2 − b. x. Usando la sentinella T.right.nil e ponendo T.mingap = ∞ per ogni nodo interno vale la relazione: x.

max(k. / 1 if i.high 10 / L’intervallo in x interseca i. i) 9 if i. x) Calcolare la complessit` !! a Versione che non modifica T : S TAMPA -I NTERVALLI (T. i) 5 while x = NIL and k < kmax 6 k = k+1 7 P RINT (x. Soluzione. x) 9 x = S EARCH (T.int . i) 10 if x = NIL 11 x = M INIMUM (T ) 12 while x = NIL 13 if i interseca x. / 8 S TAMPA -R IC (x.left .root. 1) log n)) dove k e il numero di intervalli trovati.left = NIL and i. Opzionale: a ` trovare una soluzione che non modifica T . i) / x = NIL radice di un sottoalbero di intervalli.left = NIL 4 S TAMPA -R IC (x. i) / T albero di intervalli. i) 5 else 6 if x. i) S TAMPA -R IC (x.low 2 / L’intervallo in x e quelli nel sottoalbero destro non intersecano i.Quindi.low ≤ x.int) 8 D ELETE (T.low ≤ x. a Esercizio 33 Descrivere un algoritmo che dato un albero di intervalli T ed un intervallo i stampa tutti gli intervalli in T che intersecano l’intervallo i.left.int) 12 if x.right = NIL 13 S TAMPA -R IC (x.left .right .int) 15 x = S UCCESSOR (T. i) / T albero di intervalli.high < x. i intervallo. / 11 P RINT (x.mingap si pu` mantenere senza aumentare o la complessit` asintotica delle operazioni di inserimento e cancallazione.size 2 kmax = n/ log2 n 3 k=0 4 x = S EARCH (T.max 7 / Gli intervalli nel sottoalbero sinistro possono intersecare i. / 1 n = T. per il teorema dell’aumento. / 3 if x.int 14 P RINT (x. i intervallo. i) 24 . anche x.root . i intervallo. L’algoritmo deve avere complessit` O(min(n.int. / 1 S TAMPA -R IC (T. Versione che modifica T : S TAMPA -I NTERVALLI (T.

1) ≤ 1 + (hx − 1) max(kx .low > x. 1) ≤ 1 + (hx − 1) max(kx . left max(kx. i x x.left In questo caso kx = kx. right ≤ 1 + nx. left + nx. Dimostriamo quindi che mx ≤ hx max(kx . i x x. kx. sia kx il numero di intervalli di tale sottoalbero che intersecano l’intervallo i e sia mx il numero di chiamate di funzione che vengono eseguite. 1). Quindi mx ≤ 1 + mx.La complessit` e proporzionale al numero m totale di chiamate alla funzione ricorsiva a` S TAMPA -R IC. left e quindi. 1) ≤ hx max(kx . nel caso peggiore. Dimostriamo dapprima mx ≤ nx per induzione su nx . vengono effettuate le chiamate ricorsive sui sottoalberi non vuoti di x. mx ≤ 1 + hx.int. left . Se si esegue il ramo else della prima if allora i.high ≥ x. right = nx per ipotesi induttiva.high e viene eseguita soltanto la prima chiamata ricorsiva. l’esecuzione di una chiamata richiede tempo costante. escludendo i tempi per eseguire le chiamate ricorsive.int. per ipotesi induttiva. left .high e viene eseguita soltanto la prima chiamata. mx ≤ 1 + hx. 1). Se nx = 1 non vengono eseguite chiamate ricorsive e quindi mx = 1 ≤ nx . 1) ≤ hx max(kx . • i. left + mx. Se nx > 1.left In questo caso x. left = kx e. per ipotesi induttiva.left 25 .low ≤ x. Se si esegue il ramo then della prima if i kx intervalli che intersecano i stanno tutti nel sottoalbero sinistro.low e ci sono le seguenti possibilit` : a • i.int . 1) per induzione sull’altezza hx del sottoalbero radicato in x.right = NIL . left max(kx. Infatti. i x x. Sia nx il numero di nodi dell’albero radicato in x.

left 26 x. right max(kx. per ipotesi induttiva mx ≤ 1 + hx.right In questo caso kx.high e viene eseguita soltanto la seconda chiamata ricorsiva. 1) ≤ hx max(kx . right . per ipotesi induttiva. 1) possiamo concludere mx ≤ hx kx − kx − hx + 2 ≤ hx max(kx .high e vengono eseguite entrambe le chiamate ricorsive. 1) ≤ 1 + (hx − 1) max(kx − 1. Quindi. • i.left x. Se kx.right = NIL . 1). 1). right ≥ 1 allora kx = kx. i x x.low > x. right + 1 ≥ 2 e kx ≤ max(kx . left + 1 ≥ 2 e kx ≤ max(kx . right = 0 allora kx = 1 e quindi mx ≤ 1 + hx. right = kx e.int. left ≤ 1 + (hx − 1)(kx − 1) = hx kx − kx − hx + 2 e siccome kx = kx. right . kx.low > x.int. 1) e quindi mx ≤ 1 + (hx − 1)(kx. 1).high e viene eseguita soltanto la seconda chiamata ricorsiva. right ≤ hx = hx max(kx .In questo caso x.left x. i x x. 1) ≤ 1 + (hx − 1) max(kx . right max(kx. mx ≤ 1 + hx. 1). • i. left ≥ 1. right = kx − 1. right − 1) = hx kx − kx − hx + 2 ≤ hx max(kx . • i. left = kx − 1 ed inoltre almeno un intervallo del sottoalbero sinistro interseca l’intervallo i e dunque kx.low ≤ x. per ipotesi induttiva mx ≤ 1 + hx. left kx.int. i x x.right In questo caso kx. 1).right . Se kx. Quindi.

hx max(kx . left + hx. Dunque mx ≤ min(nx . • i. right max(kx. 1). 1). right kx. 1). Se kx. mx ≤ ≤ = ≤ 1 + hx. left kx. left + kx. left +kx. right ed inoltre almeno un intervallo del sottoalbero sinistro interseca l’intervallo i e dunque kx ≥ kx. right +1 ed inoltre almeno un intervallo del sottoalbero sinistro interseca l’intervallo i e dunque kx ≥ kx. Per ipotesi induttiva. left + 1 e quindi mx ≤ 1 + hx. In altre parole non pu` succedere che la strada pi` breve per arrivare alla stazione j-esima della prima catena passi u per la stazione precedente della seconda catena e contemporaneamente la strada pi` breve u per arrivare alla stazione j-esima della seconda catena passi per la stazione precedente della prima catena. left kx. Per ipotesi induttiva mx ≤ 1 + hx. right .high e vengono eseguite entrambe le chiamate ricorsive. Negli alberi rosso-neri hx = O(log nx ) e quindi mx = O(min(nx .right Se kx. left kx. left + kx. 1)). left + hx. 1) Se kx. right max(kx. 1). Esercizio 34 Dimostrare che nel problema delle catene di montaggio se p1 [j] = 2 allora o anche p2 [j] = 2 e viceversa se p2 [j] = 1 allora anche p1 [j] = 1.int. left ≥ 1 e hx ≥ 2. 1). right ≥ 1 allora mx ≤ 1 + hx. left e quindi mx ≤ 1 + hx. right 1 + (hx − 1)(kx. left = 1 + hx kx − kx ≤ hx max(kx .In questo caso kx = kx.left x. i x x. 27 . right ≤ 1 + (hx − 1)(kx − 1) + hx − 1 = hx kx − kx + 1 ≤ hx kx ≤ hx max(kx . right ) 1 + (hx − 1)(kx − 1) = hx kx − kx − hx + 2 hx kx = hx max(kx . log nx max(kx . mx ≤ 1 + hx. right . left kx. right kx. left ≥ 1. right ) = 1 + hx kx − kx ≤ hx kx ≤ hx max(kx . left kx. left + hx. left + kx. right = 0 allora kx = kx. right ≤ 1 + (hx − 1)(kx. left kx. right = 0 allora kx = kx. right ≥ 1 allora In questo caso kx = kx.low ≤ x. left + hx. Se kx. left + hx. 1))).

m una soluzione del nuovo problema. . s2 .1 . . u Esercizio 37 (*) Descrivere un algoritmo che risolve il problema dell’esercizio precedente in tempo O(n log n). assurdo. . Esercizio 35 Dimostrare che una parentesizzazione completa di un prodotto di n matrici contiene esattamente n − 1 coppie di parentesi. . . Sia p(n) il numero di coppie di parentesi. . Tale sottosequenza e u ` anche la pi` lunga sottosequenza crescente di A. sm. 28 .2.k ed Sj e la sottosequenza crescente di ` lunghezza j con ultimo elemento sj. Sia A la sequenza. Usiamo quindi la programmazione dinamica per calcolare la pi` lunga sottosequenza comune (LCS) tra A e B (tempo O(n2)). Sommando le due disequazioni si ottiene t2 [j − 1] + t1 [j − 1] < 0. m con m lunghezza di una LIS. . . sm.Soluzione.2 .j−1 < f1 [j − 1] ed f1 [j − 1] + t1 [j − 1] < f2 [j − 1]. . Se n = 1 allora non occorre nessuna parentesi e p(1) = 0 = 1 − 1. Soluzione. . Possiamo inoltre supporre che tra tutte le sottosequenze crescenti di Xn−1 di lunghezza m − 1 il cui ultimo elemento e minore o uguale di xn la sottosequenza Sm−1 sia quella con ` ultimo elemento minimo. Dunque p(n) = 1 + p(k) + p(n − k) e quindi. xn la sequenza di n numeri interi. L’algoritmo deve richiedere tempo O(n2 ). Sm = sm.1 . Verifichiamo quindi la sottostruttura ottima del nuovo problema. . Facciamo una copia B di A e poi ordiniamo B in ordine crescente (tempo O(n log n)). Soluzione. Allora f2 [j − 1] + t2. .1 . . . .j minimo. .k in quanto il prefisso sk. .j < sk. . sm una LIS e sia m la sua lunghezza. Sia dunque S1 = s1. S2 = s2. Infatti se esistesse una sottosequenza crescente S del prefisso Xn−1 il cui ultimo elemento e minore o uguale di xn ed avente lunghezza maggiore di ` Sm−1 la sottosequenza S xn sarebbe una soluzione migliore di S.j di Sk di lunghezza j ha ultimo elemento minore di sk. Supponiamo p1 [j] = 2 e p2 [j] = 1. ` Se sm = xn il prefisso Sm−1 e una LIS del prefisso Xn−1 il cui ultimo elemento e ` ` minore o uguale di xn . u Esercizio 36 Descrivere un algoritmo che trova la pi` lunga sottosequenza crescente di una sequenza di n numeri interi. Il ragionamento precedente ci porta a considerare il problema pi` generale che consiste u nel calcolare la sottosequenza di lunghezza j il cui ultimo elemento e minimo per ogni ` j = 1. sk. .2 . . per ipotesi induttiva. 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.1 . . sia S = s1 . . Se sm = xn la sequenza S e una LIS per il prefisso Xn−1 . Invece di trasformare il problema nel problema della ricerca della pi` lunga u sottosequenza comune (LCS) applichiamo direttamente la programmazione dinamica al problema originale della ricerca della pi` lunga sottosequenza crescente (LIS). x2 . u Sottostruttura ottima: Sia X = x1 . Notiamo che se j < k allora sj. sk. Soluzione. s2. . . p(n) = 1 + k − 1 + n − k − 1 = n − 1.

1. s2.1 .2 .j . sj. Allora il prefisso sj. P.2 . . . . n) 1 S[1] = X[1].1 . sm .m una soluzione per il prefisso Xi ed S1 = s1.j−1 < xn < sj.1 abbiamo m = m .2.j altrimenti Sj−1 .1 . • per i > 1 ed sj−1.2 .j per qualche j. 1) e la sottosequenza di lunghezza 1 con ultimo elemento minimo. . . . . Sm = Sm . Infatti non pu` essere xn < s1. . . sj. sm . . m]” 5 / Usando la ricerca binaria / 6 S[j] = X[i].m < xn altrimenti Sm . . . 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.2 . xn sarebbe una sottosequenza crescente di lunghezza j con ultimo elemento minore di sj. L 29 .j della lista Sj e ` ` P [j] e il puntatore all’elemento precedente della lista Sj . S[j] e l’ultimo elemento sj.j−1 di Sj e la sottosequenza crescente di Xn−1 di lunghezza j − 1 il cui ultimo elemento e minimo mentre le altre ` sottosequenze rimangono le stesse della soluzione per il prefisso Xn−1 . Soluzione ricorsiva: Sia S1 = s1.j ed infine non pu` essere sm. s2. S2 = s2. L[1] = 1.1 . xn o sarebbe una sottosequenza crescente di lunghezza m + 1 contro l’ipotesi che m sia la lunghezza di una LIS. . Allora: • per i = 1 abbiamo m = 1 ed S1 = x1 .m abbiamo m = m + 1. Sm = sm. 3 for i = 2 to n 4 “Cerca l’indice j tale che S[1 . • per i > 1 ed xi < s1. . . .Inoltre deve essere xn = sj. Calcolo della LIS: LIS(X.j−1 ≤ xi < sj. / ` L[j] e l’indice dell’ultimo elemento sj. .j della lista Sj . . . .1 . S1 = xi ed Sj = Sj per j > 1. sm. . xi ed Sk = Sk per k < m. m. sm. . xi ed Sk = Sk per k = j. S2 = s2. .1 perch´ o e (s1 .1 . . P [1] = NIL .j abbiamo m = m . . . m = 1 2 / Per ogni j = 1. • per i > 1 ed xi ≥ sm .m una soluzione per il prefisso Xi−1 . ` Sia quindi xn = sj. j − 1] ≤ X[i] < S[j . non pu` essere ` o sj−1. Sj = Sj−1 . Sm = sm .

La ricostruzione della soluzione si fa in tempo O(m) usando a gli array L e P . 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 . Cancellazione: non scrive il carattere letto. Esercizio 38 Uno dei test per l’assunzione di un dattilografo consiste nel copiare un testo X di n caratteri. Usiamo la programmazione dinamica. e Scambio: legge due caratteri consecutivi e li scrive in ordine inverso. Inserimento: scrive un carattere che non c’` nel testo originale. c) Ia l’inserimento del carattere a. a causa degli errori. Soluzione. Il testo Y scritto dal dattilografo viene poi confrontato con il testo X per controllare quanti errori egli ha commesso. b) D a la cancellazione del carattere a. I tipi o di errori che un dattilografo pu` commettere sono i seguenti: o Sostituzione: legge un carattere e ne scrive un altro.b lo scambio dei due caratteri consecutivi a e b. Indichiamo con a) Ra. Naturalmente.b la sostituzione del carattere a con il carattere b. ed inoltre con e) Ca il ricopiamento corretto del carattere a. d) Sa. la lunghezza m del testo Y pu` non essere uguale alla lunghezza n del testo originario.i 1 2 3 4 5 X 11 12 10 1 9 P NIL 1 NIL NIL 4 6 3 4 7 16 6 8 5 6 9 10 11 12 13 21 13 14 7 8 8 8 10 8 12 L S 4 1 6 3 8 5 12 13 7 8 Complessit` O(n log n). Con un sufficiente 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` modi diversi (ad esempio lo stesso effetto di una sostituzione si puo ottenere con una u cancellazione ed un inserimento). 30 .

1 + ei. 1 + ei−1. Calcolo del numero minimo di errori: DATTILOGRAFA (X.j il numero di errori contenuto in Si. Quindi ei.m−2 . j ≥ 1 ed xi = yj = a le operazioni possibili sono D a .b .j−1 . ` b) se S = S D a la sequenza S e ottima per Xn−1 ed Y ed e = 1 + en−1.b la sequenza S e ottima per Xn−1 ed Ym−1 ed e = 1 + en−1. 1 + ei.j = min(1 + ei−1.m−1 .j . Sottostruttura ottima: Sia S una soluzione ottima e sia e il numero di errori contenuto in S. 1 + ei−2. j ≥ 1. Da . j > 1. j − 1). m) 1 for i = 0 to n 2 E[i.j−2 ). Una soluzione e ottima se contiene il minimo numero ` possibile delle prime quattro operazioni (gli errori). ` e) se S = S Ca la sequenza S e ottima per Xn−1 ed Ym−1 ed e = en−1.m−1 . Allora a) se S = S Ra.j−1.j−1 . Quindi ei.b . Se i ≥ 1. ` Soluzione ricorsiva: Sia Si.j = min(1 + ei−1.m . j ≥ 1. Se j = 0 le uniche operazioni possibili sono le cancellazioni e quindi ei.a . 1 + ei. Se i = 0 le uniche operazioni possibili sono gli inserimenti e quindi e0.j = j. Ib ed S b.j−1. Se i ≥ 1. xi = a = yj = b ed i > 1.j−1 ). ` c) se S = S Ia la sequenza S e ottima per X ed Ym−1 ed e = 1 + en. I a e C a . 1 + ei−1. Quindi ei.m−1 .b la sequenza S e ottima per Xn−2 ed Ym−2 ed e = 1 + en−2. xi−1 = yj e xi = yj−1 le operazioni possibili sono Ra. Da ed Ib . n.j .j . 0] = i 3 for j = 1 to m 4 E[0.Una soluzione del problema e allora una sequenza S di tali cinque operazioni che ` trasforma il testo X nel testo Y . ` d) se S = S Sa. ei − 1.j una soluzione ottima per i due prefissi Xi ed Yj e sia ei.j . Y. xi = a = yj = b e o i = 1 o j = 1 o xi−1 = yj o xi = yj−1 le operazioni possibili sono Ra.j = min(1 + ei−1. j] = j 5 for i = 1 to n 6 for j = 1 to m 31 . Se i ≥ 1.0 = i.

j] == S / 16 S TAMPA -S OLUZIONE (O. i − 2. j − 1). j] == C 8 S TAMPA -S OLUZIONE (O. P RINT(S) Esercizio 39 Il problema dello zaino frazionario e il seguente: ` Dati n tipi di merce M1 . . . j] > 1 + E[i − 1. 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. X[i − 1] == Y [j] e X[i] == Y [j − 1]. P RINT(R) 11 elseif O[i. Mn in quantit` rispettive q1 . j] = 1 + E[i − 1. j] > 1 + E[i − 2. O[i. . j − 2] E[i. O[i. j] > 1 + E[i − 1.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 / Un errore di tipo I pu` sempre esserci. P RINT(D) 15 else / O[i. O[i. j] E[i. . j] == R 10 S TAMPA -S OLUZIONE (O. j) 1 if i == 0 2 for k = 1 to j 3 P RINT(I) 4 elseif j == 0 5 for k = 1 to i 6 P RINT(D) 7 elseif O[i. j − 1] E[i. / o if E[i. cn si vuole riempire uno zaino di capacit` Q in modo che il contenuto abbia valore a massimo. j] = 1 + E[i − 2. P RINT(C) 9 elseif O[i. j > 1. / o E[i. j − 1]. i. qn e con valori unitari a c1 . P RINT(I) 13 elseif O[i. i. j] = S return E. j] = E[i − 1. j] = R / Pu` essere avvenuto un errore di tipo S solo / o se i > 1. j − 1). . Pu` esserci un errore di tipo R. O[i. . O[i. j] = D / Un ricopiamento corretto pu` esserci solo se X[i] == Y [j] e in tal / o caso non possono esserci altri errori. j] = 1 + E[i − 1. . i − 1. j] == I 12 S TAMPA -S OLUZIONE (O. . j − 1]. . . i − 1. j] = I / Un errore di tipo D pu` sempre esserci. j] = C else / X[i] = Y [j]. j). O Stampa della sequenza di operazioni: S TAMPA -S OLUZIONE (O. . j − 1]. . j]. j − 1). / o if E[i. if X[i] == Y [j] if E[i. j] > E[i − 1. j] == D 14 S TAMPA -S OLUZIONE (O. Mostrare che il seguente algoritmo risolve il problema: 32 . j − 2]. j − 2). i − 1. j − 1] E[i.

. yn ha ` lo stesso costo ed e quindi una soluzione ottima che contiene zk . . zk−1 . zk−1 . . . . L’algoritmo sceglie k−1 zk = min qk . . . . . . c. yn . . Ma la soluzione e z1 . . yn e soluzione del sottoproblema con ` le merci M2 . . . Q) 1 / Precondizione: c1 ≥ c2 ≥ . . i = 1 3 while i ≤ n 4 if Sp ≥ qi 5 zi = qi 6 else zi = Sp 7 Sp = Sp − zi 8 i = i+1 9 return z Soluzione. Mn con zaino di capacit` Q−y1 . ` Prova diretta L’algoritmo pone zi = qi per ogni indice i fino ad un certo indice k per il quale pone k−1 zk = Sp = Q − zi i=1 e pone quindi zi = 0 per tutti gli indici i successivi. . yn e ottima e quindi la soluzione ottenuta z1 . ed e ottima altrimenti potremmo trovare a ` una soluzione del problema originale migliore di y1 . . . . . . yk . Allora y2 . ` a Se nella soluzione ottima z1 . . . . . Sottostruttura ottima Sia y1 . . Propriet` della scelta golosa a Sia z1 . . yn una qualsiasi altra soluzione. . . yk+1 . . . . . . . zk . . . . . yn sostituisco yk con zk e diminuisco corrispondentemente le quantit` successive trovo una soluzione di costo maggiore o uguaa le (poich´ le merci successive hanno costo unitario minore o uguale). zk−1 . . y2. n. . . . . . . . . Quindi yk ≤ zk . . . .R IEMPI -Z AINO (q. Sia y1 . Q − zi i=1 che e la massima quantit` possibile. . yk . . La differenza tra il costo C della soluzione calcolata dall’algoritmo e il costo C dell’altra soluzione e: ` n C −C = i=1 (zi − yi )ci Per i < k abbiamo ci ≥ ck e zi − yi = qi − yi ≥ 0 e quindi (zi − yi )ci ≥ (zi − yi )ck 33 . . zk−1 fatte precedentemente. yn una soluzione ottima. yk . . ≥ cn / 2 Sp = Q. . . . yn una soluzione ottima che contiene le scelte z1 .

c3 = 12 e zaino di capacit` V = 10. V ) 1 / Precondizione: c1 /v1 ≥ c2 /v2 ≥ . ` Esercizio 40 Il problema dello zaino 0-1 il seguente: Dati n tipi di oggetti O1 . si vuole riempire uno zaino di capacit` V con tali oggetti in modo che il contenuto abbia valore a massimo. Basta trovare un controesempio. La soluzione ottima e invece prendere un oggetto di valore 24 ed uno ` di valore 12. . On di volumi v1 . . l’algoritmo prende solamente un oggetto di valore 35. tra le attivit` compatibili con a quelle gi` scelte. . v3 = 4 e valori c1 = 35. i = 1 3 while i ≤ n 4 zi = Sp/vi . Sp = Sp − zi vi 5 i = i+1 6 return z Soluzione. . Mostrare che il seguente algoritmo non risolve il problema: R IEMPI -Z AINO (v. cn . n. ≥ cn /vn / 2 Sp = V .Per i = k abbiamo ci = ck e zk = Q − k−1 i=1 zi e quindi zi − yi )ck k−1 (zk − yk )ck = (Q − Per i > k abbiamo ci ≤ ck e zi = 0 e quindi i=1 (zi − yi )ci ≥ −yi ck Mettendo tutto assieme: k−1 n i=k+1 C −C = i=1 (zi − yi )ci + (zk − yk )ck − k−1 i=1 (zi − yi )ci n k−1 ≥ i=1 (zi − yi )ck + Q − n (zi − yi ) ck − y i ck i=k+1 ≥ ck (Q − yi ) = 0 i=1 Quindi la soluzione trovata dall’algoritmo e ottima. Mostrare che questo non e vero se a ` scegliamo quella di durata minima. . . . . vn e valori c1 . a 34 . Supponiamo di avere tre tipi di oggetti di a volumi v1 = 7. c2 = 24. quella con tempo di fine minimo. Si suppone di avere a disposizione un numero illimitato di oggetti di ciascun tipo. c. Siccome c1 /v1 = 5 ≥ c2 /v2 = 4 ≥ c3 /v3 = 3. . . . . . . v2 = 6. Mostrare che non e vero neppure scegliendo quella ` incompatibile con il minimo numero di attivit` rimanenti. Esercizio 41 Nel problema della scelta delle attivit` abbiamo visto come si arrivi ad una a soluzione globalmente ottima scegliendo ad ogni passo.

. . . L’algoritmo goloso e il seguente: ` 35 . 10). 3). 11).Soluzione. . . . a5 . ` a Consideriamo le attivit` a1 = (0. Dunque sono necessarie almeno m aule. a Soluzione. . . . n] e una programmazione ottima delle n / ` attivit` didattiche che usa il minimo numero m di aule. a a6 = (6. a4 = (1. a5 = (4. 12). . . f. 13). Consideriamo le tre attivit` a1 = (0. . n) / Precondizione: s1 ≤ s2 ≤ . Trovare un algoritmo goloso per programmare tutte le attivit` a nel minimo numero possibile m di aule. Se h == m + 1 / o in nessuna delle m aule usate finora si pu` effettuare ai . an delle attivit` didattiche aventi tempi di inizio s1 . . 4) e a3 = (3. o 7 J[i] = h 8 th = fi 9 if h == m + 1 then m = m + 1 10 / Postcondizione: J[1 . a11 . Se a scegliamo quella di durata minima otteniamo una soluzione costituita soltanto dall’attivit` a a2 mentre una soluzione ottima e costituita dalle altre due attivit` . . a6 . . A2 . L’algoritmo goloso e il seguente: ` P ROGRAMMA -L EZIONI (s. a2 = (2. 7). . n] usi il minimo numero possibile m di aule e evidente. a J[i] == j significa che la lezione ai si terr` nell’aula Aj . in cui svolgerle. a10 = (10. . 6). Trovare un algoritmo goloso per programmare il massimo numero possibile di attivit` nelle m aule disponibili. 14) e a11 = (11. . an delle attivit` didattiche aventi tempi di inizio s1 . 5). . . . . . . a 11 return J. fn e supponiamo di avere un insieme sufficientemente grande di aule A1 . . a7 = (8. Soluzione. . a3 = (1. 6). . . a8 = (10. fn e supponiamo di avere a disposizione m aule A1 . m Che la soluzione J[1. Am in cui svolgerle. a7 . Al tempo si le a precedenti m − 1 aule erano tutte occupate e quindi tra le n attivit` ce ne sono almeno m a che si sovrappongono nello stesso istante. Se scegliamo quella incompatibile con il minimo numero di attivit` rimanenti otteniamo a una soluzione costituita dalle attivit` a2 . a8 mentre una soluzione ottima e costituita a ` dalle attivit` a1 . ≤ sn / 1 m=0 2 for i = 1 to n 3 h = m+1 4 for j = 1 to m 5 if si ≥ tj then h = j 6 / La lezione ai si pu` eseguire nell’aula Ah . a9 = (10. . a Esercizio 42 Siano a1 . . 4). Esercizio 43 Siano a1 . a2 = (3. Sia ai ` la prima attivit` assegnata all’ultima aula Am ed si il suo tempo di inizio. . . sn e a tempi di fine f1 . . . sn e a tempi di fine f1 . 14). 5).

P ROGRAMMA -L EZIONI (s. n] e una programmazione ottima tale che B [1. f. n] all’aula Ah . i]. n] e una programmazione ottima. a 12 return J Supponiamo che l’assegnazione J[1. ossia B[1. a Se B[i] = 0. assegna la i-esima attivit` ai all’aula Ah che si libera per a ultima tra tutte quelle che si liberano prima dell’inizio si di ai . i]. ≤ fn / 1 for j = 1 to m 2 tj = 0 3 t0 = −1 4 for i = 1 to n 5 h=0 6 for j = 1 to m 7 if si ≥ tj and tj ≥ th then h = j 8 / Ah e l’aula che si libera per ultima tra quelle in cui e possibile / ` ` effettuare la lezione ai . Quindi. n] la programmazione a che assegna le attivit` come B[1. n] tranne che sostituisce la j-esima con la i-esima (ossia a B [i] = J[i] e B [j] = 0). n] e una programmazione ottima del massimo / ` numero di attivit` didattiche che si possono svolgere nelle m aule. Se h == 0 in nessuna delle m aule si pu` effettuare ai . m) / Precondizione: f1 ≤ f2 ≤ . n] non assegna nessuna aula all’attivit` a i-esima. Supponiamo quindi J[i] = 0 e B[i] = J[i]. Consideriamo la prima a attivit` di indice j > i assegnata da B[1. . n] che assegna le prime i − 1 lezioni come l’algoritmo. i] = J[1. i] = J[1. Se J[i] = 0 l’attivit` i-esima e incompatibile con le a ` attivit` J[1. i − 1] e quindi B[i] = J[i] = 0. la programmazione ottima B[1. n] assegna l’attivit` i-esima ad una a diversa aula Ah con h = B[i] diverso da h = J[i]. ` Se B[i] = 0. dopo aver assegnato le prime i−1 attivit` entrambe le aule Ah ed Ah dovevano a a essere libere. dopo aver assegnato le prime i − 1 attivit` didattiche alle aule J[1. n] deve assegnare all’aula Ah a cui l’algoritmo assegna la i-esima attivit` a qualche altra attivit` successiva (altrimenti non sarebbe ottima). i − 1] = J[1. J[i] == 0 significa che la lezione ai non si terr` in nessuna delle m aule. i − 1]. n] di tutte le attivit` . la programmazione ottima B[1. i − 1] delle prime i − 1 attivit` didattiche sia a estensibile ad una programmazione ottima B[1. Se nessuna delle aule si libera prima di si l’attivit` ai non viene assegnata e J[i] = 0. B[1. o 9 J[i] = h 10 if h = 0 then th = fi 11 / Postcondizione: J[1 . i]. Se B[i] = J[i] siamo a posto. i] = J[1. a a J[i] == j = 0 significa che la lezione ai si terr` nell’aula Aj . a Supponiamo che esista una programmazione ottima B[1. e dimostriamo che esiste una programmazione ottima B [1. Quando l’algoritmo termina J[1. In B possiamo quindi scambiare tra le due aule Ah ed Ah le attivit` di indice maggiore o uguale ad i ottenendo anche in questo caso una programmazione ottima tale che B [1. n] tale che B [1. n] = B[1. Siccome fj ≥ fi questo non comporta sovrapposizioni nell’aula Ah e quindi B [1. Sia B [1. . ` 36 . a L’algoritmo. i − 1]. a con l’assegnazione J[i] = h. n. .

. Supponiamo. ` Pertanto la codifica di f che ha anch’essa lunghezza minore di n deve coincidere con la codifica di una delle sequenze pi` corte di n: assurdo perch´ sequenze diverse devono u e avere codifiche diverse. 37 . .Esercizio 44 Dimostrare che ogni algoritmo di compressione che accorcia qualche sequenza di bit deve necessariamente allungarne qualche altra. . quando si incontra un bit 1 si risale fino a che si incontra un antenato privo di figlio destro. . 2. (Suggerimento: usare 2n − 1 bit per rappresentare la struttura dell’albero ed n log2 n bits per elencare i caratteri nell’ordine in cui compaiono nelle foglie dell’albero del codice. . Mostrare come si possa rappresentare ogni codice prefisso ottimo per C con una sequenza di 2n − 1 + n log2 n bits.) Soluzione.) Soluzione. cn } un insieme di caratteri ed f1 . Altrimenti abbiamo finito. (Suggerimento: confrontare il numero di sequenze con il numero di codifiche. ` Siccome codifiche di sequenze distinte devono essere necessariaente distinte (altrimenti la decodifica sarebbe ambigua) il numero di sequenze che sono codifiche di sequenze di lunghezza minore di n e pure 2n − 1. che esista un algoritmo di compressione che non allunga nessuna sequenza di bit ma ne accorcia qualcuna. Esercizio 45 Sia C = {c1 . Se lo si trova si aggiunge un figlio destro a tale antenato e ci si sposta su tale figlio. fn le loro frequenze in un file. ` Siccome l’algoritmo non allunga nessuna sequenza ognuna delle sequenza di bit di lunghezza minore di n e codifica di qualche file di lunghezza minore di n. Sia f la pi` corta sequenza che viene accorciata dall’algoritmo e sia n la sua lunghezza. u Il numero di sequenze di lunghezza minore di n e 2n − 1. ogni volta che si incontra un bit 0 si aggiunge un figlio sinistro e si scende sul figlio appena aggiunto. . . 120 0 a:57 25 1 63 1 0 0 f:5 14 38 1 d:24 0 1 b:13 0 c:12 1 e:9 Per ricostruire la strutture dell’albero si parte dalla radice e quindi: 1. per assurdo. Rappresentiamo la struttura dell’albero con la sequenza in preordine dei bit che etichettano gli archi dell’albero a cui aggiungiamo un bit 1 alla fine. .

o c c c c c c 1e e 2e. . A8 e dunque ` l’algoritmo calcola una soluzione ottima. . ck dove c e un intero maggiore di 1 e k ≥ 0. t3 = 50/. . Soluzione. c c c Quindi R6 ≤ 4/ < t6 . t6 = 5/. La scelta golosa consiste nello scegliere il massimo numero possibile di monete di taglio massimo. A[2]. t4 = 20/. . a) I tagli sono t1 = 2e.Rappresentiamo la sequenza dei caratteri associati alle foglie elencando i loro codici a lunghezza fissa (servono log2 n bits per ogni carattere). 8 i=k+1 Ai ti la somma delle monete di taglio minore di tk . . . t5 = 10/. t2 . ` c) Trovare un insieme di tagli di monete per i quali l’algoritmo goloso non funziona. Quindi R = Sia Rk = 8 i=1 Ai ti con n = 8 i=1 Ai minimo. . 2/. 50/. . c1 = c. a) Descrivere un algoritmo goloso per fare ci` con tagli da 1/. A[n] sono il numero di monete del resto / di tagli rispettivi t1 . tn . . . . n) 1 / Precondizione: t1 > t2 > . . c c c / R6 : R6 = 2A7 + A8 c. . c 38 . . . > tn = 1 sono i tagli delle monete. A2 . c / R7 : R7 = A8 c. . . R ESTO (R. Se riusciamo a dimostrare che Rk < tk per ogni k allora la soluzione che viene calcolata dall’algoritmo e proprio la soluzione ottima A1 . c Sia A1 . . . Per ricostruire l’associazione tra caratteri e foglie basta associare i caratteri alle foglie nell’ordine da sinistra a destra. Per l’ottimalit` A8 < 2 (altrimenti potrei sostituire due monete da a 1/ con una da 2/) e quindi R7 ≤ 1/ < t7 . t2 = 1e. 20/. . . 5/. R e il / ` resto da dare. t7 = 2/ e c c c c c t8 = 1/. Per l’ottimalit` A7 < 3 (altrimenti potrei sostituire tre a monete da 2/ con una da 5/ ed una da 1/) ed inoltre se A7 = 2 allora A8 = 0 c c c (altrimenti potrei sostituire due monete da 2/ ed una da 1/ con una da 5/). 10/. Esercizio 46 Un cassiere vuole dare un resto di n centesimi di euro usando il minimo numero di monete. t. b) Dimostrare che l’algoritmo goloso funziona anche con monete di tagli c0 = 1. R8 : R8 = 0/ < t8 . A2 . . A8 una qualsiasi soluzione ottima per un certo resto R. 2 for i = 1 to n 3 A[i] = R/ti 4 R = R mod ti 5 / Postcondizione: A[1].

Intanto Rk = 0 < tk . c c c c R5 : R5 = 5A6 + R6 /. . . Esercizio 48 Su di una certa struttura dati viene eseguita una sequenza di n operazioni. Quindi R1 ≤ 1e + R2 < 1e + t2 = t1. Quindi Rj ≤ (c − 1)tj+1 + Rj+1 < ctj+1 = tj . Se riusciamo a provare che Rj < tj per ogni j allora la soluzione calcolata dall’algoritmo e proprio la soluzione ottima A1 . . t2 = 5/. Quindi R4 ≤ 10/ + R5 < 10/ + t5 = t4 . Per l’ottimalit` A2 < 2 (altrimenti potrei sostituire c a due monete da 1e con una da 2e). Per l’ottimalit` Aj+1 < c a (altrimenti potrei sostituire c monete di taglio tj+1 = ck−j−1 con una di taglio tj = ck−j ). o Soluzione. A2 . . c) Scegliamo i tagli t1 = 7/. . Per l’ottimalit` A6 < 2 (altrimenti potrei sostituire due c a monete da 5/ con una da 10/). c Esercizio 47 Mostrare che se al contatore binario A di k bit aggiungiamo anche una operazione D ECREMENT (A) che decrementa di una unit` il valore del contatore allora una a sequenza di n operazioni pu` costare O(nk). Quindi R2 ≤ 50/ + R3 < 50/ + t3 = t2. Consideriamo una successione di n = 2k − 1 operazioni (con k > 2) di cui le prime 2k−1 − 1 sono I NCREMENT (dopo di che i bit del contatore sono tutti 1) mentre le altre 2k−1 sono una ripetizione di 2k−2 gruppi di due operazioni: una I NCREMENT seguita da una D ECREMENT . t2 = ck−2 . Il numero di bit che modificati dalle due operazioni di un gruppo e k + k = 2k e il ` k−2 numero totale di bit modificati e maggiore di 2k2 ` = O(kn). Quindi R3 ≤ 40/ + R5 < 40/ + t5 < t3. . b) I tagli sono t1 = ck−1 . La soluzione ottima e invece costituita da due c c ` sole monete da 5/. 39 . 8 i=j+1 Ai ti la somma delle monete di taglio minore di tj . A2 . c c c R3 : R3 = 20A4 + 10A5 + R5 /. Per l’ottimalit` A5 < 2 (altrimenti potrei sostituire due c a monete da 10/ con una da 20/). . c c c R4 : R4 = 10A5 + R5 /. tk−1 = c1 = c e tk = c0 = 1 con c > 1 e k ≥ 1. e t3 = 1/. . c c c c R1 : Infine R1 = 100A2 + R2/. . . Sia A1 . Per l’ottimalit` A4 < 3 (altrimenti potrei sostituire c a tre monete da 20/ con una da 50/ ed una da 10/) ed inoltre se A4 = 2 allora c c c A5 = 0 (altrimenti potrei sostituire due monete da 20/ ed una da 10/ con una c c da 50/). Ak e dunque l’algoritmo calcola ` una soluzione ottima. Ak una soluzione ottima per un certo resto R. ` Mostrare che tali operazioni hanno costo ammortizzato costante. . . k i=1 Quindi R = Sia Rj = Ai ti con n = k i=1 Ai minimo. Per ogni j < k abbiamo Rj = ck−j−1Aj+1 + Rj+1 .R2 : R2 = 50A3 + R3 /. L’operazione i-esima costa i quando i e una potenza di 2 mentre ha costo 1 negli altri casi. Con R = 10/ l’algoritmo goloso c c c c sceglie una moneta da 7/ e tre da 1/. Per l’ottimalit` A3 < 2 (altrimenti potrei sostituire due c a monete da 50/ con una da 1e). Quindi R5 ≤ 5/ + R6 < 5/ + t6 = t5 .

Suggerimento: a memorizzare la posizione m del bit 0 successivo al bit 1 pi` significativo (ossia m e il u ` minimo tale che tutti i bit in A[m . . Il costo effettivo di una operazione R ESET e m + 1. oltre all’operazione I NCREMENT. anche una operazione R ESET che azzera il contatore. Fare in modo che la complessit` ammortizzata delle operazioni risulti costante.Soluzione. ` Tra questi vi e un certo numero t ≥ 0 di 1 trasformati in 0 e al pi` un solo 0 trasformato ` u in 1. k] sono 0). R ESET (A) / Tutti i bit in A[m. / 1 for i = 0 to m − 1 2 A[i] = 0 3 m=0 I NCREMENT (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 I NCREMENT e t+1 pari al numero di bit modificati. Il costo dell’i-esima operazione e: ` ci = 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 log2 n + 1 e quindi il costo totale della ` sequenza di n operazioni e: ` n log2 n C(n) = i=1 ci = n − log2 n − 1 + log2 n +1 2j j=0 = n − log2 n + 2 ≤ 3n −2 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] sono 0. una unit` di costo la attribuiamo come credito prepagato a tale bit 1 a 40 . ` Attribuiamo un costo ammortizzato 3 all’operazione I NCREMENT ed un costo ammortizzato 1 a R ESET. Quando eseguiamo una I NCREMENT usiamo una unit` di costo per pagare l’eventuale a bit 0 trasformato in 1. k] che prevede. Soluzione.

dopo l’operazione se ne ricaricano alcuni in memoria. Se una operazione P OP toglie l’ultimo elemento in memoria e ci sono degli elementi registrati su disco. Quando eseguiamo una R ESET usiamo gli m crediti attribuiti alla variabile m per pagare l’azzeramento dei primi m bit del registro. Quando eseguiamo una I NCREMENT paghiamo la trasformazione dei t bit 1 in 0 usando i crediti prepagati attribuiti a tali bit. Se la memoria e piena quando si esegue u ` una P USH. Quindi a ogni bit 1 ha sempre un suo credito prepagato e la variabile m ha sempre esattamente m crediti prepagati. Una scelta che funzione e scaricare e ricaricare dalla memoria secondaria ` blocchi di M/2 elementi. Soluzione. Alternativamente possiamo usare la funzione potenziale k Φ=m+ i=0 A[i] Esercizio 50 Realizzare una pila P con operazioni di costo ammortizzato costante avendo a disposizione memoria per al pi` M elementi. prima di eseguire l’operazione vengono scaricati su disco alcuni 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) = Se b = tizzato: FALSE n se b = FALSE |n − M/2| + M/2 se b = TRUE una P USH senza scaricamenti in memoria secondaria ha costo ammorc = 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 P OP ha costo ammortizzato c = c + Φ(n − 1) − Φ(n) ˆ = 1 + (n − 1) − (n) = 0 Se b = tizzato: TRUE una P USH senza scaricamenti in memoria secondaria ha costo ammor- c = c + Φ(n + 1) − Φ(n) ˆ = 1 + (|n + 1 − M/2| + M/2) − (|n − M/2| + M/2) ≤ 2 41 .e l’altra unit` di costo la attribuiamo alla variabile m se essa viene incrementata.

42 . E NQUEUE (Q. Infine il costo ammortizzato di D EQUEUE con travaso e c = c + ∆Φ = ` ˆ 1 + |P1 | − |P1 | = 1. quello di una D EQUEUE senza travaso e 1 ` ` mentre quello di una D EQUEUE con travaso e 1 + |P1 |. x) D EQUEUE (Q. ` Usiamo la funzione potenziale Φ = |P1 |. x) 1 P USH (P1 . x) 5 return P OP (P2 ) Il costo effettivo di una E NQUEUE e 1.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 P OP 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 Infine 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 P1 e P2 in modo che le operazioni E NQUEUE (Q. Soluzione. x) 1 if E MPTY (P2 ) 2 while not E MPTY (P1 ) 3 x = P OP (P1 ) 4 P USH (P2 . Il costo ammortizzato di D EQUEUE senza travaso e c = c + ∆Φ = `ˆ 1 + 0 = 1. E NQUEUE ha costo ammortizzato c = c + ˆ ∆Φ = 1 + 1 = 2. x) e D EQUEUE (Q) richiedano tempo ammortizzato costante.

) ˆ Se α < 1/2 non vi e sicuramente espansione e il costo ammortizzato di una I NSERT e: ` ` ci = ˆ = = = ci + Φi − Φi−1 1 + (sizei − 2 numi ) − (sizei−1 − 2 numi−1 ) 1 + sizei−1 − 2(numi−1 + 1) − sizei−1 + 2 numi−1 ) −1 Se α ≤ 1/2 il costo ammortizzato di una D ELETE senza contrazione e: ` ci = ˆ = = = ci + Φi − Φi−1 1 + (sizei − 2 numi ) − (sizei−1 − 2 numi−1 ) 1 + sizei−1 − 2(numi−1 − 1) − sizei−1 + 2 numi−1 ) 3 mentre con contrazione e: ` ci = ˆ = = = ci + Φi − Φi−1 1 + numi−1 + (sizei − 2 numi ) − (sizei−1 − 2 numi−1 ) 1 + numi−1 + 2 numi−1 − 2(numi−1 − 1) − 3 numi−1 + 2 numi−1 ) 3 43 . Il costo effettivo di una I NSERT senza espansione e di una D ELETE senza contrazione e 1. ` Se α ≥ 1/2 il costo ammortizzato di una I NSERT senza espansione e ` ci = ˆ = = = ci + Φi − Φi−1 1 + (2 numi − sizei ) − (2 numi−1 − sizei−1 ) 1 + 2(numi−1 + 1) − sizei−1 − 2 numi−1 + sizei−1 ) 3 mentre con espansione e: ` ci = ˆ = = = ci + Φi − Φi−1 1 + numi−1 + (2 numi − sizei ) − (2 numi−1 − sizei−1 ) 1 + numi−1 + 2(numi−1 + 1) − 2 numi−1 − 2 numi−1 + numi−1 ) 3 (Per i = 1 abbiamo sizei = numi−1 + 1 invece di sizei = 2 numi−1 . quello di una I NSERT con espansione e quello di una D ELETE con contrazione ` e 1 + num.Esercizio 52 Assumere che la contrazione della tavola dinamica venga effettuata quando 1 1 α = 3 invece che quando α = 4 e che invece di ridurre la sua dimensione ad 1 size essa 2 2 venga ridotta a 3 size. Calcolare il costo ammortizzato delle operazioni usando la funzione potenziale: Φ = |2 · num − size| Soluzione. In questo caso ci = 2.

root 2 if x == NIL 3 return NIL 4 while not x.c1 6 D ISK R EAD(x) 7 return x. ` a Le foglie dell’albero ottenuto saranno tutte alla stessa altezza: l’altezza nera che avevano nell’albero rosso nero. I nodi di un B-albero con grado minimo t = 1 contengono 0 o 1 chiavi ed hanno 1 o 2 figli. ossia un 2-3-4-albero. Siccome. ossia la minima chiave k > k presente nel B-albero. ` ` la radice contiene almeno una chiave essa e sempre piena.leaf 5 x = x. 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. Soluzione. 2 o 3 chiavi e. Otteniamo quindi un B-albero con grado minimo t = 2. Siccome ogni nodo rosso ha padre nero tutti i nodi rossi vengono assorbiti dai rispettivi padri. Soluzione. Un nodo contenente 1 chiave e pieno. Ogni nodo nero assorbe 0. Esercizio 54 Calcolare il numero massimo di chiavi che pu` contenere un B-albero in o funzione del suo grado minimo t e della sua altezza h. Il numero massimo M di chiavi si ha quando tutti i nodi sono pieni: h M = (2t − 1) i=0 (2t)i = (2t − 1) (2t)h+1 − 1 = (2t)h+1 − 1 2t − 1 Esercizio 55 Dire quale struttura dati si ottiene se in ogni nodo nero di un albero rossonero conglobiamo i suoi eventuali figli rossi. 3 o 4 figli. Soluzione. Di conseguenza ogni I NSERT ` spezza la radice in due nodi vuoti ed aggiunge una nuova radice. se a non e foglia avr` 2. 1 o 2 nodi rossi e quindi alla fine avr` 1. Dopo n inserimenti l’albero ha altezza h = n.key 1 44 .Se α > 1/2 non vi e sicuramente contrazione e il costo ammortizzato di una D ELETE e: ` ` ci = ˆ = = = ci + Φi − Φi−1 1 + (2 numi − sizei ) − (2 numi−1 − sizei−1 ) 1 + 2(numi−1 − 1) − sizei−1 − 2 numi−1 + sizei−1 ) −1 Esercizio 53 Perch´ in un B-albero non possiamo avere grado minimo t = 1? e Soluzione. se l’albero non e vuoto. La chiave minima e la prima chiave della prima foglia del B-albero: ` M INIMO (T ) 1 x = T.

.x. Le eventuali operazioni S PLIT C HILD vengono eseguite quando un nodo contiene 3 chiavi.ci . . k) 18 / ks e la minima chiave maggiore di k nel sottoalbero di radice x..key m ≤ k 7 i = m+1 8 else j = m 9 / x.ci ) 17 ks = S UCCESSIVO R IC (x.key 1.key i. n / 10 if i ≤ x. A quello dei due figli che non si trova sul ramo di destra non verranno mai aggiunte altre chiavi.i−1 ≤ k < x. ` Tali sottoalberi hanno 2i − 1 nodi e altrettante chiavi. 2. .. / ` 22 return ks Esercizio 57 In un B-albero con grado minimo t = 2 vengono inserite le chiavi 1.key 1.La chiave successiva alla chiave k si trova con una versione modificata della funzione S EARCH che usa un diverso invariante per la ricerca binaria. Valutare il numero m di nodi del B-albero risultante in funzione di n. S UCCESSIVO R IC (x. / ` 14 ks = NIL 15 if not x. k) 1 i=1 2 j = x. .key j.i−1 ≤ k < x. n / 4 while i < j 5 m = (i + j)/2 6 if x.ci .x. Indichiamo con ci il numero di chiavi contenute nel nodo di altezza i sull’ultimo ramo.n 11 ks = x. / ` 19 if ks = NIL 20 ks = ks 21 / ks e la minima chiave maggiore di k nel sottoalbero di radice x.leaf 16 D ISK R EAD (x. 2 o 3 chiavi. Pertanto i nodi che non stanno sull’ultimo ramo hanno sempre una sola chiave mentre i nodi sull’ultimo ramo possono avere 1. n nell’ordine. In tal caso una chiave viene spostata nel padre (che si trova sempre sul ramo di destra) e vengono creati du nodi figli con una sola chiave. L’inserzione avviene sempre nell’ultima foglia a destra e quindi si percorre sempre l’ultimo ramo a destra del B-albero.key i 12 else ks = NIL 13 / ks e la minima chiave maggiore di k nel nodo x.n + 1 3 / INVARIANTE: x. Il numero totale di chiavi e quindi: h h n= i=0 [ci + ci (2i − 1)] = 45 ci 2 i i=0 . . Soluzione.. Ad ognuna delle ci chiavi ad altezza i contenute nel nodo di altezza i dell’ultimo ramo rimane associato un sottoalbero di altezza i − 1 costituito da nodi che non appartengono all’ultimo ramo.

size i di 1. i. Siccome ` f (179) = 7163. Siccome la derivata e negativa per t < t0 e positiva per t > t0 il punto t0 e un punto di minimo.n + y.size i che x. Basta calcolare il minimo della funzione: f (t) = (a + bt) logt n = ln n la cui derivata e: ` f (t) = ln n a + bt ln t bt(ln t − 1) − a t ln2 t che si annulla per t(ln t − 1) = a/b.size j se y e foglia ` altrimenti I NSERT percorre un cammino dalla radice fino alla foglia in cui si deve inserire la nuova chiave. n+1 j=1 y.size i = y.ci ) dobbiamo ricalcolare sia x.ci aumentiamo x.ci .size i+1 usando la formula precedente.ci figlio di x.size i relativi ai figli che vengono modificati. Soluzione. Quando eseguiamo una S PLIT C HILD (x. ` Esercizio 59 Mostrare come sia possibile aggiungere ad ogni nodo interno x di un Balbero i campi x.ci ) dobbiamo usare la formula precedente per ricalcolare i campi x.size i che contengono il numero di chiavi presenti nei sottoalbero di radici x.mentre il numero totale di nodi e: ` h h h m= i=0 (1 + ci (2 − 1)) = i i=0 ci 2 − i i=0 (ci − 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.54 180(ln 180 − 1) = 754.53 ln n f (180) = 7163. Vale la seguente formula: x.n y. Prima di scendere da un nodo x al figlio x.ci diminuiamo x. Quando eseguiamo una AUGMENT C HILD (x.73 ` la derivata si annulla in un punto t0 compreso tra 179 e 180. Per t intero il punto di ` minimo e o 179 o 180. Sia x un nodo interno e sia y = x. Nel nostro caso a/b = 30000/40 = 750.55 ln n il punto di minimo e 179.size i di 1. Poich´ e 179(ln 179 − 1) = 749. Assicurarsi che la complessit` asintotica non aumenti. Suggerire un valore ottimo di t nel caso a = 30ms e b = 40µs. Prima di scendere da un nodo x al figlio x. x. a Soluzione. D ELETE percorre un cammino dalla radice fino alla foglia da cui viene rimossa o la chiave data o la successiva. x. i. 46 . Dire quali sono le modifiche da apportare a I NSERT e D ELETE.

Assumiamo che i B-alberi siano aumentati con il campo height come visto nel precedente esercizio. ` Soluzione. / ` 2 if r1 . quando si crea una nuova radice si assegna ad essa l’altezza della vecchia radice aumentata di 1. In particolare: 1. r2 ) 47 . k. quando si divide un nodo in due parti si assegna al nuovo nodo l’altezza del nodo iniziale. r2 radici di T1 e T2 . r2 ) 1 / r1 . J OIN (r1 .height 3 return J OIN H1E Q H2(r1 .height == r2 . 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. Dire quali sono le modifiche da apportare a I NSERT e D ELETE. r2 ) 6 else return J OIN H1LT H2(r1 . alla fine r e la radice di T . r2 ) 4 elseif r1 . Scrivere un algoritmo che riunisce T1 .Esercizio 60 Mostrare come sia possibile aggiungere ad ogni nodo x di un B-albero un campo height che contiene l’altezza del sottoalbero di radice x.height 5 return J OIN H1G T H2(r1 . a Soluzione. 3. quando si inserisce il primo nodo in un albero vuoto gli viene assegnata altezza 0. Se h1 ed h2 sono le altezze rispettive di T1 e T2 l’algoritmo deve avere complessit` O(1 + |h1 − h2 |). k. k.height > r2 . Assicurarsi che la complessit` asintotica non a aumenti. k. T2 e k in un’unico B-albero T (operazione J OIN (T1 . T2 )). E sufficiente quino di assegnare l’altezza corretta ai nodi quando essi vengono creati da una I NSERT. k. L’altezza di un nodo di un B-albero non pu` cambiare. 2.

D ISK W RITE (r) 5 else if r1 . 9 “Aggiungi alla fine di r1 la chiave k.” 19 k = r1 .key r1 .” 13 elseif r2 .” 4 r = r1 .” 22 / Sia r1 che r2 hanno almeno t − 1 chiavi.” 11 k = r2 .n 7 / Ad r1 mancano i chiavi ed r2 ne ha almeno i pi` / u 8 del necessario.n + r2 .n < t − 1 14 i = t − 1 − r2 . / 23 “Crea un nuovo nodo r con la sola chiave k e figli r1 ed r2 . D ISK W RITE (r2 ) 25 return r 48 . D ISK W RITE (r1 ). le chiavi di r2 e 3 i puntatori ai discendenti di r2 .” 24 D ISK W RITE (r).J OIN H1E Q H2(r1 .n + 1 ≤ 2t − 1 2 “Aggiungi alla fine di r1 la chiave k. k. r2 ) 1 if r1 .n < t − 1 6 i = t − 1 − r1 . le prime i − 1 10 chiavi di r2 e i primi i puntatori di r2 .key i 12 “Togli da r2 le prime i chiavi ed i primi i puntatori.n 15 / Ad r2 mancano i chiavi ed r1 ne ha almeno i pi` / u 16 del necessario. n−i+1 20 “Togli da r1 le ultime i chiavi e gli ultimi i 21 puntatori. 17 “Aggiungi all’inizio di r2 la chiave k. le ultime i − 1 18 chiavi di r1 e gli ultimi i puntatori di r1 .

n+1 . le chiavi di r2 e 13 i puntatori ai discendenti di r2 .cx. 19 “Aggiungi all’inizio di r2 la chiave k. y) 9 y = x.n 17 / Ad r2 mancano i chiavi ed y ne ha almeno i pi` / u 18 del necessario.n + 1. D ISK R EAD (y) 6 while y. y = x.” 21 k = y.n < t − 1 16 i = t − 1 − r2 . n+1 . r1 ) 5 x = r.n < 2t − 1 2 r = r1 3 else “Crea un nodo r vuoto e con unico figlio r1 . k. r2 ) 1 if r1 .key y.J OIN H1G T H2(r1 . n−i+1 22 “Togli da y le ultime i chiavi e gli ultimi i puntatori. le ultime i − 1 20 chiavi di y e gli ultimi i puntatori di y.cx. n+1 10 x = y. 26 return r 49 .” 4 S PLIT C HILD (r.n == 2t − 1 8 S PLIT C HILD (x.” 23 / Sia y che r2 hanno almeno t − 1 chiavi. 1. / 24 “Aggiungi alla fine di x la chiave k ed un puntatore ad r2 . D ISK R EAD (y) 11 if y. x. D ISK W RITE (r2 ). y = x.cx.n + 1 ≤ 2t − 1 12 “Aggiungi alla fine di y la chiave k.” 25 D ISK W RITE (x).height > r2 . D ISK W RITE (y).height 7 if y.n + r2 .” 14 D ISK W RITE (y) 15 else if r2 .

” 21 k = y. r2 ) 5 x = r.height 7 if y. Se h1 = h2 l’algoritmo richiede un tempo proporzionale a t.” 14 D ISK W RITE (y) 15 else if r1 . 1. y) 9 y = x. / 24 “Aggiungi all’inizio di x la chiave k ed un puntatore ad r1 . D ISK W RITE (y).n + 1 ≤ 2t − 1 12 “Aggiungi all’inizio di y le chiavi di r1 e la chiave k e 13 i puntatori di r1 . y = x. ` a Se h1 > h2 l’algoritmo richiede un tempo proporzionale a t pi` un tempo proporziou nale ad (h1 − h2 )t per il ciclo while.n + y.key i 22 “Togli da y le prime i chiavi e i primi i puntatori.” 4 S PLIT C HILD (r. 26 return r Complessit` .c1 .n < 2t − 1 2 r = r2 3 else “Crea un nodo r vuoto e con unico figlio r2 . le prime i − 1 20 chiavi di y e i primi i puntatori di y. k.n 17 / Ad r1 mancano i chiavi ed y ne ha almeno i pi` / u 18 del necessario. y = x. 1.” 25 D ISK W RITE (x). D ISK R EAD (y) 10 11 if r1 .height > r1 . D ISK W RITE (r1 ).n = 2t − 1 8 S PLIT C HILD (x. 19 “Aggiungi alla fine di r1 la chiave k. Siccome t e una costante l’algoritmo ha complessit` ` a O(1 + |h1 − h2 |). r2 ) 1 if r2 .” 23 / Sia r1 che y hanno almeno t − 1 chiavi. Il caso h1 < h2 e simmetrico.J OIN H1LT H2(r1 . D ISK R EAD (y) 6 while y.c1 x = y. ` 50 .c1 .n < t − 1 16 i = t − 1 − r1 . Siccome a t e una costante l’algoritmo ha complessit` O(1) = O(1 + |h1 − h2 |).

ci+1 . Trovare un algoritmo che divide T in un B-albero T1 contenente tutte le chiavi di T minori di k.key i .key r.key i+1 . x2 = S PLIT (r..ci+2 . . r. .key i−1 ed i puntatori r.ci . . y2 ) 33 return r1 . n+1 . / ` 22 else “Crea un nuovo nodo y1 . . . e la chiave k (operazione S PLIT). n ed i puntatori r. L’algoritmo deve avere complessit` O(h) in cui h e l’altezza di T . . . / ` 13 else “Crea un nuovo nodo r2 . r. k) 19 / Costruisci T1 / 20 if i == 0 21 r1 = x1 / r. / ` 7 else “Crea un nuovo nodo r1 . . n+1 .” 14 “Copia da r in r2 le chiavi r.i ≤ k < r.” 15 D ISK W RITE (r2 ) 16 else / La chiave k si trove nel sottoalbero r. . Per dimostrare a a che l’algoritmo richiede tempo O(h) = O(log n) calcoliamo separatamente il tempo T1 (h) 51 .ci+1 e il primo figlio. .key i == k 3 / La chiave k si trove nella radice.ci . . .” 9 D ISK W RITE (r1 ) 10 / Costruisci T2 / 11 if i == r.ci+1 . .” 23 “Copia da r in y1 le chiavi r. k) / r radice di T . / 4 / Costruisci T1 / 5 if i == 1 6 r1 = r. r2 Complessit` . / 17 D ISK R EAD (r. . / ` 29 else “Crea un nuovo nodo y2 . r. . . . . . r.ci+1 ) 18 x1 . . .cr. . n ed i puntatori r.n 28 r2 = x2 / r. r. r.Esercizio 62 Siano dati un B-albero T ed una chiave k di T . r. a ` Soluzione.cr. Ritorna le radici r1 e r2 di T1 e T2 . . .ci+1 e l’ultimo figlio.key 1 .” 30 “Copia da r in y2 le chiavi r. / S PLIT (r.key r. x1 ) 26 / Costruisci T2 / 27 if i == r. un B-albero T2 contenente tutte le chiavi di T maggiori di k.” 8 “Copia da r in r1 le chiavi r. Calcoliamo la complessit` in funzione di h = r. r.cr.” 31 D ISK W RITE (y2 ) 32 r2 = J OIN (x2 .” 2 if i > 0 and r.c1 . n+1 / La chiave k e l’ultima. . . .key 1 .height. 1 “Con la ricerca binaria trova l’indice i tale che r.key 1.key i−1 ed i puntatori r.c1 . . r. .key i+1 .key i+2 .key i+1 . .n 12 r2 = r.” 24 D ISK W RITE (y1 ) 25 r1 = J OIN (y1 . r. .ci+1 . .c1 / La chiave k e la prima.

k) la sua ` altezza h1 = x1 . 8. em le m operazioni E XTRACT M IN nell’ordine in cui compaiono in S. 6. Una possibile u sequenza con n = 9 ed m = 6 e: ` S = {4. r. . mettendo in Ii quelli inseriti tra ei−1 ed ei . . 0. Si chiede un algoritmo efficiente che data la sequenza di operazioni S = {o1 . 3. 0. 6. 8. . 0. 2. . Simmetricamente si ` dimostra che il tempo totale T2 (h) per le J OIN necessarie a costruire r2 e O(h). . Esercizio 63 Sia T = {1.richiesto per eseguire le chiamate alla funzione J OIN necessarie a costruire r1 . o2 . 3. 9. La versione astratta dell’algoritmo e: ` 52 . Raggruppiamo gli interi 1. o2 . con la sequenza S = {4. . 5} il risultato dovrebbe essere X = {4. 2. Siccome alcuni degli insiemi possono essere vuoti aggiungiamo a ciascun insieme Ii un elemento fittizio di valore n + i. 7. Siccome x1 e la radice del primo sottoalbero ritornato da S PLIT (r. . Indichiamo con e1 .ci+1 . Inoltre.height = h − 1. oi−1 } deve contenere pi` I NSERT che E XTRACT M IN. .key i . . . 0. . . 0. x2 . 7. . 0. x1 ) pi` il tempo per eseguire le J OIN u necessarie a costruire x1 . Dunque il tempo totale T1 (h) per le J OIN necessarie a costruire r1 e dato dalla rela` zione di ricorrenza T1 (h) = O(h − h1 ) + T1 (h1 ) ed e quindi O(h).key i . . 2.height|) = O(h − h1 ) dato che y1 . . . o2 . Ad esempio. . 1. . . . Im+1 mettendo in I1 gli interi inseriti prima della prima E XTRACT M IN e1 . . n in m + 1 insiemi disgiunti I1 . Le operazioni possono essere in un ordine qualsiasi con il solo vincolo che se oi e una E XTRACT M IN la ` sequenza {o1 . . il tempo T2 (h) richiesto per eseguire le chiamate alla funzione J OIN necessarie a costruire r2 ed il tempo T3 (h) richiesto per eseguire tutte le altre operazioni. . 0.height − x1 . . r. 1} Soluzione. 5} in cui le operazioni I NSERT sono indicate con il numero x ∈ T che viene inserito mentre le operazioni E XTRACT M IN sono indicate con uno 0. x1 ) e ` O(1 + |y1 . . nel rappresentante di ciascun insieme memorizziamo l’indice dell’insieme stesso. 0. ` Il tempo richiesto nel caso pessimo per eseguire le J OIN necessarie a costruire r1 e ` il tempo necessario ad eseguire la J OIN (y1 . on+m } calcoli la sequenza X = {x1 . on+m} una sequenza di operazioni contenente n operazioni I NSERT che inseriscono ogni intero x ∈ T una e una sola volta ed m operazioni E XTRACT M IN che estraggono m degli interi inseriti. 6. . 2. per 2 ≤ i ≤ m. . n} ed S = {o1 . 2. . . 3. .height e sicuramente minore di h. ` Il tempo necessario ad eseguire J OIN (y1 . Il tempo richiesto nel caso pessimo per eseguire tutte le altre operazioni e dato dalla ` relazione di ricorrenza T3 (h) = t + T3 (h − 1) e quindi T3 (h) = O(ht) che e O(h) dato ` che t e una costante. 0. . ` Quindi l’intero algoritmo richiede tempo O(h). 9. in Im+1 quelli inseriti dopo l’ultima E XTRACT M IN em e. 0. 1. xm } degli m interi estratti. 0. 0. . 8.

p e indice di m+n+1 elementi ciascuno. Dettagli implementativi: Gli interi da raggruppare (quelli inseriti e quelli fittizzi) sono gli m + n + 1 interi 1. n + m + 1. . Correttezza dell’algoritmo: Aggiungiamo all’algoritmo le asserzioni per la prova di correttezza. . m+n+1. 2. . .” 5 if j ≤ m 6 Xj = i 7 “Trova il primo h > j con Xh non ancora calcolato. . . p e indice relativi a tale intero x. Per ogni intero 1.M INIMO O FF L INE (S. n + 1. . n + 1. rank [x]. I2 . Xm sono gli interi estratti. . . . I2 . . . 2 “Raggruppa gli interi inseriti e quelli fittizi negli insiemi I1 . n. . . n + m + 1 dobbiamo quindi prevedere i campi rank e p ed un campo indice in cui memorizzare l’indice dell’insieme di appartenenza (aggiungiamo tale campo a tutti ma ci preoccuperemo di mantenere aggiornato soltanto quello del rappresentante). .” 8 Ih = Ij ∪ Ih / 9 / X1 . X. . . . Per ogni intero x = 1. Im+1 ” 3 for i = 1 to n 4 “Trova l’indice j dell’insieme Ij che contiene i. . . . Im+1 useremo le foreste di insiemi disgiunti. . . Come struttura dati di supporto per memorizzare gli interi da raggruppare usiamo tre array rank . 53 . m. 2. . . n. Per ragrupparli negli insiemi disgiunti I1 . n) 1 / S e l’array di interi che rappresenta la sequenza di n I NSERT (denotate / ` dall’intero x inserito) e di m E XTRACT M IN (denotate con uno 0). . . . . . . p[x] e indice[x] sono i valori dei campi rank . .

o c) ek estrae un intero y ≥ i. Si chiede di calcolare la lista W = {w1 . . . . . . 5 “Trova l’indice j dell’insieme Ij che contiene i. se j = m + 1 allora i non pu` essere estratto mentre se o j ≤ m allora i e stato inserito prima di ej ed e il pi` piccolo ` ` u inserito prima di ej che possa essere estratto da ej . v2 ). Im+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 ∈ Ik allora: a) x e stato inserito prima di ek . . I2 . Se i ≤ x ≤ n e x ∈ Ik allora: a) x e stato inserito prima di ek . n) 1 / S e l’array di interi che rappresenta la sequenza di n I NSERT (denotate / ` dall’intero x inserito) e di m E XTRACT M IN (denotate con uno 0). v1 ). Quindi ej estrae i. . ` b) x non pu` essere estratto prima di ek . . . d) i ∈ Ij e j ≤ m. Nel problema dei minimi comuni a ` antenati off-line sono dati un albero T con n nodi ed una lista L = {(u1 . o c) ek estrae un intero y ≥ i.” 6 / Ogni x < i estratto e stato memorizzato al posto giusto in X. / 13 / X1 . (u2 . ` b) x non pu` essere estratto prima di ek . / ` Se i ≤ x ≤ n e x ∈ Ik allora: a) x e stato inserito prima di ek . . vm )} di m coppie di nodi. w2 . . d) i ∈ Ij . Xm sono gli interi estratti. 10 “Trova il primo h > j con Xh non ancora calcolato. ` b) x non pu` essere estratto prima di ek . X.M INIMO O FF L INE (S. . . m. / ` Se i + 1 ≤ x ≤ n e x ∈ Ik allora: a) x e stato inserito prima di ek . 2 “Raggruppa gli interi inseriti e quelli fittizi negli insiemi I1 . (um. Esercizio 64 Il minimo antenato comune di due nodi u e v in un albero T e il nodo w di ` massima profondit` che e antenato sia di u che di v.” 11 Ih = Ij ∪ Ih 12 / Ogni x < i + 1 estratto e stato memorizzato al posto giusto in X. ` b) x non pu` essere estratto prima di ek . Inoltre. 7 if j ≤ m 8 Xj = i 9 / Ogni x < i + 1 estratto e stato memorizzato al posto / ` giusto in X. wm } 54 . . o c) ek estrae un intero y ≥ i + 1. . o c) ek estrae un intero y ≥ i.

color == BLACK 12 wi = F IND S ET (vi ). M IN C OM A NT (u) 1 M AKE S ET (u) 2 u. dovuto a Robert E. .p puntatore al padre in una foresta di insiemi disgiunti (non al padre nell’albero T ). xk = u il cammino dalla radice r dell’albero T al nodo u su cui viene richiamata la funzione M IN C OM A NT. .sibling puntatore al fratello. vi ) ∈ L con ui == u” 11 if vi . Assumiamo che ogni nodo dell’albero abbia i seguenti campi: . . .color che all’inizio e white per tutti i nodi e alla fine e black. Correttezza. .ancestor puntatore ad un suo antenato nell’albero. v) 7 F IND S ET (u).color = BLACK 10 for “ogni (ui .ancestor = u 3 v = u. xk−1 di u: 55 .child 4 while v = NIL 5 M IN C OM A NT (v) 6 U NION (u.child puntatore al primo figlio.sibling 9 u. L’algoritmo di Tarjan e descritto dalla seguente funzione ricorsiva che deve essere ` chiamata sulla radice dell’albero. . ` ` ed inoltre i seguenti campi necessari per raggruppare i nodi dell’albero in foreste di insiemi disgiunti: . .ancestor = u 8 v = v. Tarjan.ancestor dimostrare che esso e corretto e valutarne la complessit` . . . Sia Inv(u) la seguente asserzione sugli antenati x0 . . Consideriamo la generica chiamata M IN C OM A NT (u) e sia r = x0 . .dei minimi comuni antenati di ogni coppia. . Esiste un algoritmo. che risolve il problema in tempo (quasi) lineare con una sola percorrenza dell’albero.rank rango del nodo nella foresta di insiemi disgiunti. ` a Soluzione.

color = BLACK e stato assegnato correttamente a wi il puntatore al minimo comune antenato ` k−1 di ui e vi . vi ∈ ∪j=1 Sj e che ui . Per ogni coppia (ui .color = WHITE .“Per ogni j = 0. Inoltre z.color = BLACK e quindi per ogni coppia in L e stato assegnato correttamente a wi il ` puntatore al minimo comune antenato di ui e vi . Per fare questa dimostrazione possiamo assumere induttivamente che le chiamate ricorsive soddisfino la medesima condizione.color = BLACK e stato ` j=1 assegnato correttamente a wi il puntatore al minimo comune antenato di ui e k vi . . Dobbiamo dimostrare che se sono vere Pre(u) e Inv(u) quando viene effettuata una generica chiamata M IN C OM A NT (u) allora sono vere Post(u) e Inv(u) quando tale chiamata termina.color = WHITE per ogni z ∈ ∪j=1 Sj . vi ∈ ∪k Sj e ui . vi ) in L tale che ui . . vi ) in L tale che ui . Se quando termina la chiamata principale M IN C OM A NT (r) risultano vere sia Post(r) che Inv(r) allora ogni nodo z ha z.color = vi .color = WHITE per ogni z ∈ ∪j=1 Sj .” Sia Pre(u) la seguente asserzione: k−1 “Per ogni coppia (ui . Inoltre z.color = BLACK per tutti i nodi z ∈ Sj escluso il nodo xj per il quale xj .” Quando viene effettuata la chiamata principale M IN C OM A NT (r) sulla radice dell’albero sono banalmente vere sia Pre(r) che Inv(r).” e Post(u) l’asserzione: “Il nodo xk = u e tutti i suoi discendenti costituiscono un insieme disgiunto Sk il cui rappresentante ha il puntatore ancestor che punta ad xk e z. Il seguente e la funzione MinComAnt annotata con le asserzioni che servono per tale ` verifica. 56 . k − 1 il nodo xj e tutti i nodi che stanno nei sottoalberi radicati nei figli di xj che precedono il figlio xj+1 costituiscono un insieme disgiunto Sj il cui rappresentante ha il puntatore ancestor che punta ad xj .color = BLACK per tutti i nodi z ∈ Sk compreso il nodo xk = u. .color = vi . . Inoltre z.

/ 9 U NION (u.ancestor = u 11 v = v. Possiamo usare una tale struttura dati per ordinare un array nel seguente modo: 57 . Esercizio 65 Dimostrare che non esiste nessuna struttura dati S che permetta di eseguire in tempo costante tutte e tre le operazioni M AKE (S).color = BLACK 14 for “ogni (ui . Inoltre z. / 7 M IN C OM A NT (v) 8 / Post(v) e Inv(v).child 5 while v = NIL 6 / Pre(v) e Inv(v). Pertanto l’intero algoritmo richiede tempo O(m + n). u Data la lista L possiamo costruire in tempo O(m) una lista Lu per ogni vertice u che contiene tutti i vertici vi tali che (ui .color = BLACK e stato assegnato correttamente a wi il puntatore al minimo comune antenato di ui e vi . Siccome la somma delle u lunghezze delle liste Lu e 2m l’esecuzione di tutti i cicli for di tutte le chiamate richiede ` tempo O(m).M IN C OM A NT (u) 1 / Pre(u) e Inv(u).sibling 12 / Inv(v). vi ) ∈ L con ui = u e tutti gli ui tali che (ui . La funzione M IN C OM A NT viene richiamata una e una sola volta su ogni vertice u dell’albero. vi ∈ ∪k Sj e j=1 ui .ancestor 17 / Post(u) e Inv(u).ancestor = u 4 v = u. Sia n il numero di nodi di T ed m il numero di coppie in L.color = WHITE . Il ciclo for esplora ciascuna lista Lu al pi` una sola volta. vi ) in L tale che ui . I NSERT (S.color = WHITE per ogni z ∈ ∪k Sj . Quindi il numero totale di chiamate e n e dunque le istruzioni interne al ciclo while ` vengono eseguite al pi` n volte. prima di terminare li colora BLACK e alla fine di tutto l’algoritmo i nodi sono tutti di colore BLACK. v) 10 F IND S ET (u). x) ed E XTRACT M IN (S) (sia caso pessimo che ammortizzato). vi ) ∈ L con vi = u.color = BLACK 16 wi = F IND S ET (vi ).color = vi . Infatti essa viene richiamata soltanto su vertici di colore WHITE. vi ) ∈ L con ui == u” 15 if vi . Soluzione. / Complessit` . Possiamo assumere che le operazioni sugli insiemi disgiunti richiedano a tempo costante.color = BLACK per tutti i nodi z ∈ Sk escluso il nodo xk = u per cui u. / 2 M AKE S ET (u) 3 u. Per ogni coppia (ui . / Il nodo xk = u e i suoi discendenti costituiscono un insieme disgiunto Sk il cui rappresentante ha il puntatore ancestor che punta ad xk e z. j=1 13 u.

58 . Esercizio 66 Sono dati un insieme p1 . . pn di n punti ed un insieme di m connessioni dirette c1 = (x1 . Soluzione. p2 . Assicurarsi che la complessit` rimanga O(log n). ym ).S ORT (A. . I NSERT (S. c2 = (x2 . n. . c. A[i]) 3 for i = 1 to n do A[i] = E XTRACT M IN (S) Se M AKE (S). q) 9 nsets = nsets − 1 10 return nsets = 1 Esercizio 67 Supponiamo che non esista una rappresentazione della chiave −∞. . . a Soluzione. . Impossibile perch´ O(n log n) e un limite e ` inferiore per l’ordinamento. x) per un mucchio binomiale H in modo che essa non usi la chiave −∞. n) 1 M AKE (S) 2 for i = 1 to n do I NSERT (S. Riscrivere D ELETE (H. Occorre effettuare le stesse ` operazioni senza cambiare chiave ad x. . cm = (xm . −∞) per far diventare il nodo x una radice e quindi la funzione E XTRACT M IN (H) per rimuovere la radice minima che a questo punto e proprio il nodo x. C ONNESSI (p. y2 ). y1). m) 1 for i = 1 to n 2 M AKE S ET (pi ) 3 nsets = n 4 for j = 1 to m 5 p = F IND S ET (xj ) 6 q = F IND S ET (yj ) 7 if p = q 8 U NION (p. Descrivere un algoritmo efficiente che utilizza una struttura dati per insiemi disgiunti per determinare se tutti i punti sono connessi tra loro. x) usa la funzione D ECREASE K EY (H. La funzione D ELETE (H. A[i]) ed E XTRACT M IN (S) richiedessero tempo costante l’algoritmo S ORT richiederebbe tempo O(n). .

y.parent / Ora x e una radice. H1 ) 16 return x Esercizio 68 Nella funzione E XTRACT M IN (H) abbiamo dovuto percorrere tutta la lista dei figli del nodo estratto per invertirne l’ordine.cima = y 15 U NION (H.cima 8 while z. Questo perch´ la lista delle radici e e ` ordinata per grado crescente mentre le liste dei figli sono ordinate per grado decrescente. Cosa succede se ordiniamo le due liste in modo concorde? Soluzione.key = y.cima = x.parent = NIL 14 y. NIL tutti i 59 . x.cima.key.sibling 13 y. / 10 H1 . La tolgo dalla lista delle radici.child . Dobbiamo comunque percorrere la lista dei figli per porre a puntatori parent. y = x.cima 6 H.sibling = H1 . H1 .child = x.sibling = x do z = z. / ` 5 if x == H.child = NIL 12 y = x.sibling / Costruisco un heap H1 con i figli di x.sibling 9 z.sibling = x.cima = NIL 11 while x.parent 2 while y = NIL 3 k = x.D ELETE (H.key. x) 1 y = x. x.sibling 7 else z = H.key = k 4 x = y.