Didattica e Fondamenti
degli Algoritmi e della
Calcolabilità
Quarta giornata
Risolvere efficientemente un problema
in P: la sequenza di Fibonacci
Guido Proietti
Email: [email protected]
URL: www.di.univaq.it/~proietti/index_personal
1
Riepilogo: gerarchia delle classi
Decidibili
P (ricerca)
NP
ExpTime
(ARRESTO(k))
NP-completi (SAT)
Congettura P ≠ NP
2
Progettare un algoritmo
Vogliamo ora progettare algoritmi (per
problemi calcolabili!) che:
– Producano correttamente il risultato
desiderato
– Siano efficienti in termini di tempo di
esecuzione ed occupazione di memoria
3
Le quattro proprietà fondamentali di un
algoritmo (oltre l’efficienza)
• La sequenza di istruzioni deve essere finita
• Essa deve portare ad un risultato corretto
• Le istruzioni devono essere eseguibili
materialmente
• Le istruzioni non devono essere ambigue
4
Algoritmi e strutture dati
• Concetto di algoritmo è inscindibile da quello
di dato
• Da un punto di vista computazionale, un
algoritmo è una procedura che prende dei dati
in input e, dopo averli elaborati, restituisce dei
dati in output
 I dati devo essere organizzati e strutturati in
modo tale che la procedura che li elabora sia
“efficiente”
5
Analisi di algoritmi
Correttezza:
– dimostrare formalmente che un algoritmo è
corretto
Complessità:
– Stimare la quantità di risorse (tempo e
memoria) necessarie all’algoritmo
– stimare il più grande input gestibile in tempi
ragionevoli
– confrontare due algoritmi diversi
– ottimizzare le parti “critiche”
6
Un problema numerico «giocattolo»:
i numeri di Fibonacci
7
Copyright © 2004 - The McGraw - Hill Companies, srl
L’isola dei conigli
Leonardo da Pisa (anche noto come Fibonacci) [11701240] si interessò di molte cose, tra cui il seguente
problema di dinamica delle popolazioni:
Quanto velocemente si espanderebbe una popolazione
di conigli sotto appropriate condizioni?
In particolare, partendo da una coppia di conigli in un’isola
deserta, e data una certa regola di riproduzione, quante
coppie si avrebbero nell’anno n?
8
Copyright © 2004 - The McGraw - Hill Companies, srl
Le regole di riproduzione
• Una coppia di conigli concepisce due coniglietti
di sesso diverso ogni anno, i quali formeranno
una nuova coppia
• La gestazione dura un anno
• I conigli cominciano a riprodursi soltanto al
secondo anno dopo la loro nascita
• I conigli sono immortali (!)
9
Copyright © 2004 - The McGraw - Hill Companies, srl
L’albero dei conigli
La riproduzione dei conigli può essere descritta in un
albero come segue:
10
Copyright © 2004 - The McGraw - Hill Companies, srl
La regola di espansione
• All’inizio dell’anno n, ci sono tutte le coppie
dell’anno precedente, e una nuova coppia di
conigli per ogni coppia presente due anni prima
• Indicando con Fn il numero di coppie all’inizio
dell’anno n, abbiamo la seguente relazione di
ricorrenza:
Fn =
11
1
se n=1,2
Fn-1 + Fn-2 se n≥3
Copyright © 2004 - The McGraw - Hill Companies, srl
Il problema
Primi numeri della sequenza di Fibonacci:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377,
610, 987, 1597, F18=2584,…
Come calcoliamo Fn ?
12
Copyright © 2004 - The McGraw - Hill Companies, srl
Digressione: la sezione aurea
Rapporto  fra due grandezze disuguali a>b, in
cui a è medio proporzionale tra b e a+b
(a+b) : a = a : b
b
a
b
e ponendo a=b
13
Copyright © 2004 - The McGraw - Hill Companies, srl
a
Un approccio numerico
Keplero [1571-1630] osservò che
Fn  1

