En inglés simple, ¿qué es la recursividad?


74

La idea de recurrencia no es muy común en el mundo real. Por lo tanto, parece un poco confuso para los programadores novatos. Aunque, supongo, se van acostumbrando al concepto gradualmente. Entonces, ¿cuál puede ser una buena explicación para que puedan comprender la idea fácilmente?


¿Se comparte más información sobre este tema en Recursos para mejorar su comprensión de la recursividad?
Kenneth


1
La recursión es cuando una función puede llamarse a sí misma. "Si comprende completamente los espacios de nombres y el alcance y cómo se pasan los parámetros a una función, entonces ya conoce la recursión. Puedo mostrar ejemplos, pero debería poder descubrir cómo funcionan por su cuenta". Los estudiantes generalmente luchan con la recursividad no tanto porque es confusa, sino porque no tienen una comprensión firme del alcance variable / espacio de nombres. Antes de sumergirse en la recursividad, asegúrese de que los estudiantes puedan rastrear adecuadamente un programa en el que les haya dado el mismo nombre a variables en diferentes ámbitos para confundirlas.
dspyz


1
Para entender la recursión, primero debes entender la recursión
Goerman

Respuestas:


110

Para explicar la recursividad , utilizo una combinación de explicaciones diferentes, generalmente para tratar de:

  • explicar el concepto
  • explica por qué es importante
  • explica cómo conseguirlo.

Para empezar, Wolfram | Alpha lo define en términos más simples que Wikipedia :

Una expresión tal que cada término se genera repitiendo una operación matemática particular.


Matemáticas

Si su estudiante (o la persona que usted explica también, a partir de ahora diré que estudiante) tiene al menos algunos antecedentes matemáticos, obviamente ya han encontrado recursividad al estudiar series y su noción de recursividad y su relación de recurrencia .

Una muy buena manera de comenzar es demostrar con una serie y decir que se trata simplemente de la recursividad:

  • una función matemática ...
  • ... que se llama a sí mismo para calcular un valor correspondiente a un enésimo elemento ...
  • ... y que define algunos límites.

Por lo general, obtienes un "huh huh, whatev 'en el mejor de los casos porque todavía no lo usan, o más probablemente solo un ronquido muy profundo.


Ejemplos de codificación

Por lo demás, en realidad es una versión detallada de lo que presenté en el Addendum de mi respuesta a la pregunta que usted señaló con respecto a los punteros (mal juego de palabras).

En esta etapa, mis alumnos generalmente saben cómo imprimir algo en la pantalla. Suponiendo que estamos usando C, ellos saben cómo imprimir un solo carácter usando writeo printf. También saben acerca de los lazos de control.

Normalmente recurro a algunos problemas de programación repetitivos y simples hasta que lo consiguen:

  • la operación factorial ,
  • una impresora de alfabeto,
  • una impresora de alfabeto invertida,
  • La operación de exponenciación .

Factorial

Factorial es un concepto matemático muy simple de entender, y la implementación está muy cerca de su representación matemática. Sin embargo, es posible que no lo entiendan al principio.

Definición recursiva de la operación factorial

Alfabetos

La versión del alfabeto es interesante para enseñarles a pensar sobre el orden de sus declaraciones recursivas. Al igual que con los punteros, simplemente te lanzarán líneas al azar. El punto es llevarlos a la conclusión de que un bucle puede invertirse modificando las condiciones O simplemente invirtiendo el orden de las declaraciones en su función. Ahí es donde ayuda la impresión del alfabeto, ya que es algo visual para ellos. Simplemente pídales que escriban una función que imprima un carácter para cada llamada, y se llame a sí mismo de forma recursiva para escribir el siguiente (o anterior).

Ventiladores de FP, omita el hecho de que imprimir cosas en el flujo de salida es un efecto secundario por ahora ... No seamos demasiado molestos en el frente de FP. (Pero si usa un lenguaje con soporte de lista, siéntase libre de concatenarse con una lista en cada iteración y simplemente imprima el resultado final. Pero generalmente comienzo con C, que desafortunadamente no es el mejor para este tipo de problemas y conceptos) .

Exponenciación

El problema de exponenciación es un poco más difícil ( en esta etapa de aprendizaje). Obviamente, el concepto es exactamente el mismo que para un factorial y no hay complejidad adicional ... excepto que tiene múltiples parámetros. Y eso suele ser suficiente para confundir a las personas y desecharlas al principio.

Su forma simple:

Forma simple de la operación de exponenciación

puede expresarse así por recurrencia:

Relación de recurrencia para la operación de exponenciación

Más fuerte

Una vez que estos problemas simples han sido mostrados Y re-implementados en tutoriales, puede dar ejercicios un poco más difíciles (pero muy clásicos):

