Sei sulla pagina 1di 6

Esercizi∗

January 29, 2011

1 Notazione
1.1
Usando la notazione di Θ(g(n)) mostrare che:
1. se f (n) = Θ(g(n)) allora anche g(n) = Θ(f (n))
2. max(f (n), g(n)) = Θ(f (n) + g(n))

3. (n + a)b = Θ(nb ) ∀a > 0, ∀b > 0

Soluzione

1. Se f (n) = Θ(g(n)) allora esistono c1 , c2 ed N tali per cui:

c1 g(n) ≤ f (n) f (n) ≤ c2 g(n) ∀n ≥ N

Dividendo la prima disuguaglianza per c1 e la seconda per c2 si ottiene


1 1
g(n) ≤ f (n) f (n) ≤ g(n) ∀n ≥ N
c1 c2

Sia c11 che c12 sono due costanti positive perciò seguendo la definizione di Θ
possiamo concludere che g(n) = Θ(f (n))

2. Visto che f (n) e g(n) sono entrambe positive1 , posso scrivere

max(f (n), g(n)) ≤ f (n) + g(n) ≤ 2max(f (n), g(n)) ∀n > N

Dalla scrittura precedente si può dedurre, quindi, che f (n)+g(n)) = Θ(max(f (n), g(n))).
Per quanto dimostrato al punto precedente, possiamo concludere che max(f (n), g(n)) =
Θ(f (n) + g(n))
∗ Esercizi estratti dal libro “Introduzione agli algoritmi e strutture dati 3/ed” – Thomas

H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein Copyright ©2010 – The
McGraw-Hill Companies srl
1 Non ha senso parlare di funzioni di complessità che non siano positive.

1
3. Scegliendo N > 2|a| la seguente disuguaglianza è sempre valida:

n/2 ≤ (n + a) ≤ 2n
Elevando tutti i membri della disuguaglianza alla potenza b, si possono verificare
due casi: se b < 1 e quindi la funzione è decrescente, posso scrivere:

(2n)b ≤ (n + a)b ≤ (n/2)b

2b nb ≤ (n + a)b ≤ (1/2b )nb


quindi avremo c1 = 2b e c2 = 1/2b . Se, invece, b ≥ 1 la funzione è crescente
posso scrivere:
(n/2)b ≤ (n + a)b ≤ (2n)b
(1/2b )nb ≤ (n + a)b ≤ 2b nb
quindi avremo c1 = 1/2b e c2 = 2b . In entrambi i casi possiamo concludere che
(n + a)b = Θ(nb ).

1.2
Confrontare le seguenti coppie di funzioni:
1. n! con en
2. en con n2n

3. n log2 n con log2 (n!)


√ log n
4. n con 2 2
Si ricorda che valgono le seguenti proprietà (sufficienti ma non necessarie):
f (n)
1. se limn→+∞ g(n) = ∞ allora f (n) = Ω(g(n)) e f (n) 6= Θ(g(n))
f (n)
2. se limn→+∞ g(n) = 0 allora f (n) = O(g(n)) e f (n) 6= Θ(g(n))
f (n)
3. se limn→+∞ g(n) = c 6= 0 allora f (n) = Θ(g(n))

1. Consideriamo, per prima cosa, il fatto che

n! ≥ (n/2)n/2 = 2(n/2) log2 (n/2)


La scrittura precedente è vera perché in n! ci sono n/2 fattori moltiplicativi più
grandi di n/2. Procediamo riscrivendo en come 2n log2 e e calcolando il limite
del rapporto fg(n)
(n)

2(n/2) log2 (n/2)


lim = 2(n/2) log2 (n/2) − 2n log2 e = ∞
n→+∞ 2n log2 e
Possiamo concludere che n! = Ω(2(n/2) log2 (n/2) ) = Ω(en ). In più possiamo
affermare che n! 6= Θ(en )

2
2. Possiamo fare direttamente il limite

en 2n log2 e
lim n
= lim n+log n = lim 2n(log2 e−1)−log2 n = ∞
n→+∞ n2 n→+∞ 2 2 n→+∞

