30
1

1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

Embed Size (px)

Citation preview

Page 1: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

1

Page 2: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

2

Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco

Per capire bene come opera una ricorsione non lineare si può tracciare un albero che mostra la successione delle chiamate o un albero che mostra la storia dello stack.

Una ricorsione si definisce lineare quando si ha al massimo una chiamata ricorsiva per blocco

Ricorsione lineare e non lineare

Page 3: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

3

Supponiamo di avere il seguente codice:

// MAINint main(){ int N; cout<<"main chiama A"<<endl;problemaA(1);cout<<"A ritorna a main"<<endl;cout<<"main chiama C(1)"<<endl;problemaC(1);cout<<"C(1) ritorna a main"<<endl;cout<<"main chiama Fine"<<endl;cout<<" Fine"<<endl;system("pause"); }

void problemaA(int N) { cout<<"A chiama B"<<endl;

problemaB(N); cout<<"B ritorna A"<<endl;}

void problemaB(int N) { cout<<"C("<<N<<") chiama C("<<N-1 <<") “<<endl; cout<<"B chiama C(1)"<<endl;

problemaC(1); cout<<" C(1) ritorna a B )"<<endl; }

void problemaC(int N) {if (N>0)

{cout<<"C("<<N<<") chiama C("<<N-1<<")"<<endl; problemaC(N-1);

cout<<" C("<<N-1<<") ritorna a C("<<N<<")"<<endl; }

}

Page 4: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

4

problema A

problema B

problema C(1)

problema C(0)

MAINproblema C(1)

problema C(0)

Possiamo rappresentarlo mediante il grafico di figura

Page 5: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

5

main chiama A

A chiama B

B chiama C(1)

C(1) chiama C(0)

C(0) ritorna a C(1)

C(1) ritorna a B

B ritorna A

A ritorna a main

main chiama C(1)

C(1) chiama C(0)

C(0) ritorna a C(1)

C(1) ritorna a main

main chiama Fine

Fine

int main(){ int N; cout<<"main chiama A"<<endl;problemaA(1);cout<<"A ritorna a main"<<endl;cout<<"main chiama C(1)"<<endl;problemaC(1);cout<<"C(1) ritorna a main"<<endl;cout<<"main chiama Fine"<<endl;cout<<" Fine"<<endl;system("pause"); } // DEFINIZIONIvoid problemaC(int N){ if (N>0) { cout<<" C("<<N<<") chiama C("<<N-1<<")"<<endl; problemaC(N-1); cout<<" C("<<N-1<<") ritorna a C("<<N<<")"<<endl; }}void problemaB(int N) { cout<<" B chiama C(1)"<<endl; // cout<<" C("<<N<<") chiama C("<<N-1<<")"<<endl;

problemaC(1); cout<<" C(1) ritorna a B )"<<endl; }void problemaA(int N) { cout<<" A chiama B"<<endl;

problemaB(N); cout<<" B ritorna A"<<endl; }

A sinistra è possibile leggere l’output generato dal codice posto a destra

Page 6: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

6

main

C(0) ritorna a C(1)

C(1) ritorna a B

B ritorna A

A ritorna a main

C(1) ritorna a main

C(0) ritorna a C(1)

main chiama A

AA chiama B

B

B chiama C(1)

C(1)

C(1) chiama C(0)

C(0)

Fine ritorna a main

main chiama C(1)

C(1)

C(1) chiama C(0)

C(0)

main chiama Fine

Rappresentiamo le chiamate alle varie function con il grafico di figura

Page 7: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

7

C(0)

main

A

B

C(1)

C(1)

C(0)

fine

Allegato: La ricorsione non lineare

Più sinteticamente rappresentato dall’albero seguente:

Page 8: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

8

