Sei sulla pagina 1di 6

Funzioni ricorsive

Autore: Manuel Dalcastagnè. Questo materiale è rilasciato secondo la licenza CC Attribution 3.0 Unported
(http://creativecommons.org/licenses/by/3.0/ (http://creativecommons.org/licenses/by/3.0/)).

Una funzione ricorsiva è una funzione contenente chiamate a sè stessa, direttamente o indirettamente
attraverso un'altra funzione, ed è costituita logicamente da due parti:

il caso base, ovvero il sottoproblema più semplice del problema principale e che fa terminare la ricorsione
il passo ricorsivo, che avviene in tutti gli altri casi, quando la funzione ricorsiva divide concettualmente il
problema in due:
il sottoproblema che la funzione sa come risolvere
il sottoproblema che la funzione non sa come risolvere (e che cerca di risolvere chiamando sè stessa,
sfruttando le proprietà del problema)

Esempio 1: fattoriale
𝑛 maggiore di 0 è
Il fattoriale di un intero

𝑛 ⋅ (𝑛–1) ⋅ (𝑛–2) ⋅ ⋯ ⋅ 1
e il fattoriale di 0 è 1. Il fattoriale può essere calcolato iterativamente con un ciclo come

int factorial = 1;
for (int counter = number; counter >= 1; --counter)
factorial *= counter;

𝑛 può essere definito ricorsivamente osservando che


Il fattoriale di

𝑛! = 𝑛 ⋅ (𝑛–1)!
Per esempio, 5! è uguale a 5 * 4!:

5! = 5 ⋅ 4 ⋅ 3 ⋅ 2 ⋅ 1
5! = 5 ⋅ (4!)
Risolvendo questo esempio usando una funzione ricorsiva, si ottiene una cascata di chiamate che termina
quando la funzione ricorsiva raggiunge il caso base (1). A questo punto, a partire dal caso base, ogni chiamata
restituisce un risultato alla funzione chiamante fino a restituire la soluzione del problema originale.
Di seguito la soluzione ricorsiva, che stampa il fattoriale dei numeri da 0 a 10:

#include <stdio.h>

int factorial(int number);

int main(){
for (int i = 0; i <= 10; ++i) {
printf("%d = %d\n", i, factorial(i));
}
}

int factorial(int number){


// caso base
if (number <= 1) {
return 1;
}
// passo ricorsivo
else {
return (number * factorial(number - 1));
}

Nota 1: Ogni volta che viene effettuata una chiamata ricorsiva, le variabili locali della funzione vengono
allocate nuovamente e sono indipendenti dalle variabili delle altre chiamate.

Nota 2: Entrambe le parti della funzione ricorsiva (caso base e passo ricorsivo) contengono uno
statement return, poichè il risultato di ogni chiamata viene combinato con il risultato delle altre
chiamate.
ESERCIZIO IN LABORATORIO:

𝑛
nell'esempio precedente, il fattoriale di viene calcolato come 𝑛 ⋅ (𝑛–1) ⋅ (𝑛–2) ⋅ ⋯ ⋅ 1
senza usare una variabile statica o una variabile glocale, modificare il codice dell'esempio in modo da
calcolare il fattoriale come 1 ⋅ ⋯ ⋅ (𝑛 − 2) ⋅ (𝑛 − 1) ⋅ 𝑛
SUGGERIMENTO: definire una funzione ricorsiva con due variabili

Esempio 2: serie di Fibonacci


La serie di Fibonacci è una sequenza di numeri interi dove ogni numero è dato dalla somma dei due numeri
precedenti (con l'eccezione di 0 e 1):

0, 1, 1, 2, 3, 5, 8, 13, 21, ...

𝑛
Dato un numero , la serie può essere definita ricorsivamente con

fibonacci(0) = 0
fibonacci(1) = 1
fibonacci(n) = fibonacci(n – 1) + fibonacci(n – 2)

dove fibonacci(0) e fibonacci(1) definiscono il caso base, mentre fibonacci(n) costituisce il passo ricorsivo.
Diversamente dall'esempio del fattoriale, in questo esempio il passo ricorsivo è definito non da una ma da due
chiamate di funzione.

Anche in questo caso, si ottiene una cascata di chiamate che termina quando la funzione ricorsiva raggiunge il
caso base e poi ogni chiamata restituisce un risultato alla funzione chiamante.

Può però sorgere una domanda: in quale ordine vengono fatte le chiamate allo stesso livello?

Il primo pensiero è: nell'ordine in cui le chiamate vengono lette dal compilatore, quindi da sinistra verso destra.
Tuttavia, non è così. Per motivi di ottimizzazione, C non specifica l'ordine in cui vengono considerati gli
operandi di un operatore (in questo caso, +). Perciò, le chiamate allo stesso livello potrebbero avvenire in una
qualunque delle combinazioni possibili (dipende dal compilatore usato e dalle operazioni effettuate).

Di seguito la soluzione ricorsiva:


#include <stdio.h>

int fibonacci(int n);

int main(void){
int number;

printf("Enter an integer: ");


scanf("%d", &number);

int result = fibonacci(number);


printf("Fibonacci(%d) = %d\n", number, result);

return 0;
}

int fibonacci(int n){


// caso base
if (0 == n || 1 == n) {
return n;
}
// passo ricorsivo
else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}

Diversi tipi di numeri interi


Sia il fattoriale che la serie di Fibonacci sono delle funzioni il cui risultato tende a crescere molto velocemente.
Può quindi essere una buona idea considerare la possibilità di estendere tali funzioni usando dei numeri interi
con un maggiore intervallo di rappresentazione.

In C, esistono diversi tipi di numeri interi:

Tipo Numero minimo di bits Segnaposto

short 16 %hi

int 16 %d

long int 32 %li

long long int 64 %lli

Per ognuno di questi tipi, esiste la rispettiva versione signed e unsigned. Se non viene specificata nessuna di
queste due keywords, allora il tipo è signed (può assumere sia volori positivi che negativi). Le variabili di tipo
unsigned possono assumere solo valori positivi (a partire da 0), e coprono il doppio dell'intervallo positivo della
rispettiva versione signed.

Nella versione signed di un intero, uno dei bit viene utilizzato per definire il segno. Usando unsigned, anche tale
bit viene utilizzato per la rappresentazione binaria del numero.

Per sapere quale è l'intervallo di valori rappresentabili su un computer, è possibile usare il seguente codice
basato sui contenuti dello header limits.h
basato su co te ut de o eade ts
#include <stdio.h>
#include <limits.h>

int main(){
printf("SHRT_MIN = %+d\n", SHRT_MIN);
printf("SHRT_MAX = %+d\n", SHRT_MAX);
printf("USHRT_MAX = %u\n", USHRT_MAX);
printf("\n");

printf("INT_MIN = %+d\n", INT_MIN);


printf("INT_MAX = %+d\n", INT_MAX);
printf("UINT_MAX = %u\n", UINT_MAX);
printf("\n");

printf("LONG_MIN = %+ld\n", LONG_MIN);


printf("LONG_MAX = %+ld\n", LONG_MAX);
printf("ULONG_MAX = %lu\n", ULONG_MAX);
printf("\n");

printf("LLONG_MIN = %+lld\n", LLONG_MIN);


printf("LLONG_MAX = %+lld\n", LLONG_MAX);
printf("ULLONG_MAX = %llu\n", ULLONG_MAX);
printf("\n");

return 0;
}

che può avere come output (dipende dall'hardware):

SHRT_MIN = -32768
SHRT_MAX = +32767
USHRT_MAX = 65535

INT_MIN = -2147483648
INT_MAX = +2147483647
UINT_MAX = 4294967295

LONG_MIN = -9223372036854775808
LONG_MAX = +9223372036854775807
ULONG_MAX = 18446744073709551615

LLONG_MIN = -9223372036854775808
LLONG_MAX = +9223372036854775807
ULLONG_MAX = 18446744073709551615

NOTA: le variabili di tipo unsigned usano dei segnaposti diversi dalle variabili di tipo signed

Esercizio 19
𝑛
Scrivere un programma che, dopo aver letto in input un numero intero positivo , stampi in output tutti gli interi
𝑛 𝑛
da a 1 (usando una funzione ricorsiva) e tutti gli interi da 1 a (usando un'altra funzione ricorsiva). Separare i
numeri usando uno spazio.
NOTA: anche le funzioni ricorsive possono essere void, ed in queste funzioni è possibile usare
l'istruzione return; senza ritornare alcun valore

Esercizio 20
𝑛
Scrivere un programma che, dopo aver letto in input un numero intero positivo , sommi tutti gli interi da 𝑛a1
(usando una funzione ricorsiva).

Esercizio 21
𝑛
Scrivere un programma che, dopo aver letto in input un numero intero positivo , stampi in output la relativa
rappresentazione binaria.

Potrebbero piacerti anche