Risorse per migliorare la comprensione della ricorsione? [chiuso]


13

So che cos'è la ricorsione (quando un patten si ripresenta in se stesso, in genere una funzione che si chiama su una delle sue linee, dopo un breakout condizionale ... giusto?), E posso capire le funzioni ricorsive se le studio da vicino. Il mio problema è che, quando vedo nuovi esempi, sono sempre inizialmente confuso. Se vedo un loop, o una mappatura, zippare, annidare, chiamate polimorfiche e così via, so cosa sta succedendo semplicemente guardandolo. Quando vedo il codice ricorsivo, il mio processo di pensiero è di solito "wtf è questo?" seguito da "oh è ricorsivo" seguito da "Immagino che debba funzionare, se dicono che lo fa".

Quindi hai qualche consiglio / piano / risorse per sviluppare competenze in questo settore? La ricorsione è in qualche modo un concetto strano, quindi sto pensando che il modo di affrontarlo possa essere altrettanto strano e invisibile.


28
Per capire la ricorsione, devi prima capire la ricorsione.
Andreas Johansson,

1
"The Cat in Hat Comes Back" del Dr. Seuss, questo potrebbe non essere del tutto utile, ma la chiamata ricorsiva sul gatto si sbarazza di quella fastidiosa macchia. :-) Ha anche il vantaggio di essere una lettura molto veloce!
DKnight

2
Pratica, pratica, pratica.
David Thornley,


3
@Graham Borland: questo è un esempio di ricorsione infinita. Nella maggior parte dei programmi, la mancanza del case base di solito provoca un overflow dello stack o un errore di memoria insufficiente. Per gli utenti del sito Web, potrebbe causare confusione. ;)
FrustratedWithFormsDesigner

Risposte:


10

Inizia con qualcosa di semplice e traccialo con carta e matita. Seriosuly. Un buon punto di partenza sono gli algoritmi di attraversamento degli alberi, poiché sono molto più facili da gestire utilizzando la ricorsione rispetto alla normale iterazione. Non deve essere un esempio complicato, ma qualcosa di semplice e su cui puoi lavorare.

Sì, è strano e talvolta contro-intuitivo, ma una volta che fa clic, una volta che dici "Eureka!" ti chiederai come non l'hai capito prima! ;) Ho suggerito alberi perché sono (IMO) la struttura più semplice da comprendere in ricorsione e sono facili da usare con una matita e un foglio di carta. ;)


1
+1 questo è il modo in cui l'ho fatto sgridare. ad esempio, se stai utilizzando OO, crea alcune classi con una relazione figlio principale e quindi prova a creare una funzione / metodo che controlli se un oggetto ha un antenato specifico.
Alb

5

Consiglio vivamente Scheme, usando il libro The Little Lisper. Una volta che hai finito con esso, si sarà capire la ricorsione, in fondo. Quasi garantito


1
+1 Questo libro l'ha fatto davvero per me. Ma è stato ribattezzato "The Little Schemer"
mike30,

4

Consiglio vivamente SICP. Inoltre, dovresti guardare qui i video introduttivi degli autori ; sono incredibilmente aperti.

Un'altra strada, non così strettamente legata alla programmazione, è la lettura di Gödel, Escher, Bach: una treccia d'oro eterna di Hofstadter. Una volta superato, la ricorsione apparirà naturale come l'aritmetica. Inoltre, sarai convinto che P = nP e vorrai costruire macchine pensanti - ma è un effetto collaterale che è così piccolo rispetto ai benefici.


Vale comunque la pena leggere GEB ; anche se alcune delle cose di cui parla sono un po 'datate (alcuni progressi sulla ricerca CS fondamentale sono stati fatti negli ultimi 40 anni) la comprensione di base non lo è.
Donal Fellows

2

Fondamentalmente si riduce alla pratica ... Prendi i problemi generali (ordinamento, ricerca, problemi di matematica, ecc.) E vedi se riesci a vedere un modo in cui tali problemi possono essere risolti se applichi una singola funzione più volte.

