Un hilo de reddit planteó una pregunta aparentemente interesante:
Las funciones recursivas de cola pueden convertirse trivialmente en funciones iterativas. Otros, se pueden transformar utilizando una pila explícita. ¿Se puede transformar cada recursión en iteración?
El ejemplo (¿contador?) En la publicación es el par:
(define (num-ways x y)
(case ((= x 0) 1)
((= y 0) 1)
(num-ways2 x y) ))
(define (num-ways2 x y)
(+ (num-ways (- x 1) y)
(num-ways x (- y 1))
choose
x = (x + y)! / (X! Y!), Que no necesita recursividad.Respuestas:
¿Siempre puedes convertir una función recursiva en una iterativa? Sí, absolutamente, y la tesis de Church-Turing lo demuestra si la memoria sirve. En términos simples, establece que lo que es computable por funciones recursivas es computable por un modelo iterativo (como la máquina de Turing) y viceversa. La tesis no te dice con precisión cómo hacer la conversión, pero sí dice que definitivamente es posible.
En muchos casos, convertir una función recursiva es fácil. Knuth ofrece varias técnicas en "El arte de la programación de computadoras". Y a menudo, una cosa calculada recursivamente puede calcularse mediante un enfoque completamente diferente en menos tiempo y espacio. El ejemplo clásico de esto son los números de Fibonacci o sus secuencias. Seguramente has encontrado este problema en tu plan de estudios.
En el reverso de esta moneda, ciertamente podemos imaginar un sistema de programación tan avanzado como para tratar una definición recursiva de una fórmula como una invitación a memorizar resultados anteriores, ofreciendo así el beneficio de velocidad sin la molestia de decirle a la computadora exactamente qué pasos debe seguir. seguir en el cálculo de una fórmula con una definición recursiva. Dijkstra casi seguramente imaginó tal sistema. Pasó mucho tiempo tratando de separar la implementación de la semántica de un lenguaje de programación. Por otra parte, sus lenguajes de programación no deterministas y multiprocesamiento están en una liga superior al programador profesional en ejercicio.
En el análisis final, muchas funciones son simplemente más fáciles de entender, leer y escribir en forma recursiva. A menos que haya una razón convincente, probablemente no debería convertir (manualmente) estas funciones a un algoritmo explícitamente iterativo. Su computadora manejará ese trabajo correctamente.
Puedo ver una razón convincente. Supongamos que tiene un sistema prototipo en un lenguaje de nivel súper alto como [ ponerse ropa interior de asbesto ] Esquema, Lisp, Haskell, OCaml, Perl o Pascal. Suponga que las condiciones son tales que necesita una implementación en C o Java. (Quizás sea política.) Entonces, ciertamente, podría tener algunas funciones escritas de forma recursiva, pero que, traducidas literalmente, explotarían su sistema de tiempo de ejecución. Por ejemplo, es posible una recursión de cola infinita en Scheme, pero el mismo idioma causa un problema para los entornos C existentes. Otro ejemplo es el uso de funciones léxicamente anidadas y alcance estático, que Pascal admite pero C no.
En estas circunstancias, puede intentar superar la resistencia política al idioma original. Puede que te encuentres reimplementando a Lisp, como en la décima ley de Greenspun (irónica). O puede que solo encuentre un enfoque de solución completamente diferente. Pero en cualquier caso, seguramente hay una manera.
fuente
Si. Una prueba formal simple es mostrar que tanto la recursividad µ como un cálculo no recursivo como GOTO están completos en Turing. Dado que todos los cálculos completos de Turing son estrictamente equivalentes en su poder expresivo, todas las funciones recursivas pueden implementarse mediante el cálculo no recursivo completo de Turing.
Desafortunadamente, no puedo encontrar una buena definición formal de GOTO en línea, así que aquí hay una:
Un programa GOTO es una secuencia de comandos P ejecutados en una máquina de registro de manera que P es uno de los siguientes:
HALT
, que detiene la ejecuciónr = r + 1
donder
hay algún registror = r – 1
donder
hay algún registroGOTO x
dondex
hay una etiquetaIF r ≠ 0 GOTO x
donder
hay algún registro yx
es una etiquetaSin embargo, las conversiones entre funciones recursivas y no recursivas no siempre son triviales (excepto por la reinstalación manual sin sentido de la pila de llamadas).
Para más información ver esta respuesta .
fuente
La recursión se implementa como pilas o construcciones similares en los intérpretes o compiladores reales. Por lo tanto, ciertamente puede convertir una función recursiva en una contraparte iterativa porque así es como siempre se hace (si es automáticamente) . Simplemente estará duplicando el trabajo del compilador de manera ad-hoc y probablemente de una manera muy fea e ineficiente.
fuente
Básicamente, sí, en esencia lo que tiene que hacer es reemplazar las llamadas a métodos (que implícitamente empujan el estado a la pila) en empujes explícitos de la pila para recordar dónde se había realizado la 'llamada anterior' y luego ejecutar el 'método llamado' en lugar.
Me imagino que la combinación de un bucle, una pila y una máquina de estado podría usarse para todos los escenarios simulando básicamente las llamadas a métodos. En general, no es posible decir si esto va a ser "mejor" (ya sea más rápido o más eficiente en algún sentido).
fuente
El flujo de ejecución de funciones recursivas se puede representar como un árbol.
La misma lógica se puede hacer mediante un bucle, que utiliza una estructura de datos para atravesar ese árbol.
El primer recorrido en profundidad se puede hacer usando una pila, el recorrido en primer lugar se puede hacer usando una cola.
Entonces, la respuesta es: sí. Por qué: https://stackoverflow.com/a/531721/2128327 .
fuente
Sí, usando explícitamente una pila (pero la recursividad es mucho más agradable de leer, en mi humilde opinión).
fuente
Sí, siempre es posible escribir una versión no recursiva. La solución trivial es usar una estructura de datos de pila y simular la ejecución recursiva.
fuente
En principio, siempre es posible eliminar la recursividad y reemplazarla con una iteración en un lenguaje que tenga un estado infinito tanto para las estructuras de datos como para la pila de llamadas. Esta es una consecuencia básica de la tesis de Church-Turing.
Dado un lenguaje de programación real, la respuesta no es tan obvia. El problema es que es bastante posible tener un lenguaje donde la cantidad de memoria que se puede asignar en el programa es limitada pero donde la cantidad de pila de llamadas que se puede usar no tiene límites (C de 32 bits donde la dirección de las variables de la pila no es accesible). En este caso, la recursividad es más poderosa simplemente porque tiene más memoria que puede usar; no hay suficiente memoria explícitamente asignable para emular la pila de llamadas. Para una discusión detallada sobre esto, vea esta discusión .
fuente
Turing Machines puede calcular todas las funciones computables y, por lo tanto, los sistemas recursivos y las máquinas Turing (sistemas iterativos) son equivalentes.
fuente
A veces, reemplazar la recursividad es mucho más fácil que eso. La recursión solía ser la moda que se enseñaba en CS en la década de 1990, por lo que muchos desarrolladores promedio de esa época pensaron que si resolvías algo con recursividad, era una mejor solución. Por lo tanto, usarían la recursión en lugar de recorrer hacia atrás para revertir el orden, o cosas tontas como esa. Entonces, a veces, eliminar la recursión es un tipo de ejercicio "duh, eso era obvio".
Esto ya no es un problema, ya que la moda ha cambiado hacia otras tecnologías.
fuente
Eliminar la recursividad es un problema complejo y es factible en circunstancias bien definidas.
Los siguientes casos se encuentran entre los fáciles:
fuente
Aparte de la pila explícita, otro patrón para convertir la recursión en iteración es con el uso de un trampolín.
Aquí, las funciones devuelven el resultado final o un cierre de la llamada de función que de otro modo habría realizado. Luego, la función de inicio (trampolín) invoca los cierres devueltos hasta que se alcanza el resultado final.
Este enfoque funciona para funciones recursivas mutuas, pero me temo que solo funciona para llamadas de cola.
http://en.wikipedia.org/wiki/Trampoline_(computers)
fuente
Yo diría que sí, una llamada a la función no es más que un goto y una operación de pila (más o menos). Todo lo que necesita hacer es imitar la pila que se construye al invocar funciones y hacer algo similar a un goto (puede imitar gotos con idiomas que tampoco tienen explícitamente esta palabra clave).
fuente
Eche un vistazo a las siguientes entradas en wikipedia, puede usarlas como punto de partida para encontrar una respuesta completa a su pregunta.
Sigue un párrafo que puede darle alguna pista sobre dónde comenzar:
También eche un vistazo al último párrafo de esta entrada .
fuente
Más detalles: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions
fuente
tazzego, recursión significa que una función se llamará a sí misma le guste o no. Cuando la gente habla sobre si las cosas se pueden hacer o no sin recurrencia, significan esto y no se puede decir "no, eso no es cierto, porque no estoy de acuerdo con la definición de recurrencia" como una declaración válida.
Con eso en mente, casi todo lo que dices no tiene sentido. La única otra cosa que dices que no es una tontería es la idea de que no puedes imaginar la programación sin una pila de llamadas. Eso es algo que se había hecho durante décadas hasta que el uso de una pila de llamadas se hizo popular. Las versiones anteriores de FORTRAN carecían de una pila de llamadas y funcionaban bien.
Por cierto, existen lenguajes completos de Turing que solo implementan la recursividad (por ejemplo, SML) como un medio de bucle. También existen lenguajes completos de Turing que solo implementan la iteración como un medio de bucle (por ejemplo, FORTRAN IV). La tesis de Church-Turing demuestra que todo lo posible en un lenguaje de recursión solo se puede hacer en un lenguaje no recursivo y viceversa por el hecho de que ambos tienen la propiedad de la integridad de Turing.
fuente
Aquí hay un algoritmo iterativo:
fuente