Domanda [C] Funzioni ricorsive

Stato
Discussione chiusa ad ulteriori risposte.

int0x80

Utente Silver
5 Aprile 2012
47
3
6
54
Ultima modifica:
Salve...mi sono appena iscritto ( http://www.inforge.net/community/presentati-alla-community/256395-salve-new-post.html ) e subito mi trovate qui a postare una "guida" che ho scritto sul Forum - di cui parlo nella presentazione e dal quale me ne sono scappato a gambe levate -. La posto qui perché mi è sembrato di vedere gente seria e pertanto gradirei un vostro parere su quanto leggerete. Mi limito ad incollare la guida, buona lettura :




Salve, oggi volevo parlarvi delle funzioni ricorsive cercando di farvi capire la logica secondo cui esse operano e il perché risultino - nella maggior parte dei casi - più utili rispetto a delle normali funzioni. Prima di parlarvi di funzioni ricorsive è bene che spieghi cos'è una "normale" funzione : ebbene una funzione non ha un significato ben definito, una funzione è un "procedimento" - si suppone matematico/logico - che permette di svolgere determinati compiti con la differenza che la funzione può essere richiamata in qualsiasi parte del programma. Si suppone che la maggior parte - se non tutti - dei programmi per computer sia costituita da funzioni che permettono sì il corretto svolgimento del programma ma che danno anche una certa logica e "semplicità" a quest'ultimo, ecco perchè è bene suddividere il proprio codice in piccole ( per "piccole" si intende non kilometriche ) funzioni, questo modo di strutturare un programma è detto dividi e conquista ( dal latino divide et impera ). A questo punto si può parlare di funzioni ricorsive : una funzione ricorsiva non è altro che una "normale" funzione che all'interno del suo corpo ( il corpo di una funzione è il blocco di istruzioni che essa esegue racchiuso tra parentesi graffe { } ) richiama se stessa. Per capire meglio come essa richiama se stessa è bene che vi faccia capire come lavora una funzione ricorsiva : normalmente quando ci troviamo davanti al computer per scrivere un programma dobbiamo prima ragionare su come potremo scrivere il programma ( quindi dovremo pensare anche a quali funzioni scrivere, cosa queste funzioni dovranno fare e via dicendo ) e quindi cercare di "costruire una logica di programmazione". Quest'insieme di ragionamenti per il computer si chiamano problemi; come tutti sapete - spero - il computer è ignorante, conosce talmente tante sequenze di 0 e 1 che è inabilitato a conoscere il resto, e allora chi glielo insegna il resto ? La risposta è semplice : noi, come ? - direte voi -, attraverso la scrittura del codice ( che si presume segua una logica ). Cos'ha tutto questo a che fare con le funzioni e specialmente con le funzioni ricorsive ? Dovreste averlo capito : le funzioni "normali" e le funzioni ricorsive non fanno altro che spezzare - logicamente, non praticamente - le istruzioni che voi avete fornito ( e che quindi volete che la funzione esegua ) in tanti piccoli "problemi"; affinché però questi problemi possano essere risolti c'è bisogno che la funzione abbia un "esempio base" da cui partire in modo da poter proseguire con la sua esecuzione. Esistono 2 tipi di problemi : problemi semplici - altro non sono che gli "esempi di base" da cui parte la funzione - e problemi "complicati"; i problemi semplici saranno risolti immediatamente dalla funzione mentre i problemi complicati non saranno risolti ( non sto parlando di "risoluzione a livello di tempo" sto parlando di "risoluzione a livello di soluzione", ciò significa che una funzione ricorsiva, prima che risolva il problema più complicato, deve avere un punto da cui partire : ovvero l'esempio di base. Adesso che avete capito - si spera - come "ragiona" una funzione ricorsiva possiamo passare ad un esempio : il classico esempio che viene fornito di funzione ricorsiva è il calcolo del fattoriale ( Fattoriale ) di un numero e anch'io in questo thread utilizzerò questo esempio facendo la distinzione tra il calcolo del fattoriale senza utilizzare una funziona e quello con una funzione ricorsiva :


Calcolo del fattoriale senza nessuna funzione :


Codice:
/* fattoriale.c */

#include "stdafx.h" /* header standard di Visual Studio, voi se usate altri IDE includete stdio.h */
#include <conio.h> /* header per la funzione getch(); portabile su linux */


int main() /* il programma inizia dalla funzione main() */
{
	int numero; /* numero del quale calcolare il fattoriale */
	int fattoriale = 1; /* il fattoriale è dato da n * (n - 1) * (n - 2) * ... * 1 per cui assegno a "fattoriale" il valore 1 */
	printf("Inserisci il numero del quale calcolare il fattoriale : "); /* stampo a schermo la stringa tra virgolette */
	scanf("%d", &numero); /* faccio scegliere il numero all'utente */
	for(int contatore = numero; contatore >= 1; contatore--) /* inizio ciclo for */
		fattoriale *= contatore; /* fine ciclo for - calcolo del fattoriale ( che equivale a scrivere fattoriale = fattoriale * contatore ) */
	printf("%d! = %d", numero, fattoriale); /* stampo il fattoriale a schermo */
	getch(); /* verrà chiesto all'utente di premere un tasto per chiudere il programma */
	return 0;
} /* fine funzione main() - fine programma */


Penso non ci sia niente da dire a riguardo, il codice e i commenti parlano da sé; al massimo vi spiego la logica del ciclo for : facciamo finta che il valore immesso dall'utente sia 5 i valori delle variabili - al primo ciclo - saranno quindi questi :


- valore variabile number = 5
- valore variabile contatore = 5
- valore variabile fattoriale = fattoriale ( 1 ) * contatore ( 5 ) quindi : 5 * 1 = 5
- nuovo valore variabile contatore = 4 ( contatore-- )


al secondo ciclo :


- valore variabile number = sempre 5
- valore variabile contatore = 4
- valore variabile fattoriale = fattoriale ( adesso vale 5 ) * contatore ( 4 ) quindi : 5 * 4 = 20
- nuovo valore variabile contatore = 3 ( contatore-- )


al terzo ciclo :


- valore variabile number = sempre 5
- valore variabile contatore = 3
- valore variabile fattoriale = fattoriale ( adesso vale 20 ) * contatore ( 3 ) quindi : 20 * 6 = 60
- nuovo valore variabile contatore = 2 ( contatore-- )


al quarto ciclo :


- valore variabile number = sempre 5
- valore variabile contatore = 2
- valore variabile fattoriale = fattoriale ( adesso vale 60 ) * contatore ( 2 ) quindi : 60 * 2 = 120
- nuovo valore variabile contatore = 1 ( contatore-- )


al quinto ciclo :


- valore variabile number = sempre 5
- valore variabile contatore = 1
- valore variabile fattoriale = fattoriale ( adesso vale 120 ) * contatore ( 1 ) quindi : 120 * 1 = 120
- nuovo valore variabile contatore = 0 ( esce quindi dal ciclo perchè nel for viene "violata" la condizione contatore >= 1 )


Calcolo del fattoriale con una funzione ricorsiva :


Codice:
/* fattoriale.c */

#include "stdafx.h" /* header standard di Visual Studio, voi se usate altri IDE includete stdio.h */
#include <conio.h> /* header per la funzione getch(); portabile su linux */




long Fattoriale(long numero); /* prototipo della funzione Fattoriale */


int main() /* il programma inizia dalla funzione main() */
{
	int numero; /* numero del quale calcolare il fattoriale */
	printf("Scrivi il numero del quale calcolare il fattoriale : "); /* stampo a schermo la stringa tra virgolette */
	scanf("%ld", &numero); /* faccio scegliere il numero all'utente */
	printf("%d! = %ld", numero, Fattoriale(numero)); /* stampo a schermo il risultato */
	getch(); /* verrà chiesto all'utente di premere un tasto per chiudere il programma */
	return 0;
} /* fine funzione main() - fine programma */


long Fattoriale(long numero) /* funzione vera e propria */
{ /* inizio funzione Fattoriale */
	if( numero <= 1 ) /* inizio istruzione if - verifico che il numero inserito dall'utente sia <= 1, se lo è restituisce 1 in quanto fattoriale di 1 = 1 */
		return 1;
	else /* altrimenti restituisco la moltiplicazione tra il numero scelto e il fattoriale del numero - 1 */
		return ( numero * Fattoriale(numero - 1)); /* restituisco il fattoriale */
} /* fine funzione Fattoriale */




al primo ciclo :


1! = 1


al secondo ciclo :


2! = 2 * 1 = 2


al terzo ciclo :


3! = 3 * 2 = 6


al quarto ciclo :


4! = 4 * 6 = 24


al quinto ciclo :


5! = 5 * 24 = 120


Importante : Volevo fare un piccolo discorso riguardo il tipo di ritorno utilizzato nella funzione Fattoriale : quando si scrive long nome_variabile è come se si scrivesse long int, lo standard del C specifica che una variabile di tipo long int debba essere immagazzinata in almeno 4 byte e di conseguenza che possa contenere un valore grande quanto +2147483647. La funzione ricorsiva per il calcolo del fattoriale - mano a mano che i numeri dei quali calcolare il fattoriale aumentano - restituisce numeri sempre più grandi ( ovviamente ) ragion per cui il fattoriale sarà calcolabile solo fino ad un certo numero. Per "ovviare" a questo scomodo problema sarebbero necessari dei valori float o double.

int0x80
 
Usa __fastcall così velocizzi quello... quella cosa li che hai messo... insomma... hai capito...
 
Secondo me è un po' troppo prolissa: una funzione ricorsiva è semplicemente una funzione che può auto-richiamarsi. Se si spiegano le funzioni ricorsive bisogna partire dal presupposto che il lettore abbia già una conoscenza minima del linguaggio, secondo me è controproducente spiegare il ciclo for e le funzioni.

Comunque sia per richiamare una funzione ci vuole "molto" (relativamente) tempo, quindi solitamente è preferibile usare i cicli piuttosto che le funzioni ricorsive... amenoché la performance non sia un problema.
 
  • Mi piace
Reazioni: marcello1993
Secondo me è un po' troppo prolissa: una funzione ricorsiva è semplicemente una funzione che può auto-richiamarsi. Se si spiegano le funzioni ricorsive bisogna partire dal presupposto che il lettore abbia già una conoscenza minima del linguaggio, secondo me è controproducente spiegare il ciclo for e le funzioni.

La spiegazione del ciclo for è una questione di logica, io non scrivo guide e "le butto lì" senza far capire al lettore una mazza di quello che ha letto. Ci sono due tipi di lettori :

- quelli che hanno le conoscenze per comprendere perfettamente quanto scritto
- e quelli che non ne hanno

ora i primi sono sicuramente avvantaggiati rispetto ai secondi, ovviamente, ma i secondi nel caso in cui non avessero le conoscenze del linguaggio ( ma per esempio conoscono il ciclo for in un altro linguaggio ) attraverso quella spiegazione riescono a capire un po' come funzionano le cose. A me non sembra che sia controproducente, anzi il contrario, dove lo trovi un tipo che ti spiega le cose dall'abc ?

Comunque sia per richiamare una funzione ci vuole "molto" (relativamente) tempo, quindi solitamente è preferibile usare i cicli piuttosto che le funzioni ricorsive... amenoché la performance non sia un problema.

Quando si programma TUTTO E' UN PROBLEMA :)

​int0x80
 
La spiegazione del ciclo for è una questione di logica, io non scrivo guide e "le butto lì" senza far capire al lettore una mazza di quello che ha letto. Ci sono due tipi di lettori :

- quelli che hanno le conoscenze per comprendere perfettamente quanto scritto
- e quelli che non ne hanno

ora i primi sono sicuramente avvantaggiati rispetto ai secondi, ovviamente, ma i secondi nel caso in cui non avessero le conoscenze del linguaggio ( ma per esempio conoscono il ciclo for in un altro linguaggio ) attraverso quella spiegazione riescono a capire un po' come funzionano le cose. A me non sembra che sia controproducente, anzi il contrario, dove lo trovi un tipo che ti spiega le cose dall'abc ?

Va bene spiegare anche le basi, ma mi sembra confusionario parlarne in quest'ordine. Sarebbe meglio iniziare dal for, passare alle funzioni e poi parlare delle funzioni ricorsive; ancora meglio sarebbe fare delle guide distinte (o una divisione in capitoli), in questo modo se voglio leggere una guida sulle funzioni ricorsive non mi devo leggere i capitoli precedenti.
Inoltre qui prima usi il ciclo for e poi ne spieghi il funzionamento.
Comunque ogni guida è ben accetta, per avere un parere più oggettivo dovrai aspettare una risposta da chi non conosce l'argomento :)

PS.
Prima mi son dimenticato di segnalarti che stdafx.h non è l'header standard di visual studio, è un header precompilato. Se crei un progetto vuoto (come sarebbe meglio fare) puoi non metterlo.
 
Va bene spiegare anche le basi, ma mi sembra confusionario parlarne in quest'ordine. Sarebbe meglio iniziare dal for, passare alle funzioni e poi parlare delle funzioni ricorsive; ancora meglio sarebbe fare delle guide distinte (o una divisione in capitoli), in questo modo se voglio leggere una guida sulle funzioni ricorsive non mi devo leggere i capitoli precedenti.
Inoltre qui prima usi il ciclo for e poi ne spieghi il funzionamento.
Comunque ogni guida è ben accetta, per avere un parere più oggettivo dovrai aspettare una risposta da chi non conosce l'argomento :)

Beh ma come ti ho detto prima : se l'utente conosce il ciclo for perché lo ha usato in altri linguaggi - diversi da quello usato nella guida - allora non ha problemi a comprenderlo.

PS.
Prima mi son dimenticato di segnalarti che stdafx.h non è l'header standard di visual studio, è un header precompilato. Se crei un progetto vuoto (come sarebbe meglio fare) puoi non metterlo.

Ah giusto, non ho completato la frase, avrei dovuto scrivere "è l'header standard che visual studio include in un progetto standard"; grazie per avermelo fatto notare :)

​int0x80
 
Scusa ma non hai parlato dello stack e del sistema LIFO, non sarebbe fondamentale parlare di queste cose se vuoi spiegare le funzioni ricorsive??...la funzione ricorsiva del fattoriale se io gli passo un numero=5 non parte da 1! ma si carica la sequenza nello stack il 5*4*3*2*1 e poi fa effetivamente 1*2*3*4*5 e hai il fattoriale di 5, o sbaglio??
 
Scusa ma non hai parlato dello stack e del sistema LIFO, non sarebbe fondamentale parlare di queste cose se vuoi spiegare le funzioni ricorsive??

Non credo sia fondamentale, a te non interessa sapere cosa succede in memoria.

...la funzione ricorsiva del fattoriale se io gli passo un numero=5 non parte da 1! ma si carica la sequenza nello stack il 5*4*3*2*1 e poi fa effetivamente 1*2*3*4*5 e hai il fattoriale di 5, o sbaglio??

Con questa frase stai chiedendo conferma o ne sei assolutamente sicuro ?
 
Ultima modifica:
Non credo sia fondamentale, a te non interessa sapere cosa succede in memoria.
Questo però è un parolone, programmare in C senza sapere quello che succede in memoria non è una delle scelte migliori
Con questa frase stai chiedendo conferma o ne sei assolutamente sicuro ?
Da buon nabbo quale mi ritengo chiedo conferma......forse non sono stato chiarissimo con il mio commento ma intendevo questo,che peraltro rispetta chiaramente il sistema LIFO(preso dal Deitel):
lvcj6.jpg


Dal tuo commento sembra che parte subito da 1! come se gli fosse stato passato il parametro uno...in realtà secondo me ti sei mangiato tutto il primo pezzo delle chiamate(che magari hai dato per scontato conoscendo l'argomento), perchè il parametro che passiamo è un 5...per me il sistema LIFO è quanto di più chiaro ci possa essere per chi approccia alla ricorsione...
 
Questo però è un parolone, programmare in C senza sapere quello che succede in memoria non è una delle scelte migliori

Hai ragione, mi sono spiegato male : intendevo dire che la guida non spiega cosa succede in memoria ma spiega l'utilità delle funzioni ricorsive senza parlare di ciò che accade in memoria

Da buon nabbo quale mi ritengo chiedo conferma......forse non sono stato chiarissimo con il mio commento ma intendevo questo,che peraltro rispetta chiaramente il sistema LIFO(preso dal Deitel):
lvcj6.jpg


Dal tuo commento sembra che parte subito da 1! come se gli fosse stato passato il parametro uno...in realtà secondo me ti sei mangiato tutto il primo pezzo delle chiamate(che magari hai dato per scontato conoscendo l'argomento), perchè il parametro che passiamo è un 5...per me il sistema LIFO è quanto di più chiaro ci possa essere per chi approccia alla ricorsione...

Questa è la spiegazione che ho fornito nel commentare il calcolo del fattoriale tramite ciclo for, il disegno da te fornito rispecchia la mia spiegazione.

​int0x80
 
Ultima modifica:
Questa è la spiegazione che ho fornito nel commentare il calcolo del fattoriale tramite ciclo for, il disegno da te fornito rispecchia la mia spiegazione.

​int0x80

Si ma l'immagine che ti ho postato io è solo la ricorsiva...è su questo che volevo farti ragionare....da come commenti nel tuo post sembri saltare tutta la prima fase delle chiamate che una funzione ricorsiva dovrebbe compiere...cosa c'entra l'iterazione con quello che ti ho postato? Forse volevi spiegare che la squenza in cui si chiama è come in un ciclo for?? Forse è un problema mio, magari non ti capisco.
 
Si ma l'immagine che ti ho postato io è solo la ricorsiva...è su questo che volevo farti ragionare....da come commenti nel tuo post sembri saltare tutta la prima fase delle chiamate che una funzione ricorsiva dovrebbe compiere...cosa c'entra l'iterazione con quello che ti ho postato?

Se ragioni da un punto di vista "logico" ti accorgi che in realtà una funzione ricorsiva ( essendo una funzione che richiama se stessa ) è come se fosse un ciclo for che viene eseguito in determinate condizioni, apposta quando si parla di funzioni ricorsive bisogna parlare dei "problemi" che riguardano la programmazione ( se non capisci a cosa mi riferisco parlo di quanto scritto al dodicesimo rigo ).

Forse volevi spiegare che la squenza in cui si chiama è come in un ciclo for??

Non volevo spiegarlo, volevo che il lettore ( avendo un minimo di conoscenza del linguaggio e dell'argomento ) capisse da solo.

Forse è un problema mio, magari non ti capisco.

Non sei stupido, stai tranquillo, basta discutere e si risolve tutto :)

​int0x80
 
Ultima modifica:
Se ragioni da un punto di vista "logico" ti accorgi che in realtà una funzione ricorsiva ( essendo una funzione che richiama se stessa ) è come se fosse un ciclo for che viene eseguito in determinate condizioni, apposta quando si parla di funzioni ricorsive bisogna parlare dei "problemi" che riguardano la programmazione ( se non capisci a cosa mi riferisco parlo di quanto scritto al dodicesimo rigo ).
Non volevo spiegarlo, volevo che il lettore ( avendo un minimo di conoscenza del linguaggio e dell'argomento ) capisse da solo.
​int0x80

Bene allora non mi sbagliavo ed era qui che volevo arrivare....se la guida è rivolta a dei novizi che non conoscono minimamente l'argomento ti puoi benissimo rendere conto che non lo capiranno mai....dovresti a mio parere (essendo una guida) spiegarlo...altrimenti è facile incorrere nell'errore...mio conosiglio è questo: mostra come è stato fatto nel deitel le due fasi della ricorsione e come funziona in se e per se(io parlerei proprio del LIFO) poi poni una domanda mostrandogli un fattoriale iterativo e trai(tu scrittore) le conclusioni...in fase di apprendimento cercherei di non far supporre nulla ai tuoi lettori perchè si potrebbero facilmente sbagliare...
se poi è una guida per chi sa già le cose(e allora è più una riflessione su alcuni concetti e non più una guida), ci può anche stare, ma rischi di essere frainteso dai non esperti del linguaggio come è successo con me ;)
 
Bene allora non mi sbagliavo ed era qui che volevo arrivare....se la guida è rivolta a dei novizi che non conoscono minimamente l'argomento ti puoi benissimo rendere conto che non lo capiranno mai....dovresti a mio parere (essendo una guida) spiegarlo...altrimenti è facile incorrere nell'errore...mio conosiglio è questo: mostra come è stato fatto nel deitel le due fasi della ricorsione e come funziona in se e per se(io parlerei proprio del LIFO) poi poni una domanda mostrandogli un fattoriale iterativo e trai(tu scrittore) le conclusioni...in fase di apprendimento cercherei di non far supporre nulla ai tuoi lettori perchè si potrebbero facilmente sbagliare...
se poi è una guida per chi sa già le cose(e allora è più una riflessione su alcuni concetti e non più una guida), ci può anche stare, ma rischi di essere frainteso dai non esperti del linguaggio come è successo con me ;)

La "guida" ( non so come definirla, non tutto è una "guida", ad esempio quello che ho scritto lo definirei più un'informazione ) che ho scritto è indirizzata a chi conosce ( anche in parte ) il linguaggio e vuole capire cos'è una funzione ricorsiva, nient'altro...se poi il lettore chiede un approfondimento ( come te ad esempio, mi chiedi di spiegare la struttura LIFO di una funzione ricorsiva in un post che spiega solo COS'E' e come essa opera ) allora è un altro discorso. Comunque se ci tieni non è un problema per me scrivere in 2 minuti quello che chiedi :)
 
La "guida" ( non so come definirla, non tutto è una "guida", ad esempio quello che ho scritto lo definirei più un'informazione ) che ho scritto è indirizzata a chi conosce ( anche in parte ) il linguaggio e vuole capire cos'è una funzione ricorsiva, nient'altro...se poi il lettore chiede un approfondimento ( come te ad esempio, mi chiedi di spiegare la struttura LIFO di una funzione ricorsiva in un post che spiega solo COS'E' e come essa opera ) allora è un altro discorso. Comunque se ci tieni non è un problema per me scrivere in 2 minuti quello che chiedi :)
Ma guarda era un consiglio alla fine a me non importa xD io queste cose già le sapevo ;)
 
Stato
Discussione chiusa ad ulteriori risposte.