• Regolamento Macrocategoria DEV
    Prima di aprire un topic nella Macrocategoria DEV, è bene leggerne il suo regolamento. Sei un'azienda o un hosting/provider? Qui sono anche contenute informazioni per collaborare con Sciax2 ed ottenere l'accredito nella nostra community!

Guida La ricorsione

Lex007

Moderatore Macro DEV
Autore del topic
2 Novembre 2011
529
100
Miglior risposta
1
Ricorsione

Con il termine ricorsione si intende l'applicazione di un processo ricorsivo per la risoluzione di un determinato problema.
Un processo ricorsivo, sostanzialmente, è uno schema di calcolo attraverso il quale il valore di una determinata funzione al passo n è calcolato tramite il valore della stessa funzione al passo n-1 e così via fino a raggiungere un caso terminale.

Un esempio di funzione ricorsiva è il fattoriale (n!), che può essere definito nel seguente modo:

RMVIbxF.gif

Detto in modo "semplice", la ricorsione si ottiene quando una funzione richiama se stessa durante la sua esecuzione.

Come creare un algoritmo ricorsivo

Per creare un algoritmo ricorsivo occorre per prima cosa isolare o designare dei casi particolari di arresto, successivamente sviluppare la soluzione in modo da convergere verso tali condizioni.

L'analisi dei casi terminali è un passo fondamentale in quanto, in assenza di essi, non si potrebbe mai giungere ad una soluzione.

Una volta fatto ciò è possibile passare alla creazione di una funzione che esegua tale algoritmo.
Una funzione ricorsiva ha sempre una o più variabili di controllo, che, variando il loro valore ad ogni chiamata, dirigono la convergenza verso il caso di arresto.

Vantaggi e svantaggi

Uno dei più grandi vantaggi degli algoritmi ricorsivi si trova nel metodo di rappresentazione della soluzione.
Essa può infatti essere espressa con una scrittura molto simile a quella matematica.

Gli algoritmi ricorsivi inoltre risultano più puliti ed eleganti rispetto ad un equivalente iterativo, dunque anche più leggibili ed apprezzabili.

Il vero problema è dato dall'impatto che essi hanno sull'efficienza, per comprendere il perché, occorre esaminare il funzionamento di un algoritmo ricorsivo.

Funzionamento

Nel momento in cui viene chiamata una funzione viene allocata una porzione di memoria riservata ai parametri (argomenti della funzione) e alle eventuali variabili locali.
Questa porzione è deallocata solo nel momento in cui la funzione termina.

Nel momento in cui una funzione ricorsiva crea una nuova istanza di sé stessa la sua esecuzione viene messa in pausa, in attesa di ricevere un valore di ritorno.
Viene dunque allocata una nuova porzione di memoria per la nuova istanza, in cui vengono copiati i nuovi parametri attuali.
Solo nel momento in cui viene raggiunto un caso terminale viene restituito un valore di ritorno, a quel punto la memoria viene deallocata, partendo dalla porzione riservata all'ultima istanza e risalendo verso la prima.
Questo fenomeno viene detto "overhead".

E' facile dunque capire che un algoritmo ricorsivo non è adatto ad eseguire una grande quantità di calcoli, in quanto ciò porterebbe ad un grosso rallentamento (dovuto per lo più al tempo perso nelle chiamate) e all'allocamento di una grossa quantità di memoria.

Algoritmi ricorsivi ed iterativi

Un algoritmo ricorsivo può essere espresso in maniera più o meno semplice in modo iterativo.
In questo caso si ottiene un miglioramento delle prestazioni a discapito però, della semplicità e leggibilità del codice.

Lascio qui alcuni esempi di algoritmi ricorsivi ed iterativi ed i benchmark relativi al calcolo dell' n-simo termine della successione di fibonacci.

Fattoriale:
Ricorsivo:
Codice:
Perfavore, Entra oppure Registrati per vedere i codici!

Iterativo:
Codice:
Perfavore, Entra oppure Registrati per vedere i codici!

Successione di Fibonacci:
Ricorsivo:
Codice:
Perfavore, Entra oppure Registrati per vedere i codici!

Iterativo:
Codice:
Perfavore, Entra oppure Registrati per vedere i codici!
Benchmark:
Perfavore, Entra oppure Registrati per vedere i Link!

Pari o dispari
Ricorsivo:
Codice:
Perfavore, Entra oppure Registrati per vedere i codici!

