Un tipico esempio è la definizione del fattoriale n! di un numero n, la cui definizione è la seguente:

Documenti analoghi
Un tipico esempio è la definizione del fattoriale n! di un numero n, la cui definizione è la seguente:

esegui Ricerca_binaria (metà sinistra di A, v) esegui Ricerca_binaria (metà destra di A, v)

Ricorsione. DD cap. 5 pp KP cap. 5 pp

Corso di Fondamenti di Programmazione canale E-O. Un esempio. Funzioni ricorsive. La ricorsione

LA RICORSIONE LA RICORSIONE LA RICORSIONE: ESEMPIO LA RICORSIONE: ESEMPIO LA RICORSIONE: ESEMPIO LA RICORSIONE: ESEMPIO

LA RICORSIONE! Una funzione matematica è definita ricorsivamente quando nella sua definizione compare un riferimento a se stessa

LA RICORSIONE. Una funzione matematica è definita ricorsivamente quando nella sua definizione compare un riferimento a se stessa

Tempo e spazio di calcolo (continua)

Tempo e spazio di calcolo (continua)

Esempio: il fattoriale di un numero fact(n) = n! n!: Z! N n! vale 1 se n " 0 n! vale n*(n-1)! se n > 0. Codifica:

Esercizi di Algoritmi e Strutture Dati

Operativamente, risolvere un problema con un approccio ricorsivo comporta

Nell informatica esistono alcuni problemi particolarmente rilevanti, poiché essi:

Ricorsione. Stefano Ferrari. Università degli Studi di Milano Programmazione. anno accademico

Fondamenti di Programmazione

Operativamente, risolvere un problema con un approccio ricorsivo comporta

La ricorsione. Sommario. Fulvio CORNO - Matteo SONZA REORDA Dip. Automatica e Informatica Politecnico di Torino

Introduzione alla Ricorsione

Programmazione ricorsiva

Una funzione matematica è definita ricorsivamente quando nella sua definizione compare un riferimento a se stessa

LA RICORSIONE LA RICORSIONE

Algoritmi e Strutture Dati

DIPARTIMENTO DI ELETTRONICA, INFORMAZIONE E BIOINGEGNERIA INFORMATICA B + C

Algoritmi e Strutture Dati

Algoritmi e Strutture Dati

DIPARTIMENTO DI ELETTRONICA, INFORMAZIONE E BIOINGEGNERIA. INFORMATICA B Ingegneria Elettrica. La ricorsione

Algoritmi e Strutture Dati

Algoritmi e Strutture Dati. Capitolo 1 Un introduzione informale agli algoritmi

Algoritmi e Strutture Dati

Programmazione ricorsiva.

Capitolo 19. Ricorsione

Programmazione ricorsiva: cenni

Programmazione dinamica Primi esempi

Programmazione a Oggetti e JAVA. Prof. B.Buttarazzi A.A. 2012/2013

LABORATORIO DI PROGRAMMAZIONE 2 Corso di laurea in matematica. Algoritmi ricorsivi

Laboratorio di Programmazione M-Z

Informatica per Statistica Riassunto della lezione del 24/10/2012

Note per la Lezione 6 Ugo Vaccaro

AA LA RICORSIONE

Corso di Informatica

5) Equazioni di ricorrenza

Algoritmi e Strutture Dati

Informatica Generale Andrea Corradini Algoritmi: ordinamento per inserimento e ricorsione

Parte 2. Ricorsione. [M.C.Escher Drawing hands, 1948] - AA. 2012/13 2.1

RICORSIONE. Informatica B - A.A. 2013/2014

Lezione 4. Problemi trattabili e soluzioni sempre più efficienti. Gianluca Rossi

Il principio di induzione. La Ricorsione. Il fattoriale: iterativo. Il fattoriale: ricorsivo. P (0) P (n) P (n + 1) per ogni n

Corso di Fondamenti di Informatica. La ricorsione

Algoritmi e Strutture Dati. Luciano Gualà

Algoritmo di ordinamento sul posto che ha tempo di esecuzione :

