Programmazione a Oggetti e JAVA Prof. B.Buttarazzi A.A. 2012/2013
Sommario La ricorsione Metodi ricorsivi Esercizi proposti 16/01/2013 2
La ricorsione In Java ogni metodo può chiamare anche se stesso, secondo una tecnica detta ricorsione. Ovviamente tali metodi devono sempre contenere un istruzione di controllo che ha il compito di interrompere la successione delle chiamate, se si verificano certe condizioni. 3
Il calcolo del fattoriale La funzione fattoriale, molto usata nel calcolo combinatorio, è così definita n! 1 n( n 1)! se se n n 0 0 dove n è un numero intero non negativo 4.11.2004 4
Confronto fra definizione iterativa e ricorsiva Funzione non ricorsiva n!= 1*2*3*...*(n-1)*n fatt(n)= 1*2*3*...*(n-1)*n; Funzione ricorsiva 0!=1 n!= n*(n-1)!; fatt(n)=n*fatt(n-1) 5
Il calcolo del fattoriale Vediamo di chiarirne il significato 0! = 1 1! = 1(1-1)! = 1 0! = 1 1 = 1 2! = 2(2-1)! = 2 1! = 2 1 = 2 3! = 3(3-1)! = 3 2! = 3 2 1 = 6 4! = 4(4-1)! = 4 3! = 4 3 2 1 = 24 5! = 5(5-1)! = 5 4! = 5 4 3 2 1 = 120 Quindi, per ogni n intero positivo, il fattoriale di n è il prodotto dei primi n numeri interi positivi
Il calcolo del fattoriale Per implementare la funzione per il calcolo del fattoriale con un metodo statico iterativo siamo costretti a fare un analisi della definizione per scrivere l algoritmo 7
Il calcolo del fattoriale Per implementare la funzione per il calcolo del fattoriale con un metodo statico iterativo siamo costretti a fare un analisi della definizione per scrivere l algoritmo: public static float fattoriale (int n) { float F=1; int i=1; do { F=F*i; i=i+1; } while (i<=n); return F; 8
Il calcolo del fattoriale Implementando direttamente la definizione ricorsiva, è naturale scrivere: { public static float fattoriale (int n) } float f; if (n == 0) f = 1; else f = n * fattoriale(n - 1); return f;
Il calcolo del fattoriale Implementando direttamente la definizione ricorsiva, è naturale scrivere: { public static float fattoriale (int n) float f; if (n == 0) f = 1; else f = n * fattoriale(n - 1); return f; } Questa soluzione si basa sulla invocazione di un metodo mentre si esegue il metodo stesso! Questa possibilità è permessa in tutti i linguaggi di programmazione che ammettono la ricorsione
Il calcolo del fattoriale La funzione ricorsiva fattoriale non necessita di iterazioni, ma include internamente il calcolo del risultato, infatti restituisce 1 se il parametro ricevuto uguale a 0 mentre in caso contrario il valore restituito è il prodotto di n per il fattoriale (n-1), pertanto fattoriale calcola il valore da restituire chiamando se stessa e "passandosi" come parametro il parametro appena ricevuto, diminuito di uno. { public static float fattoriale (int n) float f; if (n == 0) f = 1; else f = n * fattoriale(n - 1); return f; } Questa soluzione si basa sul fatto invocare un metodo mentre si esegue il metodo stesso! Questa possibilità è lecito in tutti i linguaggi di programmazione che ammettono la ricorsione
La ricorsione Per capire come utilizzare correttamente la ricorsione, vediamo innanzitutto come funziona. Quando un metodo ricorsivo invoca se stesso, la macchina virtuale Java esegue le stesse azioni che vengono eseguite quando viene invocato un metodo qualsiasi sospende l esecuzione del metodo invocante (le variabili locali rimangono congelate) esegue il metodo invocato fino alla sua terminazione (con nuove variabili locali e passando i parametri per valore) riprende l esecuzione del metodo invocante dal punto in cui era stata sospesa (recuperando le variabili locali) 4.11.2004 12
La ricorsione Vediamo la sequenza delle istruzioni per calcolare 3! si invoca f(3) f(3) lascia in sospeso il calcolo 3* f(2) e invoca f(2) f(2) lascia in sospeso il calcolo 2* f(1) invoca f(1) f(1) lascia in sospeso il calcolo 1* f(0) e invoca f(0) f(0) restituisce 1 a f(1) f(1) calcola1*1 e restituisce 1 a f(2) f(2) calcola 2*1 e restituisce 2 a f(3) f(3) calcola 3*2 e restituisce 6 al chiamante Si crea quindi una pila ( stack) di metodi in attesa, ciascuno con le sue variabili locali, che si allunga e che poi si accorcia fino ad esaurirsi
La ricorsione Invocare un metodo mentre si esegue lo stesso metodo è un paradigma di programmazione che si chiama ricorsione e un metodo che ne faccia uso si chiama metodo ricorsivo La ricorsione è uno strumento molto potente per realizzare alcuni algoritmi (ma è anche fonte di errori di difficile diagnosi) Esistono infatti due regole ben definite (passo base e passo ricorsivo) che vanno utilizzate per scrivere metodi ricorsivi che funzionino 4.11.2004 14
Prima regola La ricorsione: passo base il metodo ricorsivo deve fornire la soluzione del problema in almeno un caso particolare, senza ricorrere ad una chiamata ricorsiva tale caso si chiama caso base della ricorsione nel nostro esempio, il caso base era if (n == 0) return 1; a volte ci sono più casi base, non è necessario che il caso base sia unico 4.11.2004 15
Seconda regola La ricorsione: passo ricorsivo il metodo ricorsivo deve effettuare la chiamata ricorsiva dopo aver semplificato il problema nel nostro esempio, per il calcolo del fattoriale di n si invoca la funzione ricorsivamente per conoscere il fattoriale di n-1, cioè per risolvere un problema più semplice f = n * fattoriale(n - 1); il concetto di problema più semplice varia di volta in volta: in generale, bisogna avvicinarsi ad un caso base 4.11.2004 16
Ricorsione infinita Si noti quindi che non tutti i metodi ricorsivi realizzano algoritmi! se manca il caso base, il metodo ricorsivo continua ad invocare se stesso all infinito se il problema non viene semplificato ad ogni invocazione ricorsiva, il metodo ricorsivo continua ad invocare se stesso all infinito Dato che la lista dei metodi in attesa si allunga indefinitamente, l ambiente runtime esaurisce la memoria disponibile per tenere traccia di questa lista, e il programma termina con un errore. 4.11.2004 17
Le regole appena viste Ricorsione infinita implicano che ad ogni invocazione il problema diventi più semplice avvicinandosi sempre più al caso base che non richiede ricorsione ovvero che per quanto complesso possa essere il problema la soluzione sia calcolata in un numero finito di passi 4.11.2004 18
Esercizio
Vediamo la codifica di tale funzione in modo iterativo /* algoritmo iterativo */ public static long iterfibonacci(int n){ if(n<=1) return n; long x=0; long y=1; long tmp; for(int i=2; i<=n; i++){ tmp = x; x = y; y = tmp+x; // dopo l'i-esima iterazione: x=f(i-1), y=f(i) } return y; }} Vediamo come si semplifica la soluzione ricorsiva
public static float fibo (int n) { } float fib; if (n == 0) { fib = 0; } else if (n == 1) { fib = 1; } else { fib = fibo(n-1)+ fibo(n-2); } return fib;
public static float fib (int n) { { if (n < 2) { return n; } else { return fib(n-1)+fib(n-2); } } }
.osservazioni La semplicità del codice ha un costo che studieremo quando parleremo di complessità per il momento basta osservare che ad ogni chiamata vengono genereate 2 variabili quindi si occupa più memoria.
Esercizi Scrivere un metodo statico ricorsivo per il calcolo della funzione esponenziale definita come segue: 16/01/2013 24
Esercizi Scrivere un metodo statico ricorsivo per il calcolo della funzione esponenziale definita come segue: 16/01/2013 25
Esercizi Scrivere un metodo statico che, dati un array di interi a ed un intero n, restituisce il numero delle occorrenze di n in a 16/01/2013 26
Esercizi Scrivere un metodo statico che, dati un array di interi a ed un intero n, restituisce la posizione della prima occorrenza di n in a, e -1 se n non compare in a. 16/01/2013 27
Esercizi Scrivere un metodo statico che, dati un array di interi a ed un intero n, restituisce true se n compare in a, false altrimenti. 16/01/2013 28
Esercizi Scrivere un metodo statico che, dati un array bidimensionale di interi a ed un intero n, restituisce true se n compare in a, false altrimenti. 16/01/2013 29
Esercizi Scrivere un metodo statico che dato un array di interi restituisca true se tutti i suoi elementi sono identici, e false altrimenti. 16/01/2013 30
Esercizi Scrivere un metodo che, dati un array bidimensionale di interi a e due interi k ed n, restituisce true se in ogni riga a[i] di a esistono almeno k elementi maggiori di n, altrimenti il metodo restituisce false. 16/01/2013 31
Cosa abbiamo imparato? Come funziona la ricorsione: il processo secondo il quale un metodo può anche richiamare se stesso; Che cos è un metodo ricorsivo: ossia un metodo che fa uso della ricorsione; Che cos è la ricorsione infinita. 16/01/2013 32
Questionario 1)Quali dei seguenti programmi è errato per calcolare il fattoriale di un numero in Java? class Fattoriale { static float fatt(int x) { int i; float f=1; do{ f=f*i; i= i+1; } while(i<=x); return f; }; class Fattoriale { static int fatt(int x) { int i; int f=1; for(i=1; i<=x; i=i+1) { f=f*i; } return f; }; 16/01/2013 33
public class Fattoriale { public static void main(string[] args) { int n, i; long fatt; if (args.length > 0) { n = Integer.parseInt(args[0]);} else { n = MyUtility.readInt("Inserisci N: ");} fatt = 1; for (i = 1; i <= n; i++) { fatt *= i;} System.out.println(n+"! = "+fatt);} }; class Fattoriale { static float fatt(float x) { int i; int f=1; for(i=1; i<=x; i=i+1) { f=f*i;} return f; }; 16/01/2013 34
2)A cosa serve la ricorsione? Ad invocare un metodo di un altra classe; Ad eseguire un metodo uno dopo l altro; Ad invocare un metodo dopo che il metodo stesso è stato eseguito; Ad invocare un metodo mentre si esegue lo stesso metodo. 3)Quali delle seguenti non è un azione che esegue la JVM quando viene invocato un metodo ricorsivo? Sospendere l esecuzione del metodo invocante; Eseguire il metodo invocato fino alla sua terminazione recuperando le variabili locali; Riprendere l esecuzione del metodo chiamante dal punto in cui era stato sospeso; Eseguire il metodo invocato fino alla sua terminazione. 4) Nei programmi scritti nella domanda 1 è presente almeno un passo ricorsivo? Si in tutti i 4 programmi; No in nessuno dei 4 programmi; Solo nel secondo programma; Solo nel terzo programma. 5) Nei programmi scritti nella domanda 1 è presente almeno un caso base? Si in tutti i 4 programmi; No in nessuno dei 4 programmi; Solo nel secondo programma; Solo nel terzo programma. 16/01/2013 35
6)Quali sono le regole che bisogna seguire per scrivere un metodo ricorsivo? Il metodo deve fornire la soluzione del problema in almeno un caso particolare, e deve effettuare la chiamata ricorsiva dopo aver semplificato il problema; Il metodo deve fornire la soluzione del problema in almeno un caso particolare, ricorrendo ad una chiamata ricorsiva e deve effettuare di nuovo la chiamata dopo aver semplificato il problema; Il metodo deve fornire la soluzione del problema in più di un caso particolare, e deve effettuare la chiamata ricorsiva dopo aver semplificato il problema; Il metodo deve semplificare il problema e deve poi effettuare la chiamata ricorsiva. 7)Quale delle seguenti affermazioni sulla ricorsione è esatta? Ad ogni invocazione del metodo il problema diventa sempre più complesso; Anche la soluzione del caso base richiede l uso della ricorsione; Bisogna effettuare la chiamata ricorsiva dopo aver semplificato il problema; Il passo ricorsivo non può essere messo all interno di un espressione. 8)In quale caso non si ha ricorsione infinita? Se il problema viene semplificato ad ogni invocazione ricorsiva; Se manca il caso base; Se il problema non viene semplificato ad ogni invocazione ricorsiva; Se il problema viene semplificato solo alla prima invocazione ricorsiva. 16/01/2013 36
9)Quale delle seguenti è un esempio di funzione ricorsiva? n!=1*2*3* *(n-1)*n; fatt(n)=n*fatt(n-1); fatt(n)= 1*2*3*...*(n-1)*n; 3!=6. 10)Quali dei seguenti programmi non è corretto per calcolare la sequenza di Fibonacci in maniera ricorsiva? class Fibonacci { public static long fibo(long n) { if(n<=1) return n; else return fibo(n-1)+ fibo(n-2); } }; class Fibonacci { public static long fibo(long n) { if(n<=1) return n; else return fibo(n-1)+ fibo(n-2);} public static void main(string args[]) { long n = Integer.parseInt(args[0]); System.out.println("fib(" + n + ")=" + fibo(n));} }; 16/01/2013 37
class Fibonacci { public static long fibo(long n) { long fibn_1 = 0, fibn_2 = 1, fibn=1; if(n<=1) return n; else { for(int i=2; i<=n; i++) { fibn=fibn_1+fibn_2; fibn_1=fibn_2; fibn_2=fibn; } return fibn; } } }; class Fibonacci { public static long fibo(int n) { if(n<=1) return n; else return fibo(n-1)+ fibo(n-2);} }. 11)Nei programmi della domanda 10 è presente almeno un caso base? Si in tutti i 4 programmi; No in nessuno dei 4 programmi; Si nel secondo e nel terzo programma; Si nel primo e nell ultimo; Si in tutti tranne nel terzo; Si in tutti ad eccezione del primo. 16/01/2013 38
12)Nel terzo programma della domanda 10 qual è il caso base? return n; return fibn; if(n<=1) return n; for(int i=2; i<=n; i++) { fibn=fibn_1+fibn_2; fibn_1=fibn_2; fibn_2=fibn; } return fibn. 13) Nei programmi della domanda 10 è presente un passo ricorsivo? Si in tutti i 4 programmi; No in nessuno dei 4 programmi; Si nel secondo e nel terzo programma; Si nel primo e nell ultimo; Si in tutti tranne nel terzo; Si in tutti ad eccezione del primo. 14) Qual è il passo ricorsivo presente nel secondo programma della domanda 10? return fibo(n-1)+ fibo(n-2); fibo(n-1)+ fibo(n-2); long n = Integer.parseInt(args[0]); System.out.println("fib(" + n + ")=" + fibo(n));; if(n<=1) return n; else return fibo(n-1)+ fibo(n-2);. 16/01/2013 39
15) Quali delle seguenti opzioni è corretta per dichiarare un metodo media() che dispone di un tipo restituito double e due parametri interi x e y? double media(x, y) { ; double media( int x, int y) { ; double Media( int x, int y) { ; public double Media(x, y) { ; 16) Per lo studio di quali funzioni può essere utilizzato il metodo della ricorsione? Per il calcolo del fattoriale; Per il calcolo del fattoriale e per la sequenza di Fibonacci; Per il calcolo delle funzioni goniometriche; Per il calcolo del fattoriale, per la sequenza di Fibonacci e per le funzioni goniometriche. 16/01/2013 40
Esercizio Scrivere una programma Java che realizza il gioco indovina che numero ho pensato, descritto come segue: inizialmente, il programma genera un numero casuale intero compreso tra 1 e 100 e lo sceglie, e poi chiede all utente di indovinare il numero scelto dopo ciascun tentativo di risposta dell utente, l applicazione deve segnalare se la risposta data è giusta, oppure il numero è maggiore oppure il numero è minore rispetto al numero scelto il programma deve continuare a chiedere numeri all utente fino a che questi non abbia dato la risposta corretta, oppure abbia scelto di rinunciare ad indovinare (digitando il numero 0) se l utente indovina il numero scelto, il programma deve congratularsi con l utente, visualizzando anche il numero di tentativi fatti dall utente 16/01/2013 41
Ad esempio, se il numero scelto dal programma fosse 21, l interazione tra applicazione e utente potrebbe essere la seguente Ho pensato un numero intero compreso tra 1 e 100. Indovina che numero ho pensato: 40 40 è troppo alto. Indovina che numero ho pensato: 30 30 è troppo alto. Indovina che numero ho pensato: 20 20 è troppo basso. Indovina che numero ho pensato: 22 22 è troppo alto. Indovina che numero ho pensato: 21 Bravo! Hai indovinato facendo 5 tentativi! 16/01/2013 42