Estaba leyendo sobre algunas prácticas de desarrollo de entrevistas, específicamente sobre las preguntas técnicas y las pruebas que se hicieron en las entrevistas y me he tropezado varias veces con los dichos del género "Ok, resolviste el problema con un bucle while, ahora puedes hacerlo con recursividad ", o" todos pueden resolver esto con un bucle while de 100 líneas, pero ¿pueden hacerlo en una función recursiva de 5 líneas? " etc.
Mi pregunta es, ¿es la recursividad generalmente mejor que si / while / for constructs?
Honestamente, siempre he pensado que no es preferible la recursión, ya que está limitada a la memoria de la pila, que es mucho más pequeña que el montón, también hacer una gran cantidad de llamadas a funciones / métodos es subóptima desde el punto de vista del rendimiento, pero puedo estar equivocado...
fuente
Respuestas:
La recursión no es intrínsecamente mejor o peor que los bucles: cada uno tiene ventajas y desventajas, y estos incluso dependen del lenguaje de programación (y la implementación).
Técnicamente, los bucles iterativos se ajustan mejor a los sistemas informáticos típicos a nivel de hardware: a nivel de código de máquina, un bucle es solo una prueba y un salto condicional, mientras que la recursión (implementada ingenuamente) implica empujar un marco de pila, saltar, regresar y retroceder de la pila OTOH, se pueden escribir muchos casos de recursión (especialmente aquellos que son trivialmente equivalentes a bucles iterativos) para evitar el stack push / pop; Esto es posible cuando la llamada de función recursiva es lo último que sucede en el cuerpo de la función antes de regresar, y se conoce comúnmente como una optimización de llamada de cola (u optimización de recursión de cola ). Una función recursiva optimizada correctamente para la cola de llamadas es en su mayoría equivalente a un bucle iterativo en el nivel de código de máquina.
Otra consideración es que los bucles iterativos requieren actualizaciones de estado destructivas, lo que los hace incompatibles con la semántica del lenguaje puro (sin efectos secundarios). Esta es la razón por la cual los lenguajes puros como Haskell no tienen construcciones de bucle, y muchos otros lenguajes de programación funcional los carecen por completo o los evitan tanto como sea posible.
Sin embargo, la razón por la que estas preguntas aparecen tanto en las entrevistas es porque, para responderlas, necesita una comprensión profunda de muchos conceptos vitales de programación (variables, llamadas a funciones, alcance y, por supuesto, bucles y recursión), y tiene para llevar a la mesa la flexibilidad mental que le permite abordar un problema desde dos ángulos radicalmente diferentes y moverse entre diferentes manifestaciones del mismo concepto.
La experiencia y la investigación sugieren que existe una línea divisoria entre las personas que tienen la capacidad de comprender las variables, los punteros y la recursividad, y los que no. Casi todo lo demás en programación, incluidos los marcos, las API, los lenguajes de programación y sus casos límite, se pueden adquirir a través del estudio y la experiencia, pero si no puede desarrollar una intuición para estos tres conceptos básicos, no está en condiciones de ser un programador. La traducción de un bucle iterativo simple a una versión recursiva es la forma más rápida posible de filtrar a los que no son programadores; incluso un programador bastante inexperto generalmente puede hacerlo en 15 minutos, y es un problema muy agnóstico del lenguaje, por lo que el candidato puede elegir un idioma de su elección en lugar de tropezar con idiosincrasias.
Si recibe una pregunta como esta en una entrevista, es una buena señal: significa que el posible empleador está buscando personas que puedan programar, no personas que hayan memorizado el manual de una herramienta de programación.
fuente
Depende.
También vale la pena señalar que el soporte para la recursión de la cola hace que la cola recursiva y los bucles iterativos sean equivalentes, es decir, la recursión no siempre tiene que desperdiciar la pila.
Además, un algoritmo recursivo siempre se puede implementar de forma iterativa mediante el uso de una pila explícita .
Finalmente, me gustaría señalar que una solución de cinco líneas es probablemente siempre mejor que una de 100 líneas (suponiendo que en realidad sean equivalentes).
fuente
No existe una definición universalmente aceptada de "mejor" cuando se trata de programación, pero lo consideraré como "más fácil de mantener / leer".
La recursión tiene más poder expresivo que las construcciones de bucle iterativo: digo esto porque un bucle while es equivalente a una función recursiva de cola y las funciones recursivas no necesitan ser recursivas de cola. Las construcciones poderosas generalmente son algo malo porque te permiten hacer cosas que son difíciles de leer. Sin embargo, la recursividad te da la capacidad de escribir bucles sin usar la mutabilidad y, en mi opinión, la mutabilidad es mucho más poderosa que la recursividad.
Entonces, desde un bajo poder expresivo hasta un alto poder expresivo, las construcciones en bucle se acumulan así:
Idealmente, usarías las construcciones menos expresivas que puedas. Por supuesto, si su idioma no es compatible con la optimización de llamadas de cola, eso también puede influir en su elección de construcción de bucle.
fuente
La recursión es a menudo menos obvia. Menos obvio es más difícil de mantener.
Si escribe
for(i=0;i<ITER_LIMIT;i++){somefunction(i);}
en el flujo principal, deja perfectamente claro que está escribiendo un bucle. Si escribesomefunction(ITER_LIMIT);
, realmente no deja en claro lo que sucederá. Solo ver contenido: esasomefunction(int x)
llamadasomefunction(x-1)
te dice que, de hecho, es un bucle que usa iteraciones. Además, no puede poner fácilmente una condición de escape enbreak;
algún lugar a la mitad de las iteraciones, debe agregar un condicional que se pasará por completo o lanzar una excepción. (y las excepciones vuelven a agregar complejidad ...)En esencia, si es una elección obvia entre iteración y recursión, haga lo intuitivo. Si la iteración hace el trabajo fácilmente, raramente vale la pena guardar 2 líneas los dolores de cabeza que puede crear a largo plazo.
Por supuesto, si te está ahorrando 98 líneas, eso es un asunto completamente diferente.
Hay situaciones en las que la recursividad simplemente encaja perfectamente, y no son realmente infrecuentes. Recorrido de estructuras de árbol, redes de enlaces múltiples, estructuras que pueden contener su propio tipo, matrices dentadas multidimensionales, esencialmente cualquier cosa que no sea un vector directo o una matriz de dimensiones fijas. Si atraviesa un camino recto conocido, repita. Si se sumerge en lo desconocido, recurse.
Esencialmente, si se
somefunction(x-1)
va a llamar desde dentro de sí mismo más de una vez por nivel, olvide las iteraciones.... Escribir iterativamente funciones para tareas que se realizan mejor por recursión es posible pero no agradable. Donde sea que uses
int
, necesitas algo comostack<int>
. Lo hice una vez, más como ejercicio que para fines prácticos. Puedo asegurarle que una vez que enfrente una tarea de este tipo, no tendrá dudas como las que expresó.fuente
map
puede definirse como una función recursiva (ver, por ejemplo, haskell.org/tutorial/functions.html ), incluso si es intuitivamente claro que atraviesa una lista y aplica una función a cada miembro de la lista.map
no es una palabra clave, es una función regular, pero eso es un poco irrelevante. Cuando los programadores funcionales usan la recursión, generalmente no es porque quieran realizar una secuencia de acciones, sino porque el problema que se está resolviendo puede expresarse como una función y una lista de argumentos. El problema puede reducirse a otra función y a otra lista de argumentos. Eventualmente, tiene un problema que puede resolverse de manera trivial.Como de costumbre, esto no se puede responder en general porque hay factores adicionales, que en la práctica son muy desiguales entre los casos y desiguales entre sí dentro de un caso de uso. Estas son algunas de las presiones.
fuente
La recursión se trata de repetir la llamada a la función, el bucle se trata de repetir el salto para colocarlo en la memoria.
Debería mencionarse también sobre el desbordamiento de la pila - http://en.wikipedia.org/wiki/Stack_overflow
fuente
Realmente depende de la conveniencia o el requisito:
Si toma el lenguaje de programación Python , es compatible con la recursividad, pero de forma predeterminada hay un límite para la profundidad de recursión (1000). Si excede el límite, obtendremos un error o una excepción. Ese límite se puede cambiar, pero si lo hacemos, podemos experimentar situaciones anormales en el idioma.
En este momento (número de llamadas más que la profundidad de recursión), necesitamos preferir construcciones de bucle. Quiero decir, si el tamaño de la pila no es suficiente, tenemos que preferir las construcciones de bucle.
fuente
Usa el patrón de diseño de la estrategia.
Dependiendo de su carga (y / u otras condiciones), elija una.
fuente