Nota: Una vez más, algunos de estos realmente no son más difíciles ... Simplemente abordan el problema desde exactamente el mismo ángulo, o uno ligeramente diferente. Pero la práctica hace la perfección.


Ayudantes

Una referencia

Algunas lecturas nunca están de más. Bueno, al principio, y se sentirán aún más perdidos. Es el tipo de cosa que crece en ti y que se sienta en la parte posterior de tu cabeza hasta que un día te das cuenta de que finalmente lo entiendes. Y luego piensas en estas cosas que leíste. La recursión , la recursividad en Ciencias de la Computación y las páginas de relación de recurrencia en Wikipedia funcionarían por ahora.

Nivel / Profundidad

Asumiendo que sus estudiantes no tienen mucha experiencia en codificación, proporcione talones de código. Después de los primeros intentos, deles una función de impresión que pueda mostrar el nivel de recurrencia. Imprimir el valor numérico del nivel ayuda.

El diagrama de apilar como cajones

La sangría de un resultado impreso (o la salida del nivel) también ayuda, ya que proporciona otra representación visual de lo que está haciendo su programa, abriendo y cerrando contextos de pila como cajones o carpetas en un explorador del sistema de archivos.

Acrónimos recursivos

Si su estudiante ya está un poco versado en la cultura informática, es posible que ya use algunos proyectos / softwares con nombres que usan acrónimos recursivos . Ha sido una tradición que existe desde hace algún tiempo, especialmente en proyectos GNU. Algunos ejemplos incluyen:

Recursivo:

  • GNU - "GNU no es Unix"
  • Nagios - "Nagios no va a insistir en la santidad"
  • PHP - "Preprocesador de hipertexto PHP" (y originalmente toda la "Página de inicio personal")
  • Vino - "El vino no es un emulador"
  • Zile - "Zile es Emacs con pérdida"

Mutuamente recursivo:

  • HURD - "HIRD of Unix-Replacing Daemons" (donde HIRD es "HURD of Interface que representa la profundidad")

Haga que traten de inventar los suyos.

Del mismo modo, hay muchos casos de humor recursivo, como la corrección de búsqueda recursiva de Google . Para obtener más información sobre la recursividad, lea esta respuesta .


Errores y aprendizaje adicional

Algunos problemas con los que la gente suele luchar y para los que necesita saber respuestas.

¿Por qué, oh Dios, por qué?

¿Por qué harías eso? Una razón buena pero no obvia es que a menudo es más simple expresar un problema de esa manera. Una razón no tan buena pero obvia es que a menudo se necesita menos tipeo (sin embargo, no los haga sentir demasiado solo por usar la recursividad ...).

Algunos problemas son definitivamente más fáciles de resolver cuando se utiliza un enfoque recursivo. Por lo general, cualquier problema que pueda resolver utilizando un paradigma Divide and Conquer se ajustará a un algoritmo de recursión de múltiples ramificaciones.

¿Qué es N otra vez?

¿Por qué mi no (sea cual sea el nombre de su variable) es diferente cada vez? Los principiantes generalmente tienen problemas para comprender qué son una variable y un parámetro, y cómo las cosas nombradas nen su programa pueden tener valores diferentes. Entonces, si este valor está en el ciclo de control o en la recursividad, ¡eso es aún peor! Sea amable y no use los mismos nombres de variables en todas partes, y deje en claro que los parámetros son solo variables .

Condiciones finales

¿Cómo determino mi condición final? Eso es fácil, solo haz que digan los pasos en voz alta. Por ejemplo, para el inicio factorial desde 5, luego 4, luego ... hasta 0.

El diablo está en los detalles

No hable con cosas tempranas sobre optimización de llamadas de cola . Lo sé, lo sé, el coste total de propiedad es bueno, pero al principio no les importa. Déles algo de tiempo para comprender el proceso de una manera que funcione para ellos. Siéntete libre de destruir su mundo nuevamente más tarde, pero dales un descanso.

Del mismo modo, no hable directamente desde la primera conferencia sobre la pila de llamadas y su consumo de memoria y ... bueno ... el desbordamiento de la pila . A menudo doy clases particulares a estudiantes en privado que me muestran conferencias donde tienen 50 diapositivas sobre todo lo que hay que saber sobre la recursividad cuando apenas pueden escribir un bucle correctamente en esta etapa. Ese es un buen ejemplo de cómo una referencia ayudará más adelante, pero en este momento solo te confunde profundamente.

Pero por favor, a su debido tiempo, deje en claro que hay razones para seguir la ruta iterativa o recursiva .

Recursividad mutua

Hemos visto que las funciones pueden ser recursivas e incluso que pueden tener múltiples puntos de llamada (8 reinas, Hanoi, Fibonacci o incluso un algoritmo de exploración para un buscaminas). Pero, ¿qué pasa con las llamadas recursivas recíprocas ? Comience con las matemáticas aquí también. f(x) = g(x) + h(x)donde g(x) = f(x) + l(x)y hy lsolo hacer cosas.