In conclusione en = Ω(n2n ) e en 6= Θ(n2n )

3. Come abbiamo visto precedentemente, possiamo minorare n! con (n/2)(n/2)


e possiamo, evidentemente, maggiorarlo con nn . Quindi

(n/2)(n/2) ≤ n! ≤ nn
Facendo il logaritmo di tutta la disuguaglianza

(n/2) log2 (n/2) ≤ log2 (n!) ≤ n log2 n


Applicando le proprietà dei logaritmi al primo membro della disuguaglianza
possiamo riscriverlo come 21 n(log2 n − 1). Per n > 4 siamo sicuri che log22 n ≥ 1
e quindi posso scrivere:
1
n log2 n ≤ log2 (n!) ≤ n log2 n
4
considerando c1 = 41 e c2 = 1 possiamo applicare la definizione di Θ e quindi
log2 (n!) = Θ(n log2 n) e viceversa.

√ log2 n
4. Riscriviamo 2 come:
√ log2 n √ √
2 = 2log2 n log2 ( 2)
= 21/2 log2 n = n
Calcolando il limite
n √
lim √ = lim n=∞
n n→+∞
n→+∞
√ log n √ log n
Possiamo concludere che n = Ω( 2 2 ) ma n 6= Θ( 2 2 )

2 Hashing
2.1
In una tavola di hash con risoluzione delle collisioni mediante liste, come cam-
bia la complessità delle operazioni SEARCH, INSERT e DELETE se le liste
vengono mantenute ordinate?

Soluzione Sia per la DELETE che per la SEARCH vale la distinzione tra
il caso in cui la chiave cercata o da cancellare sia presente o meno nella tavola.
Nel caso di presenza non cambia nulla avere le liste ordinate, infatti il tempo
medio per trovare la chiave nella lista rimane lo stesso. Nel caso in cui la
chiave cercata o da cancellare non è presente nelle liste, l’ordinamento delle liste
cambia la situazione. Mentre, prima, per stabilire l’assenza di una chiave dalla
lista bisognava scorrere l’intera lista, adesso basta trovare il primo valore più
grande della chiave cercata o da cancellare per stabilirne l’assenza. dal punto di

3
vista della complessità asintotica non c’è nessun cambiamento (essa resta infatti
pari a Θ(1 + α) ma in realtà si dimezza il tempo medio per la DELETE e la
SEARCH in caso di chiave mancante.
Per quanto riguarda la INSERT questa non potrà più essere completata in
Θ(1) ma bisognerà scorrere la lista in cui inserire il nuovo elemento fino a trovare
la posizione adatta. Questo significa che, in media, l’INSERT avrà complessità
Θ(1 + α).
Per concludere, ordinare le liste è una buona idea solo nel caso in cui ci
si aspetta di fare pochi inserimenti nella tabella di hash rispetto al numero di
operazioni ricerca e o cancellazione di chiavi non presenti nella tabella stessa.

2.2
Considerare una tavola di hash T di dimensione pari a 255 che usi la funzione
di 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 = cn−1 cn−2 c1 c0 come il nu-
Pn−1
mero intero k = i=0 ci 256i ), allora tutti gli anagrammi di una stessa stringa
vengono mappati nella stessa cella dalla funzione hash.

Soluzione Applicando la funzione di hash alla generica parola:

h(k) = k mod255
n−1
X
=( ci 256i ) mod 255
i=0
n−1
X
=( ci (256 mod 255)i ) mod 255
i=0
n−1
X
=( ci ) mod 255
i=0

Considerando che la somma è commutativa, una parola ed ogni suo ana-


gramma verrà mappato sulla stessa cella.

3 Algoritmi greedy
3.1
Un cassiere vuole dare un resto di n centesimi di euro usando il minimo numero
di monete. Descrivere un algoritmo greedy per farlo con tagli da 1, 2, 5, 10,
20, 50, 1e, 2e.

Soluzione In questo caso sceglieremo per primo il numero massimo di monete


del taglio più grande possibile. La funzione che descrive l’algoritmo prende in
ingresso il resto totale da dare R, il vettore t contente i valori dei tagli di