3 aprile o Esonero: mercoledi 17 aprile ore 11:30 14:00 consulta la pag. WEB alla voce esoneri. si raccomanda la puntualita!

INFORMATICA A. Titolo presentazione sottotitolo. Laboratorio n 5 Ing. Gian Enrico Conti Dott. Michele Zanella

Ricorsione: C era una volta un Re

UNIVERSITÀ DEGLI STUDI DI PAVIA FACOLTÀ DI INGEGNERIA. Matlab: esempi ed esercizi

Sommario della lezione:

Pile Le pile: specifiche e realizzazioni attraverso rappresentazioni sequenziali e collegate. Pile e procedure ricorsive

Fondamenti di Informatica T. Linguaggio C: Stack e Ricorsione

Fondamenti di Informatica - 1. Prof. B.Buttarazzi A.A. 2011/2012

RICORSIONE RICORSIONE

Il paradigma divide et impera. Paolo Camurati Dip. Automatica e Informatica Politecnico di Torino

Ricorsione in C. slides credit Prof. Paolo Romano

Ricorsione. Lucidi a cura di Gianpaolo Cugola, Carlo Ghezzi, Gian Pietro Picco Dipartimento di Elettronica e Informazione

n n 1 n = > Il calcolo del fattoriale La funzione fattoriale, molto usata nel calcolo combinatorio, è così definita

Algoritmi e Strutture Dati. Luciano Gualà

CORSO DI PROGRAMMAZIONE

1) Somma dei primi n numeri naturali: 3) Ricerca di un elemento el in una sequenza di interi: FONDAMENTI DI INFORMATICA II Ricorsione 2

FUNZIONI RICORSIVE PROGRAMMAZIONE RICORSIVA: Esempi di problemi ricorsivi:

Il Branch & Bound. Definizione 1. Sia S R n. La famiglia S = {S 1, S 2,..., S k S} tale che S 1 S 2 S k = S viene detta suddivisione di S.

ESERCITAZIONI DI INTRODUZIONE AGLI ALGORITMI (A.A. 08/09)

Moltiplicazione veloce di interi

A. Ferrari Informatica

ma può aiutare La ricorsione all opera

Corso di Perfezionamento

PROGRAMMAZIONE STRUTTURATA