Comenzar solo con series matemáticas hace que sea más fácil escribir e implementar, ya que el contrato está claramente definido por las expresiones. Por ejemplo, las secuencias femeninas y masculinas de Hofstadter :

Secuencias masculinas y femeninas de Hofstadter

Sin embargo, en términos de código, es de notar que la implementación de una solución mutuamente recursivo a menudo conduce a la duplicación de código y más bien debería simplificarse en una sola forma recursiva (Ver Peter Norvig 's Solución Cada Sudoku .


55
Estoy leyendo su respuesta después de verla después de casi la quinta o sexta vez. Fue agradable pero demasiado largo para atraer a otros usuarios aquí, creo. Aprendí muchas cosas sobre la enseñanza de la recursividad aquí. Como profesor, ¿podría evaluar mi idea para la enseñanza de recursion- programmers.stackexchange.com/questions/25052/...
Gulshan

9
@Gulshan, creo que esta respuesta es casi tan completa como cualquier otra, y un lector casual la "pasa por alto" fácilmente. Por lo tanto, recibe una static unsigned int vote = 1;de mí. Perdona el humor estático, si quieres :) Esta es la mejor respuesta hasta ahora.
Tim Post

1
@Gulsan: solo el que quiere aprender está dispuesto a tomarse el tiempo para hacerlo, es correcto :) Realmente no me importa. A veces, una respuesta corta es elegante y transmite mucha información útil y necesaria para comenzar o explicar un concepto general. Solo quería una respuesta más larga para esa, y teniendo en cuenta que el OP menciona una pregunta para la que obtuve la respuesta "correcta" y hace una pregunta similar, pensé que era apropiado entregar el mismo tipo de respuesta. Me alegra que hayas aprendido algo.
haylem

@Gulshan: Ahora con respecto a su respuesta: el primer punto me confundió mucho, tengo que decir. Me gusta que describas el concepto de funciones recursivas como algo que cambia de estado gradualmente con el tiempo, pero creo que tu forma de presentar es un poco extraña. Después de leer sus 3 puntos, no esperaría que muchos estudiantes de repente puedan resolver Hanoi. Pero podría ser solo un problema de redacción.
haylem

Me he encontrado con una buena manera de demostrar que la misma variable puede tener diferentes valores en diferentes profundidades de recursión: considere hacer que los estudiantes escriban los pasos que están tomando y los valores de las variables, siguiendo un código recursivo. Cuando lleguen a una recursión, pídales que comiencen nuevamente en una nueva pieza. Una vez que alcanzan una condición de salida, haga que regresen a la pieza anterior. Esto esencialmente es emular una pila de llamadas, pero es una buena manera de mostrar estas diferencias.
Andy Hunt

58

La invocación de una función desde esa misma función.


2
Esta es la mejor explicación para comenzar realmente. Simple y al grano; Una vez que haya establecido este resumen, ingrese a todos los detalles del viaje.
jhocking

27

La recursión es una función que se llama a sí misma.

Es importante saber cómo usarlo, cuándo usarlo y cómo evitar un mal diseño, lo que requiere que lo pruebes por ti mismo y entiendas lo que sucede.

Lo más importante que debe saber es tener mucho cuidado de no obtener un bucle que nunca termina. La respuesta de pramodc84 a su pregunta tiene esta falla: nunca termina ...
Una función recursiva siempre debe verificar una condición para determinar si debe llamarse nuevamente o no.

El ejemplo más clásico para usar la recursividad es trabajar con un árbol sin límites estáticos de profundidad. Esta es una tarea que debe utilizar la recursividad.


Todavía puede implementar su último párrafo de manera iterativa, aunque de forma recursiva obviamente sería mejor. "Es importante saber cómo usarlo, cuándo usarlo y cómo evitar un mal diseño, lo que requiere que lo pruebes por ti mismo y entiendas lo que sucede". Pensé que el punto de la pregunta era obtener una explicación para ese tipo de cosas.
haylem

@haylem: Tiene razón acerca de esa respuesta a "Cómo usarlo, cuándo usarlo y cómo evitar un mal diseño ..." sería más acertado de lo que pide el OP (no solo "pruébelo usted mismo "como dije), pero eso requeriría una extensa conferencia del tipo más didáctico, en lugar de una respuesta rápida a una pregunta aquí. Sin embargo, hiciste un muy buen trabajo con tu respuesta . +1 por eso ... Aquellos que realmente necesiten comprender mejor el concepto se beneficiarán al leer su respuesta.
asombro