Intorno all’anno 1170 a Pisa un commerciante di nome Bonaccio Pisano ebbe un figlio che chiamò Leonardo. Per motivi di lavoro si trasferì presto a Algeri dove Leonardo crebbe, studiò e imparò l’arabo avendo dei precettori musulmani. Da costoro imparò la matematica indo-arabica, il sistema decimale con la notazione posizionale delle cifre e l’uso dello zero.

Leonardo Pisano scrisse il primo libro di matematica occidentale il

“Liber Abaci” introducendo le cifre arabe

Affascinato dal mondo e dalla cultura araba dove ogni persona ha nel suo cognome anche il nome del padre aggiunse al suo nome di Leonardo il cognome Filius Bonacci trasformato per brevità in

Fibonacci.

La successione di Fibonacci

Page 9: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

9

La copia del Liber Abaci posseduta dalla Biblioteca Nazionale di Napoli

Page 10: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

10

Problema

Nel 1228 messer Leonardo Pisano detto Fibonacci si pose il

seguente problema:

posta una coppia di conigli in un recinto supponiamo che questa

coppia dopo due mesi generi un’altra coppia e successivamente ne

generi una al mese. La seconda coppia a sua volta dopo altri due

mesi ne genera un’altra e così via .

Dopo un anno, cioè dopo 12 mesi quante coppie di conigli ci

saranno nel recinto?

Page 11: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

11

2 1

3 2

4 3

5 5

6 8

7 13

La successione di Fibonacci

1 0

Page 12: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

12

La successione di numeri interi che così si ottiene è detta successione di Fibonacci.E’ possibile rinvenire tale successione in molti esempi presenti in natura.Nella diapositiva successiva è mostrato il caso delle spirali dei girasole e dei cammini che un ape può percorrere per andare dalla prima cella di un alveare alla n-esima, supponendo che tutte le celle siano di forma esagonale.

Page 13: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

13

ALCUNI CASI STRANI

L’ape può raggiungere la cella n seguendo FIB(n+1) percorsi diversi

Girasole con 53 (FIB(10)) spirali antiorarie e 89 (FIB(11)) orarie

Page 14: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

14

La funzione FIB(N) che calcola i numeri di Fibonacci relativa ad un intero N obbedisce ad una regola molto semplice che si può descrivere in modo ricorsivo:Se N=0 allora FIB(N)=0Se N=1 allora FIB(N)=1Se N>1 allora FIB(N)=FIB(N-1)+FIB(N-2)La funzione FIB(N) si presta ad essere subito tradotta nel seguente codice

Osserviamo che in una stessa riga di codice abbiamo due chiamate ricorsive (FIB(N-1)+FIB(N-2))

int FIB(int N) {if (N==0) return 0;else if (N==1) return 1; else return FIB(N-1)+FIB(N-2);}

Page 15: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

15

Siamo di fronte ad una ricorsione non lineare quando nell’ambito di uno stesso processo ricorsivo si ha più di una chiamata ricorsiva.Pur nella sua semplicità, l’algoritmo mostrato consuma molte risorse: per numeri grandi c’è il pericolo di uno stack overflow. Per mostrare questo aspetto, introduciamo una nuova variabile, chiama, che ci restituisca il numero di volte in cui la funzione richiama se stessa; tale variabile, passata per riferimento, viene aggiunta alla lista dei parametri formali. int FIB(int N, int &chiama)

{chiama++;if (N==0) return 0;else if (N==1) return 1; else return FIB(N-1, chiama)+FIB(N-2, chiama);}

Page 16: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

16

Come precedentemente introdotto è possibile rappresentare

l’evoluzione di un processo ricorsivo mostrando l’albero delle

successive chiamate ricorsive.

Quest’albero è mostrato nel lucido che segue per FIB(4).

Nel lucido succesivo è mostrato l’albero per FIB(6) e una

corrispondente rappresentazione dello stack relativo ai processi

ricorsivi man mano aperti.

Page 17: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

17

main

F(3)

F(1) F(2)

F(4)

F(2)

Fibonacci(4)

