En cuanto a los detalles de su pregunta,
Una solución recursiva ingenua puede aumentar la complejidad del tiempo para ciertos problemas, pero hay formas de evitarlo. Tome el clásico enésimo problema del número de Fibonacci, por ejemplo. La solución iterativa se ejecuta en tiempo O (n), mientras que la solución recursiva ingenua (reproducida a continuación) toma tiempo O (2 ^ n).
fib (n):
si n <= 1: devuelve n
de lo contrario: devuelve fib (n-1) + fib (n-2)
La razón de esta gran discrepancia es que para que la solución recursiva calcule fib (n-1) necesita calcular fib (n-1-1), etc., hasta n = 1, y hace todo esto nuevamente para el término fib (n-2). No solo eso, sino que cada llamada recursiva hace que se bifurque nuevamente, exponencialmente. Por lo tanto, se están volviendo a hacer muchos cálculos, pero podemos evitar esos cálculos adicionales utilizando la memorización .
Memorización
simplemente significa guardar el valor de retorno de la función por cada entrada y devolver ese valor en lugar de rehacer el cálculo. (Esto solo funciona si la función siempre devuelve el mismo valor dada la misma entrada, que en nuestro caso es verdadera). Si memoriza la función fib, se ejecuta nuevamente en O (n):
memo = nuevo mapa
fib (n):
memoed = memo.get (n)
si se recuerda: volver memorado
más si n <= 1: devuelve n
más:
ret = fib (n-1) + fib (n-2)
memo.put (n, ret)
volver ret
(fib (n-1) se calcula hasta n = 1, luego, cuando se ejecuta fib (n-2), ese valor ya se ha calculado para que regrese instantáneamente, lo que resulta en invocaciones de O (n) de fib.)
Si se deja como está, este código ahora es más difícil de leer, pero su idioma puede tener alguna forma de memorizarlo para que no contamine su código. Parece que hay una forma en Java 8, pero debe implementarla usted mismo. Si está usando Clojure, simplemente puede usar la función de memorización incorporada (ejemplos incluidos). Apuesto a que su solución recursiva en la competencia de programación podría haberse beneficiado de la memorización.
Como nota al margen, la solución recursiva memorable todavía usa el espacio O (n) en comparación con el espacio O (1) utilizado por la solución iterativa, pero muchos lenguajes funcionales tienen una forma automática inteligente de evitar el crecimiento de la pila llamada Tail Call Optimization (TCO) .
Tail Call Optimization (TCO)
significa que, en algunos idiomas, si coloca una llamada a la función como la última instrucción que se ejecutará y devolverá, el compilador puede desechar el marco de la pila actual antes de llamar a la función. Puede hacer esto porque sabe que ya no necesita ninguna de las variables locales o valores intermedios, ya que solo está devolviendo el valor de retorno de la función que está a punto de llamar. Tendrá que buscar en Google si su compilador / lenguaje tiene TCO (fuera de mi cabeza, java, C # y Python no, pero algunos compiladores de C ++ sí, y Clojure si usa la construcción loop / recur).
En la mayoría de los problemas recursivos, la llamada recursiva termina naturalmente en la posición de cola, pero en nuestra solución de Fibonacci, desafortunadamente, este no es el caso (ya que tenemos la memorización y la adición). Sin embargo, así como todas las soluciones iterativas pueden escribirse recursivamente, también pueden reescribirse todas las soluciones recursivas para que la llamada recursiva esté en la posición final. Esto se logra agregando uno o más parámetros “acumuladores” a la función y pasando los valores intermedios que necesite a través de ellos (esto solo debería agregar una cantidad constante al uso de memoria). Después de hacer todo esto, su código puede volver a ser feo, desafortunadamente.
Con todo esto en mente, para responder finalmente a su pregunta,
Sí, uso la recursión profesionalmente, pero solo cuando es más fácil de leer que la iteración y no se acumula el desbordamiento. Usaré la recursividad al atravesar un árbol o gráfico, como al analizar un archivo JSON o XML, o al atravesar un sistema de archivos. Estos son casos simples que tienen una profundidad finita que la pila puede manejar, por lo que el uso de memoria no es un problema. En cuanto a la complejidad del tiempo, debería ser O (n), igual que la iteración. Y la solución recursiva sería mucho más fácil de leer especialmente para el análisis JSON o XML, ya que el contexto generalmente importa dentro de los elementos anidados, y este contexto puede encapsularse en funciones separadas que se llaman recursivamente.
Últimamente he estado usando Clojure y, afortunadamente, lenguajes funcionales como ese contienen varias construcciones que te ayudan a evitar la necesidad de escribir funciones recursivas o bucles imperativos en primer lugar.
Espero que esto ayude.