Complejidad recursiva.
Hola a todos. Dando vueltas por internet he encontrado este foro, muy interesante, en el cual espero que me puedan resolver un par de dudas que tengo con respecto a la complejidad temporal de los algoritmos recursivos. Aquí dejo la primer duda que tengo:
Normal 0 21 false false false MicrosoftInternetExplorer4
int g (int x) {
// pre: x >=0
if (x = = 0)
return(1);
else
return( f (g(x-1) * 3 )); } // f pertenece a O(k)
Mi duda es cómo calculo el tiempo de f(g(x-1)*3.
La multiplicación por 3 es una constante O(1), mientras que f pertenece a O(k), pero para llamarla, invoca a la recursión.
Me he perdido bastante así que gracias de antemano a todos.
Aún cuando la recursión por la cola significa una mejora cuando se traducen funciones recursivas en un compilador, en los lenguajes imperativos queda un problema. Cada vez que se hace una llamada recursiva queda pendiente el contexto de control a la espera del valor a retornar. Por lo que de todas maneras el valor retornado al final debe volver a recorrer toda la pila de llamadas para que el valor sea devuelto por la llamada original a "factorial" en tu ejemplo.
Una diferencia se ve en Scheme por ejemplo:
(define (factorial n p)
(if (= n 1)
p
(factorial (- n 1) (* p n))
)
)
En este caso, el interprete empieza a evaluar una expresion como (factorial 5 1) por ejemplo, pero la diferencia es que cuando llega al final de la recursión (cuando tiene que devolver p), directamente devuelve el valor, y eso se debe a que el interprete de Scheme se da cuenta que la función usa recursión por la cola, por lo que el contexto de control es vacío. Bueno, esto solo es posible dado que en Scheme todo se reduce a un valor del lenguaje y si ese valor ya se calculó, no hay nada más que hacer.
Saludos.
Bueno, cuando pones :
(factorial (- n 1) (* p n))
lo último que realiza el código no es la llamada a función, sino que tiene que esperar la respuesta para realizar el producto. Tail recursion sería :
Factorial( -n 1, p)
En ese caso lo único que hace es llamar a la función(adentro ya podrías multiplicar p), condición necesaria para tail recursion.
Es que Scheme usa notación infija de invocación a funciones(osea (- n 1) es el primer parámetro de la función y (* n p) es el segundo parámetro, obviamente la primera expresión "factorial" es la llamada a la función recursivamente) y y la evaluación de expresiones es estricta, por eso los argumentos se evalúan antes de invocar a la función :).
De hecho es la versión Scheme del mismo procedimiento que colocaste tú, solo que mi intención era diferenciar lo que sucedía con los contextos de control durante las llamadas recursivas, que en este caso se desestiman cuando el valor final es devuelto :).
Saludos.
Si me acabo de dar cuenta, justo en el siguiente link hay un ejemplo con la misma función y explica como funciona esto del Tail recursion:
















Hay una capacidad en los compiladores modernos llamada Tail Recursion. Consiste en que si la última llamada propicia la recursión, esta puede ser generada de manera casi iterativa, creo que con un poco más de memoria pero igual de rápida.
Ejemplo:
int factorial(int number) {
if(number == 0) {
return 1;
}
factorial_i(number, 1);
}
int factorial_i(int currentNumber, int sum) {
if(currentNumber == 1) {
return sum;
} else {
return factorial_i(currentNumber - 1, sum*currentNumber);
}
}
Por cierto, ¿ como coloco un bloque de código?
Manuel A. Bellido OViedo,
Student of the School of Computer Science,
San Pablo Catholic University,Arequipa, Peru
http://www.mbellido.info/,