Dada cualquier función arbitrariamente doble recursiva, ¿cómo se calcularía su tiempo de ejecución?
Por ejemplo (en pseudocódigo):
int a(int x){
if (x < = 0)
return 1010;
else
return b(x-1) + a(x-1);
}
int b(int y){
if (y <= -5)
return -2;
else
return b(a(y-1));
}
O algo por el estilo.
¿Qué métodos podrían o deberían usarse para determinar algo como esto?
Respuestas:
Sigues cambiando tu función. Pero sigue eligiendo los que funcionarán para siempre sin conversión.
La recursión se complica, rápido. El primer paso para analizar una función doblemente recursiva propuesta es tratar de rastrearla en algunos valores de muestra, para ver qué hace. Si su cálculo entra en un bucle infinito, la función no está bien definida. Si su cálculo entra en una espiral que sigue obteniendo números más grandes (lo que sucede muy fácilmente), probablemente no esté bien definido.
Si rastrearlo da una respuesta, intente encontrar algún patrón o relación de recurrencia entre las respuestas. Una vez que tenga eso, puede intentar averiguar su tiempo de ejecución. Resolverlo puede ser muy, muy complicado, pero tenemos resultados como el teorema del Maestro que nos permite descubrir la respuesta en muchos casos.
Tenga en cuenta que incluso con una recursión simple, es fácil encontrar funciones cuyo tiempo de ejecución no sabemos cómo calcular. Por ejemplo, considere lo siguiente:
Es actualmente se desconoce si siempre está bien definida esta función, por no hablar de lo que su tiempo de ejecución es.
fuente
El tiempo de ejecución de ese par particular de funciones es infinito porque ninguno regresa sin llamar al otro. El valor de retorno de
a
es siempre dependiente del valor de retorno de una llamada ab
la que siempre se llamaa
... y eso es lo que se conoce como recursión infinita .fuente
a
solo llamab
si el número pasado es> = 0. Pero sí, hay un bucle infinito.El método obvio es ejecutar la función y medir cuánto tiempo lleva. Sin embargo, esto solo te dice cuánto tiempo lleva una entrada en particular. Y si no sabe de antemano que la función termina, difícil: no hay una forma mecánica de determinar si la función termina, ese es el problema de detención y es indecidible.
Encontrar el tiempo de ejecución de una función es igualmente indecidible, según el teorema de Rice . De hecho, el teorema de Rice muestra que incluso decidir si una función se ejecuta a
O(f(n))
tiempo es indecidible.Entonces, lo mejor que puede hacer en general es usar su inteligencia humana (que, hasta donde sabemos, no está limitada por los límites de las máquinas de Turing) e intentar reconocer un patrón o inventar uno. Una forma típica de analizar el tiempo de ejecución de una función es convertir la definición recursiva de la función en una ecuación recursiva en su tiempo de ejecución (o un conjunto de ecuaciones para funciones recursivas mutuas):
¿Qué sigue? Ahora tiene un problema matemático: necesita resolver estas ecuaciones funcionales. Un enfoque que a menudo funciona es convertir estas ecuaciones en funciones enteras en ecuaciones en funciones analíticas y usar el cálculo para resolverlas, interpretando las funciones
T_a
yT_b
como funciones generadoras .Sobre la generación de funciones y otros temas de matemática discreta, recomiendo el libro Matemáticas concretas , de Ronald Graham, Donald Knuth y Oren Patashnik.
fuente
Como otros señalaron, analizar la recursividad puede ser muy difícil muy rápido. Aquí hay otro ejemplo de tal cosa: http://rosettacode.org/wiki/Mutual_recursion http://en.wikipedia.org/wiki/Hofstadter_sequence#Hofstadter_Female_and_Male_sequences es difícil calcular una respuesta y un tiempo de ejecución para estos. Esto se debe a que estas funciones mutuamente recursivas tienen una "forma difícil".
De todos modos, veamos este sencillo ejemplo:
http://pramode.net/clojure/2010/05/08/clojure-trampoline/
Comencemos tratando de calcular
funa(m), m > 0
:El tiempo de ejecución es:
Ahora escojamos otro ejemplo un poco más complicado:
Inspirado por http://planetmath.org/encyclopedia/MutualRecursion.html , que es una buena lectura en sí misma, echemos un vistazo a: "" "Los números de Fibonacci se pueden interpretar mediante recursión mutua: F (0) = 1 y G (0 ) = 1, con F (n + 1) = F (n) + G (n) y G (n + 1) = F (n). "" "
Entonces, ¿cuál es el tiempo de ejecución de F? Nosotros iremos por el otro lado.
Bueno, R (F (0)) = 1 = F (0); R (G (0)) = 1 = G (0)
Ahora R (F (1)) = R (F (0)) + R (G (0)) = F (0) + G (0) = F (1)
...
No es difícil ver que R (F (m)) = F (m), por ejemplo, el número de llamadas a funciones necesarias para calcular un número de Fibonacci en el índice i es igual al valor de un número de Fibonacci en el índice i. Esto supone que sumar dos números juntos es mucho más rápido que una llamada a función. Si este no fuera el caso, entonces esto sería cierto: R (F (1)) = R (F (0)) + 1 + R (G (0)), y el análisis de esto habría sido más complicado, posiblemente sin una solución fácil de forma cerrada.
La forma cerrada para la secuencia de Fibonacci no es necesariamente fácil de reinventar, sin mencionar algunos ejemplos más complicados.
fuente
Lo primero que debe hacer es mostrar que las funciones que ha definido terminan y para qué valores exactamente. En el ejemplo que has definido
b
solo terminay <= -5
porque si inserta cualquier otro valor, tendrá un término del formulariob(a(y-1))
. Si se expande un poco más, verá que un término del formulariob(a(y-1))
eventualmente conduce al términob(1010)
que conduce a un términob(a(1009))
que nuevamente conduce al términob(1010)
. Esto significa que no puede conectar ningún valora
que no satisfagax <= -4
porque si lo hace, terminará con un bucle infinito en el que el valor a calcular depende del valor a calcular. Entonces, esencialmente este ejemplo tiene un tiempo de ejecución constante.Entonces, la respuesta simple es que no existe un método general para determinar el tiempo de ejecución de las funciones recursivas porque no existe un procedimiento general que determine si una función definida recursivamente termina.
fuente
Tiempo de ejecución como en Big-O?
Eso es fácil: O (N) , suponiendo que hay una condición de terminación.
La recursión es solo un bucle, y un bucle simple es O (N) sin importar cuántas cosas haga en ese bucle (y llamar a otro método es solo otro paso en el bucle).
Lo interesante sería si tienes un bucle dentro de uno o más de los métodos recursivos. En ese caso, terminaría con algún tipo de rendimiento exponencial (multiplicando por O (N) en cada pasada a través del método).
fuente
O(2^n)
yO(n*log(n))
, respectivamente.a
llamab
yb
llama,a
por lo que no puede simplemente asumir que cualquiera de los métodos lleva tiempoO(1)
.