F(1) F(0)

F(1) F(0)

main

F(3)

F(1) F(2)

F(1) F(0)

Fibonacci(3)

int FIB(int N, int &chiama) {chiama++;if (N==0) return 0;else if (N==1) return 1; else return FIB(N-1, chiama)+FIB(N-2,chiama);}

Albero delle chiamate ricorsive di FIB(3) e FIB(4)

Page 18: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

18F(6)

F(5)F(4) +

F(2) F(3)+

F(2) F(1)+F(0)F(1) + 01 +

1 F(3)+

F(0)F(1) + 01 +

2

1 1+

3

F(3)

F(1)

F(2)

F(4)

F(5)

F(3)

F(6)

F(1) F(2)

F(3)

F(4)

F(2)

F(2)F(0)F(1)

F(0)

F(1)

F(0)F(1) F(1) F(0)F(0)F(1)

int FIB(int N) {if (N==0) return 0;else if (N==1) return 1; else return FIB(N-1)+FIB(N-2);}

Albero delle chiamate ricorsive di FIB(6)

stack

Page 19: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

19

Complessità dell’algoritmo per il calcolo dei numeri di Fibonacci

E’ stato dimostrato che per N abbastanza grande la complessità di F(N), cioè il numero di chiamate ricorsive, è circa O(1.61N).

N 1,61^N 30 MFLOP 1 TERAFLOP

1 2

10 117

20 13.694 sec

40 187.514.580 6,3 sec

50 21.942.888.767 12,2 min

100 481.490.367.451.071.000.000 508.931 anni 508

200 231.832.973.948.168.000.000.000.000.000.000.000.000.000 3E+26 anni 3E+23

Page 20: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

20

// La successione di fibonacci ricorsiva#include <iostream>using namespace std;

// PROTOTIPIdouble FIB(int , double &); // MAINint main(){ double N;double Tot; do { cout<<"Assegna N "<<endl; cin>>N; Tot=0; cout<<" Il numero di fibonacci "<<N<<" = "<< FIB(N,Tot)<<"chiamate = "<< Tot

<<endl; }while (N>=0); system("pause"); }

// DEFINIZIONI

double FIB(int N, double &chiama) {chiama++;if (N==1) return 1;else if (N==2) return 1; else return FIB(N-1,chiama)+FIB(N-2,chiama);}

Il codice che segue mostra il calcolo della successione di Fibonacci per un preassegnato valore e il numero di chiamate ricorsive necessarie.

Page 21: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

21

Fibonacci(5) = 5 chiamate= 9Fibonacci(10) = 55 chiamate= 109Fibonacci(15) = 610 chiamate= 1.219Fibonacci(20) = 6.765 chiamate= 13.529Fibonacci(25) = 75.025 chiamate= 150.049Fibonacci(30) = 832.040 chiamate= 1.664.080Fibonacci(35) = 9.227.465 chiamate= 18.454.900Fibonacci(40) = 102.334.155 chiamate= 204.668.000Fibonacci(45) = 1.134.903.170 chiamate= 2.269.810.000

Output del codice precedente per diversi valori di N.

Page 22: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

22

Per ridurre da O (1.61N) a O(N) la complessità di calcolo per i numeri di Fibonacci invece di chiamare ricorsivamente la procedura di calcolo per ogni nodo dell’albero dello stack depositiamo i risultati di ogni computazione in un array e li richiamiamo, senza più calcolarli ogni volta che ne abbiamo bisogno.

Detto FibNos l’array in cui si depositano i numeri parziali possiamo costruire una procedura ricorsiva alla seguente maniera:caso baseQuando N=2 allora poni

FibNos[0] 0FibNos[1] 1FibNos[2] 1

CHIAMATA RICORSIVA

FibNos[N] FibNos[N-2] + FibNos[N-1]

Page 23: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

23

