Ricorsione
La ricorsione è il processo di definizione di un oggetto o di una operazione
in termini di se stesso.
Un linguaggio di programmazione è ricorsivo se
è possibile che una funzione invochi se stessa.
Un semplice esempio di funzione ricorsiva è quella per il calcolo del fattoriale
che in matematica è definito come
ad esempio
Versione iterativa
#include<iostream>
using namespace std;
int fat(int n){
int f,j; f=1;
for(j=1;j<=n;j++)f=f*j;
return f;
}
main(){
cout<<fat(4);
}
Versione ricorsiva
#include<iostream>
using namespace std;
int rfat(int n){
int f; if(n==1) return 1;
f=rfat(n-1)*n;
return f;
}
main(){
cout<<rfat(5);
}
Il funzionamento della funzione fat() non ricorsiva, dovrebbe essere evidente
: usa un ciclo che inizia da 1 e finisce al valore di n di cui bisogna calcolare
il fattoriale, moltiplicando ogni numero per il risultato parziale. La funzione
rfat() ricorsiva, è più complessa.
Se rfat() viene chiamata con il valore 1, restituisce 1. Se viene chiamata
con un valore diverso da 1, restituisce il prodotto rfat(n-1)*n.
Questa espressione è valutata calcolando ricorsivamente il valore di rfat(n-1).
Il processo prosegue fino a quando n raggiunge il valore 1, dopo di che
iniziano i ritorni dalle funzioni chiamate.
Se calcoliamo il fattoriale di 2, la prima chiamata ad rfat() provoca una
seconda chiamata con il valore 1. Il ritorno da questa seconda chiamata
avviene con il valore 1 , che è poi moltiplicato per 2 (il precedente valore
di n) che è anche il risultato.
Può essere interessante inserire all'interno del listato delle istruzioni
di stampa all'interno della funzione rfat() che possono mostrare il livello
di ricorsione raggiunto ed il valore dei risultati intermedi.
Quando una funzione chiama se stessa , l'elaboratore colloca nello stack
le nuove variabili locali ed i parametri formali: l'esecuzione della funzione
parte dall'inizio del codice ed utilizza queste nuove variabili. Non viene
invece creata una nuova copia del codice della funzione. Al ritorno da ogni
chiamata ricorsiva, i parametri e le variabili allocate al momento della
chiamata corrispondente vengono rimossi l'esecuzione riprende dall'istruzione
seguente alla chiamata della funzione.
Qui sotto il diagramma dello stack per la funzione rfat(3)
Generalmente le funzioni ricorsive non conducono a risparmi nelle dimensioni
del codice o nel numero delle variabili necessarie. Può invece accadere
che alcune funzioni ricorsive siano più lente delle loro equivalenti iterative
(che usano i cicli) a causa del tempo aggiuntivo necessario per le chiamate.
Il problema maggiore delle funzioni ricorsive sta nello spazio richiesto
allo stack ad ogni chiamata .
Se il livello di ricorsione diventa eccessivo , è possibile che lo stack
vada a sconfinare in aree di memoria occupate dal codice o da variabili
locali.
Tuttavia se le funzioni sono state scritte in modo corretto non ci si dovrebbe
preoccupare di questo problema. Il vantaggio delle funzioni ricorsive risiede
nella maggior naturalezza con cui è possibile scrivere alcuni algoritmi
rispetto alle forme iterative; ad esempio uno dei più famosi algoritmi di
ordinamento : il quicksort è di tipo ricorsivo anche se è possibile realizzarlo
in forma iterativa che però è più complessa e macchinosa.
All'interno di una funzione ricorsiva deve esistere un if() che forza l'uscita dalla funzione senza ulteriori chiamate ricorsive, in caso contrario non sarà più possibile uscite dalla funzione una volta entrati.