Ad esempio, l'ordinamento rapido opera nel senso che sposta l'elemento in un elenco in due metà e quindi si applica nuovamente a ciascuna di quelle metà. Quando si verifica l'ordinamento iniziale, non si preoccupa di ordinare le due metà in quel punto. Piuttosto, prende l'elemento perno e mette tutti gli elementi più piccoli di quell'elemento su un lato e tutti gli elementi più grandi o uguali sull'altro lato. Ha senso come può ricorsivamente chiamarsi a quel punto per ordinare le due nuove liste più piccole? Sono anche liste. Solo più piccolo. Ma devono ancora essere ordinati.

Il potere dietro la ricorsione è il concetto di divisione e conquista. Rompere ripetutamente un problema in problemi più piccoli che sono identici in natura ma solo più piccoli. Se lo fai abbastanza alla fine arrivi a un punto in cui l'unico pezzo rimanente è già risolto, ti basta tornare indietro dal ciclo e il problema è risolto. Studia quegli esempi che hai citato fino a quando non li capisci . Potrebbe volerci un po 'ma alla fine sarà più facile. Quindi prova a prendere altri problemi e crea una funzione ricorsiva per risolverli! In bocca al lupo!

EDIT: devo anche aggiungere che un elemento chiave per la ricorsione è la capacità garantita della funzione di essere in grado di arrestarsi. Ciò significa che la scomposizione del problema originale deve essere costantemente ridotta e alla fine deve esserci un punto di arresto garantito (un punto in cui il nuovo sotto-problema è risolvibile o già risolto).


Sì, penso di aver visto una rapida spiegazione dell'ordinamento prima, posso immaginare come funziona dal tuo promemoria sopra. Quanto è espressiva / flessibile la ricorsione - la maggior parte dei problemi può essere forzata in un approccio ricorsivo (anche se non è ottimale)? Ho visto persone rispondere ai puzzle di codifica in rete che la maggior parte delle persone stava affrontando proceduralmente, come se potessero usare la ricorsione ogni volta che volevano solo per l'inferno. Ho anche letto una volta, penso, che alcune lingue dipendono o ricorrono per sostituire il costrutto del ciclo. E menzioni il punto di arresto garantito. Sento che una di quelle cose potrebbe essere la chiave.
Andrew M,

Un buon problema iniziale da creare da soli sarebbe quello di scrivere un programma ricorsivo che trova il fattoriale di un numero.
Kenneth,

Qualsiasi struttura ad anello può essere inserita in una struttura ricorsiva. Qualsiasi struttura ricorsiva può essere inserita in una struttura ciclica ... più o meno. Ci vuole tempo e pratica per essere in grado di imparare quando e quando non usare la ricorsione perché bisogna ricordare che quando si utilizza la ricorsione ci sono MOLTE spese generali in termini di risorse utilizzate a livello hardware.
Kenneth,

Ad esempio, ho potuto vedere che era possibile creare una struttura ad anello che eseguisse un ordinamento rapido ... MA è certo che diamine sarebbe un dolore reale e, a seconda di come è stato fatto, alla fine potrebbe usare più risorse di sistema di una funzione ricorsiva per array di grandi dimensioni.
Kenneth,

quindi ecco il mio tentativo di fattoriale. per essere onesti, l'ho già visto prima, e anche se l'ho scritto da zero, non dalla memoria, probabilmente è ancora più facile di quanto sarebbe stato. Ho provato in JS ma ha avuto un errore di analisi, ma funziona in Python def factorial(number): """return factorial of number""" if number == 0: return 0 elif number == 1: return 1 else: return number * factorial(number - 1)
Andrew M

2

Personalmente penso che la tua scommessa migliore sia attraverso la pratica.

Ho imparato la ricorsione con LOGO. Puoi usare LISP. La ricorsione è naturale in quelle lingue. Altrimenti puoi paragonarlo allo studio di suite matematiche e serie in cui esprimi ciò che è successivo in base a ciò che è venuto prima, cioè u (n + 1) = f (u (n)), o serie più complesse in cui hai più variabili e dipendenze multiple, ad es. u (n) = g (u (n-1), u (n-2), v (n), v (n-1)); v (n) = h (u (n-1), u (n-2), v (n), v (n-1)) ...