¿Qué pasa con un par de funciones que se llaman entre sí? A llama a B, que vuelve a llamar a A hasta que se alcanza alguna condición. ¿esto todavía se considerará recursividad?
santiagozky

Sí, la función atodavía se llama a sí misma, solo indirectamente (llamando b).
poco

1
@santiagozky: Como Kindall dijo, todavía es la recursividad. Sin embargo, recomendaría no hacerlo. Al usar la recursión, debe quedar muy claro en el código que la recursión está en su lugar. Si se llama indirectamente a través de otra función, es mucho más difícil ver lo que está sucediendo. Si no sabe que una función se llama efectivamente a sí misma, puede entrar en una situación en la que usted (u otra persona que no creó esta función) rompa alguna condición para la recursión (mientras cambia la funcionalidad en el código), y finaliza arriba en un punto muerto con un bucle interminable.
asombro

21

La programación recursiva es el proceso de reducir progresivamente un problema en versiones más fáciles de resolver de sí mismo.

Toda función recursiva tiende a:

  1. tomar una lista para procesar, o alguna otra estructura, o dominio problemático
  2. lidiar con el punto / paso actual
  3. llamarse a sí mismo en el resto (s) / subdominio (s)
  4. combinar o usar los resultados del trabajo de subdominio

Cuando el paso 2 es anterior al 3, y cuando el paso 4 es trivial (una concatenación, suma o nada) esto permite la recursión de la cola . El paso 2 a menudo debe venir después del paso 3, ya que los resultados de los subdominios del problema pueden ser necesarios para completar el paso actual.

Tome el recorrido de un árbol binario directo. El recorrido se puede realizar en preorden, en orden o en orden posterior, según lo que se requiera.

   B
A     C

Pre-pedido: BAC

traverse(tree):
    visit the node
    traverse(left)
    traverse(right)

En orden: ABC

traverse(tree):
    traverse(left)
    visit the node
    traverse(right)

Post-orden: ACB

traverse(tree):
    traverse(left)
    traverse(right)
    visit the node

Muchos problemas recursivos son casos específicos de una operación de mapa , o un pliegue : comprender solo estas dos operaciones puede conducir a una comprensión significativa de los buenos casos de uso para la recursividad.


El componente clave para la recursividad práctica es la idea de usar la solución a un problema un poco más pequeño para resolver uno más grande. De lo contrario, solo tienes una recursión infinita.
Barry Brown

@Barry Brown: Muy bien. De ahí mi afirmación "... reducir un problema a versiones más fáciles de resolver de sí mismo"
Orbling

No lo diría necesariamente ... A menudo es el caso, especialmente en problemas de dividir y conquistar o en situaciones en las que realmente define una relación de recurrencia que se reduce a un caso simple. Pero yo diría que se trata más de probar que para cada iteración N de su problema, hay un caso computable N + 1.
haylem

1
@Sean McMillan: Recursion es una herramienta poderosa cuando se usa en dominios que le convienen. Con demasiada frecuencia veo que se usa como una forma inteligente de manejar un problema relativamente trivial, que ofusca masivamente la verdadera naturaleza de la tarea en cuestión.
Orbling

20

El OP dijo que la recursividad no existe en el mundo real, pero le ruego que difiera.

Tomemos la "operación" del mundo real de cortar una pizza. Has sacado la pizza del horno y para servirla tienes que cortarla por la mitad, luego cortar esas mitades por la mitad, luego cortar nuevamente las mitades resultantes por la mitad.

La operación de cortar la pizza que realiza una y otra vez hasta que obtenga el resultado que desea (el número de rebanadas). Y por el bien de los argumentos, digamos que una pizza sin cortar es una rebanada en sí misma.

Aquí hay un ejemplo en Ruby:

def cut_pizza (rebanadas existentes, rebanadas deseadas)
  if existentes_slices! = deseado_slices
    # todavía no tenemos suficientes rebanadas para alimentar a todos, así que
    # estamos cortando las rebanadas de pizza, duplicando así su número
    new_slices = existente_slices * 2 
    # y esta es la llamada recursiva
    cut_pizza (nuevos_slices, deseado_slices)
  más
    # tenemos el número deseado de rebanadas, así que devolvemos
    # aquí en lugar de continuar recurriendo
    devolver existentes_slices
  fin
fin

pizza = 1 # una pizza entera, 'una rebanada'
cut_pizza (pizza, 8) # => obtendremos 8

Entonces, la operación del mundo real es cortar una pizza, y la recurrencia es hacer lo mismo una y otra vez hasta que tenga lo que quiere.

Las operaciones que encontrará que recortar que puede implementar con funciones recursivas son:

  • Cálculo del interés compuesto durante varios meses.
  • Buscando un archivo en un sistema de archivos (porque los sistemas de archivos son árboles debido a los directorios).
  • Supongo que todo lo que implica trabajar con árboles en general.

