Soy nuevo en este sitio y esta pregunta ciertamente no tiene un nivel de investigación, pero bueno. Tengo un poco de experiencia en ingeniería de software y casi ninguno en CSTheory, pero me parece atractivo. Para resumir, quisiera una respuesta más detallada a lo siguiente si esta pregunta es aceptable en este sitio.
Entonces, sé que cada programa recursivo tiene un análogo iterativo y entiendo la explicación popular que se le ofrece al mantener algo similar a la "pila del sistema" y presionar la configuración del entorno como la dirección de retorno, etc. Encuentro este tipo de manual .
Siendo un poco más concreto, me gustaría ver (formalmente) cómo se prueba esta afirmación en los casos en que tiene una función que invoca la cadena . Además, ¿qué pasa si hay algunas declaraciones condicionales que podrían hacer que un F i llame a algún F j ? Es decir, el gráfico de llamada de función potencial tiene algunos componentes fuertemente conectados.
Me gustaría saber cómo se pueden manejar estas situaciones, digamos un convertidor recursivo a iterativo. ¿Y la descripción manual que mencioné anteriormente es realmente suficiente para este problema? Quiero decir, entonces, ¿por qué es fácil eliminar la recursión en algunos casos? En particular, eliminar la recursividad del recorrido previo al pedido de un árbol binario es realmente fácil: es una pregunta estándar de la entrevista, pero eliminar la recurrencia en el caso de un pedido posterior siempre ha sido una pesadilla para mí.
Lo que realmente estoy preguntando son preguntas
(1) ¿Existe realmente una prueba más formal (convincente?) De que la recursión se puede convertir en iteración?
(2) Si esta teoría está realmente ahí afuera, entonces ¿por qué es que encuentro, por ejemplo, iterar preordenar más fácilmente y postorder tan difícil? (aparte de mi inteligencia limitada)
fuente
Respuestas:
Si lo entiendo correctamente, tiene claro acerca de la conversión de funciones que no contienen otras llamadas a funciones sino a sí mismas.
Así que supongamos que tenemos una "cadena llamada" . Si además suponemos que F 1 , ... , F n no son recursivos en sí mismos (porque ya los hemos convertido), podemos incluir todas esas llamadas en la definición de F, que se convierte en una función recursiva directa con la que ya podemos tratar.F→ F1→ ⋯ → Fnorte→ F F1, ... , Fnorte F
Esto falla si alguna tiene una cadena de llamada recursiva en la que ocurre F , es decir, F j → ⋯ → F → ⋯ → F j . En este caso, tenemos una recursión mutua que requiere otro truco para deshacerse. La idea es calcular ambas funciones simultáneamente. Por ejemplo, en el caso trivial:Fj F Fj→ ⋯ → F→ ⋯ → Fj
con
f'
yg'
funciones no recursivas (o al menos independientes def
yg
) se convierte enEsto naturalmente se extiende a más funciones involucradas y funciones más complicadas.
fuente
f
yg
acepta diferentes tipos de tipos, se necesita un truco más general.f
g
Sí, hay razones convincentes para creer que la recursión puede convertirse en iteración. Esto es lo que hace cada compilador cuando traduce el código fuente al lenguaje máquina. Para la teoría, debe seguir las sugerencias de Dave Clarke. Si desea ver el código real que convierte la recursividad a código no recursivo, eche un vistazo
machine.ml
en el lenguaje MiniML en mi PL Zoo (observe que laloop
función en la parte inferior, que realmente ejecuta código, es recursiva de cola y así puede ser trivialmente convertido a un bucle real).Una cosa más. MiniML no admite funciones recursivas mutuas. Pero esto no es un problema. Si tiene recursividad mutua entre funciones
la recursividad puede expresarse en términos de un solo mapa recursivo
fuente
Es posible que desee mirar la máquina SECD . Un lenguaje funcional (aunque podría ser cualquier idioma) se traduce en una serie de instrucciones que manejan cosas como poner argumentos de pilas, "invocar" nuevas funciones, etc., todo administrado por un simple ciclo.
Las llamadas recursivas nunca se invocan realmente. En cambio, las instrucciones del cuerpo de la función que se llama se coloca en la pila para ejecutar.
Un enfoque relacionado es la máquina CEK .
Ambos han existido durante mucho tiempo, por lo que hay mucho trabajo sobre ellos. Y, por supuesto, hay pruebas de que funcionan y el procedimiento para "compilar" un programa en instrucciones SECD es lineal en el tamaño del programa (no tiene que pensar en el programa).
El punto de mi respuesta es que hay un procedimiento automático para hacer lo que quieres. Desafortunadamente, la transformación no será necesariamente en términos de cosas que son inmediatamente fáciles de interpretar para un programador. Creo que la clave es que cuando desea iterizar un programa, necesita almacenar en la pila lo que el programa debe hacer cuando regresa de una llamada de función iterizada (esto se llama continuación). Para algunas funciones (como las funciones recursivas de cola) la continuación es trivial. Para otros, la continuación puede ser muy compleja, especialmente si tiene que codificarla usted mismo.
fuente
P : "¿Existe realmente una prueba más formal (convincente) de que la recursión se puede convertir en iteración?"
A : La integridad de Turing de una máquina de Turing :-)
Bromas aparte, el modelo de máquina del programa almacenado de acceso aleatorio (RASP) equivalente de Turing está cerca de cómo funcionan los microprocesadores reales y su conjunto de instrucciones contiene solo un salto condicional (sin recursión). La posibilidad de modificar dinámicamente el código hace que la tarea de implementar subrutinas y llamadas recursivas sea más fácil.
Creo que puede encontrar muchos artículos / artículos sobre la " conversión recursiva a iterativa " (vea la respuesta de Dave o simplemente Google las palabras clave), pero quizás un enfoque menos conocido (y práctico ) es la investigación más reciente sobre la implementación de hardware de algoritmos recursivos ( usando el lenguaje VHDL que se "compila" directamente en una pieza de hardware). Por ejemplo, ver el artículo de V.Sklyarov " Implementación basada en FPGA de algoritmos recursivos " ( El artículo sugiere un método novedoso para implementar algoritmos recursivos en hardware ... Se han estudiado dos aplicaciones prácticas de algoritmos recursivos en el área de clasificación y compresión de datos en detalle .... ).
fuente
Si está familiarizado con los idiomas que admiten lambdas, entonces una vía es investigar la transformación de CPS. Eliminar el uso de la pila de llamadas (y la recursividad en particular) es exactamente lo que hace la transformación CPS. Transforma un programa que contiene llamadas de procedimiento en un programa con solo llamadas de cola (puede pensar en ellas como gotos, que es una construcción iterativa).
La transformación CPS está estrechamente relacionada con el mantenimiento explícito de una pila de llamadas en una pila basada en una matriz tradicional, pero en lugar de en una matriz, la pila de llamadas se representa con cierres vinculados.
fuente
En mi opinión, esta pregunta se remonta a los orígenes de las definiciones de computación y hace mucho tiempo se demostró rigurosamente en esa época cuando el cálculo lambda de la iglesia (que captura el concepto de recursión) demostró ser equivalente a las máquinas de Turing, y está contenido en la terminología aún utilizada "lenguajes / funciones recursivas". También, aparentemente, una referencia clave posterior a lo largo de estas líneas es la siguiente
Gran parte del bkd sobre esto está en esta tesis de la iglesia de la página de Wikipedia . No estoy seguro de los detalles exactos, pero el artículo de Wikipedia parece indicar que fue Rosser (1939) quien primero probó esta equivalencia entre el cálculo lambda y las máquinas de Turing. ¿quizás / presumiblemente su artículo tiene un mecanismo en forma de pila para convertir las llamadas lambda (posiblemente recursivas) a la construcción tm?
Rosser, JB (1939). "Una exposición informal de pruebas del teorema de Godel y el teorema de la iglesia". The Journal of Symbolic Logic (The Journal of Symbolic Logic, Vol. 4, Núm. 2) 4 (2): 53–60. doi: 10.2307 / 2269059. JSTOR 2269059.
Nota, por supuesto, para cualquier persona interesada en los principios, el lenguaje Lisp moderno y el Esquema variante tienen una fuerte semejanza con el cálculo lambda. El estudio del código del intérprete para la evaluación de expresiones conduce a ideas que originalmente estaban contenidas en documentos para la integridad del cálculo lambda.
fuente