Sei sulla pagina 1di 20

Universit di Torino Facolt di Scienze MFN Corso di Studi in Informatica a.a.

2011-12

Algoritmi e Strutture D ti Al it i St tt Dati Lezioni


prof. Ferruccio Damiani (corso B), prof. Elio Giovannetti (corso A) Lezione 15 I numeri di Fibonacci 18/12/11 1 27 1.27

Quest' opera pubblicata sotto una Licenza Creative Commons Attribution-NonCommercial-ShareAlike 2.5.

I numeri rossi sulla Mole Antonelliana a Natale

Che numeri sono ?


18/12/11 1.27 ASD-11-12 - Lez.15 2

I conigli di Leonardo di Pisa, figlio di Bonaccio.


Leonardo Pisano, o Leonardo Fi(lius)bonacci all'istante 0, nessun coniglio: 0 coppie; alla fine del 1o anno, compra 1 coppia di coniglietti; alla fine del 2o anno: i coniglietti sono diventati conigli adulti: 1 coppia alla fine del 3o anno: la coppia adulta genera una coppia di coniglietti: 2 coppie alla fine del 4o anno: la coppia adulta genera una coppia di coniglietti: 3 coppie; pp g pp g pp i coniglietti dell'anno prima sono diventati adulti; alla fine del 5o anno: le 2 coppie adulte generano ciascuna una coppia di coniglietti: 3+2 = 5 coppie; ...
18/12/11 1.27 ASD-11-12 - Lez.15 3

Le regole della riproduzione dei conigli.


Nessun coniglio muore mai (i conigli sono immortali). I conigli diventano adulti (e cominciano a riprodursi) soltanto al secondo anno di vita. Ogni coppia adulta genera 1 coppia di coniglietti all'anno, per sempre.

18/12/11 1.27

ASD-11-12 - Lez.15

(continua)
Allora, dopo n anni: num. di coppie = num. di coppie esistenti 1 anno prima + num. di nuove coppie generate; ma: num. di nuove coppie = num. di coppie adulte 1 anno prima = = num. di coppie esistenti 2 anni prima quindi: num. di coppie = num. di coppie esistenti 1 anno prima + num di coppie esistenti 2 anni prima. num. prima

18/12/11 1.27

ASD-11-12 - Lez.15

L'albero dei conigli


La riproduzione dei conigli pu essere descritta dall'albero seguente:

18/12/11 1.27

ASD-11-12 - Lez.15

I numeri rossi sulla Mole Antonelliana


fib 0 1 2 3 4 5 6 7 8 9 10 11 12 13 ... = 0 1 1 2 3 5 8 13 21 34 55 89 144 233 ...
7

fib(0) = 0 fib(1) = 1 fib(n) = fib(n-1) + fib(n-2)

18/12/11 1.27

ASD-11-12 - Lez.15

L'n-esimo numero di Fibonacci: algoritmo ricorsivo


static if(n else else } long fibRic(int n) { == 0) return 0; if(n == 1) return 1; return fibRic(n-2) + fibRic(n-1);

T(0) = 1; T(1) = 1; T(n) = T(n-1) + T(n-2) + 1 per n > 1 quindi T(0) = T(1) ( ) ( ) T(n) > T(n-1) per n > 1, cio T(1) < T(2) < T(3) < ... quindi T(n) = T(n-1) + T(n-2) + 1 2T(n-2) + 1 dove per n > 2 si ha in realt la disuguaglianza stretta T(n) > 2T(n-2) + 1
18/12/11 1.27 ASD-11-12 - Lez.15 8

Tempo di calcolo:

Rappresentazione grafica delle eq. di ricorrenza (albero di ricorsione)

T(1)

1 1

T(n)

>=

T(n-2)
18/12/11 1.27 ASD-11-12 - Lez.15

T(n-2)
9

liv. 0

T(n) >=

T(n-2)

T(n-2)

18/12/11 1.27

ASD-11-12 - Lez.15

10

liv. 0

T(n) >=

liv. 1

T(n-4)

T(n-4)