Recomiendo escribir un programa para buscar un archivo en función de su nombre e intentar escribir una función que se llame a sí misma hasta que se encuentre, la firma se vería así:

find_file_by_name(file_name_we_are_looking_for, path_to_look_in)

Entonces podrías llamarlo así:

find_file_by_name('httpd.conf', '/etc') # damn it i can never find apache's conf

En mi opinión, es simplemente mecánica de programación, una forma de eliminar inteligentemente la duplicación. Puede reescribir esto usando variables, pero esta es una solución 'mejor'. No hay nada misterioso o difícil al respecto. Escribirás un par de funciones recursivas, harás clic y harás otro truco mecánico en tu caja de herramientas de programación.

Crédito adicional El cut_pizzaejemplo anterior le dará un error demasiado profundo en el nivel de la pila si le solicita una cantidad de cortes que no sea una potencia de 2 (es decir, 2 u 4 u 8 o 16). ¿Puedes modificarlo para que si alguien pide 10 rebanadas no se ejecute para siempre?


16

Bien, voy a tratar de mantener esto simple y conciso.

La función recursiva son funciones que se llaman a sí mismas. La función recursiva consta de tres cosas:

  1. Lógica
  2. Una llamada a si misma
  3. Cuando terminar

La mejor manera de escribir métodos recursivos es pensar en el método que está tratando de escribir como un ejemplo simple que solo maneja un ciclo del proceso sobre el que desea iterar, luego agregue la llamada al método en sí y agregue cuando desee Terminar. La mejor manera de aprender es practicar como todas las cosas.

Dado que este es el sitio web de los programadores, me abstendré de escribir código, pero aquí hay un buen enlace

si entendiste ese chiste, entendiste lo que significa la recursividad.


Consulte también esta respuesta: programmers.stackexchange.com/questions/25052/… (-:
Murph

44
Estrictamente, la recursión no necesita una condición de terminación; Garantizar que la recursión termina limita los tipos de problemas que la función recursiva puede resolver, y hay algunos tipos de presentaciones semánticas que no requieren terminación en absoluto.
Donal Fellows

6

Recursion es una herramienta que un programador puede usar para invocar una llamada de función sobre sí mismo. La secuencia de Fibonacci es el ejemplo de libro de texto de cómo se usa la recursión.

La mayoría del código recursivo, si no todos, se pueden expresar como una función iterativa, pero generalmente es desordenado. Un buen ejemplo de otros programas recursivos son las estructuras de datos, como los árboles, el árbol de búsqueda binaria e incluso la clasificación rápida.

La recursión se usa para hacer que el código sea menos descuidado, tenga en cuenta que generalmente es más lento y requiere más memoria.


Si es más lento o requiere más memoria depende mucho del uso que tenga a mano.
Orbling

55
El cálculo de la secuencia de Fibonacci es algo terrible que hacer recursivamente. El recorrido del árbol es un uso mucho más natural de la recursividad. Por lo general, cuando la recursividad se usa bien, no es más lenta y no requiere más memoria, ya que tendría que mantener una pila propia en lugar de la pila de llamadas.
David Thornley

1
@Dave: No discutiría eso, pero creo que Fibonacci es un buen ejemplo para comenzar.
Bryan Harrington

5

Me gusta usar este:

¿Cómo caminas a la tienda?

Si estás en la entrada de la tienda, simplemente pasa por ella. De lo contrario, dé un paso, luego camine el resto del camino hasta la tienda.

Es crítico incluir tres aspectos:

  • Un caso base trivial
  • Resolviendo una pequeña parte del problema
  • Resolviendo el resto del problema de forma recursiva

De hecho, utilizamos mucho la recursividad en la vida diaria; simplemente no pensamos de esa manera.


Eso no es recursividad. Sería si lo dividiera en dos: camine hasta la mitad de la tienda, camine la otra mitad. Recurse

2
Esto es recursividad. Esto no es dividir y conquistar, pero es solo un tipo de recursión. Los algoritmos gráficos (como la búsqueda de rutas) están llenos de conceptos recursivos.
deadalnix

2
Esto es recurrencia, pero lo considero un mal ejemplo, ya que es demasiado fácil traducir mentalmente "dar un paso, luego caminar el resto del camino a la tienda" en un algoritmo iterativo. Siento que esto es equivalente a convertir un forbucle bien escrito en una función recursiva sin sentido.
Brian

3

El mejor ejemplo al que le señalaría es C Programming Language de K&R. En ese libro (y estoy citando de memoria), la entrada en la página de índice para la recursión (solo) enumera la página real donde hablan sobre la recursividad y la página de índice también.


2

Josh K ya mencionó las muñecas Matroshka . Suponga que quiere aprender algo que solo la muñeca más corta sabe. El problema es que realmente no puedes hablar con ella directamente, porque originalmente vive dentro de la muñeca más alta que en la primera imagen se coloca a su izquierda. Esta estructura es así (una muñeca vive dentro de la muñeca más alta) hasta terminar solo con la más alta.

Entonces, lo único que puedes hacer es hacer tu pregunta a la muñeca más alta. La muñeca más alta (que no sabe la respuesta) deberá pasar su pregunta a la muñeca más baja (que en la primera imagen está a su derecha). Como tampoco tiene la respuesta, debe preguntarle a la próxima muñeca más pequeña. Esto seguirá así hasta que el mensaje llegue a la muñeca más corta. La muñeca más corta (que es la única que sabe la respuesta secreta) pasará la respuesta a la siguiente muñeca más alta (que se encuentra a su izquierda), que la pasará a la siguiente muñeca más alta ... y esto continuará hasta la respuesta llega a su destino final, que es la muñeca más alta y finalmente ... tú :)

