Tempo e spazio di calcolo
Modelli di calcolo e
metodologie di analisi
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
In quale modo stimiamo il tempo di calcolo?
Possiamo considerare due approcci:
• Approccio empirico (a posteriori)
• Approccio teorico (a priori)
SECONDO VOI QUALE STUDIEREMO IN
QUESTO CORSO ?
SECONDO VOI QUALE E’ IL PIU’ UTILE IN
PRATICA ?
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
Sviluppiamo una metodologia per l’approccio teorico
Per sviluppare la metodologia per stimare il tempo e lo
spazio di calcolo dobbiamo precisare:
1.
2.
3.
4.
Linguaggio per descrivere gli algoritmi
Modello computazionale d’esecuzione
Metrica per misurare il tempo di calcolo
Modo per caratterizzare il tempo di calcolo
per gli algoritmi ricorsivi
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
1. Linguaggio per descrivere gli algoritmi
Pseudo-codice Java-like:
assegnazione: ←
i ← j ← k equivale alla seq.: j ← k; i ← j
espressioni: simboli matematici standard per espressioni
numeriche e booleane
commento: { }
dichiarazione di metodo: nome (param 1, param 2, ...)
chiamata di un metodo: nome (param 1, param 2, ...)
ritorno da un metodo: return valore
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
Pseudo-codice Java-like (continua):
• dati composti:
• i-esimo elemento array A: A[i]
• A[i . . j] ≡ <A[i], A[i+1], . . . , A[j]>, se i ≤ j
sequenza vuota, se i > j
• i dati composti sono organizzati in oggetti, che
sono strutturati in attributi o campi: ad es. length[A]
• una variabile che rappresenta un oggetto è un riferimento
• un riferimento che non si riferisce a nessun oggetto: nil
• parametri alle procedure passati per valore (per gli oggetti una
copia del riferimento)
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
Pseudo-codice Java-like (continua):
• struttura di blocco: spaziatura
• costrutti iterativi e condizionali :
if condizione
then azioni
(else azioni)
while condizione do
azioni
do azioni
while condizione
for variabile ← val-iniz. to val-fin. (incremento) do
azioni
for variabile ← val-iniz. downto val-fin. (decremento) do
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
azioni
2. Il modello computazionale d’esecuzione
Consideriamo come operazioni primitive le seguenti
operazioni :
1.
2.
3.
4.
5.
6.
7.
assegnazione di un valore ad una variabile
chiamata di un metodo
eseguire un’operazione aritmetica
confronto di due numeri
indicizzazione di un elemento in un array
riferimento a un oggetto
rientro da un metodo
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
Assunzione implicita: il numero di operazioni
primitive è proporzionale al tempo di esecuzione
dell’algoritmo
• Questo approccio dà origine al modello computazionale
chiamato Random Access Machine (RAM):
• CPU connessa a un banco di celle di memoria
• ogni cella memorizza una parola (un numero, un carattere, . . .
In generale: il valore di un tipo di base o un riferimento ad un
oggetto)
• la CPU accede ad una cella di memoria arbitraria con una
operazione primitiva (ogni operando e’ una cella di memoria)
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
La quantità di tempo (e di spazio) consumato dall’esecuzione
di un programma RAM su un dato input può essere determinato
essenzialmente usando due criteri:
Criterio di costo uniforme:
l’esecuzione di un’istruzione primitiva richiede un tempo
indipendente dalla “grandezza” degli operandi (ricordate:
un operando e’ o un valori di un tipo base o un riferimento
ad un oggetto)
Criterio di costo logaritmico:
il tempo di calcolo richiesto da un’istruzione primitiva
dipende dal numero di bit necessari a rappresentare
gli operandi
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
3. Metrica per misurare il tempo di calcolo
Misureremo il tempo di calcolo contando le operazioni primitive
Ad esempio:
Massimo (A, n)
current-max ← A[0]
for i ← 1 to n-1 do
if current-max < A[i]
then current-max ← A[i]
return current-max
2
1+n
4*(n-1)…6 *(n-1)
1
Numero di operazioni primitive: t (n) =
minimo 2 + 1 + n + 4*(n-1) + 1 = 5*n
massimo 2 + 1 + n + 6*(n-1) + 1 = 7*n - 2
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
In generale, istanze diverse avranno tempi di calcolo diversi,
ad esempio:
tempo nel caso
peggiore
5 ms
tempo
di
calcolo
tempo nel caso
migliore
3 ms
1 ms
A
B
C
D
E
F
G
istanze in input
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
Analisi del caso medio
Il tempo di calcolo medio dell’algoritmo definito come
media dei tempi per tutti i possibili input
Per poterlo calcolare si deve conoscere
la distribuzione di probabilità sull’insieme dei possibili input
In generale si tratta di un compito non banale. In mancanza di
informazioni si puo’ assumere una distribuzione uniforme,
ma si tratta di un’assunzione arbitraria!
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
Analisi del caso peggiore
• non richiede teoria probabilità
• caso peggiore quasi sempre facile da identificare
• se un algoritmo si “comporta bene” nel caso peggiore, si
“comporta bene” su ogni input (fornisce una garanzia!)
Analisi del caso ottimo
• non richiede teoria probabilità
• caso ottimo quasi sempre facile da identificare
• se un algoritmo si comporta male nel caso ottimo, si
comporta male su ogni input
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
4. Modo per caratterizzare il tempo di calcolo degli algoritmi ricorsivi
Equazione di ricorrenza: una funzione che esprime il numero
di operazioni sull’input di dimensione n in funzione del
numero di operazioni su input di dimensione inferiore.
Ad esempio:
{ n > 1}
Massimo-ricorsivo (A, n)
if n = 1
then return A[0]
return max (Massimo-ricorsivo (A, n-1), A[n-1])
T(n) =
3
T(n-1) + k
se n = 1
altrimenti
T(n) = k*(n-1) + 3
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
ESEMPIO: tempo di calcolo della moltiplicazione per somme successive
Moltiplicazione per “somme successive”
y
x2
45
44
43
..
.
4
3
2
1
19
19
19
..
.
19
19
19
19
moltiplicazione (x1, x2)
y ← x1
prod ← 0
while y > 0 do
prod ← prod + x2
y ←y-1
return prod
855
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
ESEMPIO: tempo di calcolo della moltiplicazione alla russa
Moltiplicazione “alla russa”
y1
45
22
11
5
2
y2
19
38
76
152
304
1
608
19
--76
152
--608
855
molt-russa (x1, x2)
y1 ← x1
y2 ← x2
prod ← 0
while y1 > 0 do
if y1 is odd
then prod ← prod + y2
y1 ← y1 div 2
y2 ← y2 + y2
return prod
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
Moltiplicazione per somme successive vs moltiplicazione alla russa
moltiplicazione (x1, x2)
y ← x1
prod ← 0
while y > 0 do
prod ← prod + x2
y ←y-1
return prod
nel while
molt-russa (x1, x2)
y1 ← x1
y2 ← x2
prod ← 0
while y1 > 0 do
if y1 is odd
then prod ← prod + y2
y1 ← y1 div 2
y2 ← y2 + y2
return prod
moltiplicazione
molt-russa
2 * x1 assegnazioni
x1 somme
x1 decrementi
x1+1 confronti
3 * lg x1 assegnazioni
lg x1 divisioni
2 * lg x1 somme
2 * lg x1 + 1 confronti
5 * x1+1 + 3 operazioni
8 lg x1+1 + 4 operazioni
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
f(m) = 5m + 4
g(m) = 8 lg m + 5
60
54
num ero di operazioni
50
49
44
40
39
34
29
30
27,46
24
19
20
29
17,68
14
13
9
10
31,58
25,68
23,58
21
30,36
5
4
0
0
1
2
3
4
5
6
7
8
9
10
moltiplicatore
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
11
LA NOTAZIONE ASINTOTICA
Introduciamo un’ulteriore astrazione :
tasso di crescita o ordine di grandezza
del tempo di calcolo
• ogni passo nello pseudo-codice (e ogni statement in un linguaggio
ad alto livello) corrisponde a un piccolo numero di operazioni
primitive che non dipendono dalla dimensione dell’input
• basta considerare il termine principale perchè i termini di ordine
inferiore non sono significativi per n grande.
L’ordine di grandezza del tempo di calcolo fornisce una
semplice caratterizzazione dell’efficienza e consente di
confrontare algoritmi alternativi.
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
Efficienza asintotica degli algoritmi: come cresce il tempo di
esecuzione con il crescere al limite della dimensione delle
istanze in input
Notazione asintotica
Consideriamo funzioni dai naturali ai numeri reali non negativi
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
Notazione O: O (g(n)) e’ l’insieme di tutte le funzioni f(n) per
cui esistono due costanti positive c ed n0 tali che
f(n) ≤ c • g(n) per tutti gli n ≥ n0
c g(n)
f(n)
tem
po
di
run
n0
n
f(n) ∈ O (g(n))
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
Notazione Ω: Ω(g(n)) e’ l’insieme di tutte le funzioni f(n) per
cui esistono due costanti positive c ed n0 tali che
f(n) ≥ c • g(n) per tutti gli n ≥ n0
f(n)
tem
po
di
run
c g(n)
n0
n
f(n) ∈ Ω (g(n))
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
Notazione Θ: Θ(g(n)) e’ l’insieme di tutte le funzioni f(n) per
cui esistono tre costanti positive c1, c2 ed n0 tali che
c1• g(n) ≤ f(n) ≤ c2 • g(n) per tutti gli n ≥ n0
c2 g(n)
f(n)
tem
po
di
run
c1 g(n)
n0
n
f(n) ∈ Θ (g(n))
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
Proprietà della notazione asintotica
Transitiva:
f(n) = Θ (g(n)) e g(n) = Θ (h(n))
f(n) = O (g(n)) e g(n) = O (h(n))
f(n) = Ω (g(n)) e g(n) = Ω (h(n))
Riflessiva:
Simmetrica:
f(n) = Θ (h(n))
f(n) = O (h(n))
f(n) = Ω (h(n))
f(n) = Θ (f(n))
f(n) = O (f(n))
f(n) = Ω (f(n))
f(n) = Θ (g(n))
Simmetrica trasposta: f(n) = O (g(n))
g(n) = Θ (f(n))
g(n) = Ω (f(n))
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
• d(n) = O(f(n))
a . d(n) = O(f(n)), per ogni costante a > 0
• d(n) = O(f(n)) & e(n) = O(g(n))
d(n) + e(n) = O(f(n) + g(n))
• d(n) = O(f(n)) & e(n) = O(g(n))
d(n) . e(n) = O(f(n) . g(n))
• f(n) funzione polinomiale di grado d:
f(n) = a0 + a1n + . . . + adnd
f(n) = O(nd)
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
Alcune classi di complessità
O(log n)
logaritmica
O(n)
lineare
O(n2)
quadratica
O(nk) (k ≥ 1)
polinomiale
O(an) (a > 1)
esponenziale
trattabili
non trattabili
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
Alcune funzioni ordinate per velocità di crescita
n
log n
n
n
n log n
n2
nn32
2n
2
1
1,41
2
2
4
8
4
4
2
2
4
8
16
64
16
8
3
2,83
8
24
64
512
256
16
4
4
16
64
256
4.096
65.536
32
5
5,66
32
160
1.024
32.768
4.294.967.296
64
6
8
64
384
4.096
262.144
1,84 x 1019
128
7
11,31
128
896
16.384
2.097.152
3,40 x 1038
256
8
16
256
2.048
65.536
16.777.216
1,15 x 1077
512
9
22,63
512
4.608
262.144
134.217.728
1,34 x 10154
1.024
10
32
1.024 10.240 1.048.576 1.073.741.824
1,79 x 10308
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
10
10
9
9
8
8
9
8
8
8
7
7
6
6
5
4
4
3
1
5
4,75
4
2
logn
radn
n
n logn
n^2
n^3
2^n
2,81
3
2 1,73
1,41
1,58
1
2
2
2,32
2,58
2,24 2,45
2,65
3
3,17
3
2,83
3,58
3,32 3,46
3,16
3,32
3,46
0
2
3
4
5
6
7
8
9
10
11
12
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
60
logn
radn
n
n logn
n^2
n^3
2^n
50
40
30
20
10
0
2
3
4
5
6
7
8
9
10
11
12
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
4500
logn
radn
n
n logn
n^2
n^3
2^n
4000
3500
3000
2500
2000
1500
1000
500
0
2
3
4
5
6
7
8
9
10
11
12
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
ESEMPIO: tempo di calcolo dell’algoritmo di ordinamento per inserzione
Problema: ordinamento di numeri.
Input: una sequenza di n numeri <a1, a2,…,an>.
Output: una permutazione <a1’, a2’,…,an’> della sequenza di
input tale che a1’≤ a2’ ≤ … ≤ an’.
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
1
2
3
4
5
3 5 1 8 2
key = 5
1
2
3
4
5
3 5 1 8 2
3 5 5 8 2
key = 1
1 3 5 8 2
1
2
3
4
3 3 5 8 2
5
1 3 5 8 2
key = 8
1
2
3
4
5
1 3 5 8 2
key = 2
1 3 5 8 8
1 3 5 5 8
1 3 3 5 8
1 2 3 5 8
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
Insertion-sort (A)
j←2
1 for j ← 2 to length[A] do
2
key ← A[j]
3
{inserisci A[j] nella sequenza A[1. .j-1]
num. volte
1
n
n–1
}
NON E’ UN INVARIANTE!
spostando a destra gli elementi > di A[j]}
i←j–1
n–1
while i > 0 and A[i] > key do
Σ tj
( j=2..n)
A[i+1] ← A[i]
Σ(tj – 1) ( j=2..n)
i←i–1
Σ(tj – 1) ( j=2..n)
A[i+1] ← key
n–1
j ← j+1
n–1
T(n) = 1 + a*n + b*(n-1) + c*Σ tj + d*Σ(tj – 1) =
= (a + b)*n + (1 – b) + c*Σ tj + d*Σ(tj – 1)
4
5
6
7
8
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
T(n) =
=
=
=
1 + a*n + b*(n – 1) + c*Σ tj + d*Σ(tj – 1) =
(a + b) *n + (1 – b) + (c+d)* Σ tj – n + 1 =
(a + b –1)*n + (2 – b) + (c+d)* Σ tj
=
e*n + f + g* Σ tj
Caso migliore: A è ordinato
T(n) è una funzione lineare di n.
Caso peggiore: A è ordinato in ordine inverso
T(n) è una funzione quadratica di n.
Caso medio (considerando ogni permutazione è ugualmente probabile)
T(n) è una funzione quadratica di n.
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
ESEMPIO: il problema della valutazione di un polinomio
Input: una sequenza di n+1 numeri reali A = <a0, a1,…,an> e il valore
della variabile x
Output: il valore del polinomio di grado n: P(x) = a0 + a1x+ … + anxn
Un algoritmo che risolve il problema:
Poly-eval (A, x, n)
1 y←1
2 result ← A[0]
3 for i ← 1 to n do
4
y←y•x
5
result ← result +A[i] • y
{y = xi}
6 return result
L’algoritmo esegue 2*n moltiplicazioni n somme e 2*n assegnazioni.
Ma si può fare meglio
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
La regola di Horner:
P(x) = a0 + x (a1+ … + x (an-1+ x an))…)
Un algoritmo basato sulla regola di Horner:
Horner (A, x, n)
1 result ← A[n]
2 for i ← n - 1 downto 0 do
3
result ← result • x + A[i]
4 return result
L’algoritmo esegue n somme, n moltiplicazioni e n assegnazioni.
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
Poly-eval (A, x, n)
1 y←1
2 result ← A[0]
3 for i ← 1 to n do
4
y←y•x
5
result ← result +A[i] • y
6 return result
fuori dal for
confronti
nel for
Poly-eval
5
n+1
8*n
Horner (A, x, n)
1 result ← A[n]
2 for i ← n - 1 downto 0 do
3
result ← result • x + A[i]
4 return result
Horner
4
n+1
6*n
Poly-eval: 9*n + 6
Horner:
7*n + 5
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
1200
T(n) = 9 n + 6
1005
1000
825
800
735
782
712
645
600
642
555
572
465
502
375
400
285
195
200
0
T(n) = 7 n + 5
915
82
12
1
15
11
432
292
362
222
152
105
21
31
41
51
61
71
81
91 101 111
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
L’algoritmo Horner è sicuramente migliore
dell’algoritmo Poly-eval
L’analisi asintotica non distingue però tra i due algoritmi:
per entrambi si ottiene Θ(n)
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
RIEPILOGO
• Una metodologia per l’approccio teorico alla
stima del tempo di calcolo
• Analisi dei casi medio, peggiore, ottimo
• Efficienza asintotica degli algoritmi
• Risposta ad alcune domande lasciate in
sospeso durante le lezioni precedenti.
• ESERCIZIO: Rispondete alle seguenti
domande. Quali domande restano ancora in
sospeso? Adesso abbiamo gli strumenti per
rispondere a qualcuna di esse? Se si, quali
sono le risposte?
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)
UN ALTRO ESERCIZIO
• La correttezza degli algoritmi considerati nei
lucidi precedenti (o di loro minime varianti) e’
stata dimostrata (nelle lezioni precedenti) con
il metodo delle asserzioni. ANNOTATE
(SCRIVENDO SULLA STAMPA DEI
LUCIDI) IL CODICE CON LE
ASSERZIONI CHE NE DIMOSTRANO LA
CORRETTEZZA. In questo modo vi sara’
piu’ facile seguire il ragionamento sulla loro
complessita’!!!
F. Damiani - Alg. & Lab. 04/05 (da M. Zacchi - Alg. & Lab. 03/04)