lim
Fn
n
da cui si può dimostrare che la soluzione in forma
chiusa della sequenza di Fibonacci, nota come
formula di Binet [1786-1856], è:
14
Copyright © 2004 - The McGraw - Hill Companies, srl
Algoritmo fibonacci1
15
Copyright © 2004 - The McGraw - Hill Companies, srl
Correttezza ed efficienza
• Molto efficiente: una sola linea di codice mandata in esecuzione
(sebbene stiamo trascurando la complessità dell’operazione in essa
contenuta)!
• Ma siamo sicuri che sia corretto? Sulla RAM (modello astratto) con
celle di memoria infinite sì, ma su un modello di calcolo reale, con
ˆ per ottenere un risultato corretto?
quale accuratezza devo fissare  e 
• Ad esempio, con 3 cifre decimali:
16
n
fibonacci1(n)
arrotondamento
Fn
3
16
18
1.99992
986.698
2583.1
2
987
2583
2
987
2584
Copyright © 2004 - The McGraw - Hill Companies, srl
Algoritmo fibonacci2
Poiché fibonacci1 non è corretto, un
approccio alternativo consiste nell’utilizzare
direttamente la definizione ricorsiva:
algoritmo fibonacci2(intero n)  intero
if (n≤2) then return 1
else return fibonacci2(n-1) +
fibonacci2(n-2)
17
Copyright © 2004 - The McGraw - Hill Companies, srl
Correttezza? Corretto per definizione!
Efficienza?
• Per valutare il tempo di esecuzione, calcoliamo
il numero di linee di codice T(n) mandate in
esecuzione
• Se n≤2: una sola linea di codice
• Se n=3: quattro linee di codice, due per la
chiamata fibonacci2(3), una per la chiamata
fibonacci2(2) e una per la chiamata
fibonacci2(1), cioè
T(3)=2+T(2)+T(1)=2+1+1=4
18
Copyright © 2004 - The McGraw - Hill Companies, srl
Relazione di ricorrenza
Per n ≥3, in ogni chiamata si eseguono due linee di
codice, oltre a quelle eseguite nelle chiamate
ricorsive
T(n) = 2 + T(n-1) + T(n-2)
n≥3
In generale, il tempo di esecuzione di un algoritmo
ricorsivo è pari al tempo speso all’interno della
chiamata più il tempo speso nelle chiamate ricorsive
19
Copyright © 2004 - The McGraw - Hill Companies, srl
Albero della ricorsione di fibonacci2
• Utile per risolvere la relazione di ricorrenza T(n)
• Ogni nodo corrisponde ad una chiamata ricorsiva
• I figli di un nodo corrispondono alle sottochiamate
20
Copyright © 2004 - The McGraw - Hill Companies, srl
Alberi: qualche definizione
albero d-ario: albero in cui tutti i nodi interni hanno (al più) d figli
d=2  albero binario
Un albero è strettamente binario se tutti nodi interni hanno esattamente 2 figli
21
Copyright © 2004 - The McGraw - Hill Companies, srl
Calcolare T(n)
• Etichettando i nodi dell’albero con il numero di
linee di codice eseguite nella chiamata
corrispondente:
– I nodi interni hanno etichetta 2
– Le foglie hanno etichetta 1
• Per calcolare T(n):
– Contiamo il numero di foglie
– Contiamo il numero di nodi interni
22
Copyright © 2004 - The McGraw - Hill Companies, srl
Calcolare T(n)
Lemma 1:
Il numero di foglie dell’albero della ricorsione di
fibonacci2(n) è pari a Fn
dim
(da fare a casa, per induzione su n)
Lemma 2:
Il numero di nodi interni di un albero strettamente binario (come
l’albero della ricorsione di fibonacci2(n)) è pari al numero di
foglie -1
dim
(da fare a casa, per induzione sul numero di nodi interni dell’albero)
In totale le linee di codice eseguite sono
T(n) = Fn + 2 (Fn-1) = 3Fn-2
23
Copyright © 2004 - The McGraw - Hill Companies, srl
Osservazioni
fibonacci2 è un algoritmo lento, perché esegue
un numero di linee di codice esponenziale in n:
T(n) ≈ Fn ≈ n
Alcuni esempi di linee di codice eseguite
n = 8 T(n)=3 · F8 – 2= 3 · 21 – 2 = 61
n = 45 T(n) =3·F45 – 2 = 3·1.134.903.170 – 2 = 3.404.709.508
n = 100… con le attuali tecnologie, calcolare F100 richiederebbe circa 8000 anni!
Possiamo fare di meglio?
24
Copyright © 2004 - The McGraw - Hill Companies, srl
Algoritmo fibonacci3
• Perché l’algoritmo fibonacci2 è lento? Perché
continua a ricalcolare ripetutamente la soluzione dello
stesso sottoproblema. Perché non memorizzare allora
in un array le soluzioni dei sottoproblemi?
algoritmo fibonacci3(intero n)  intero
sia Fib un array di n interi
Fib[1]  Fib[2]  1
for i = 3 to n do
Fib[i]  Fib[i-1] + Fib[i-2]
return Fib[n]
Correttezza? Corretto per definizione!
25
Copyright © 2004 - The McGraw - Hill Companies, srl
La struttura dati vettore o array
• Il vettore o array è una struttura dati utilizzata per
rappresentare sequenze di elementi omogenei
• Un vettore è visualizzabile tramite una struttura
unidimensionale di celle; ad esempio, un vettore di 5
interi ha la seguente forma
5
23
1
12
5
• Ciascuna delle celle dell'array è identificata da un valore
di indice
• Gli array sono (generalmente) allocati in celle contigue
della memoria del computer
26
Copyright © 2004 - The McGraw - Hill Companies, srl
Calcolo del tempo di esecuzione
•
•
•
•
Linee 1, 2, e 5 eseguite una sola volta
Linea 3 eseguita n – 1 volte (una sola volta per n=1,2)
Linea 4 eseguita n – 2 volte (non eseguita per n=1,2)
T(n): numero di linee di codice mandate in
esecuzione da fibonacci3
T(n) = n – 1 + n – 2 + 3 = 2n
T(1) = 4
T(45) = 90
n>1
Per n=45, circa 38 milioni di volte più veloce
dell’algoritmo fibonacci2!
T(100) = 200
27
Copyright © 2004 - The McGraw - Hill Companies, srl
Calcolo del tempo di esecuzione
• L’algoritmo fibonacci3 impiega tempo
proporzionale a n invece che esponenziale in n,
come accadeva invece per fibonacci2
• Tempo effettivo richiesto da implementazioni in
C dei due algoritmi su piattaforme diverse (un po’
obsolete ):
28
Copyright © 2004 - The McGraw - Hill Companies, srl
Occupazione di memoria
• Il tempo di esecuzione non è la sola risorsa di calcolo
che ci interessa. Anche la quantità di memoria necessaria
può essere cruciale.
• Se abbiamo un algoritmo lento, dovremo solo attendere
più a lungo per ottenere il risultato
• Ma se un algoritmo richiede più spazio di quello a
disposizione, non otterremo mai la soluzione,
indipendentemente da quanto attendiamo!
• È il caso di Fibonacci3, la cui correttezza è
subordinata alla dimensione della memoria allocabile
Copyright © 2004 - The McGraw - Hill Companies, srl
Algoritmo fibonacci4
• fibonacci3 usa un array di dimensione n prefissata
• In realtà non ci serve mantenere tutti i valori di Fn
precedenti, ma solo gli ultimi due, riducendo lo spazio a
poche variabili in tutto:
algoritmo fibonacci4(intero n)  intero
ab1
for i = 3 to n do
c  a+b
ab
bc
return b
Copyright © 2004 - The McGraw - Hill Companies, srl
Correttezza? Corretto per definizione!
Efficienza?
• Per la risorsa tempo, calcoliamo il numero di linee di
codice T(n) mandate in esecuzione
– Se n≤2: tre sole linee di codice
– Se n3: T(n) = 2+(n-1)+3·(n-2) = 4n-5 (per
Fibonacci3 avevamo T(n)=2n)
• Per la risorsa spazio, contiamo il numero di variabili
di lavoro utilizzate: S(n)=4 (per Fibonacci3
avevamo S(n)=n+1) [NOTA: stiamo assumendo che
ogni locazione di memoria può contenere un valore
infinitamente grande!]
Copyright © 2004 - The McGraw - Hill Companies, srl
Notazione asintotica
• Misurare T(n) come il numero di linee di
codice mandate in esecuzione è una misura
molto approssimativa del tempo di
esecuzione
• Se andiamo a capo più spesso, aumenteranno
le linee di codice sorgente, ma certo non il
tempo richiesto dall’esecuzione del
programma!
Copyright © 2004 - The McGraw - Hill Companies, srl
Notazione asintotica
• Per lo stesso programma impaginato diversamente
potremmo concludere ad esempio che T(n)=3n
oppure T(n)=5n
• Vorremmo un modo per descrivere l’ordine di
grandezza di T(n) ignorando dettagli "inessenziali"
come le costanti moltiplicative, additive e
sottrattive
• Useremo a questo scopo la notazione asintotica Θ,
simile alla notazione O che abbiamo già visto
Copyright © 2004 - The McGraw - Hill Companies, srl
Notazione asintotica Q (definizione
informale)
Supponiamo che f e g siano due funzioni
definitivamente diverse da zero per n∞, e che
f ( n)
lim
 L, 0  L  
n  g ( n )
Allora, scriveremo che f(n) = Q(g(n)).
Copyright © 2004 - The McGraw - Hill Companies, srl
Esempi:
Sia f(n) = 2n2 + 3n, allora f(n)=Θ(n2)
Sia f(n) = n2 – n log n, allora f(n)=Θ(n2)
Sia f(n) = n3 -2n2+3n, allora f(n)=Θ(n3)
Sia f(n) = 23, allora f(n)=Θ(1)
Sia f(n) = 3n +2n, allora f(n)=Θ(3n)
Copyright © 2004 - The McGraw - Hill Companies, srl
Andamento asintotico per i Fibonacci
• Fibonacci2 T(n)=3Fn-2  T(n)=Θ(Fn) 
T(n)=Θ(n), poiché
1
lim
n 


5
n
n
n


 1
5
• Fibonacci3 T(n)=2n  T(n)=Θ(n), S(n)=Θ(n)
• Fibonacci4 T(n)=4n-5  T(n)=Θ(n),
S(n)=Θ(1)
Copyright © 2004 - The McGraw - Hill Companies, srl