Esto es lo que realmente hace la recursión. Una función / método se llama a sí mismo hasta obtener la respuesta esperada. Es por eso que cuando escribes código recursivo es muy importante decidir cuándo debe terminar la recursividad.

No es la mejor explicación, pero espero que ayude.


2

Recursion n. - Un patrón de diseño de algoritmos donde una operación se define en términos de sí misma.

El ejemplo clásico es encontrar el factorial de un número, n !. 0! = 1, y para cualquier otro número natural N, el factorial de N es el producto de todos los números naturales menores o iguales a N. ¡Entonces, 6! = 6 * 5 * 4 * 3 * 2 * 1 = 720. Esta definición básica le permitiría crear una solución iterativa simple:

int Fact(int degree)
{
    int result = 1;
    for(int i=degree; i>1; i--)
       result *= i;

    return result;
}

Sin embargo, examine la operación nuevamente. 6! = 6 * 5 * 4 * 3 * 2 * 1. Por la misma definición, 5! = 5 * 4 * 3 * 2 * 1, lo que significa que podemos decir 6! = 6 * (5!) A su vez, 5! = 5 * (4!) Y así sucesivamente. Al hacer esto, reducimos el problema a una operación realizada en el resultado de todas las operaciones anteriores. Esto eventualmente se reduce a un punto, llamado caso base, donde el resultado se conoce por definición. En nuestro caso, 0! = 1 (en la mayoría de los casos también podríamos decir que 1! = 1). En informática, a menudo se nos permite definir algoritmos de una manera muy similar, haciendo que el método se llame a sí mismo y pase una entrada más pequeña, reduciendo así el problema a través de muchas recursiones a un caso base:

int Fact(int degree)
{
    if(degree==0) return 1; //the base case; 0! = 1 by definition
    else return degree * Fact(degree -1); //the recursive case; N! = N*(N-1)!
}

Esto puede, en muchos idiomas, simplificarse aún más utilizando el operador ternario (a veces visto como una función Iif en idiomas que no proporcionan el operador como tal):

int Fact(int degree)
{
    //reads equivalently to the above, but is concise and often optimizable
    return degree==0 ? 1: degree * Fact(degree -1);
}

Ventajas:

  • Expresión natural: para muchos tipos de algoritmos, esta es una forma muy natural de expresar la función.
  • LOC reducido: a menudo es mucho más conciso definir una función de forma recursiva.
  • Velocidad: en ciertos casos, dependiendo del lenguaje y la arquitectura de la computadora, la recurrencia de un algoritmo es más rápida que la solución iterativa equivalente, generalmente porque hacer una llamada a la función es una operación más rápida a nivel de hardware que las operaciones y el acceso a la memoria necesarios para realizar un bucle iterativo.
  • Divisibilidad: muchos algoritmos recursivos tienen la mentalidad de "divide y vencerás"; El resultado de la operación es una función del resultado de la misma operación realizada en cada una de las dos mitades de la entrada. Esto le permite dividir el trabajo en dos en cada nivel y, si está disponible, puede asignar la otra mitad a otra "unidad de ejecución" para que la procese. Esto suele ser más difícil o imposible con un algoritmo iterativo.

Desventajas

  • Requiere comprensión: simplemente debe "comprender" el concepto de recursividad para comprender lo que está sucediendo y, por lo tanto, escribir y mantener algoritmos recursivos efectivos. De lo contrario, solo parece magia negra.
  • Depende del contexto: si la recursión es una buena idea o no depende de cuán elegantemente se pueda definir el algoritmo en términos de sí mismo. Si bien es posible construir, por ejemplo, un SelectionSort recursivo, el algoritmo iterativo suele ser el más comprensible.
  • Intercambia el acceso RAM para la pila de llamadas: por lo general, las llamadas a funciones son más baratas que el acceso a la caché, lo que puede hacer que la recursión sea más rápida que la iteración. Pero, por lo general, hay un límite en la profundidad de la pila de llamadas que puede causar recurrencia al error donde funcionará un algoritmo iterativo.
  • Recurrencia infinita: debe saber cuándo detenerse. La iteración infinita también es posible, pero las construcciones de bucle involucradas generalmente son más fáciles de entender y, por lo tanto, de depurar.