T(n-4)

T(n-4)

18/12/11 1.27

ASD-11-12 - Lez.15

11

liv. 0

T(n) >=

liv. 1 liv. 2 liv. 3

1
1 1 1

1
1

T(n-23)T(n-23) T(n-23)T(n-23)

T(n-23)

...

...

T(n-6)

18/12/11 1.27

ASD-11-12 - Lez.15

12

livello

T(n) >=

1
1 1 1

1
1 1 1 1 1 1

1
1 1 1

2 3

T(0) T(0)

18/12/11 1.27

T(0)

T(0)

livello k tale che n 2k = 0 o 1


ASD-11-12 - Lez.15

13

T(0)

livello

T(n) >=

1
1 1 1

1
1 1 1 1 1 1

1
1 1 1

2 3

18/12/11 1.27

ASD-11-12 - Lez.15

livello k = n/2

14

analogo all'albero per le torri di Hanoi !


Il tempo di calcolo sicuramente non minore del tempo rappresentato dall'albero. Ogni nodo dell'albero rappresenta un tempo 1; quindi il tempo totale rappresentato dall'albero , come per le torri di Hanoi, il numero dei s i nodi. H i m d i suoi di La differenza rispetto alle torri di Hanoi che, poich ad ogni successivo livello di ricorsione si scende di 2, per raggiungere la base 0 occorrono solo n/2 livelli invece di n. Come per le torri di Hanoi, si ha quindi un albero completo, ma di altezza n/2 invece di n. Il numero dei nodi quindi 1 + 2 + ... + 2n/2-1 + 2n/2 = 2n/2+1 1 = 2(2)n - 1 Quindi T(n) > 2(2)n - 1 T(n) = (an)
18/12/11 1.27

esponenziale ! intrattabile !
ASD-11-12 - Lez.15 15

In modo puramente algebrico:


T(n) > 2T(n-2) + 1 > 2(2T(n-4) + 1) + 1 = 22T(n-4) + 2 + 1 > 22(2T(n 6) + 1) + 2 + 1 (2T(n-6) 3T(n-6) + 22 + 2 + 1 = =2 ... > 2kT(n-2k) + 2k-1 + ... + 2 + 1 = per n = 2k si ha: n/2-1 > 2n/2 + 2n/2 1 + ... + 2 + 1 = = 2n/2+1 1 = = 2(2)n - 1
18/12/11 1.27 ASD-11-12 - Lez.15 16

Nota
Dalle equazioni di ricorrenza T(0) = 1; T(1) = 1; T(n) = T(n-1) + T(n-2) + 1 si ha anche T(n) 2T(n-1) + 1 Sviluppando si ottiene esattamente lo stesso albero delle torri di Hanoi, quindi T(n) = O(2n) In conclusione, si ha T(n) = (an) N Nota: Il tempo " "esatto" d " dato d l numero di nodi dal di dell'albero di ricorsione effettivo (cio non approssimato), che un albero completo fino al livello n/2, e che "pende" a destra (o a sinistra, a seconda di come si disegna) fino al livello n.
18/12/11 1.27 ASD-11-12 - Lez.15 17

L'algoritmo intuitivo
Eppure, se calcoliamo a mano la sequenza di Fibonacci, usiamo un ovvio algoritmo lineare: sommiamo i due ultimi numeri ottenuti, e otteniamo un nuovo ultimo numero della sequenza, ma non buttiamo via il precedente; invece ... ... la procedura ricorsiva, per calcolare fib(n-2) + fib(n-1) calcola due volte fib(n-2), a sua volta per calcolare fib(n-2) calcola due volte fib(n-4) ... ripete inutilmente i calcoli ! Proviamo a implementare l'algoritmo "manuale" intuitivo per mezzo di una procedura iterativa iterativa.

18/12/11 1.27

ASD-11-12 - Lez.15

18

L'n-esimo numero di Fibonacci: algoritmo iterativo.


Per calcolare l'n-esimo numero occorre aver calcolato tutti i precedenti, e ad ogni passo si usano gli ultimi due. Servono allora tre variabili: i, ultimo, penult INVARIANTE ultimo l'i-esimo numero di Fibonacci; penult l'(i-1)-esimo numero di Fibonacci. CORPO DEL CICLO ultimo + penult diventa il nuovo ultimo, il vecchio ultimo diventa il nuovo penult: nuovoUltimo = ultimo + penult; penult = ultimo; ultimo = nuovoUltimo; i++;
18/12/11 1.27 ASD-11-12 - Lez.15 19

Si esce quando i = n: in tal caso, evidentemente, ultimo l'n-esimo numero di Fibonacci; quindi: while(i < n)

Test del ciclo

Inizializzazione
0 lo 0-esimo numero di Fibonacci; 1 ... l'1-esimo numero di Fibonacci; Allora, ricordando l'invariante: ultimo l'i-esimo numero di Fibonacci, penult l'(i-1)-esimo numero di Fibonacci, si vede che l'inizializzazione deve essere: h l' l i = 1; penult = 0; ultimo = 1;
18/12/11 1.27 ASD-11-12 - Lez.15 20

10

La procedura completa
static long fibonacci(int n) { long penult = 0; long ultimo = 1; if(n <= 0) return 0; for(int i = 1; i < n; i++) { long nuovoUltimo = ultimo + penult; penult = ultimo; ultimo = nuovoUltimo; } return ultimo; } osserva che vecchio ultimo uguale a nuovoUltimo - penult quindi si potrebbe scrivere: long nuovoUltimo = ultimo + penult; penult = nuovoUltimo penult; ultimo = nuovoUltimo;
18/12/11 1.27 ASD-11-12 - Lez.15 21

La procedura completa
static long fibonacci(int n) { long penult = 0; long ultimo = 1; if(n <= 0) return 0; for(int i = 1; i < n; i++) { long nuovoUltimo = ultimo + penult; penult = ultimo; ultimo = nuovoUltimo; } return ultimo; } osserva che vecchio ultimo uguale a nuovoUltimo - penult quindi si potrebbe scrivere: long nuovoultimo = ultimo + penult; penult = nuovoultimo penult; ultimo = nuovoUltimo;
18/12/11 1.27 ASD-11-12 - Lez.15 22

11

L'n-esimo numero di Fibonacci: algoritmo iterativo. Versione finale.


static long fibonacci(int n) { long penult = 0; long ultimo = 1; if(n <= 0) return 0; for(int i = 1; i < n; i++) { ultimo = ultimo + penult; penult = ultimo - penult;// = vecchio ultimo } return ultimo; }

18/12/11 1.27

ASD-11-12 - Lez.15

23

La sequenza di Fibonacci: versione finale.


Naturalmente se, come nel caso della Mole Antonelliana, vogliamo in output non solo l'n-esimo numero, ma l'intera sequenza dei primi n numeri di Fibonacci, non calcoliamo separatamente ogni numero ! PRECOND: n > 1 static long[] sequenzaDiFib(int n) { // sequenza degli n+1 numeri di Fibonacci da fib(0) a fib(n) n++; long[] a = new long[n]; a[0] = 0; a[1] = 1; for(int i = 2; i < n; i++) a[i] = a[i-1] + a[i-2]; return a; }
18/12/11 1.27 ASD-11-12 - Lez.15 24

12

Numeri di Fibonacci arbitrariamente grandi


Per superare la limitazione della dimensione fissa del tipo long, si pu usare la classe BigInteger che permette di trattare interi di grandezza arbitraria (vedi docum. Java): static BigInteger bigFibonacci(int n) { BigInteger penult = ZERO; BigInteger ultimo = ONE; if(n <= 0) return ZERO; for(int i = 2; i <= n; i++) { p ultimo = ultimo.add(penult); penult = ultimo.subtract(penult); } return ultimo; }
18/12/11 1.27 ASD-11-12 - Lez.15 25

Esercizio
Si scriva una versione della procedura sequenzaDiFib che restituisca un array di BigInteger.

18/12/11 1.27

ASD-11-12 - Lez.15

26

13

Osservazione
Ci vuol dire che la versione ricorsiva di un algoritmo pu avere complessit asintoticamente peggiore della versione iterativa ? pp , g m NO ! Nonostante le apparenze, l'algoritmo ricorsivo un algoritmo DIVERSO da quello iterativo. Il fatto che l'algoritmo ricorsivo PI NATURALE esponenziale. possibile scrivere una procedura ricorsiva di complessit lineare, corrispondente all'algoritmo iterativo (vedi slide seguente). seguente)

18/12/11 1.27

ASD-11-12 - Lez.15

27

L'n-esimo numero di Fibonacci: procedura ricorsiva di complessit lineare.


La procedura ricorsiva deve restituire gli ultimi due numeri di Fibonacci, non solo l'ultimo ! D f Definiamo allora una semplice funzione ausiliaria la quale, ll l f l l l data una coppia [Fi-1, Fi] di numeri di Fibonacci consecutivi, restituisce la nuova coppia [Fi, Fi+1] di numeri di Fibonacci: static long[] nextFib(long[] dueNum) { return new long[] {dueNum[1], dueNum[0] + dueNum[1] }; } Definiamo ora la funzione ricorsiva static long[] fibDue(int n) che restituisce la coppia di numeri di Fibonacci [Fn-1, Fn].

(continua nella slide successiva)

18/12/11 1.27

ASD-11-12 - Lez.15

28

14

L'n-esimo numero di Fibonacci: procedura ricorsiva di complessit lineare.


base della ricorsione (base dell'induzione): fibDue(1) = [F0, F1] = [0, 1] passo: assumiamo che fibDue(n 1) restituisca [Fn-2, Fn-1]; 2 1 allora nextFib(fibDue(n 1)) restituisce [Fn-1, Fn] . static long[] fibDue(int n) { if(n == 1) return new long[]{0,1}; else return nextFib(fibDue(n-1)); } La f funzione principale l l long fib(int n) sar ovviamente: f b( ) static long fibRicLin(int n) { if(n == 0) return 0; else return fibDue(n)[1]; }
18/12/11 1.27 ASD-11-12 - Lez.15 29

possibile fare di meglio ?


possibile calcolare fib(n) in tempo inferiore a (n) ? Naturalmente perch sia possibile bisognerebbe riuscire a calcolare l'n-esimo numero di Fibonacci senza calcolare tutti gli n-1 numeri di Fibonacci precedenti. Attenzione: il problema quindi quello di trovare l'n-esimo l l ll l' numero di Fibonacci, NON la sequenza dei primi n numeri di Fibonacci. Il secondo problema ovviamente si pu risolvere solo con il banale algoritmo lineare ricordato prima. Naturalmente risolvendo il secondo problema si risolve anche il primo ! Noi siamo per ora interessati a risolvere il secondo senza necessariamente risolvere il primo: cio a trovare l'n-esimo numero di Fibonacci senza trovare tutti quelli prima. Ci pu avere senso se vogliamo conoscere solo fib(n) per un certo n molto grande.
18/12/11 1.27 ASD-11-12 - Lez.15 30

15

Un approccio basato sull'analisi matematica


Possiamo usare una funzione matematica che d direttamente i numeri di Fibonacci. Si pu infatti dimostrare che:

dove:
18/12/11 1.27 ASD-11-12 - Lez.15 31

Algoritmo fibonacci1

Osserviamo per che tale funzione matematica utilizza dei numeri reali irrazionali, che sono degli enti matematici ideali contenenti una quantit infinita di informazione (le infinite cifre decimali, non periodiche) e che quindi non possono essere rappresentati esattamente nel calcolatore.

18/12/11 1.27

ASD-11-12 - Lez.15

32

16

Infatti: fibonacci1 corretto?


Qual laccuratezza di , e 5 necessaria per ottenere un risultato corretto ? Ad esempio, con 3 cifre decimali:

n 3 16 18

fibonacci1(n) 1.99992 986.698 2583.1

arrotondamento 2 987 2583

Fn 2 987 2584

18/12/11 1.27

ASD-11-12 - Lez.15

33

Fibonacci per mezzo delle matrici.


Ricordando la definizione di prodotto di matrici

a 11 a12 b11 a 21 a 22 b 21
si ha:

b12 a11b11 + a 12 b 21 a11b12 + a 12 b 22 = b 22 a 21b11 + a 22 b 21 a 21b12 + a 22 b 22

Fn Fn-1 1 Fn-1 + 1 Fn-2 1 Fn-2 + 1 Fn-3 1 1 Fn-1 Fn-2 ... ... = = ... ... F ... ... n-2 Fn-3
allora:

Fn Fn-1 1 Fn-1 +1 Fn-2 1 Fn-2 +1 Fn-3 1 1 Fn-1 Fn-2 F F = 1 F + 0 F 1 F + 0 F = 1 0 F n -2 n -2 n-3 n-2 Fn-3 n-1 n-2 n-1
18/12/11 1.27 ASD-11-12 - Lez.15 34

17

Si ha dunque:

Fn Fn-1 1 1 Fn-1 Fn-2 = F Fn-2 1 0 Fn-2 Fn-3 n-1


inoltre: La matrice unit 1, elemento neutro del prodotto di matrici, :

F2 F1 1 1 F F = 1 0 1 0

1 0 F1 F0 X Y = = 1= 0 1 0 1 Z W
n1

Quindi per n 1 si ha:

Fn Fn-1 1 1 = F Fn-2 1 0 n-1


18/12/11 1.27 ASD-11-12 - Lez.15

35

Si pu calcolare Fibonacci come potenza di matrice


static long fibMatrixIngenuo(int n) { if(n <= 0) return 0; Matrix M = 1 1
1 0

Matrix R = 1; for(int i = 1; i < n; i++) R = R M; return R00; } Il numero di passi ancora li m ssi lineare i n; per di pi, ciascun in ; i i s passo pi costoso che nell'algoritmo banale, poich consiste in una moltiplicazione di matrici invece che in una semplice somma di interi ! Ma ...
18/12/11 1.27 ASD-11-12 - Lez.15 36

18

... si pu usare l'algoritmo dell'esponenziale veloce !


static long fibMatrix(int n) { Matrix M = 1 1
1 0

Matrix R = 1; if(n if( <= 0) return 0 0; n--; while(n > 0) { if(n%2 == 0) { M = M M; n = n/2; } else { R = R M; n--; } } return R00; }18/12/11 1.27
ASD-11-12 - Lez.15 37

La procedura java
static long fibMatrix(int n) { if(n <= 0) return 0; long[][] m = {{1,1}, {1,0}}; long[][] ris = ...; n--; while(n > 0) { ... } return ris[0][0]; } Esercizio: Si completi il codice della procedura fibMatrix definendo prima, e poi utilizzando, una procedura long[][] moltiplica(long[][] a, long[][] b) che effettua la moltiplicazione di due matrici 22.
18/12/11 1.27 ASD-11-12 - Lez.15 38

19

Il prodotto di due matrici 22 richiede un tempo costante, indipendente da n. Come nell'esponenziale veloce di semplici numeri, il tempo di calcolo perci proporzionale al numero di iterazioni del ciclo while. Il tempo quindi logaritmico in n: T(n) = (log n) Poich il prodotto di due matrici 22 pi costoso della somma di due interi, la costante moltiplicativa nascosta in (log n) maggiore di quella nascosta in (n): TfibStandard( ) C1 n (n) TfibMatrix( ) C2 l n (n) log con C2 > C1 Per n sufficientemente grande, fibMatrix pi veloce dell'algoritmo lineare standard.
18/12/11 1.27 ASD-11-12 - Lez.15 39

Conclusioni

Esercizio
Si scriva una versione della procedura fibMatrix che restituisca un BigInteger, utilizzando quindi matrici di BigInteger.

18/12/11 1.27

ASD-11-12 - Lez.15

40

20

Potrebbero piacerti anche