Iterativo:
Codice:
Perfavore, Entra oppure Registrati per vedere i codici!
 
Ammetto di essere un grande sostenitore della filosofia (da me creata) "ciò che puoi fare con l'iterazione, non fare con la ricorsione": forse perché l'approccio bottom-up mi viene più naturale di creare tutte le possibili condizione di fine.
Tralasciando lo spazio in memoria, la ricorsione, a volte, va a rallentare le prestazioni a livelli enormi (vedesi ad esempio trovare il 30esimo numero della successione di Fibonacci, ove fibonacci(2) viene richiamato e calcolato molto più di 1 singola volta).
Un buon rimedio sarebbe quindi di applicare la memoizzazione :emoji_thumbsup:

PS: È ovvio che a livello stilistico, un buon codice ricorsivo, ha un impatto migliore ma tanto, le righe in più non si pagano (medesima cosa per l'indentazione)
 
  • Like
Reactions: Lex007
Ammetto di essere un grande sostenitore della filosofia (da me creata) "ciò che puoi fare con l'iterazione, non fare con la ricorsione": forse perché l'approccio bottom-up mi viene più naturale di creare tutte le possibili condizione di fine.
Tralasciando lo spazio in memoria, la ricorsione, a volte, va a rallentare le prestazioni a livelli enormi (vedesi ad esempio trovare il 30esimo numero della successione di Fibonacci, ove fibonacci(2) viene richiamato e calcolato molto più di 1 singola volta).
Un buon rimedio sarebbe quindi di applicare la memoizzazione :emoji_thumbsup:

PS: È ovvio che a livello stilistico, un buon codice ricorsivo, ha un impatto migliore ma tanto, le righe in più non si pagano (medesima cosa per l'indentazione)

Concordo con te, anche se è un approccio adottato molto spesso.
Anch'io tendo ad evitarla, anche se non la rifiuto del tutto, in fin dei conti in alcuni casi rende il lavoro molto più veloce (almeno in fase di sviluppo).
E poi beh, diciamo che la frase "iterare è umano, la ricorsione è divina" ha un suo perché :emoji_smirk:
 
  • Like
Reactions: sant0
Ammetto di essere un grande sostenitore della filosofia (da me creata) "ciò che puoi fare con l'iterazione, non fare con la ricorsione": forse perché l'approccio bottom-up mi viene più naturale di creare tutte le possibili condizione di fine.
Tralasciando lo spazio in memoria, la ricorsione, a volte, va a rallentare le prestazioni a livelli enormi (vedesi ad esempio trovare il 30esimo numero della successione di Fibonacci, ove fibonacci(2) viene richiamato e calcolato molto più di 1 singola volta).
Un buon rimedio sarebbe quindi di applicare la memoizzazione :emoji_thumbsup:

PS: È ovvio che a livello stilistico, un buon codice ricorsivo, ha un impatto migliore ma tanto, le righe in più non si pagano (medesima cosa per l'indentazione)

In alcuni casi è preferita la ricorsione dato la natura delle strutture dati , tipo gli alberi , in cui risulta naturale visitare/eliminare/aggiungere un nodo in maniera ricorsiva.
Oppure problemi che in versione iterativa risulterebbero troppo lunghi e complessi da implementare ( tipo il problema della torre di hanoi)
In definitiva non è un bene demonizzare una determinata tecnica , ma bensì essere coscienti dei vantaggi e degli svantaggi e usarla ove è opportuno
 
  • Like
Reactions: Lex007
In alcuni casi è preferita la ricorsione dato la natura delle strutture dati , tipo gli alberi , in cui risulta naturale visitare/eliminare/aggiungere un nodo in maniera ricorsiva.
Oppure problemi che in versione iterativa risulterebbero troppo lunghi e complessi da implementare ( tipo il problema della torre di hanoi)
In definitiva non è un bene demonizzare una determinata tecnica , ma bensì essere coscienti dei vantaggi e degli svantaggi e usarla ove è opportuno
Ovvio, infatti ho detto "ciò che puoi fare con l'iterazione, non fare con la ricorsione" e non "fai tutto iterativamente". Io sono il primo ad impiegare la ricorsione nei BSTs, ma sono consapevole che con 20-30 righe di codice in più lo potrei fare senza ricorsione, ma, è comunque un algoritmo pensato ricorsivamente
 
  • Like
Reactions: TBH