Si cada solución recursiva se puede transformar en una iterativa, ¿por qué usar la recursividad?

Cada solución iterativa puede transformarse en una solución recursiva. ¿Por qué usar iteración? Las soluciones iterativas tradicionales ofrecidas por la mayoría de los lenguajes imperativos son verbosas y propensas a errores. Las soluciones recursivas no sufren errores off-by-one / fencepost y son menos vulnerables a otros casos extremos.

Si una solución recursiva es apropiada, la solución recursiva será menos frágil (especialmente si el lenguaje admite la recursión correctamente), posiblemente más eficiente, generalmente más fácil de optimizar y definitivamente más clara (si no lo es, probablemente no fue la solución adecuada) ) Una gran cantidad de estructuras de datos son recursivas (por ejemplo, listas, árboles), al igual que un conjunto completo de algoritmos (por ejemplo, la ruta más corta)

Menos frágil porque no hay un código de contabilidad adicional para realizar un seguimiento de dónde se encuentra. Las ramas del código simplemente coinciden con los casos posibles, sin cruft extra. Eso hace que la solución sea menos propensa a errores, más fácil de depurar y modificar.

¿Más eficiente? Por algunas soluciones. La evaluación perezosa de Haskell se puede utilizar para ejecutar soluciones recursivas en un espacio constante incluso donde

¿Más fácil de optimizar? Sí, a menudo. Dado que el código está claramente separado en ramas / funciones que coinciden con los diferentes casos, la optimización para los diferentes casos es más fácil. Es posible que termine con una solución final más fea, pero es mucho mejor comenzar limpio y agregar complicaciones más tarde. Si elige una solución iterativa, comienza con las complicaciones.

Más claro (con las ventajas prácticas mencionadas anteriormente). Como ya dije, el código solo tiene que lidiar con los estados específicos que cualquier parte del problema puede presentar. Cada caso se puede manejar por separado, limpiamente. No está claro si no has aprendido la recursividad.

Lamentablemente, puede ser mal enseñado y muchos desarrolladores no se dan cuenta de que es algo que incluso deberían aprender.

Buen soporte para la recursividad

Si el lenguaje que está utilizando admite la recursividad de cola y / o la evaluación diferida, las soluciones explícitamente recursivas (aquellas en las que su código contiene no solo las ramas sino también el marco recursivo circundante) no amenazan con arruinar su pila. La recursión se usa más comúnmente en los idiomas que ofrecen esto. Pero..

No necesitas el apoyo

Los pliegues y las reducciones son funciones que manejan la implementación de la recursión básica para usted, dejándole escribir el código que maneja cada caso. Incluso en lenguajes funcionales que tienen un buen soporte para la recursividad, los desarrolladores experimentados usan mucho estas funciones, y tienden a escribir solo funciones explícitamente recursivas donde el uso de la función de plegado de la biblioteca sería poco elegante.

Debido a que la implementación de la función de plegado está oculta para usted, generalmente escrita por alguien que conoce el idioma y su rendimiento muy bien, pueden implementarse incluso en idiomas que no tienen un buen soporte para la recursividad. Muchos lenguajes imperativos ahora tienen esto en sus bibliotecas principales, incluso Java, desde Java 8.

Los pliegues son fundamentalmente un concepto recursivo, incluso si el código de la biblioteca evita la recursividad explícita con fines de optimización. Para usarlos bien, ayuda haber aprendido a escribir soluciones explícitamente recursivas.

La recursión a veces resulta en un menor número de líneas de código y lo hace comprensible.

Por ejemplo, vea a continuación la implementación del recorrido en orden de un BST (árbol de búsqueda binario) sin recursividad

void inOrder (struct tNode * root)
{
/ * establece la corriente en la raíz del árbol binario * /
struct tNode * current = root;
struct sNode * s = NULL; / * Inicializar pila s * /
bool hecho = 0;
mientras (! hecho)
{
/ * Alcanzar el tNode más a la izquierda del tNode actual * /
if (actual! = NULL)
{
/ * coloca el puntero a un nodo de árbol en la pila antes de atravesar
el subárbol izquierdo del nodo * /
empujar (& s, actual);
actual = actual-> izquierda;
}
/ * retroceda desde el subárbol vacío y visite tNode
en la parte superior de la pila; sin embargo, si la pila está vacía,
estás listo */
más
{
if (! isEmpty (s))
{
actual = pop (& s);
printf (“% d”, actual-> datos);
/ * hemos visitado el nodo y su subárbol izquierdo.
Ahora, es el turno correcto del subárbol * /
actual = actual-> derecha;
}
más
hecho = 1;
}
} / * fin de while * /
}

Código fuente Inorder Tree Traversal sin recursión – GeeksforGeeks

Y el mismo código que usa recursión se verá así

vacío printInorder (struct node * node)
{
if (nodo == NULL)
regreso;
/ * primera repetición en el hijo izquierdo * /
printInorder (nodo-> izquierda);
/ * luego imprime los datos del nodo * /
printf (“% d”, nodo-> datos);
/ * ahora se repite en el hijo derecho * /
printInorder (nodo-> derecha);
}

Código fuente transversal de árbol (orden, preorden y posorden) – GeeksforGeeks

Entonces puedes encontrar la diferencia ahora 🙂

Pero no es que siempre deba usar la recursión porque si alguien no quiere usar memoria adicional para la pila en caso de recursión, se usa algo iterativo.

Porque alguien quiere enseñarte la técnica y asume (erróneamente) que el espacio de la pila es un recurso inagotable que puede quemarse con un abandono salvaje.

Para ser justos, eso suele ser casi cierto en algunos campos, pero con la recursividad laboral integrada es uno de los antipatrones más comunes. Cuando tienes 48k de RAM para jugar en total, casi nunca quieres considerar la recurrencia.

Personalmente, casi siempre codifico iterativamente. Podría convertir la lógica para que sea recursiva como parte de un proceso de revisión y optimización de código más adelante, pero no quemo espacio de pila directamente cuando no lo necesito.

Debido a que los algoritmos recursivos son mucho más fáciles de entender ya que es más fácil pensar en resolver un problema más pequeño y construir para resolver un problema más grande.

Porque la recursividad puede hacer que los problemas sean simples. Solo necesita considerar un paso, pero no todos.