void FibVett(int N, double FibNos[]){ if (N==2) { FibNos[0] =0; FibNos[1] =1; FibNos[2] =1; } else FibVett(N-1,FibNos);

FibNos[N]=FibNos[N-2] + FibNos[N-1];

SvantaggioSiamo limitati dalla dimensione dell’array per determinare N massimo

F(3)

F(1) F(2)

F(4)

F(2)

FibVetNo(4)

FibVetNo(3)

FibVetNo(2)

FibNos[4]=3

FibNos[0]=0

FibNos[1]=1

FibNos[2]=1

FibNos[3]=2

Di seguito mostriamo il codice, l’albero ricorsivo, e lo stack delle chiamate

fibonacci

Page 24: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

24

double fibo(double N, double prec, double att, double i, double &chiama){ double temp; if (i<N) { temp=prec+att; prec=att; att=temp; chiama++; return fibo(N,prec,att,i+1,chiama); } else return att; }

int main () { double Num; cout<<" Quale numero di fibonacci desideri ? "; cin>>Num; double i=2, p=1, chiama=0; cout<<" Il fibonacci di "<<Num<<" e' "<<fibo(Num,p,a,i,chiama)<<" passi “<<chiama << endl;

system("pause");}

Un’altra soluzione sempre di complessità lineare sfrutta due variabili di appoggio ed è illustrata di seguito.

Page 25: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

25N=6, prec=1, att=1, i=2

N=6, prec=1, att=2, i=3

N=6, prec=2, att=3, i=4

Allegato: fibonacci

Lo stack ricorsivo di fibo(6)

double fibo(double N, double prec, double att, double i, double &chiama){ double temp; if (i<N) { temp=prec+att; prec=att; att=temp; chiama++; return fibo(N,prec,att,i+1,chiama); } else return att; }

N=6, prec=3, att=5, i=5

N=6, prec=5, att=8, i=6 att=8

+

Page 26: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

26

ESERCIZIO

Detto Fn l’ennesimo numero di Fibonacci scrivere un programma che calcoli l’espressione: A=F

n+1* F

n-1 - F2

n

Mostrare i valori di A per N=1,2,3,4,5,6,7,8,9,10

Allegato: fibondiff

Page 27: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

27

Esercizio

1- Assegnato un intero N, calcolare il rapporto tra le coppie di numeri di Fibonacci F(n+1)/F(n) e per ogni coppia sottrarre a detto rapporto la radice positiva dell’equazione

x2-x-1=0.

2- Calcolare il MCD tra la somma dei numeri di Fibonacci compresi tra 1 e 10 e quelli compresi tra 11 e 20compresi tra 11 e 20 e quelli compresi tra 21 e 30compresi tra 21 e 30 e quelli compresi tra 31 e 40

Testo consigliato da leggere MARIO LIVIO – La sezione aurea

Page 28: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

28

Calcolare con una funzione ricorsiva l’N-esimo termine della successione seguente

a1=3a2=1a3=-1

an=an-1*an-2-(an-3+an-1+an-2)

Caso base -> S(1)=3, S(2)=1, S(3)=-1

Chiamata ricorsiva -> S(N)=S(N-1)*S(N-2)-(S(N-3)+S(N-1)+S(N-2))

ESERCIZIO

Di seguito è riportato il codice corrispondente.

Page 29: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

29

/*Calcolare con una funzione ricorsiva l’N-esimo termine della successione seguentea1=3a2=1a3=-1an=an-1*an-2-(an-3+an-1+an-2)*/

double succ(double N) {if (N==1) return 3;else if (N==2) return 1; else if (N==3) return -1; else return succ(N-1)*succ(N-2)-(succ(N-3)+succ(N-1)+succ(N-2));

Page 30: 1. 2 Una ricorsione si definisce non lineare quando si ha più di una chiamata ricorsiva per blocco Per capire bene come opera una ricorsione non lineare

30

Riscrivere la procedura precedente rendendola lineare