1

El ejemplo que uso es un problema que enfrenté en la vida real. Tiene un contenedor (como una mochila grande que piensa llevar de viaje) y desea saber el peso total. Tiene en el contenedor dos o tres artículos sueltos, y algunos otros contenedores (por ejemplo, bolsas de cosas). El peso del contenedor total es obviamente el peso del contenedor vacío más el peso de todo lo que contiene. Para los artículos sueltos, solo puede pesarlos, y para los sacos de cosas puede simplemente pesarlos o puede decir "bueno, el peso de cada bolsa es el peso del contenedor vacío más el peso de todo lo que contiene". Y luego sigue entrando en contenedores en contenedores y así sucesivamente hasta llegar a un punto donde solo hay artículos sueltos en un contenedor. Eso es recursividad.

Puede pensar que eso nunca sucede en la vida real, pero imagine tratar de contar o sumar los salarios de personas en una empresa o división en particular, que tiene una mezcla de personas que solo trabajan para la empresa, personas en divisiones, y luego en En las divisiones hay departamentos, etc. O ventas en un país que tiene regiones, algunas de las cuales tienen subregiones, etc. etc. Este tipo de problemas ocurren todo el tiempo en los negocios.


0

La recursión se puede usar para resolver muchos problemas de conteo. Por ejemplo, supongamos que tiene un grupo de n personas en una fiesta (n> 1), y todos dan la mano a todos exactamente una vez. ¿Cuántos apretones de manos tienen lugar? Puede saber que la solución es C (n, 2) = n (n-1) / 2, pero puede resolver recursivamente de la siguiente manera:

Supongamos que solo hay dos personas. Entonces (por inspección) la respuesta es obviamente 1.

Supongamos que tienes tres personas. Escoja a una persona y observe que él / ella le da la mano a otras dos personas. Después de eso tienes que contar solo los apretones de manos entre las otras dos personas. Ya lo hicimos justo ahora, y es 1. Entonces, la respuesta es 2 + 1 = 3.

Supongamos que tienes n personas. Siguiendo la misma lógica que antes, es (n-1) + (número de apretones de manos entre n-1 personas). Expandiendo, obtenemos (n-1) + (n-2) + ... + 1.

Expresado como una función recursiva,

f (2) = 1
f (n) = n-1 + f (n-1), n> 2


0

En la vida (a diferencia de en un programa de computadora), la recursión rara vez ocurre bajo nuestro control directo, porque puede ser confuso que suceda. Además, la percepción tiende a ser sobre los efectos secundarios, en lugar de ser funcionalmente pura, por lo que si ocurre la recursión, es posible que no lo note.

Sin embargo, la recursión ocurre aquí en el mundo. Mucho.

Un buen ejemplo es (una versión simplificada de) el ciclo del agua:

  • El sol calienta el lago.
  • El agua sube al cielo y forma nubes.
  • Las nubes se desplazan hacia una montaña
  • En la montaña, el aire se vuelve demasiado frío para retener su humedad.
  • Caídas de lluvia
  • Se forma un río
  • El agua del río corre hacia el lago.

Este es un ciclo que hace que vuelva a ocurrir. Es recursivo.

Otro lugar donde puede obtener recursividad es en inglés (y en lenguaje humano en general). Es posible que no lo reconozca al principio, pero la forma en que podemos generar una oración es recursiva, porque las reglas nos permiten incrustar una instancia de un símbolo en otra instancia del mismo símbolo.

Del Instinto del lenguaje de Steven Pinker:

si la niña come helado o la niña come dulces, entonces el niño come perritos calientes

Esa es una oración completa que contiene otras oraciones completas:

la niña come helado

la niña come dulces

el niño come perritos calientes

El acto de comprender la oración completa implica comprender oraciones más pequeñas, que utilizan el mismo conjunto de trucos mentales para entenderse como la oración completa.

Para comprender la recursividad desde una perspectiva de programación, es más fácil observar un problema que se puede resolver con recursividad y comprender por qué debería ser y qué significa eso que debe hacer.

Para el ejemplo, usaré la función divisor común más grande, o mcd para abreviar.

Tienes tus dos números ay b. Para encontrar su mcd (suponiendo que ninguno sea 0), debe verificar si aes divisible en b. Si es así, bes el mcd, de lo contrario, debe verificar el mcd de by el resto de a/b.