Quindi il mio suggerimento sarebbe quello di trovare semplici "problemi" di ricorsione standard (nella loro espressione) e provare a metterli in pratica nella lingua che preferisci. La pratica ti aiuterà a imparare a pensare, leggere ed esprimere quei "problemi". Si noti che spesso alcuni di questi problemi possono essere espressi attraverso l'iterazione, ma la ricorsione potrebbe essere un modo più elegante per risolverli. Uno di questi è il calcolo dei numeri fattoriali.

"Problemi" grafici che trovo rendono più facile da vedere. Quindi cerca i fiocchi di Koch, Fibonacci, la curva del drago e i frattali in generale. Ma guarda anche l'algoritmo di ordinamento rapido ...

È necessario interrompere alcuni programmi (loop infiniti, uso provvisorio di risorse infinite) e gestire in modo errato le condizioni finali (per ottenere risultati inaspettati) prima di aggirare il problema. E anche quando lo capirai, commetterai comunque quegli errori, solo meno spesso.



0

Per quanto mi piaccia SICP e Gödel, Escher, Bach: una treccia d'oro eterna , la LISP di Touretzky : una dolce introduzione al calcolo simbolico fa anche un buon lavoro nell'introdurre la ricorsione.

Il concetto di base è questo: in primo luogo, devi sapere quando la tua funzione ricorsiva è terminata, in modo che possa restituire un risultato. Quindi, devi sapere come prendere il caso incompiuto e ridurlo a qualcosa su cui puoi ricorrere. Per l'esempio fattoriale tradizionale (N), hai finito quando N <= 1 e il caso incompiuto è N * fattoriale (N-1).

Per un esempio molto più brutto, c'è la funzione A di Ackermann (m, n).

A(0,n) = n+1.                                   This is the terminal case.
A(m,0) = A(m-1,1) if m > 0.                     This is a simple recursion.
A(m,n) = A(m-1, A(m, n-1)) if m > 0 and n > 0.  This one is ugly.

0

Suggerisco di giocare con alcuni linguaggi funzionali in stile ML come OCaml o Haskell. Ho scoperto che la sintassi del pattern matching mi ha davvero aiutato a capire anche funzioni ricorsive relativamente complicate, sicuramente molto meglio di quelle di Scheme ife delle conddichiarazioni. (Ho imparato Haskell e Scheme allo stesso tempo.)

Ecco un esempio banale per il contrasto:

(define (fib n)
   (cond [(= n 0) 0]
         [(= n 1) 1]
         [else (+ (fib (- n 1)) (fib (- n 2)))]))

e con pattern matching:

fib 0 = 0
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)

Questo esempio non rende davvero la differenza - non ho mai avuto problemi con nessuna delle versioni della funzione. È solo per illustrare come appaiono le due opzioni. Una volta arrivate a funzioni molto più complesse, usando cose come liste e alberi, la differenza diventa molto più pronunciata.

Consiglio in particolare Haskell perché è un linguaggio semplice con una sintassi molto piacevole. Inoltre, rende molto più semplice lavorare con idee più avanzate come la corecursion :

fibs = 0 : 1 : zipWith (+) fibs (drop 1 fibs)
fib n = fibs !! n

(Non capirai il codice sopra finché non giochi un po 'con Haskell, ma ti assicuro che è sostanzialmente magico: P.) Certo che potresti fare lo stesso con i flussi in Scheme, ma è molto più naturale in Haskell.


0

È esaurito, ma se riesci a trovarlo, "Recursive Algorithms" di Richard Lorentz non è altro che una ricorsione. Copre le basi della ricorsione, nonché specifici algoritmi ricorsivi.

Gli esempi sono in Pascal, ma non sono così grandi che la scelta della lingua è fastidiosa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.