Fondamenti di informatica Oggetti e Java Luca Cabibbo Ricorsione Capitolo 22 marzo 2004 1 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Definizioni induttive La ricorsione è una tecnica di programmazione basata sulla definizioni di metodi e classi (tipi) ricorsivi la ricorsione è connessa alla nozione matematica di induzione un insieme è definito in modo induttivo se è definito in termini di se stesso una funzione è definita in modo induttivo se è definita in termini di se stessa 2 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Definizione induttiva di insiemi Un insieme definito in modo induttivo è un insieme definito in termini di se stesso l’insieme N dei numeri naturali 0 N se n N, allora anche n+1 N l’insieme S delle stringhe su un alfabeto di caratteri "" S se CAR e S S, allora anche CAR+S S l’insieme delle espressioni Java sugli interi l’insieme delle istruzioni di Java Nella definizione induttiva di un insieme è possibile identificare uno o più casi base uno o più casi induttivi 3 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Definizione induttiva di funzioni Una funzione definita in modo induttivo è una funzione definita in termini di se stessa nella definizione induttiva di una funzione è possibile identificare uno o più casi base uno o più casi induttivi i diversi casi partizionano il dominio della funzione 4 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Esempio — la funzione fattoriale La funzione fattoriale N! (N fattoriale), con N naturale, è definita in modo “iterativo” come segue (con l’avvertenza che 0! vale 1) N! = 12 ... (N-2)(N-1)N Definizione induttiva della funzione fattoriale 1 se N=0 – caso base N (N-1)! se N>0 – caso induttivo N! = 5 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Esempio — la funzione cifre Sia cifre(N) una funzione, definita per N naturale, tale che cifre(N) è il numero di cifre decimali nella rappresentazione decimale del numero naturale N ad esempio, cifre(765) vale 3, cifre(8) e cifre(0) valgono 1 Definizione induttiva della funzione cifre(N), con N naturale (il simbolo / indica la divisione intera) 1 se N<10 1 + cifre(N/10) se N>=10 cifre(N) = 6 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Esempio — massimo comun divisore Massimo comun divisore di due numeri interi positivi mcd(N, M) = 7 N (oppure M) se N=M mcd(N-M, M) se N>M mdc(N, M-N) se N<M Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Metodi ricorsivi La ricorsione è una tecnica di programmazione utile ed efficace nell’implementazione di operazioni (metodi) su insiemi e tipi di dato definiti in modo induttivo un metodo ricorsivo è un metodo che, direttamente o indirettamente, può invocare se stesso 8 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Definizione di metodi ricorsivi N! = 1 se N=0 – caso base N (N–1)! se N>0 – caso induttivo /* Calcola il fattoriale del numero naturale n. */ public static int fattoriale(int n) { // pre: n>=0 int nfatt; // il fattoriale di n if (n==0) // caso base nfatt = 1; else // caso induttivo nfatt = n * fattoriale(n-1); return nfatt; } 9 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Esempio — il metodo cifre 1 se N<10 1 + cifre(N/10) se N>=10 cifre(N) = /* Calcola il numero di cifre nella rappresentazione * decimale di n. */ public static int cifre(int n) { // pre: n>=0 int c; // il numero di cifre nella // rappresentazione decimale di n if (n<10) // caso base c = 1; else // caso induttivo c = 1 + cifre(n/10); return c; } 10 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Esecuzione di metodi ricorsivi Che cosa accade durante l’esecuzione di fattoriale(2)? l’esecuzione dei metodi ricorsivi viene gestita con il modello basato su pila di attivazione e record di attivazione un diverso record di attivazione per ciascuna differente attivazione di fattoriale esecutore fattoriale punto di ritorno a n nfatt 11 Ricorsione … m, ..., ... 2 Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Esecuzione di metodi ricorsivi Calcolo di fattoriale(2) 12 esecutore fattoriale punto di ritorno b n nfatt … a, ..., ... 1 esecutore fattoriale punto di ritorno a n nfatt … m, ..., ... 2 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Esecuzione di metodi ricorsivi Calcolo di fattoriale(2) 13 esecutore fattoriale punto di ritorno c n nfatt … b, ..., ... 0 esecutore fattoriale punto di ritorno b n nfatt … a, ..., ... 1 esecutore fattoriale punto di ritorno a n nfatt … m, ..., ... 2 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Esecuzione di metodi ricorsivi Calcolo di fattoriale(2) 14 esecutore fattoriale punto di ritorno c n nfatt … b, ..., ... 0 1 esecutore fattoriale punto di ritorno b n nfatt … a, ..., ... 1 esecutore fattoriale punto di ritorno a n nfatt … m, ..., ... 2 Ricorsione 1 Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Esecuzione di metodi ricorsivi Calcolo di fattoriale(2) 15 esecutore fattoriale punto di ritorno b n nfatt … a, ..., ... 1 1 esecutore fattoriale punto di ritorno a n nfatt … m, ..., ... 2 Ricorsione 1 Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Esecuzione di metodi ricorsivi Calcolo di fattoriale(2) esecutore fattoriale punto di ritorno a n nfatt 16 Ricorsione … m, ..., ... 2 2 2 Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Progettazione di metodi ricorsivi Linee guida per la progettazione di metodi ricorsivi determina i casi base determina i casi induttivi verifica che i casi base e induttivi partizionano l’insieme dei dati di ingresso per il metodo usa una istruzione condizionale per selezionare qual è il caso corrente può essere utile dichiarare una variabile per memorizzare il risultato del metodo talvolta è necessario scrivere anche un metodo per avviare la ricorsione (mostrato più avanti) 17 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Esempio — il metodo mcd mcd(N, M) = N (oppure M) se N=M mcd(N-M, M) se N>M mdc(N, M-N) se N<M /* Calcola il massimo comun divisore di n e m. */ public static int mcd(int n, int m) { // pre: n>0 && m>0 int mcd; // massimo comun divisore di n e m /* calcola il massimo comun divisore di n e m * usando l'algoritmo di Euclide */ if (n==m) // caso base mcd = n; else if (n>m) // caso induttivo mcd = mcd(n-m, m); else // altro caso induttivo, n<m mcd = mcd(n, m-n); return mcd; } 18 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Errori comuni Alcuni errori comuni nella scrittura di metodi ricorsivi errori nella gestione dei casi base e induttivi errori nella definizione del caso base errori nella definizione del caso induttivo 19 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Errori nella gestione dei casi Uso di una sequenza di istruzioni if /* Calcola il fattoriale del numero naturale n. */ public static int fattoriale(int n) { // pre: n>=0 int nfatt; // il fattoriale di n if (n==0) // caso base nfatt = 1; if (n>0) // caso induttivo, SBAGLIATO nfatt = n * fattoriale(n-1); return nfatt; } 20 il metodo non viene compilato correttamente Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Errori nella gestione dei casi Un errore simile /* Calcola il fattoriale del numero naturale n. */ public static int fattoriale(int n) { // pre: n>=0 int nfatt; // il fattoriale di n if (n==0) // caso base nfatt = 1; else if (n>0) // caso induttivo, SBAGLIATO nfatt = n * fattoriale(n-1); return nfatt; } 21 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Errori nella gestione dei casi Un possibile errore nella gestione dei casi /* Calcola il fattoriale del numero naturale n. */ public static int fattoriale(int n) { // pre: n>=0 int nfatt; // il fattoriale di n if (n==1) // caso base, SBAGLIATO nfatt = 1; else // caso induttivo nfatt = n * fattoriale(n-1); return nfatt; } 22 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Errori nella definizione del caso base Un possibile errore nella definizione del caso base /* Calcola il fattoriale del numero naturale n. */ public static int fattoriale(int n) { // pre: n>=0 int nfatt; // il fattoriale di n if (n==0) // caso base nfatt = 0; // NO, sbagliato else // caso induttivo nfatt = n * fattoriale(n-1); return nfatt; } 23 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Errori nella definizione del caso induttivo Un possibile errore nella definizione del caso induttivo /* Calcola il fattoriale del numero naturale n. */ public static int fattoriale(int n) { // pre: n>=0 int nfatt; // il fattoriale di n if (n==0) // caso base nfatt = 1; else // caso induttivo nfatt = n * fattoriale(n); // NO, sbagliato return nfatt; } 24 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Ricorsione lineare e non lineare Ricorsione lineare ciascuna attivazione del metodo ricorsivo causa direttamente al massimo un’altra attivazione ricorsiva dello stesso metodo Ricorsione non lineare una attivazione del metodo ricorsivo può causare direttamente due o più ulteriori attivazioni ricorsive dello stesso metodo la pila di attivazione può evolvere in modo complesso 25 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Esempio — numeri di Fibonacci I numeri di Fibonacci sono definiti, per N naturale, come segue fib(N) = 1 se N=0 1 se N=1 fib(N-1) + fib(N-2) se N>1 ad esempio, fib(3) = fib(2)+fib(1) = (fib(1)+fib(0))+fib(1) = 3 /* Calcola l'n-esimo numero di Fibonacci. */ public static int fib(int n) { // pre: n>=0 int f; // n-esimo numero di Fibonacci if (n==0 || n==1) // casi base f = 1; else // caso induttivo f = fib(n-1) + fib(n-2); return f; } 26 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Tipi ricorsivi Un tipo ricorsivo è un tipo definito in termini di se stesso Esempio si vuole definire una classe CorpoCeleste per modellare i corpi celesti (il sole, i pianeti e i satelliti) del sistema solare di ciascun corpo celeste si vogliono rappresentare le seguenti proprietà il nome – una stringa il centro di rotazione del corpo celeste centroRotazione – che è un altro corpo celeste (è di tipo CorpoCeleste) 27 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Corpi celesti — oggetti Alcuni oggetti CorpoCeleste mars nome = "marte" centroRotazione moon earth nome = "luna" centroRotazione 28 nome = "terra" centroRotazione Ricorsione sun nome = "sole" centroRotazione = null Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Corpi celesti — variabili d’istanza La classe CorpoCeleste ha una definizione ricorsiva /* Un oggetto CorpoCeleste rappresenta un corpo celeste. */ class CorpoCeleste { /* Il nome del corpo celeste. */ private String nome; /* Il centro di rotazione del corpo celeste, * ovvero il corpo celeste intorno a cui * avviene la rotazione. */ private CorpoCeleste centroRotazione; ... altro ... } 29 qual è il caso base? Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Tipi ricorsivi e metodi ricorsivi Nel caso dei corpi celesti, si vuole scrivere il metodo String toString() che calcola una descrizione di un corpo celeste la descrizione deve comprendere sia il nome del corpo celeste che una descrizione del centro di rotazione del corpo celeste sun.toString() deve restituire "sole" earth.toString() deve restituire "terra (ruota intorno a sole)" moon.toString() deve restituire "luna (ruota intorno a terra (ruota intorno a sole))" 30 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl CorpoCeleste — String toString() /* Calcola la descrizione testuale * di questo corpo celeste. */ public String toString() { String descrizione; descrizione = nome; if (centroRotazione!=null) // caso induttivo descrizione += " (ruota intorno a " + centroRotazione.toString() + ")"; return descrizione; } 31 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Ricorsione e stringhe L’insieme S delle stringhe su un alfabeto di caratteri "" S se CAR e S S, allora anche CAR+S S Come definire metodi ricorsivi che operano su stringhe? solitamente il caso base è relativo alla stringa vuota il caso induttivo è relativo a stringhe non vuote, considerate formate da un carattere seguito da una stringa primo(S) e resto(S) 32 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Esempio — inversione di stringhe La funzione inversa(S) rappresenta la stringa inversa di S la stringa ottenuta da S invertendone l’ordine dei caratteri "" se S è vuota inversa(S) = inversa(resto(S)) + primo(s) se s è non vuota 33 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Esempio — inversione di stringhe /* Calcola l’inversa della stringa s, ovvero la stringa * ottenuta da s invertendone l’ordine dei caratteri. */ public static String inversa(String s) { // pre: s!=null String inv; // l’inversa di s if (s.length()==0) // caso base inv = ""; else // caso induttivo inv = inversa(s.substring(1)) + s.charAt(0); return inv; } 34 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Esempio – uguaglianza tra stringhe /* Verifica se le stringhe s e t sono uguali. */ public static boolean uguali(String s, String t) { // pre: s!=null && t!=null boolean ug; // s e t sono uguali if (s.length()==0 && t.length()==0) // caso base /* entrambe le stringhe sono vuote */ ug = true; else if (s.length()>0 && t.length()>0) // caso induttivo /* entrambe le stringhe sono non vuote */ ug = (s.charAt(0)==t.charAt(0)) && uguali(s.substring(1), t.substring(1)); else // altro caso base /* solo una delle stringhe è vuota */ ug = false; return ug; } 35 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Ricorsione e array Per gestire gli array in modo ricorsivo in genere la ricorsione non viene effettuata sugli array ma piuttosto sugli indici usati per accedere l’array 36 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Somma degli elementi di un array di interi /* Calcola la somma degli elementi dell'array a. */ public static int somma(int[] a) { // pre: a!=null /* calcola la somma degli elementi di a * a partire da quello di indice 0 */ return somma(a, 0); } /* Calcola la somma degli elementi di a che hanno indice * maggiore o uguale a i. */ private static int somma(int[] a, int i) { // pre: a!=null && i>=0 && i<=a.length int s; // la somma degli elementi di a // che hanno indice maggiore o uguale a i if (a.length<=i) // caso base: "porzione" vuota s = 0; else // caso induttivo: "porzione" non vuota s = a[i] + somma(a, i+1); return s; } 37 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Ricerca binaria L’algoritmo di ricerca binaria può anche essere implementato in modo ricorsivo /* Ricerca chiave nell'array a ordinato in modo * non descrescente, restituendo l'indice di un * elemento di a uguale a chiave (oppure -1). * Implementazione ricorsiva della ricerca binaria. */ public static int ricercaBinaria(int[] a, int chiave) { // pre: a!=null && // a è ordinato in modo non decrescente /* cerca chiave tra tutti gli elementi di a */ return ricBinRic(a, chiave, 0, a.length); } 38 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Ricerca binaria /* Verifica se a contiene un elemento uguale a chiave * di indice tra sinistra (compreso) e destra (escluso). */ private static int ricBinRic(int[] a, int chiave, int sinistra, int destra) { int posizione; // posizione di un elemento di a // uguale a chiave, se esiste int centro; // indice dell'elemento centrale dello // spazio di ricerca /* cerca l'indice di un elemento di a, tra sinistra * e destra, uguale a chiave */ centro = (sinistra+destra)/2; if (sinistra>=destra) // la ricerca è fallita posizione = -1; else if (a[centro]==chiave) // trovato! posizione = centro; else if (a[centro]>chiave) // continua a sinistra posizione = ricBinRic(a, chiave, sinistra, centro); else // continua a destra posizione = ricBinRic(a, chiave, centro+1, destra); return posizione; } 39 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Quando usare (o non usare) la ricorsione Per ogni metodo ricorsivo, è sempre possibile implementare un metodo iterativo che si comporta in modo equivalente in quali casi è opportuno risolvere un problema in modo ricorsivo e in quali casi è opportuno risolverlo in modo iterativo? alcuni criteri per scegliere la ricorsione può essere usata in modo naturale nel calcolo di funzioni definite induttivamente e nell’elaborazione di dati di tipo ricorsivo e di insiemi definiti in modo induttivo tempo di esecuzione e occupazione di memoria costo (umano) della soluzione del problema 40 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Esempio — contenuto di un file system Nei file system di un calcolatore, i documenti sono memorizzati in una struttura gerarchica per file si intende un documento oppure una cartella Si vuole scrivere un metodo che, dato un file (documento o cartella), stampa la struttura del file system a partire da quel file progetti c:\progetti c:\progetti\alfa alfa beta gamma.txt c:\progetti\alfa\alfa.doc c:\progetti\beta alfa.doc beta.doc budget.xls c:\progetti\beta\beta.doc c:\progetti\beta\budget.xls c:\progetti\gamma.txt 41 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl La classe File Va usata la classe File del package java.io un oggetto File è una rappresentazione astratta di un file (documento o cartella) e del suo nome ad esempio, di c:\progetti o di c:\progetti\gamma.txt è possibile creare un oggetto File a partire dal nome del file da rappresentare il metodo String toString() restituisce il nome del file ad esempio, la stringa c:\progetti\gamma.txt il metodo boolean exists() verifica se il file esiste il metodo boolean isFile() verifica se il file esiste ed è un documento il metodo boolean isDirectory() verifica se il file esiste ed è una cartella se il file è una cartella, il metodo File[] listFiles() restituisce l’elenco dei file contenuti nella cartella 42 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Contenuto di un file system Si vuole scrivere un metodo void dir(File f) che visualizza il contenuto del file system a partire da f ad esempio dir( new File("c:\\Progetti") ); Il metodo void dir(File f) può essere implementato ricorsivamente casi possibili f non esiste – !f.exists() f è un documento – f.isFile() f è una cartella – f.isDirectory() 43 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Il metodo void dir(File f) /* Visualizza il contenuto del file system * a partire dal file f. */ public static void dir(File f) { // pre: f!=null File[] elencoFile; // elenco dei file contenuti in f, // se f è una cartella int i; // indice per la scansione di elencoFile /* stampa il contenuto del file system a partire da f */ if (f.isFile()) { // caso base /* f è un documento, stampane il nome */ System.out.println(f.toString()); } else if (f.isDirectory()) { // caso induttivo /* f è una cartella, stampane il nome * e, ricorsivamente, il contenuto */ System.out.println(f.toString()); elencoFile = f.listFiles(); for (i=0; i<elencoFile.length; i++) dir(elencoFile[i]); } /* altro caso base, f non esiste */ } 44 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl Esercizio Esercizio (difficile) (provare a) scrivere un metodo non ricorsivo equivalente al metodo void dir(File f) 45 Ricorsione Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies srl