Ya debería poder ver que esta es una función recursiva, ya que tiene la función gcd que llama a la función gcd. Solo para martillarlo en casa, aquí está en c # (nuevamente, suponiendo que 0 nunca se pase como parámetro):

int gcd(int a, int b)
{   
    if (a % b == 0) //this is a stopping condition
    {
        return b;
    }

    return (gcd(b, a % b)); //the call to gcd here makes this function recursive
}

En un programa, es importante tener una condición de detención, de lo contrario, su función se repetirá para siempre, lo que eventualmente causará un desbordamiento de la pila.

La razón para usar la recursividad aquí, en lugar de un ciclo while o alguna otra construcción iterativa, es que a medida que lees el código, te dice lo que está haciendo y lo que sucederá después, por lo que es más fácil descubrir si funciona correctamente .


1
El ejemplo del ciclo del agua me pareció iterativo. El segundo ejemplo de lenguaje parece más dividir y conquistar que la recursividad.
Gulshan

@Gulshan: Diría que el ciclo del agua es recursivo porque hace que se repita, como una función recursiva. No es como pintar una habitación, donde llevas a cabo el mismo conjunto de pasos sobre varios objetos (paredes, techo, etc.), como en un bucle for. El ejemplo del lenguaje usa divide y vencerás, pero también la "función" que se llama se llama a sí misma para trabajar en las oraciones anidadas, por lo que es recursiva de esa manera.
Matt Ellen

En el ciclo del agua, el sol inicia el ciclo y ningún otro elemento del ciclo hace que el sol lo inicie nuevamente. Entonces, ¿dónde está la llamada recursiva?
Gulshan

¡No hay una llamada recursiva! No es una función. : D Es recursivo porque hace que se repita. El agua del lago regresa al lago y el ciclo comienza nuevamente. Si algún otro sistema estuviera poniendo agua en el lago, entonces sería iterativo.
Matt Ellen

1
El ciclo del agua es un ciclo while. Claro, un ciclo while se puede expresar usando recursividad, pero hacerlo volará la pila. Por favor no lo hagas.
Brian

0

Aquí hay un ejemplo del mundo real para la recursividad.

Déjales imaginar que tienen una colección de cómics y que vas a mezclarlo todo en una gran pila. Cuidado: si realmente tienen una colección, pueden matarte instantáneamente cuando solo mencionas la idea de hacerlo.

Ahora permítales clasificar esta gran pila de cómics sin clasificar con la ayuda de este manual:

Manual: How to sort a pile of comics

Check the pile if it is already sorted. If it is, then done.

As long as there are comics in the pile, put each one on another pile, 
ordered from left to right in ascending order:

    If your current pile contains different comics, pile them by comic.
    If not and your current pile contains different years, pile them by year.
    If not and your current pile contains different tenth digits, pile them 
    by this digit: Issue 1 to 9, 10 to 19, and so on.
    If not then "pile" them by issue number.

Refer to the "Manual: How to sort a pile of comics" to separately sort each
of the new piles.

Collect the piles back to a big pile from left to right.

Done.

Lo bueno aquí es: cuando se reducen a problemas únicos, tienen el "marco de pila" completo con las pilas locales visibles ante ellos en el suelo. Déles varias copias impresas del manual y ponga una a un lado de cada nivel de pila con una marca donde se encuentra actualmente en este nivel (es decir, el estado de las variables locales), para que pueda continuar allí en cada Hecho.

De eso se trata básicamente la recursividad: realizar el mismo proceso, solo en un nivel de detalle más fino cuanto más entras en él.


-1
  • Terminar si se alcanza la condición de salida
  • hacer algo para alterar el estado de las cosas
  • hacer todo el trabajo comenzando con el estado actual de las cosas

La recursión es una forma muy concisa de expresar algo que debe repetirse hasta que se alcance algo.



-2

Una buena explicación de la recursividad es literalmente "una acción que se repite desde dentro de sí misma".

Considere un pintor que pinta una pared, es recursivo porque la acción es "pintar una tira del techo al piso que deslizarse un poco hacia la derecha y (pintar una tira del techo al piso que deslizarse un poco hacia la derecha y (pintar un pelar del techo al piso que deslizarse un poco hacia la derecha y (etc.)) ".

Su función paint () se llama a sí misma una y otra vez para compensar su función paint_wall () más grande.

Esperemos que este pobre pintor tenga algún tipo de condición de parada :)


66
Para mí, el ejemplo parece más un procedimiento iterativo, no recursivo.
Gulshan

@Gulshan: La recursión y la iteración hacen cosas similares, y a menudo las cosas funcionarán bien con cualquiera de ellas. Los lenguajes funcionales generalmente usan recursividad en lugar de iteración. Hay mejores ejemplos de recursión en los que sería incómodo escribir lo mismo de forma iterativa.
David Thornley
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.