Uno de los temas que parece aparecer regularmente en las listas de correo y las discusiones en línea son los méritos (o la falta de ellos) de obtener una Licenciatura en Informática. Un argumento que parece surgir una y otra vez para la parte negativa es que han estado codificando durante varios años y nunca han usado la recursividad.
Entonces la pregunta es:
- ¿Qué es la recursividad?
- ¿Cuándo usaría la recursión?
- ¿Por qué la gente no usa la recursividad?
recursion
computer-science
Mike Minutillo
fuente
fuente
Respuestas:
Hay una serie de buenas explicaciones de recursividad en este hilo, esta respuesta es acerca de por qué no debe usarlo en la mayoría de los idiomas. * En la mayoría de las principales implementaciones de lenguaje imperativo (es decir, cada implementación principal de C, C ++, Basic, Python , Ruby, Java y C #) la iteración es muy preferible a la recursividad
Para ver por qué, siga los pasos que usan los idiomas anteriores para llamar a una función:
Hacer todos estos pasos lleva tiempo, generalmente un poco más de lo que se necesita para iterar a través de un bucle. Sin embargo, el verdadero problema está en el paso 1. Cuando se inician muchos programas, asignan un solo fragmento de memoria para su pila, y cuando se quedan sin esa memoria (a menudo, pero no siempre debido a la recursividad), el programa se bloquea debido a un desbordamiento de la pila .
Entonces, en estos idiomas, la recursividad es más lenta y te hace vulnerable a fallas. Sin embargo, todavía hay algunos argumentos para usarlo. En general, el código escrito recursivamente es más corto y un poco más elegante, una vez que sabe cómo leerlo.
Hay una técnica que los implementadores de lenguaje pueden usar llamada optimización de llamada de cola que puede eliminar algunas clases de desbordamiento de pila. En pocas palabras: si la expresión de retorno de una función es simplemente el resultado de una llamada a la función, entonces no necesita agregar un nuevo nivel en la pila, puede reutilizar el actual para la función que se llama. Lamentablemente, pocas implementaciones de lenguaje imperativas tienen incorporada la optimización de llamadas de cola.
* Me encanta la recursividad. Mi lenguaje estático favorito no usa bucles, la recursión es la única forma de hacer algo repetidamente. Simplemente no creo que la recursión sea generalmente una buena idea en idiomas que no están ajustados para ello.
** Por cierto Mario, el nombre típico para su función ArrangeString es "unirse", y me sorprendería si su idioma de elección aún no tiene una implementación.
fuente
Inglés simple ejemplo de recursión.
fuente
En el sentido más básico de la informática, la recursividad es una función que se llama a sí misma. Digamos que tiene una estructura de lista vinculada:
Y desea saber cuánto tiempo dura una lista vinculada, puede hacer esto con recursividad:
(Por supuesto, esto también se puede hacer con un bucle for, pero es útil como una ilustración del concepto)
fuente
length(list->next)
todavía necesita volver alength(list)
para que este último pueda agregar 1 al resultado. Si se escribiera para pasar el largo hasta ahora, solo entonces podríamos olvidar que la persona que llama existió. Al igualint length(const Node* list, int count=0) { return (!list) ? count : length(list->next, count + 1); }
.Cada vez que una función se llama a sí misma, creando un ciclo, entonces eso es recursividad. Como con cualquier cosa, hay buenos usos y malos usos para la recursividad.
El ejemplo más simple es la recursión de cola donde la última línea de la función es una llamada a sí misma:
Sin embargo, este es un ejemplo cojo, casi inútil, porque puede reemplazarse fácilmente por una iteración más eficiente. Después de todo, la recursión sufre sobrecarga de llamadas a funciones, que en el ejemplo anterior podría ser sustancial en comparación con la operación dentro de la función misma.
Entonces, toda la razón para hacer recursividad en lugar de iteración debería ser aprovechar la pila de llamadas para hacer algunas cosas inteligentes. Por ejemplo, si llama a una función varias veces con diferentes parámetros dentro del mismo bucle, entonces esa es una manera de lograr la ramificación . Un ejemplo clásico es el triángulo de Sierpinski .
Puede dibujar uno de esos de manera muy simple con recursividad, donde la pila de llamadas se ramifica en 3 direcciones:
Si intentas hacer lo mismo con la iteración, creo que encontrarás que se necesita mucho más código para lograrlo.
Otros casos de uso comunes pueden incluir jerarquías de desplazamiento, por ejemplo, rastreadores de sitios web, comparaciones de directorios, etc.
Conclusión
En términos prácticos, la recursión tiene más sentido siempre que necesite una ramificación iterativa.
fuente
La recursión es un método para resolver problemas basado en la mentalidad de dividir y conquistar. La idea básica es tomar el problema original y dividirlo en instancias más pequeñas (más fáciles de resolver) de sí mismo, resolver esas instancias más pequeñas (generalmente utilizando el mismo algoritmo nuevamente) y luego volver a ensamblarlas en la solución final.
El ejemplo canónico es una rutina para generar el factorial de n. El factorial de n se calcula multiplicando todos los números entre 1 y n. Una solución iterativa en C # se ve así:
No hay nada sorprendente en la solución iterativa y debería tener sentido para cualquiera que esté familiarizado con C #.
La solución recursiva se encuentra al reconocer que el enésimo factorial es n * hecho (n-1). O para decirlo de otra manera, si sabes qué es un número factorial en particular, puedes calcular el siguiente. Aquí está la solución recursiva en C #:
La primera parte de esta función se conoce como Caso base (o, a veces, Cláusula de protección) y es lo que evita que el algoritmo se ejecute para siempre. Simplemente devuelve el valor 1 cada vez que se llama a la función con un valor de 1 o menos. La segunda parte es más interesante y se conoce como el paso recursivo . Aquí llamamos al mismo método con un parámetro ligeramente modificado (lo decrementamos por 1) y luego multiplicamos el resultado con nuestra copia de n.
Cuando se encuentra por primera vez, esto puede ser un poco confuso, por lo que es instructivo examinar cómo funciona cuando se ejecuta. Imagina que llamamos FactRec (5). Entramos en la rutina, no nos recoge el caso base y terminamos así:
Si volvemos a ingresar el método con el parámetro 4, nuevamente no nos detendrá la cláusula de protección, por lo que terminaremos en:
Si sustituimos este valor de retorno en el valor de retorno anterior, obtenemos
Esto debería darle una pista sobre cómo se llega a la solución final, por lo que aceleraremos y mostraremos cada paso en el camino hacia abajo:
Esa sustitución final ocurre cuando se activa el caso base. En este punto tenemos una fórmula algrebraica simple para resolver que equivale directamente a la definición de Factoriales en primer lugar.
Es instructivo observar que cada llamada al método da como resultado que se active un caso base o una llamada al mismo método donde los parámetros están más cerca de un caso base (a menudo llamado una llamada recursiva). Si este no es el caso, el método se ejecutará para siempre.
fuente
FactRec()
debe multiplicarse porn
antes de regresar.La recursión es resolver un problema con una función que se llama a sí misma. Un buen ejemplo de esto es una función factorial. Factorial es un problema matemático donde el factorial de 5, por ejemplo, es 5 * 4 * 3 * 2 * 1. Esta función resuelve esto en C # para enteros positivos (no probado, puede haber un error).
fuente
La recursión se refiere a un método que resuelve un problema resolviendo una versión más pequeña del problema y luego usando ese resultado más algún otro cálculo para formular la respuesta al problema original. Muchas veces, en el proceso de resolver la versión más pequeña, el método resolverá una versión aún más pequeña del problema, y así sucesivamente, hasta que llegue a un "caso base" que es trivial de resolver.
Por ejemplo, para calcular un factorial para el número
X
, uno puede representarlo comoX times the factorial of X-1
. Por lo tanto, el método "recurre" para encontrar el factorial deX-1
, y luego multiplica todo lo que obtuvoX
para dar una respuesta final. Por supuesto, para encontrar el factorial deX-1
, primero calculará el factorial deX-2
, y así sucesivamente. El caso base sería cuandoX
es 0 o 1, en cuyo caso sabe regresar1
desde entonces0! = 1! = 1
.fuente
Considere un viejo problema bien conocido :
La definición de gcd es sorprendentemente simple:
donde mod es el operador de módulo (es decir, el resto después de la división de enteros).
En Inglés, esta definición dice que el máximo común divisor de cualquier número y cero es ese número, y el máximo común divisor de dos números m y n es el máximo común divisor de n y el resto después de dividir m por n .
Si desea saber por qué funciona esto, consulte el artículo de Wikipedia sobre el algoritmo euclidiano .
Calculemos gcd (10, 8) como ejemplo. Cada paso es igual al anterior:
En el primer paso, 8 no es igual a cero, por lo que se aplica la segunda parte de la definición. 10 mod 8 = 2 porque 8 entra en 10 una vez con un resto de 2. En el paso 3, la segunda parte se aplica nuevamente, pero esta vez 8 mod 2 = 0 porque 2 divide 8 sin resto. En el paso 5, el segundo argumento es 0, por lo que la respuesta es 2.
¿Notó que aparece mcd en los lados izquierdo y derecho del signo igual? Un matemático diría que esta definición es recursiva porque la expresión que está definiendo se repite dentro de su definición.
Las definiciones recursivas tienden a ser elegantes. Por ejemplo, una definición recursiva para la suma de una lista es
donde
head
es el primer elemento de una lista ytail
es el resto de la lista. Tenga en cuenta que sesum
repite dentro de su definición al final.Quizás prefiera el valor máximo en una lista:
Puede definir la multiplicación de enteros no negativos de forma recursiva para convertirla en una serie de adiciones:
Si eso de transformar la multiplicación en una serie de adiciones no tiene sentido, intente expandir algunos ejemplos simples para ver cómo funciona.
El tipo de fusión tiene una definición recursiva encantadora:
Las definiciones recursivas están por todas partes si sabe qué buscar. Observe cómo todas estas definiciones tienen casos base muy simples, por ejemplo , gcd (m, 0) = m. Los casos recurrentes reducen el problema para llegar a las respuestas fáciles.
Con esta comprensión, ¡ahora puede apreciar los otros algoritmos en el artículo de Wikipedia sobre la recursividad !
fuente
El ejemplo canónico es el factorial que se ve así:
En general, la recursividad no es necesariamente rápida (la sobrecarga de llamadas a funciones tiende a ser alta porque las funciones recursivas tienden a ser pequeñas, ver arriba) y pueden sufrir algunos problemas (¿alguien desborda la pila?). Algunos dicen que tienden a ser difíciles de "acertar" en casos no triviales, pero realmente no estoy de acuerdo con eso. En algunas situaciones, la recursividad tiene más sentido y es la forma más elegante y clara de escribir una función en particular. Cabe señalar que algunos idiomas favorecen las soluciones recursivas y las optimizan mucho más (me viene a la mente LISP).
fuente
Una función recursiva es aquella que se llama a sí misma. La razón más común que he encontrado para usarlo es atravesar una estructura de árbol. Por ejemplo, si tengo un TreeView con casillas de verificación (piense en la instalación de un nuevo programa, página "elegir características para instalar"), podría querer un botón "verificar todo" que sería algo así (pseudocódigo):
Entonces puede ver que checkRecursively primero verifica el nodo que se pasa, luego se llama a sí mismo para cada uno de los hijos de ese nodo.
Necesitas ser un poco cuidadoso con la recursividad. Si entra en un bucle recursivo infinito, obtendrá una excepción de desbordamiento de pila :)
No puedo pensar en una razón por la cual la gente no debería usarlo, cuando sea apropiado. Es útil en algunas circunstancias, y no en otras.
Creo que debido a que es una técnica interesante, algunos codificadores tal vez terminen usándola más a menudo de lo que deberían, sin justificación real. Esto le ha dado a la recursión un mal nombre en algunos círculos.
fuente
La recursión es una expresión que hace referencia directa o indirectamente a sí misma.
Considere las siglas recursivas como un ejemplo simple:
Más ejemplos en Wikipedia
fuente
La recursión funciona mejor con lo que me gusta llamar "problemas fractales", donde se trata de una gran cosa que está hecha de versiones más pequeñas de esa gran cosa, cada una de las cuales es una versión aún más pequeña de la gran cosa, y así sucesivamente. Si alguna vez tiene que atravesar o buscar a través de algo como un árbol o estructuras idénticas anidadas, tiene un problema que podría ser un buen candidato para la recursividad.
Las personas evitan la recursividad por varias razones:
La mayoría de las personas (incluido yo mismo) cortan sus dientes de programación en la programación orientada a procedimientos u objetos en lugar de la programación funcional. Para esas personas, el enfoque iterativo (generalmente usando bucles) se siente más natural.
A aquellos de nosotros que cortamos los dientes de la programación en la programación orientada a procedimientos u objetos, a menudo se nos ha dicho que evitemos la recurrencia porque es propensa a errores.
A menudo se nos dice que la recursividad es lenta. Llamar y regresar de una rutina repetidamente implica una gran cantidad de empujes y estallidos de la pila, que es más lento que el bucle. Creo que algunos lenguajes manejan esto mejor que otros, y es probable que esos lenguajes no sean aquellos en los que el paradigma dominante es de procedimiento u orientado a objetos.
Por lo menos para un par de lenguajes de programación que he usado, recuerdo haber escuchado recomendaciones de no usar la recursividad si supera una cierta profundidad porque su pila no es tan profunda.
fuente
Una declaración recursiva es aquella en la que define el proceso de qué hacer a continuación como una combinación de las entradas y lo que ya ha hecho.
Por ejemplo, tome factorial:
Pero es fácil ver factorial (6) también es:
Entonces generalmente:
Por supuesto, lo complicado de la recursividad es que si desea definir las cosas en términos de lo que ya ha hecho, debe haber algún lugar para comenzar.
En este ejemplo, solo hacemos un caso especial definiendo factorial (1) = 1.
Ahora lo vemos de abajo hacia arriba:
Como definimos factorial (1) = 1, llegamos al "fondo".
En términos generales, los procedimientos recursivos tienen dos partes:
1) La parte recursiva, que define algún procedimiento en términos de nuevas entradas combinadas con lo que "ya ha hecho" a través del mismo procedimiento. (es decir
factorial(n) = n*factorial(n-1)
)2) Una parte base, que asegura que el proceso no se repita para siempre al darle un lugar para comenzar (es decir
factorial(1) = 1
)Puede ser un poco confuso entenderlo al principio, pero solo mire algunos ejemplos y todo debería unirse. Si desea una comprensión mucho más profunda del concepto, estudie la inducción matemática. Además, tenga en cuenta que algunos idiomas se optimizan para llamadas recursivas, mientras que otros no. Es bastante fácil hacer funciones recursivas increíblemente lentas si no tienes cuidado, pero también hay técnicas para que funcionen en la mayoría de los casos.
Espero que esto ayude...
fuente
Me gusta esta definición:
en la recursividad, una rutina resuelve una pequeña parte de un problema en sí, divide el problema en partes más pequeñas y luego se llama a sí mismo para resolver cada una de las partes más pequeñas.
También me gusta la discusión de Steve McConnells sobre la recursión en Code Complete donde critica los ejemplos utilizados en los libros de Computer Science sobre Recursion.
Pensé que este era un punto muy interesante para plantear y puede ser una razón por la cual la recursión a menudo se malinterpreta.
EDITAR: Esto no fue una excavación en la respuesta de Dav: no había visto esa respuesta cuando publiqué esto
fuente
1.) Un método es recursivo si puede llamarse a sí mismo; ya sea directamente:
o indirectamente:
2.) Cuándo usar la recursividad
3.) Las personas usan la recursividad solo cuando es muy complejo escribir código iterativo. Por ejemplo, las técnicas de recorrido de árboles como preordenar, postorder pueden hacerse tanto iterativas como recursivas. Pero usualmente usamos recursivo debido a su simplicidad.
fuente
Aquí hay un ejemplo simple: cuántos elementos hay en un conjunto. (Hay mejores formas de contar cosas, pero este es un buen ejemplo simple y recursivo).
Primero, necesitamos dos reglas:
Supongamos que tiene un conjunto como este: [xxx]. Vamos a contar cuántos artículos hay.
Podemos representar esto como:
Al aplicar una solución recursiva, generalmente tiene al menos 2 reglas:
Si traducimos lo anterior a pseudocódigo, obtenemos:
Hay muchos más ejemplos útiles (atravesando un árbol, por ejemplo) que estoy seguro de que otras personas cubrirán.
fuente
Bueno, esa es una definición bastante decente que tienes. Y Wikipedia también tiene una buena definición. Entonces agregaré otra definición (probablemente peor) para usted.
Cuando las personas se refieren a la "recursión", generalmente están hablando de una función que han escrito que se llama a sí misma repetidamente hasta que termina con su trabajo. La recursión puede ser útil al atravesar jerarquías en estructuras de datos.
fuente
Un ejemplo: una definición recursiva de una escalera es: Una escalera consta de: - un solo escalón y una escalera (recursividad) - o solo un solo escalón (terminación)
fuente
Para recurrir a un problema resuelto: no hacer nada, ya está.
Para recurrir en un problema abierto: realice el siguiente paso, luego repita en el resto.
fuente
En inglés simple: suponga que puede hacer 3 cosas:
Tienes muchas manzanas frente a ti en una mesa y quieres saber cuántas manzanas hay.
El proceso de repetir lo mismo hasta que termines se llama recursividad.
¡Espero que esta sea la respuesta de "inglés simple" que estás buscando!
fuente
Una función recursiva es una función que contiene una llamada a sí misma. Una estructura recursiva es una estructura que contiene una instancia de sí misma. Puedes combinar los dos como una clase recursiva. La parte clave de un elemento recursivo es que contiene una instancia / llamada de sí mismo.
Considere dos espejos uno frente al otro. Hemos visto el efecto infinito que hacen. Cada reflejo es una instancia de un espejo, que está contenido dentro de otra instancia de un espejo, etc. El espejo que contiene un reflejo de sí mismo es la recursividad.
Un árbol de búsqueda binario es un buen ejemplo de programación de recursión. La estructura es recursiva con cada nodo que contiene 2 instancias de un nodo. Las funciones para trabajar en un árbol de búsqueda binario también son recursivas.
fuente
Esta es una pregunta antigua, pero quiero agregar una respuesta desde el punto de vista logístico (es decir, no desde el punto de vista de la corrección del algoritmo o el punto de vista del rendimiento).
Uso Java para el trabajo, y Java no admite funciones anidadas. Como tal, si quiero hacer una recursión, podría tener que definir una función externa (que existe solo porque mi código choca contra la regla burocrática de Java), o podría tener que refactorizar el código por completo (lo que realmente odio hacer).
Por lo tanto, a menudo evito la recursión, y uso la operación de pila en su lugar, porque la recursión en sí misma es esencialmente una operación de pila.
fuente
Desea usarlo cada vez que tenga una estructura de árbol. Es muy útil para leer XML.
fuente
La recursividad, tal como se aplica a la programación, es básicamente llamar a una función desde dentro de su propia definición (dentro de sí misma), con diferentes parámetros para realizar una tarea.
fuente
"Si tengo un martillo, haz que todo parezca un clavo".
La recursión es una estrategia de resolución de problemas para grandes problemas, donde en cada paso simplemente, "convierta 2 cosas pequeñas en una cosa más grande", cada vez con el mismo martillo.
Ejemplo
Supongamos que su escritorio está cubierto con un desorden desorganizado de 1024 papeles. ¿Cómo se hace una pila ordenada y limpia de papeles del desastre, utilizando la recursividad?
Tenga en cuenta que esto es bastante intuitivo, aparte de contar todo (lo cual no es estrictamente necesario). En realidad, es posible que no llegue a pilas de 1 hoja, pero podría y aún funcionaría. La parte importante es el martillo: con tus brazos, siempre puedes poner una pila encima de la otra para hacer una pila más grande, y no importa (dentro de lo razonable) qué tan grande sea cada pila.
fuente
La recursión es el proceso en el que un método se llama a sí mismo para poder realizar una determinada tarea. Reduce la redundancia de código. La mayoría de las funciones o métodos recursivos deben tener una condición para interrumpir la llamada recusiva, es decir, evitar que se llame a sí misma si se cumple una condición; esto evita la creación de un bucle infinito. No todas las funciones son adecuadas para usarse recursivamente.
fuente
oye, perdón si mi opinión está de acuerdo con alguien, solo estoy tratando de explicar la recursividad en inglés simple.
supongamos que tiene tres gerentes: Jack, John y Morgan. Jack maneja a 2 programadores, John - 3 y Morgan - 5. le darás a cada gerente 300 $ y quieres saber cuánto costaría. La respuesta es obvia, pero ¿qué pasa si 2 de los empleados de Morgan también son gerentes?
AQUÍ viene la recursión. comienzas desde la parte superior de la jerarquía. El costo veraniego es de 0 $. comienzas con Jack, luego verifica si tiene algún gerente como empleado. si encuentra alguno de ellos, verifique si tienen gerentes como empleados, etc. Agregue 300 $ al costo de verano cada vez que encuentre un gerente. cuando termines con Jack, ve con John, sus empleados y luego con Morgan.
Nunca sabrá cuántos ciclos realizará antes de obtener una respuesta, aunque sepa cuántos gerentes tiene y cuántos Presupuesto puede gastar.
La recursión es un árbol, con ramas y hojas, llamados padres e hijos respectivamente. Cuando usa un algoritmo de recursión, más o menos conscientemente está construyendo un árbol a partir de los datos.
fuente
En inglés simple, recursión significa repetir algo una y otra vez.
En la programación, un ejemplo es llamar a la función dentro de sí misma.
Mire el siguiente ejemplo de cálculo factorial de un número:
fuente
Cualquier algoritmo exhibe recursividad estructural en un tipo de datos si básicamente consiste en una declaración de cambio con un caso para cada caso del tipo de datos.
por ejemplo, cuando está trabajando en un tipo
un algoritmo estructural recursivo tendría la forma
Esta es realmente la forma más obvia de escribir cualquier algoritmo que funcione en una estructura de datos.
ahora, cuando miras los enteros (bueno, los números naturales) como se definen usando los axiomas de Peano
ves que un algoritmo estructural recursivo en enteros se ve así
La función factorial demasiado conocida es el ejemplo más trivial de esta forma.
fuente
La función se llama a sí misma o utiliza su propia definición.
fuente