Capitolo 11 Ricorsione Lucidi relativi al volume: Java – Guida alla programmazione James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Definizioni ricorsive Una definizione che definisce qualcosa in base a sé stessa è chiamata definizione ricorsiva. I discendenti di una persona sono i figli della persona e tutti i discendenti dei figli della persona. Un elenco di numeri è un numero o un numero seguita da una virgola e un elenco di numeri. Un algoritmo ricorsivo è un algoritmo che invoca sé stesso per risolvere istanze più piccole o più semplici di un problema. Il fattoriale di un numero n è n volte il fattoriale di n-1. Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Fattoriale Una definizione imprecisa 1 n! n (n 1) n0 1 n 1 Una definizione precisa n0 1 n! n (n -1)! n 1 I puntini di sospensione comunicano al lettore di utilizzare l'intuizione per riconoscere lo schema Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Metodi ricorsivi Un metodo ricorsivo possiede generalmente due parti. Una parte di terminazione che ferma la ricorsione. È chiamata caso di base. Il caso di base deve avere una soluzione semplice o banale. Una o più chiamate ricorsive. È chiamato caso ricorsivo. Il caso ricorsivo chiama lo stesso metodo ma con argomenti più semplici o più piccoli. Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Method factorial() public static int factorial(n) { if (n == 0) { Caso di base return 1; } else{ return n * factorial(n-1); } } Il caso ricorsivo gestisce una versione più semplice (piccola) dell'attività Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Method factorial() public static int factorial(n) { if (n == 0) { return 1; } else{ return n * factorial(n-1); } } public static void main(String[] args){ BufferedReader stdin = new BufferedReader( new InputStreamReader(System.in)); int n = Integer.parseInt(stdin.readLine()); int nfactorial = factorial(n); System.out.println(n + “! = “ + nfactorial; } Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Invocazione ricorsiva Un nuovo record di attivazione viene creato per ogni invocazione del metodo Comprese le invocazioni ricorsive main() int nfactorial = factorial(n); Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Invocazione ricorsiva Un nuovo record di attivazione viene creato per ogni invocazione del metodo Comprese le invocazioni ricorsive main() factorial() int n = 3 nfactorial return n * = factorial(n); factorial(n-1); Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Invocazione ricorsiva Un nuovo record di attivazione viene creato per ogni invocazione del metodo Comprese le invocazioni ricorsive main() int nfactorial = factorial(n); factorial() n = 3 return n * factorial(n-1); factorial() n = 2 return n * factorial(n-1); Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Invocazione ricorsiva Un nuovo record di attivazione viene creato per ogni invocazione del metodo Comprese le invocazioni ricorsive main() int nfactorial = factorial(n); factorial() n = 3 return n * factorial(n-1); factorial() n = 2 return n * factorial(n-1); factorial() n = 1 return n * factorial(n-1); Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Invocazione ricorsiva Un nuovo record di attivazione viene creato per ogni invocazione del metodo Comprese le invocazioni ricorsive main() int nfactorial = factorial(n); factorial() n = 3 return n * factorial(n-1); factorial() n = 2 return n * factorial(n-1); factorial() n = 1 return n * factorial(n-1); factorial() n = 0 return 1; Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Invocazione ricorsiva Un nuovo record di attivazione viene creato per ogni invocazione del metodo Comprese le invocazioni ricorsive main() int nfactorial = factorial(n); factorial() n = 3 return n * factorial(n-1); factorial() n = 2 return n * factorial(n-1); factorial() n = 1 return n * factorial(n-1); factorial() n = 0 return 1; Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Invocazione ricorsiva Un nuovo record di attivazione viene creato per ogni invocazione del metodo Comprese le invocazioni ricorsive main() int nfactorial = factorial(n); factorial() n = 3 return n * factorial(n-1); factorial() n = 2 return n * factorial(n-1); factorial() n = 1 factorial() n = 0 return n * 1; return 1; Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Invocazione ricorsiva Un nuovo record di attivazione viene creato per ogni invocazione del metodo Comprese le invocazioni ricorsive main() int nfactorial = factorial(n); factorial() n = 3 return n * factorial(n-1); factorial() n = 2 return n * factorial(n-1); factorial() n = 1 return 1 * 1; Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Invocazione ricorsiva Un nuovo record di attivazione viene creato per ogni invocazione del metodo Comprese le invocazioni ricorsive main() int nfactorial = factorial(n); factorial() n = 3 factorial() n = 2 return n * 1 factorial() n = 1 return 1 * 1; return n * factorial(n-1); Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Invocazione ricorsiva Un nuovo record di attivazione viene creato per ogni invocazione del metodo Comprese le invocazioni ricorsive main() int factorial() n = 3 factorial() n = 2 nfactorial return n * = factorial(n); factorial(n-1); return 2 * 1 Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Invocazione ricorsiva Un nuovo record di attivazione viene creato per ogni invocazione del metodo Comprese le invocazioni ricorsive main() int nfactorial = factorial(n); factorial() n = 3 return n * 2; factorial() n = 2 return 2 * 1; Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Invocazione ricorsiva Un nuovo record di attivazione viene creato per ogni invocazione del metodo Comprese le invocazioni ricorsive main() factorial() int n = 3 nfactorial = factorial(n); return 3 * 2; Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Invocazione ricorsiva Un nuovo record di attivazione viene creato per ogni invocazione del metodo Comprese le invocazioni ricorsive main() factorial() int nfactorial = 6; n = 3 return 3 * 2; Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Invocazione ricorsiva Un nuovo record di attivazione viene creato per ogni invocazione del metodo Comprese le invocazioni ricorsive main() int nfactorial = 6; Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Numeri di Fibonacci Sviluppati da Leonardo Pisano nel 1202. Analisi della velocità con cui i conigli possono figliare in circostanze ideali. Supposizioni Una coppia di conigli maschio e femmina figlia sempre e produce un'altra coppia di conigli maschio e femmina. Un coniglio diventa sessualmente maturo dopo un mese; anche il periodo di gestazione è pari a un mese. Pisano voleva conoscere la risposta alla domanda: quanti conigli esisteranno dopo un anno? Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Numeri di Fibonacci La sequenza generata è: 1, 1, 2, 3, 5, 8, 13, 21, 34, … Il numero di coppie per un mese è uguale alla somma del numero di coppie nei due mesi precedenti. Inserimento dell'equazione di Fibonacci Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Numeri di Fibonacci Pattern del metodo ricorsivo if ( codice di terminazione soddisfatto ) { return value; } else{ esegui chiamata ricorsiva più semplice; } public static int fibonacci(int n) { if (n <= 2) { return 1; } else{ return fibonacci(n-1) + fibonacci(n-2); } } Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Numeri di Fibonacci Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Ricorsione infinita Un errore di programmazione comune nell'utilizzo della ricorsione è non terminare le chiamate ricorsive. Il programma continuerà la ricorsione fino a esaurimento della memoria. Occorre assicurarsi che le chiamate ricorsive siano eseguite su sottoproblemi più semplici o piccoli, e che l'algoritmo abbia un caso di base che termini la ricorsione. Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Ricerca binaria Confrontare la voce con l'elemento centrale dell'elenco. Se la voce corrisponde all'elemento centrale, la voce desiderata è stata individuata e la ricerca termina. Se la voce non corrisponde, allora se la voce è nell'elenco deve trovarsi a sinistra o a destra dell'elemento centrale. L'elenco secondario corretto può essere ricercato utilizzando la stessa strategia. 0 1 2 3 4 5 6 10 24 33 45 56 81 95 entryquesto to this element. If the matches, the la ricerca è ConfrontareCompare la voce con elemento. Seentry corrispondono, If thesignifica entry is less 45, then the in elenco, finita. Se lasearch voce is è successful. minore di 45, che,than se è presente entry, if ait sinistra is in the list, must be to the left in elements 0-2. può solo trovarsi dell'elemento centrale, ossia compresa tra 0 If the entry èis più greater thandi 45,45, then thesolo entry, if it is inathe e 2. Se invece grande può trovarsi destra list, mustcentrale, be to the ossia right incompresa elements 4-6. dell'elemento tra 4 e 6. Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Sviluppo della ricerca per una rubrica public class AddressEntry { private String personName; private String telephoneNumber; public AddressEntry(String name, String number) { personName = name; telephoneNumber = number; } public String getName() { return personName; } public String getNumber() { return telephoneNumber; } public void setName(String Name) { personName = Name; } public void setTelephoneNumber(String number) { telephoneNumber = number; } } Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Ricerca binaria Interfaccia pubblica Dovrebbe essere il più semplice possibile Nessun parametro estraneo public static AddressEntry recSearch(AddressEntry[] addressBook, String name) Interfaccia privata Invocata dall'implementazione dell'interfaccia pubblica Dovrebbe supportare l'invocazione ricorsiva per implementazione dell'interfaccia privata private static AddressEntry recSearch(AddressEntry[] addressBook, String name, int first, int last) Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Ricerca binaria Implementazione dell'interfaccia pubblica public static AddressEntry recSearch(AddressEntry[] addressBook, String name) { return recSearch(addressBook, name, 0, addressBook.length-1); } Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Implementazione dell'interfaccia privata static AddressEntry recSearch(AddressEntry[] addressBook, String name, int first, int last) { // caso di base: se la sezione dell'array è vuota l'elemento non è stato trovato if (first > last) return null; else{ int mid = (first + last) / 2; // se il valore è stato trovato la ricerca termina if (name.equalsIgnoreCase(addressBook[mid].getName())) return addressBook[mid]; else if (name.compareToIgnoreCase( addressBook[mid].getName()) < 0) { // se il valore è presente è nella metà sinistra return recSearch(addressBook, name, first, mid-1); } else // array[mid] < value // se il valore è presente è nella metà destra return recSearch(addressBook, name, mid+1, last); } } } Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Verifica Sviluppare casi di verifica per provare ogni possibile situazione univoca public static void main(String[] args){ // l'elenco deve essere ordinato AddressEntry addressBook[] = { new AddressEntry("Audrey", "434-555-1215"), new AddressEntry("Emily" , "434-555-1216"), new AddressEntry("Jack" , "434-555-1217"), new AddressEntry("Jim" , "434-555-2566"), new AddressEntry("John" , "434-555-2222"), new AddressEntry("Lisa" , "434-555-3415"), new AddressEntry("Tom" , "630-555-2121"), new AddressEntry("Zach" , "434-555-1218") }; Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Verifica Ricerca del primo elemento AddressEntry p; // primo elemento p = recSearch(addressBook, "Audrey"); if (p != null) { System.out.println("Audrey's telephone number is " + p.getNumber()); } else{ System.out.println("No entry for Audrey"); } Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Verifica Ricerca dell'elemento centrale p = recSearch(addressBook, "Jim"); if (p != null) { System.out.println("Jim's telephone number is " + p.getNumber()); } else{ System.out.println("No entry for Jim"); } Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Verifica Ricerca dell'ultimo elemento p = recSearch(addressBook, "Zach"); if (p != null) { System.out.println("Zach's telephone number is " + p.getNumber()); } else{ System.out.println("No entry for Zach"); } Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Verifica Ricerca di un elemento inesistente p = recSearch(addressBook, "Frank"); if (p != null) { System.out.println("Frank's telephone number is " + p.getNumber()); } else{ System.out.println("No entry for Frank"); } Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Efficienza della ricerca binaria L'altezza di una struttura binaria è il numero massimo di confronti necessari per la ricerca in un elenco Una struttura con 31 nodi ha altezza 5 In generale, una struttura con n nodi ha un altezza pari a log2(n+1) La ricerca in un elenco con un miliardo di nodi richiede solo 31 confronti La ricerca binaria è efficiente! Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Mergesort Mergesort è un ordinamento ricorsivo che divide concettualmente il suo elenco di n elementi da ordinare in due elenchi secondari di dimensione n/2. Se un elenco secondario contiene più di un elemento, l'elenco secondario viene ordinato con una chiamata ricorsiva a mergeSort(). Dopo che i due elenchi secondari di dimensione n/2 sono ordinati, vengono uniti per produrre un singolo elenco ordinato di dimensione n. Questo tipo di strategia è detta dividi e conquista: il problema è diviso in sottoproblemi di complessità minore e le soluzioni dei sottoproblemi sono utilizzate per produrre la soluzione generale. Il tempo di esecuzione del metodo mergeSort() è proporzionale a n log n. Questa prestazione è a volte detta prestazione linearitmica. Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Mergesort Si supponga di voler ordinare l'array mostrato. 'Q' 'W' 'E' 'R' 'T' 'Y' 'U' 'I' 'O' 'P' Dopo l'ordinamento dei due elenchi secondari, l'array sarà: 'E' 'Q' 'R' 'T' 'W' 'I' 'O' 'P' 'U' 'Y' Left sorted sublist Right sorted sublist Ora è possibile svolgere l'attività di unione dei due array per ottenere: 'E' 'I' 'O' 'P' 'Q' 'R' 'T' 'U' 'W' 'Y' Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Mergesort Interfaccia pubblica Dovrebbe essere il più semplice possibile Nessun parametro estraneo public static void mergeSort(char[] a) Interfaccia privata Invocata dall'implementazione dell'interfaccia pubblica Dovrebbe supportare l'invocazione ricorsiva per implementazione dell'interfaccia privata private static void mergeSort(char[] a, int left, int right) Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Mergesort private static void mergeSort(char[] a, int left, int right) { if (left < right) { // esistono più elementi da ordinare. // per prima cosa, ordina in modo ricorsivo gli elenchi secondari int mid = (left + right)/2; mergeSort(a, left, mid); mergeSort(a, mid+1, right); Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Mergesort // poi unisce gli elenchi secondari ordinati // nell'array temp char[] temp = new char[right - left + 1]; int j = left;// indice dell'elemento più piccolo a sinistra int k = mid + 1;// indice dell'elemento più piccolo a destra Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Mergesort for (int i = 0; i < temp.length; ++i) { // memorizza l'elemento più piccolo in temp if ((j <= mid) && (k <= right)) { // deve prendere il più piccolo tra a[j] // e a[k] if (a[j] <= a[k]) // sinistra contiene l'elemento più piccolo temp[i] = a[j]; ++j; } else // destra contiene l'elemento più piccolo temp[i] = a[k]; ++k; } } Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Mergesort else if (j <= mid) // può prendere solo da sinistra temp[i] = a[j]; ++j; } else // può prendere solo da destra temp[i] = a[k]; ++k; } } Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Mergesort // infine copia temp in a for (int i = 0; i < temp.length; ++i) { a[left + i] = temp[i]; } } } Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Mergesort L'invocazione considera gli elementi di EQRTWYUIOP con indici compresi nell'intervallo da 5 a 9 e produce EQRTWIOPUY L'invocazione iniziale considera gli elementi di QWERTYUIOP nell'intervallo da 0 a 9 e produce EIOPQRTUWY Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Ricorsione e iterazione L'iterazione può essere più efficiente Sostituisce le chiamate ai metodi con i cicli Viene utilizzata meno memoria (nessun record di attivazione per ogni chiamata) Molti problemi sono risolti più naturalmente con la ricorsione Torri di Hanoi Mergesort La scelta dipende dal problema e dal contesto della soluzione Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Confusione giornaliera Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Confusione giornaliera Attività Genera tutte le possibili permutazioni delle lettere Per n lettere ci sono n! possibilità n scelte per la prima lettera n-1 scelte per la seconda lettera n-2 scelte per la terza lettera … Iteratore Oggetto che produce valori successivi in una sequenza Progettazione Classe iteratore PermuteString che supporta l'enumerazione delle permutazioni Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Classe PermuteString Costruttore public PermuteString(String s) Costruisce un generatore di permutazioni per la stringa s Metodi public String nextPermutation() Restituisce la successiva permutazione della stringa associata public boolean morePermutations() Indica se esiste una permutazione non enumerata della stringa associata Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Classe PermuteString Variabili istanza private String word Rappresenta la parola da permutare private int index Posizione all'interno della parola su cui operare public PermuteString substringGenerator Generatore di sottostringhe Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Costruttore public PermuteString(String s){ word = s; index = 0; if (s.length() > 1) { String substring = s.substring(1); substringGenerator = new PermuteString(substring); } else{ substringGenerator = null; } } Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Considerare Cosa accade? PermuteString p = new PermuteString(“ath“); p word: "ath" index: 0 substringGenerator: word: "th" index: 0 substringGenerator: word: "h" index: 0 substringGenerator: null Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Metodo public boolean morePermuations() { return index < word.length; } Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Metodo public boolean nextPermutation() { if (word.length() == 1) { ++index; return word; } else{ String r = word.charAt(index) + substringGenerator.nextPermutation(); if (!substringGenerator.morePermutations()) { ++index; if (index < word.length()) { String tail = word.substring(0, index) + word.substring(index + 1); substringGenerator = new permuteString(tail); } } return r; } } Java – Guida alla programmazione - James Cohoon, Jack Davidson Copyright © 2004 - The McGraw-Hill Companies srl Confusione giornaliera