¿Recursos para mejorar su comprensión de la recursividad? [cerrado]

13

Sé lo que es la recursividad (cuando un patten vuelve a aparecer dentro de sí mismo, generalmente una función que se llama a sí misma en una de sus líneas, después de un rompimiento condicional ... ¿verdad?), Y puedo entender las funciones recursivas si las estudio detenidamente. Mi problema es que cuando veo nuevos ejemplos, siempre estoy inicialmente confundido. Si veo un bucle, o un mapeo, compresión, anidamiento, llamadas polimórficas, etc., sé lo que sucede con solo mirarlo. Cuando veo código recursivo, mi proceso de pensamiento suele ser '¿qué es esto?' seguido de 'oh, es recursivo' seguido de 'Supongo que debe funcionar, si dicen que sí'.

Entonces, ¿tiene algún consejo / plan / recurso para desarrollar habilidades en esta área? La recursión es una especie de concepto extraño, así que creo que la forma de abordarla puede ser igualmente extraña e inmune.

Andrew M
fuente
28
Para comprender la recursividad, primero debe comprender la recursividad.
Andreas Johansson
1
'El gato con sombrero regresa' del Dr. Seuss, esto puede no ser del todo útil, pero la llamada recursiva sobre el gato elimina esa molesta mancha. :-) ¡También tiene la ventaja de ser una lectura muy rápida!
DKnight
2
Práctica práctica práctica.
David Thornley
20
Ya respondí en esta pregunta: programmers.stackexchange.com/questions/57243/…
Graham Borland
3
@Graham Borland: Ese es un ejemplo de recursión infinita. En la mayoría de los programas, perder el caso base generalmente resulta en un desbordamiento de la pila o un error de falta de memoria. Para los usuarios del sitio web, podría dar lugar a confusión. ;)
FrustratedWithFormsDesigner

Respuestas:

10

Comience con algo simple y tracéelo con lápiz y papel. En serio. Un buen lugar para comenzar son los algoritmos de recorrido de árbol, ya que se manejan mucho más fácilmente usando la recursión que la iteración regular. No tiene que ser un ejemplo complicado, sino algo simple y con lo que puedas trabajar.

Sí, es extraño y a veces contra-intuitivo, pero una vez que hace clic, una vez que dices "¡Eureka!" ¡te preguntarás cómo no lo entendiste antes! ;) Sugerí árboles porque son (IMO) la estructura más fácil de entender en la recursividad, y son fáciles de trabajar con un lápiz y papel. ;)

FrustratedWithFormsDesigner
fuente
1
+1 esta es la forma en que lo asimilé. por ejemplo, si está utilizando OO, haga algunas clases con una relación padre-hijo y luego intente crear una función / método que verifique si un objeto tiene un antepasado específico.
Alb
5

Recomiendo encarecidamente Scheme, utilizando el libro The Little Lisper. Una vez que haya terminado con él, usted va a entender la recursividad, en el fondo. Casi garantizado

jcomeau_ictx
fuente
1
+1 Este libro realmente lo hizo por mí. Pero cambió su nombre a "The Little Schemer"
mike30
4

Definitivamente sugiero SICP. Además, debe consultar los videos del curso introductorio de los autores aquí ; son increíblemente alucinantes.

Otro camino, no tan estrictamente relacionado con la programación, es leer Gödel, Escher, Bach: an Eternal Golden Braid de Hofstadter. Una vez que lo superes, la recursión se verá tan natural como la aritmética. Además, estará convencido de que P = nP y de que querrá construir máquinas pensantes, pero es un efecto secundario que es tan pequeño en comparación con los beneficios.

cbrandolino
fuente
Vale la pena leer GEB de todos modos; incluso si algunas de las cosas de las que habla están un poco anticuadas (en los últimos 40 años se han realizado algunos avances en la investigación fundamental de CS), la comprensión básica no lo es.
Donal Fellows
2

Esencialmente, todo se reduce a la práctica ... Tome los problemas generales (clasificación, búsqueda, problemas matemáticos, etc.) y vea si puede ver una forma de resolver esos problemas si aplica una sola función varias veces.

Por ejemplo, la ordenación rápida funciona porque mueve el elemento de una lista en dos mitades y luego se aplica nuevamente a cada una de esas mitades. Cuando ocurre la clasificación inicial, no está preocupado por ordenar las dos mitades en ese punto. Más bien, toma el elemento pivote y coloca todos los elementos más pequeños que ese elemento en un lado y todos los elementos más grandes o iguales que en el otro lado. ¿Tiene sentido cómo puede llamarse recursivamente en ese punto para ordenar las dos nuevas listas más pequeñas? También son listas. Solo más pequeño. Pero aún necesitan ser ordenados.

El poder detrás de la recursión es la noción de dividir y conquistar. Divide un problema repetidamente en problemas más pequeños que son idénticos en naturaleza pero solo más pequeños. Si haces eso lo suficiente, eventualmente llegas a un punto en el que la única pieza restante ya está resuelta, entonces simplemente sales del bucle y el problema está resuelto. Estudie los ejemplos que mencionó hasta que los entienda . Puede tomar un tiempo, pero eventualmente será más fácil. ¡Entonces trata de resolver otros problemas y haz una función recursiva para resolverlos! ¡Buena suerte!

EDITAR: También debo agregar que un elemento clave para la recursión es la capacidad garantizada de la función para poder detenerse. Esto significa que la ruptura hacia abajo del problema original deben obtener continuamente más pequeño y, finalmente, es necesario que haya un punto de parada garantizada (un punto en el que el nuevo problema es secundario, ya sea resoluble o ya resuelto).