Divide et impera (Divide and Conquer) Dividi il problema in sottoproblemi piu` semplici e risolvili ricorsivamente

Ricorsione. quindi si può definire n! mediante i seguenti due punti:

Esercizi di Algoritmi e Strutture Dati

Fondamenti di Informatica

IEIM Esercitazione IX Puntatori, Enumerazione e Ricorsione. Alessandro A. Nacci -

INFORMATICA A. Titolo presentazione sottotitolo. Laboratorio n 6 Dott. Michele Zanella Ing. Gian Enrico Conti

Lezione di Laboratorio di Prgrammazione: /05/2019 a.a. 2018/2019 R.Prevete

Pensiero Algoritmico. Lezione 3 23 Novembre Ripasso. Anatomia di un programma. Anatomia di un programma. Ozalp Babaoglu Università di Bologna

CAPITOLO 2. Divide et Impera

Laboratorio di Informatica

Esercitazione 7 Algorithmi e Strutture Dati (Informatica) A.A 2015/2016

Sommario. Tecniche di Decomposizione dei Problemi. Algoritmi: Costruzione. Tipi di problemi

Terzo allenamento. Olimpiadi Italiane di Informatica - Selezione territoriale

PROGRAMMAZIONE DINAMICA. Prof. Reho Gabriella Olimpiadi di Informatica

Analisi asintotica. Astrazione: come il tempo di esecuzione cresce in funzione della taglia dell input asintoticamente.

Analisi algoritmi ricorsivi e relazioni di ricorrenza

Note per la Lezione 4 Ugo Vaccaro

liceo B. Russell PROGRAMMAZIONE INDIRIZZO: SCIENTIFICO SCIENZE APPLICATE BIENNIO: SECONDA DISCIPLINA: INFORMATICA

Funzioni in C. Informatica Generale - Funzioni in C Versione 1.0, aa p.1/25

Ricerca di Massimo e Minimo di un Array

(X (, Y, Y Z ) Z " Z Z

Note sull algoritmo di Gauss

corso regionale di preparazione per le OII Giorgio Audrito 30 Settembre 2010

Transcript:

Pag 36 4) La ricorsione 4.1 Funzioni matematiche ricorsive Partiamo da un concetto ben noto, quello delle funzioni matematiche ricorsive. Una funzione matematica è detta ricorsiva quando la sua definizione è espressa in termini di se stessa. Un tipico esempio è la definizione del fattoriale n! di un numero n, la cui definizione è la seguente: n! = n*(n 1)! Se n > 0 0! = 1 In questa definizione la prima riga determina il meccanismo di calcolo ricorsivo vero e proprio, ossia stabilisce come, conoscendo il valore della funzione per un certo numero intero, si calcola il valore della funzione per l intero successivo. La seconda riga della definizione invece determina un altro aspetto fondamentale, il caso base: in questo caso esso indica che, per il numero 0, il valore della funzione fattoriale è 1. Una funzione matematica ricorsiva deve sempre avere un caso base, altrimenti non è possibile calcolarla poiché si riapplicherebbe all infinito la definizione ricorsiva, senza mai iniziare a effettuare il calcolo vero e proprio. 4.2 Algoritmi ricorsivi Nel campo degli algoritmi vi è un concetto del tutto analogo, quello degli algoritmi ricorsivi: un algoritmo è detto ricorsivo quando è espresso in termini di se stesso. Analogamente, una funzione (o una procedura) viene detta ricorsiva quando all interno del corpo della funzione vi è una chiamata alla funzione (o procedura) stessa. Un algoritmo ricorsivo ha sempre queste proprietà: la soluzione del problema complessivo è costruita risolvendo (ricorsivamente) uno o più sottoproblemi di dimensione minore e successivamente producendo la soluzione complessiva mediante combinazione delle soluzioni dei sottoproblemi;

Pag 37 la successione dei sottoproblemi, che sono sempre più piccoli, deve sempre convergere ad un sottoproblema che costituisca un caso base (detto anche condizione di terminazione), incontrato il quale la ricorsione termina. 4.2.1 Calcolo del fattoriale Una possibile funzione (non ricorsiva) che calcoli il fattoriale è la seguente: Funzione Fattoriale_Iter (n: intero non negativo) if (n > 0) fatt 1 for i=1 to n do fatt fatt*i return fatt return 1 Vediamo ora una funzione ricorsiva per il calcolo del fattoriale che riflette molto fedelmente la definizione matematica sopra vista: Funzione Fattoriale_Ric (n: intero non negativo) if (n > 0) return n*fattoriale (n 1) return 1 E fondamentale assicurarsi che le funzioni ricorsive abbiano almeno un caso base, altrimenti la loro esecuzione genererebbe una catena illimitata di chiamate ricorsive che non terminerebbe mai (provocando ben presto la terminazione forzata dell elaborazione a causa dell esaurimento delle risorse di calcolo, per ragioni che vedremo fra breve). Bisogna quindi essere assolutamente certi che il caso base non possa essere mai saltato durante l esecuzione. Nulla vieta di prevedere più di un caso base, basterà assicurarsi che uno tra i casi base sia sempre e comunque incontrato.

Pag 38 Ad esempio, la seguente formulazione della funzione non terminerebbe mai se le venisse passato come valore iniziale un intero negativo: Funzione Fattoriale_Infinita (n: intero) if (n = 0) return 1 return n*fattoriale (n 1) Ma come si sviluppa il procedimento di calcolo effettivo? La figura seguente illustra sinteticamente il flusso dell esecuzione che ha luogo a seguito della chiamata Fattoriale_Ric(4): Fattoriale(4) Fattoriale(3) Fattoriale(2) Fattoriale(1) Caso base Fattoriale(0) calcolo 0! calcolo 1! calcolo 2! calcolo 3! calcolo 4! Si noti che nel momento in cui facciamo la prima operazione di calcolo vera e propria, il che avviene solo quando abbiamo raggiunto il caso base n = 0, vi è

Pag 39 una catena di chiamate della funzione ricorsiva (caselle gialle) ancora aperte, cioè chiamate della funzione che sono già iniziate ma non ancora terminate. Dopo aver effettuato il calcolo del caso base la chiamata della funzione Fattoriale(0) temina e innesca la catena (caselle verdi) dei successivi ritorni alle funzioni chiamanti, che via via effettuano il loro calcolo e terminano. Ora, ogni funzione, sia essa ricorsiva o no, richiede per la sua esecuzione una certa quantità di memoria RAM, per: caricare in memoria il codice della funzione; passare i parametri alla funzione; memorizzare i valori delle variabili locali della funzione. Quindi le funzioni ricorsive in generale hanno maggiori esigenze, in termini di memoria, delle funzioni non ricorsive (dette anche funzioni iterative). Per inciso, questa è la ragione per la quale una funzione ricorsiva mal progettata, nella quale la condizione di terminazione non si incontra mai, esaurisce con estrema rapidità la memoria disponibile con la conseguente inevitabile terminazione forzata dell esecuzione. Inoltre, va detto che qualsiasi problema risolvibile con un algoritmo ricorsivo può essere risolto anche con un algoritmo iterativo. Ma allora, in quali situazioni è consigliabile utilizzare un algoritmo ricorsivo anziché uno iterativo? Queste considerazioni possono essere d aiuto nella decisione: è conveniente utilizzare la ricorsione quando essa permette di formulare la soluzione del problema in un modo che è chiaro ed aderente alla natura del problema stesso, mentre la soluzione iterativa è molto più complicata o addirittura non evidente; non è conveniente utilizzare la ricorsione quando esiste una soluzione iterativa altrettanto semplice e chiara; non è conveniente utilizzare la ricorsione quando l efficienza è un requisito primario. La ricorsione può essere diretta o indiretta (detta anche mutua ricorsione): si ha ricorsione diretta nel caso in cui nel corpo di una funzione vi è la chiamata alla funzione stessa (come nel caso del calcolo del fattoriale sopra visto); si ha ricorsione indiretta quando nel corpo di una funzione A vi è la chiamata a una funzione B ed in quello di B vi è la chiamata alla funzione A; nella ricorsione indiretta

Pag 40 possono in generale essere coinvolte più di due funzioni: ad esempio A chiama B, B chiama C, C chiama A. 4.2.2 Ricerca sequenziale ricorsiva Progettiamo ora un algoritmo ricorsivo per la soluzione del problema della ricerca sequenziale che abbiamo definito nel par. 3.1. Un idea può essere questa. Per risolvere il problema su n elementi: ispezioniamo l n- esimo elemento; se non è l elemento cercato risolviamo il problema - ricorsivamente - sui primi (n 1) elementi. La formulazione di questo algoritmo ricorsivo è la seguente. Funzione Ricerca_sequenziale_ricorsiva(A: vettore; v: intero; n: intero) if (A[n] = v) return true if (n = 1) return false return Ricerca_sequenziale_ricorsiva(A, v, n - 1) Notiamo che nel corpo della funzione si attiva una sola chiamata ricorsiva, quindi la successione delle chiamate segue uno schema simile a quello del par. 4.2.1. Nel prossimo capitolo valuteremo il costo computazionale di questo algoritmo.

Pag 41 4.2.3 Ricerca binaria ricorsiva Formuliamo ora più precisamente la versione ricorsiva della ricerca binaria: Funzione Ricerca_binaria_ricorsiva(A: vettore; v:intero; i_min, i_max: intero) if (i_min > i_max) return null m!_!"#!!_!"#! if (A[m] = v) return m if (A[m] > v) return Ricerca_binaria_ricorsiva(A, v, i_min, m - 1) return Ricerca_binaria_ricorsiva(A, v, m + 1, i_max) La formulazione ricorsiva della ricerca binaria mantiene praticamente tutte le buone proprietà della versione iterativa. Infatti: anche in questo caso nel corpo della funzione si attiva una sola chiamata ricorsiva, dato che le due chiamate sono alternative l una all altra; quindi la successione delle chiamate segue uno schema simile a quello del par. 4.2.1; ogni nuova chiamata ricorsiva riceve un sottoproblema da risolvere la cui dimensione è circa la metà di quello originario quindi, come nella versione iterativa, si giunge molto rapidamente al caso base. Da ciò deriva un costo computazionale che, come vedremo, è O(log n) nel caso peggiore come per la ricerca binaria iterativa. L unica differenza è una maggiore richiesta di memoria, dovuta al problema già descritto delle catene di chiamate ricorsive aperte, compensata però dalla notevole chiarezza e leggibilità della soluzione.

Pag 42 4.2.4 Calcolo dei numeri di Fibonacci Vediamo ora un altro esempio di algoritmo ricorsivo, il calcolo dei numeri di Fibonacci, per evidenziare un altro aspetto importante. Ogni numero di Fibonacci è la somma dei due numeri di Fibonacci precedenti: F(0) = 0 F(1) = 1 F(i) = F(i 1) + F(i 2) se i > 1 Una funzione ricorsiva che li calcola è la seguente: Funzione Fibonacci (n: intero non negativo) if ((n = 0) oppure (n = 1)) return n return (Fibonacci(n 1) + (Fibonacci(n 2)) Notiamo che, nel corpo della funzione, ci sono due chiamate alla funzione stessa, non una sola come nel caso precedente. Questo si riflette in una più complessa articolazione delle chiamate di funzione, che è illustrata, per n = 5, nella figura seguente. Fibonacci(5) Fibonacci(4) Fibonacci(3) Fibonacci(3) Fibonacci(2) Fibonacci(2) Fib.(1) Fibonacci(2) Fib.(1) Fib.(1) Fib.(0) Fib.(1) Fib.(0) Fib.(1) Fib.(0) Questa figura ci consente di focalizzare due aspetti importanti.

Pag 43 Il primo è relativo al costo computazionale. Come vedremo più precisamente quando avremo gli strumenti (equazioni di ricorrenza) necessari, il costo computazionale è elevato: cresce esponenzialmente in funzione del valore dato in input. La ragione per cui questo accada si può intuire già ora osservando che uno stesso calcolo viene ripetuto molte volte: ad esempio, nella figura, Fibonacci(2) viene richiamata 3 volte e Fibonacci(1) 5 volte. Questo deriva da una proprietà non molto desiderabile della soluzione ricorsiva proposta: in sostanza, per risolvere il problema su n dobbiamo risolvere altri due problemi di costo computazionale ben poco inferiore: quello su (n 1) e quello su (n 2). Il secondo aspetto che vogliamo sottolineare è relativo all occupazione dello spazio di memoria, legata alle chiamate di funzione. Il numero totale delle chiamate effettuate dall inizio alla fine dell elaborazione cresce molto velocemente con n. Inoltre, quando si arriva a ciascun caso base vi è una catena di chiamate aperte lungo il percorso che va dalla chiamata iniziale Fibonacci(n)al caso base in questione. La seguente tabella dà un idea del numero totale di chiamate in funzione di n. n F(n) Numero di chiamate di funzione 0 0 1 1 1 1 2 1 3 3 2 5 23 28657 92735 24 46368 150049 25 75025 242785 41 165580141 535828591 42 267914296 866988873 Considerando i costi già citati legati all attivazione di una nuova chiamata di funzione, tutto questo fiorire di chiamate e relativi ritorni non è consigliabile,

Pag 44 soprattutto perché può facilmente essere evitato mediante il ricorso a un algoritmo iterativo, come il seguente: Funzione Fibonacci_iterativa (n: intero non negativo) if ((n = 0)oppure (n = 1)) return n fib_prec_prec 0 fib_prec 1 for (i = 2 to n) fib fib_prec + fib_prec_prec fib_prec_prec fib_prec fib_prec fib; return fib che, banalmente, ha un costo computazionale di Ө(n) e si risolve con una sola chiamata di funzione.