A veces, en entrevistas, puedo usar la recursividad para resolver un problema (como agregar 1
a un entero de precisión infinita), o cuando el problema se presenta adecuado para usar la recursividad. A veces, puede deberse simplemente al uso de la recursividad para resolver problemas, por lo que, sin pensar mucho, la recursión se utiliza para resolver el problema.
Sin embargo, ¿cuáles son las consideraciones antes de decidir si es adecuado usar la recursividad para resolver un problema?
Algunos pensamientos que tuve:
Si usamos la recursividad en los datos que se reducen a la mitad cada vez, parece que no es un problema usar la recursividad, ya que todos los datos que pueden caber en 16 GB de RAM, o incluso un disco duro de 8 TB, pueden manejarse por recursión a solo 42 niveles de profundidad. (por lo que no hay desbordamiento de la pila (creo que en algún entorno, la pila puede tener 4000 niveles de profundidad, más de 42, pero al mismo tiempo, también depende de cuántas variables locales tenga, ya que cada pila de llamadas, ocupa más memoria si hay muchas variables locales, y es el tamaño de la memoria, no el nivel, lo que determina el desbordamiento de la pila)).
Si calcula números de Fibonacci utilizando recursividad pura, realmente tiene que preocuparse por la complejidad del tiempo, a menos que guarde en caché los resultados intermedios.
¿Y qué tal agregar 1
a un entero de precisión infinita? Tal vez sea discutible, ya que ¿trabajará con números de 3000 dígitos de largo o 4000 dígitos de largo, tan grandes que pueden causar un desbordamiento de la pila? No lo pensé, pero tal vez la respuesta sea no, no deberíamos usar la recursión, sino solo un bucle simple, porque ¿qué pasa si en alguna aplicación, el número realmente necesita ser de 4000 dígitos para verificar si hay alguna? propiedades del número, como si el número es primo o no.
La última pregunta es: ¿cuáles son las consideraciones antes de que pueda decidir utilizar la recursividad para resolver un problema?
fuente
1
al entero de precisión infinita? Se puede decir, sí, se reducen a problemas más pequeños, pero la recursión pura no es adecuada para elloRespuestas:
Una consideración es si su algoritmo está destinado a ser una solución abstracta o una solución práctica ejecutable. En el primer caso, los atributos que busca son la corrección y la facilidad de comprensión para su público objetivo 1 . En este último caso, el rendimiento también es un problema. Estas consideraciones pueden influir en su elección.
Una segunda consideración (para una solución práctica) es si el lenguaje de programación (o más estrictamente, su implementación) que está utilizando elimina la llamada de cola. Sin la eliminación de la cola, la recursividad es más lenta que la iteración, y la recursión profunda puede conducir a problemas de desbordamiento de pila.
Tenga en cuenta que una solución recursiva (correcta) se puede transformar en una solución no recursiva equivalente, por lo que no necesariamente tiene que tomar una decisión difícil entre los dos enfoques.
Finalmente, a veces la elección entre formulaciones recursivas y no recursivas está motivada por la necesidad de probar (en el sentido formal) propiedades sobre un algoritmo. Las formulaciones recursivas permiten más directamente la prueba por inducción.
1 - Esto incluye consideraciones como si el público objetivo ... y esto podría incluir a los programadores que leen código práctico ... verían un estilo de solución como "más natural" que el otro. La noción de "natural" variará de persona a persona, dependiendo de cómo aprendieron programación o algoritmos. (Desafío a cualquiera que proponga la "naturalidad" como criterio principal para decidir usar la recursividad (o no) para definir la "naturalidad" en términos objetivos; es decir, cómo la mediría).
fuente
Como programador de C / C ++, mi principal consideración es el rendimiento. Mi proceso de decisión es algo como:
¿Cuál es la profundidad máxima de la pila de llamadas? Si es demasiado profundo, elimine la recurrencia. Si es poco profundo, vaya a 2.
¿Es probable que esta función sea un cuello de botella de mi programa? En caso afirmativo, vaya a 3. Si no, mantenga la recursividad. Si no está seguro, ejecute un generador de perfiles.
¿Cuál es la fracción del tiempo de CPU dedicado a las llamadas a funciones recursivas? Si las llamadas a funciones toman mucho menos tiempo que el resto del cuerpo de la función, está bien usar la recursividad.
fuente
Al escribir funciones en Scheme, me parece natural escribir funciones recursivas de cola sin pensar demasiado.
Al escribir funciones en C ++, me encuentro debatiendo antes de usar una función recursiva. Las preguntas que me hago son:
¿Se puede realizar el cálculo utilizando un algoritmo iterativo? En caso afirmativo, utilice un enfoque iterativo.
¿Puede la profundidad de recursión crecer según el tamaño del modelo? Recientemente me encontré con un caso en el que la profundidad de la recursión aumentó a casi 13000 debido al tamaño del modelo. Tuve que convertir la función para usar un algoritmo iterativo post-prisa.
Por esta razón, no recomendaría escribir un algoritmo transversal de árbol usando funciones recursivas. Nunca se sabe cuándo el árbol se vuelve demasiado profundo para su entorno de tiempo de ejecución.
¿Puede la función volverse demasiado complicada usando un algoritmo iterativo? En caso afirmativo, use una función recursiva. No he intentado escribir
qsort
usando un enfoque iterativo, pero tengo la sensación de que usar una función recursiva es más natural.fuente
Para los números de Fibonacci, la ingenua "recursión" es totalmente estúpida. Eso es porque lleva a que el mismo subproblema se resuelva una y otra vez.
En realidad, hay una variación trivial de los números de Fibonacci donde la recursión es muy eficiente: dado un número n ≥ 1, calcule tanto fib (n) como fib (n-1). Por lo tanto, necesita una función que devuelva dos resultados, llamemos a esta función fib2.
La implementación es bastante simple:
fuente
fib2
devuelve un par de números, y sufib2()
no se ajusta a la interfaz defib()
, que es, dado un número, devolver un número. Parece quefib(n)
va a volver,fib2(n)[0]
pero sea específico