4
moneta, ed n il numero di tagli. La funzione restituisce il vettore A in cui
l’i-esima posizione contiene il numero di monete dell’i-esimo taglio.

Algorithm 1
function RESTO (R, t, n) // t1 ≤ t2 ≤ ... ≤ tn
for i = 1 to n do
Ai ← bR/ti c0
R = R mod ti
return A

3.2
Siano a1 , ...., an delle attività didattiche aventi tempi di inizio s1 , ..., sn e tempi
di fine f1 , ..., fn e supponiamo di avere un insieme sufficiente grande di aule
A1 , A2 , ... in cui svolgerle. Trovare un algoritmo greedy per programmare tutte
le attività nel minimo numero possibile m di aule.

Soluzione L’idea di base dell’algoritmo greedy che andremo a implementare


è quella di programmare le attività una per volta partendo da quella che inizia
per prima fino a quella che inizia per ultima. Ogni volta che devo scegliere
dove programmare una certa attività scorro tutte le aule e inserisco l’attività
nella prima aula con una programmazione compatibile. La programmazione
di un’aula è compatibile con un’attività didattica se l’orario di fine dell’ultima
attività programmata nell’aula è precedente all’orario d’inizio dell’attività di-
dattica che sto cercando di programmare. In 2 è delineato lo pseudocodice per
implementare l’algoritmo. Quest’ultimo prende in ingresso i due vettori s ed f
e la loro dimensione n e restituisce il vettore J contenente la programmazione.
La posizione i-esima di J contiene il numero dell’aula all’interno della quale
è programmata l’i-esima attività. La funzione fa anche uso del vettore t che
contiene l’attuale situazione delle aule. Più rigorosamente, l’i-esima posizione
di t contiene l’orario di fine dell’ultima attività programmata nell’i-esima aula.

Algorithm 2
function PROGRAMMA-LEZIONI (s, f, n) // s1 ≤ s2 ≤ ... ≤ sn
m←0
for i = 1 to n do
h←m+1
for j = 1 to m do
if si ≥ tj then
h←j
Ji ← h
th ← fi
if h == m + 1 then
m←m+1
return J

5
3.3
Siano a1 , ...., an delle attività didattiche aventi tempi di inizio s1 , ..., sn e tempi
di fine f1 , ..., fn e supponiamo di avere a disposizione m aule A1 , ..., Am in cui
svolgerle. Trovare un algoritmo greedy per programmare il massimo numero
possibile di attività nelle m aule disponibili.

Soluzione Il criterio di appetibilità è il tempo di fine di ogni attività. In-


fatti, così facendo, lasciamo più spazio possibile per altre attività che potrebbe
essere programmate in futuro in ognuna delle aule. Inoltre facciamo in modo
che, se un’attività può essere programmata in più di un’aula, scegliamo di pro-
grammarla nell’aula in cui l’attività precedente finisce più tardi. In questo modo
minimizziamo i tempi morti e lasciamo più spazio per eventuali attività che finis-
cano dopo quella attuale ma che inizino prima2 . Passando allo pseudocodice,
abbiamo gli stessi parametri d’ingresso dell’esericizio precedente più m che rap-
presenta il numero di aule disponibili. In questo caso il vettore t (che ha la
stessa semantica dell’esericizio precedente) ha anche una posizione 0 che user-
emo come posizione ausiliaria per implementare l’algoritmo. Invece il vettore J
contenente la programmazione completa, adesso ammette anche la presenza del
valore 0 che viene utilizzato per indicare che non è stato possibile programmare
l’i-esima attività.

Algorithm 3
function PROGRAMMA-LEZIONI (s, f, n, m) // f1 ≤ f2 ≤ ... ≤ fn
for j = 1 to m do
tj ← 0
t0 ← −1
for i = 1 to n do
h←0
for j = 1 to m do
if si ≥ tj andtj ≥ th then
h←j
Ji ← h
if h 6= 0 then
tH ← fi
return J

2 Ricordiamo che abbiamo ordinato le attività per tempo di fine perciò un’attività a puà
i
essere considerata prima dell’attività ai+1 anche se si+1 < si .