Kenneth
fuente
Sí, creo que he visto una explicación rápida antes, puedo imaginar cómo funciona desde su recordatorio anterior. ¿Qué tan expresivo / flexible es la recursividad? ¿Se puede forzar la mayoría de los problemas a un enfoque recursivo (incluso si no es óptimo)? He visto a personas responder acertijos de codificación en la red que la mayoría de las personas estaban abordando de manera procesal, como si pudieran usar la recursividad cuando quisieran por el mero hecho de hacerlo. También leí una vez, creo, que algunos idiomas dependen o recurren para reemplazar la construcción del bucle. Y mencionas el punto de parada garantizado. Siento que una de esas cosas puede ser la clave.
Andrew M
Un buen problema de inicio simple para crear por su cuenta sería escribir un programa recursivo que encuentre el factorial de un número.
Kenneth
Cualquier estructura de bucle se puede poner en una estructura recursiva. Cualquier estructura recursiva se puede poner en una estructura de bucle ... más o menos. Se necesita tiempo y práctica para poder aprender cuándo y cuándo no usar la recursividad porque debes recordar que cuando utilizas la recursión hay MUCHA sobrecarga en términos de recursos utilizados a nivel de hardware.
Kenneth
Por ejemplo, podría ver que es factible crear una estructura de bucle que realice una ordenación rápida ... PERO seguro que sería un verdadero problema y, dependiendo de cómo se hizo, podría usar más recursos del sistema al final que una función recursiva. para grandes matrices.
Kenneth
Así que aquí está mi intento de factorial. Para ser justos, he visto esto antes, y aunque lo escribí desde cero, no de memoria, probablemente todavía sea más fácil de lo que hubiera sido. Lo intenté en JS pero tuve un error de análisis, pero funciona en 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 creo que tu mejor apuesta es a través de la práctica.

Aprendí la recursividad con LOGO. Podrías usar LISP. La recursión es natural en esos idiomas. De lo contrario, puede compararlo con el estudio de series y series matemáticas donde expresa lo que sigue a partir de lo que vino antes, es decir, u (n + 1) = f (u (n)), o series más complejas donde tiene múltiples variables y dependencias múltiples, por ejemplo, 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)) ...

Por lo tanto, mi sugerencia sería que encuentre "problemas" de recursión estándar simples (en su expresión) e intente implementarlos en el idioma que elija. La práctica lo ayudará a aprender a pensar, leer y expresar esos "problemas". Tenga en cuenta que a menudo algunos de estos problemas se pueden expresar a través de la iteración, pero la recursión podría ser una forma más elegante de resolverlos. Uno de ellos es el cálculo de números factoriales.

Los "problemas" gráficos que encuentro hacen que sea más fácil de ver. Así que busque los copos de Koch, Fibonacci, la curva del dragón y los fractales en general. Pero también mira el algoritmo de clasificación rápida ...

Debe bloquear algunos programas (bucles interminables, uso tentativo de recursos infinitos) y manejar mal las condiciones finales (para obtener resultados inesperados) antes de entenderlo todo. E incluso cuando lo obtenga, seguirá cometiendo esos errores, con menos frecuencia.

asoundmove
fuente
2

Estructura e interpretación de programas de computadora

Es el libro utilizado para el aprendizaje, no solo de recursión, sino de programación en general en varias universidades. Es uno de esos libros fundamentales que proporciona más información cuanto más experiencia ganes y más lo leas.

dietbuddha
fuente
0

Por mucho que me guste SICP y Gödel, Escher, Bach: una eterna trenza dorada , el LISP de Touretzky : una introducción suave a la computación simbólica también hace un buen trabajo al introducir la recursividad.

El concepto básico es el siguiente: primero, debe saber cuándo finaliza su función recursiva, para que pueda devolver un resultado. Luego, debe saber cómo tomar el caso inacabado y reducirlo a algo en lo que pueda recurrir. Para el ejemplo factorial tradicional (N), ha terminado cuando N <= 1, y el caso sin terminar es N * factorial (N-1).

Para un ejemplo mucho más feo, está la función A de 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.
John R. Strohm
fuente
0

Sugiero jugar con algunos lenguajes funcionales de estilo ML como OCaml o Haskell. Descubrí que la sintaxis de coincidencia de patrones realmente me ayudó a comprender incluso funciones recursivas relativamente complicadas, ciertamente mucho mejores que las de Scheme ify las conddeclaraciones. (Aprendí Haskell y Scheme al mismo tiempo).

Aquí hay un ejemplo trivial para el contraste:

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

y con coincidencia de patrones:

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

Este ejemplo realmente no hace justicia a la diferencia: nunca tuve problemas con ninguna de las versiones de la función. Es solo para ilustrar cómo son las dos opciones. Una vez que llega a funciones mucho más complejas, usando cosas como listas y árboles, la diferencia se vuelve mucho más pronunciada.

Recomiendo especialmente Haskell porque es un lenguaje simple con una sintaxis muy agradable. También hace mucho más fácil trabajar con ideas más avanzadas como corecursion :

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

(No entenderá el código anterior hasta que juegue un poco con Haskell, pero tenga la seguridad de que es básicamente mágico: P.) Seguro que podría hacer lo mismo con las transmisiones en Scheme, pero es mucho más natural en Haskell.

Tikhon Jelvis
fuente
0

Está agotado, pero si puede encontrarlo, "Algoritmos recursivos" de Richard Lorentz no es más que recurrencia. Cubre los conceptos básicos de recursividad, así como algoritmos recursivos específicos.

Los ejemplos están en Pascal, pero no son tan grandes que la elección del idioma sea molesta.

Wayne Conrad
fuente