La recursión , como todos sabemos, es uno de esos problemas, que envolver la cabeza se siente como lograr un "hito" en su viaje de programación.
Pero cuando se trata de usarlo realmente en problemas del mundo real, conocer la mecánica de la recursión NO es suficiente, también se debe comprender la naturaleza de los problemas en los que la recursión es la solución más adecuada.
Así que mi pregunta es esta...
- ¿Cuáles son los "patrones problemáticos" que requieren la solución de la recursividad?
- es la recurrencia una forma de estrategia de "divide y vencerás" o una forma de "reutilización de código", o es un patrón de diseño en sí mismo
- ¿Puede darnos un ejemplo de un problema del mundo real en el que la recursión viene a la mente como una solución inmediata?
- ACTUALIZACIÓN -
muchas respuestas se refieren a "problemas reales" como el desplazamiento de árboles, factorial, etc. Preferiría "los problemas reales REALES" - déjenme darles un ejemplo ...
Teníamos una GRAN tirada de texto (aproximadamente 30 MB de texto como una lista vinculada de structs
), y necesitábamos hacer un índice para la búsqueda de texto completo. Necesitábamos mantener todo el índice en la memoria y volver a indexar el texto cada 10 minutos.
Cada 10 minutos, compararíamos todo el texto (dos listas vinculadas, línea por línea) con un fragmento de texto recién generado, para ver qué línea se cambió, y luego volveríamos a indexar solo esa línea, de esa manera podríamos evitar tener que volver a indexar TODO el texto. Recuerde: necesitábamos encontrar los puntos de diferencia entre dos listas vinculadas de 30 MB.
A uno de mis colegas se le ocurrió un programa fantástico que utilizaba la recursión PESADA para comparar las líneas, y luego recolectaba las posiciones en las que los mandriles diferían en una matriz. Sí, sé que suena desconcertante. lo hizo.
El punto es: ¿cómo podría ver que este problema podría resolverse de manera inteligente con un uso intensivo de la recursividad?
Respuestas:
No diría que existe un patrón de problema para el uso de la recursividad. Cada función que se puede implementar con recursividad también se puede implementar de forma iterativa, a menudo presionando y haciendo estallar una pila.
Es una cuestión de expresión y también de rendimiento. Los algoritmos iterativos a menudo tienen un mejor rendimiento y son más fáciles de optimizar. Sin embargo, los algoritmos recursivos se benefician de una expresión más clara y, por lo tanto, a menudo son más fáciles de leer, comprender e implementar.
Algunas cosas incluso no se pueden expresar sin recurrencia, por ejemplo, listas infinitas. Los llamados lenguajes funcionales dependen en gran medida de la recursividad, ya que es su forma natural de expresión. El dicho es: "La programación recursiva es una programación funcional bien hecha".
No lo llamaría un patrón de diseño. Es una cuestión de expresión. A veces, una expresión recursiva es simplemente más poderosa y más expresiva y, por lo tanto, conduce a un código mejor y más limpio.
Cualquier cosa que necesite atravesar árboles se expresará adecuadamente mediante un algoritmo recursivo.
fuente
Ninguno. Divide y vencerás utiliza la recursividad. Pero la recursión no necesariamente divide y vencerás, ya que esto último significa dividir un problema en dos (o más) partes y resolver cada una de ellas simétricamente. En la recursión, no haces esto.
La reutilización del código no tiene ninguna relación, y un patrón de diseño entra en juego en un nivel mucho más alto. Por ejemplo, incluso dividir y conquistar (también un patrón de nivel más alto que la recursividad) todavía no se considera un patrón de diseño, sino que es un patrón algorítmico .
Transversal del árbol. O, más generalmente, el recorrido transversal del gráfico. Esto incluye notablemente atravesar una estructura de carpetas.
Y, por supuesto, cualquier cosa que use la división y la conquista o la programación dinámica, ya que ambas se expresan naturalmente en términos de recursividad.
fuente
Derivado de la auto-similitud de los fractales, yo diría que la auto-igualdad o la auto-identidad (o como se llame) es un patrón de problema típico para la recursividad. Es decir, un problema puede dividirse en subproblemas que tienen la misma estructura que el problema principal.
En el recorrido del árbol mencionado, cada árbol secundario es un árbol completo en sí mismo, al igual que el árbol principal, y el árbol principal puede ser un árbol secundario dentro de otro árbol.
Así que supongo que su colega descubrió las propiedades de auto-igualdad de su problema de indexación. O fue al revés y transformó el problema en una representación auto-igualitaria.
fuente
Bueno, la recursividad se puede entender fácilmente si uno intenta transformar los bucles imperativos en funciones funcionales. De todos modos, intentemos dar respuestas a todas las preguntas:
Si tiene una estructura o algoritmo similar a un árbol, necesitará recurrencia. Si su código imperativo trata con una pila, necesitará recurrencia. Si un cierto paso en su algoritmo depende de pasos anteriores (think loops), necesita recurrencia. La necesidad aquí debe ser interpretada como puede usar.
Ninguna. Dividir y conquistar utiliza la recursividad, pero se puede implementar con pilas. La reutilización del código se refiere a otra cosa. Los patrones de diseño son más complicados que la simple recursión.
Análisis y todo lo que se refiere a estructuras de árboles. Incluso estructuras arbóreas implícitas.
fuente
Si hay una manera de simplificarlo para que sea fácil, esa puede ser la pista de que la recursión podría funcionar. Podrías ordenar y buscar ejemplos donde existan soluciones recursivas como Merge Sort y Binary Search respectivamente.
Algo a tener en cuenta es cómo algunos problemas pueden resolverse mal con la recursividad como un factorial.
En cuanto a un ejemplo del mundo real en el que usaría la recursividad, buscar un libro en un estante de libros se puede hacer de manera bastante recursiva. Solo miro el libro y, si no es lo que quiero, paso al siguiente. Me detengo cuando encuentro el libro o llego al final de la fila. El bucle de consultar un libro y pasar al siguiente podría hacerse recursivamente. Quizás este es un ejemplo demasiado real.
fuente
En términos muy generales, se requiere recursividad cuando se resuelve un problema donde f (x) = f (g (x)) . A menos que esté de acuerdo con la recursión infinita, g (x) no debería evaluar a x .
Ninguna de las anteriores. Es solo una forma de hacer lo mismo repetidamente, a veces basado en variaciones en la entrada. El concepto ha existido mucho más tiempo que los patrones de diseño, la reutilización de código o incluso las computadoras, para el caso.
Los factores factoriales, la secuencia de Fibonacci, el recorrido del árbol y muchos otros problemas se pueden resolver con recusrion. La recursión en el sentido de una función que se llama a sí misma no es necesariamente la mejor manera de implementar ese tipo de cosas; Hay otras formas de lograr el mismo efecto (por ejemplo, una pila y un bucle) que podrían ser más deseables.
fuente
Cuando escribe un algoritmo recursivo, generalmente traduce una definición recursiva del problema al código. Entonces ni siquiera necesita saber cómo se ejecutará.
Es lo que sucede en la programación funcional. De hecho, especifica qué (definición) en lugar de cómo . En otras palabras, usted define la base y luego define su problema en el término de un subproblema.
Por ejemplo, considere el
Factorial
algoritmoLuego, cuando encuentre un problema, debe pensar si puede definirlo recursivamente o no, si puede definirlo recursivamente, casi lo ha resuelto.
Sin embargo, cualquier función recursiva no debería ser una definición recursiva. Puede definir la base y relacionar (definir) la solución del problema principal con la solución (definición) de subproblemas. Pero para esta relación es posible que necesite un procedimiento.
Un ejemplo es el
MergeSort
quemerge
podría ser un procedimiento imperativo para relacionar la definición o la solución de ordenar toda la matriz con la clase de las sub-matrices.fuente