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?
74
Respuestas:
Para explicar la recursividad , utilizo una combinación de explicaciones diferentes, generalmente para tratar de:
Para empezar, Wolfram | Alpha lo define en términos más simples que Wikipedia :
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:
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
write
oprintf
. También saben acerca de los lazos de control.Normalmente recurro a algunos problemas de programación repetitivos y simples hasta que lo consiguen:
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.
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:
puede expresarse así por recurrencia:
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:
Mutuamente recursivo:
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
n
o (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 nombradasn
en 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)
dondeg(x) = f(x) + l(x)
yh
yl
solo 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 :
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 .
fuente
static unsigned int vote = 1;
de mí. Perdona el humor estático, si quieres :) Esta es la mejor respuesta hasta ahora.La invocación de una función desde esa misma función.
fuente
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.
fuente
a
todavía se llama a sí misma, solo indirectamente (llamandob
).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:
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.
Pre-pedido: BAC
En orden: ABC
Post-orden: ACB
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.
fuente
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:
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:
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_pizza
ejemplo 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?fuente
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:
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.
fuente
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.
fuente
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:
De hecho, utilizamos mucho la recursividad en la vida diaria; simplemente no pensamos de esa manera.
fuente
for
bucle bien escrito en una función recursiva sin sentido.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.
fuente
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.
fuente
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:
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:
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):
Ventajas:
Desventajas
fuente
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.
fuente
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
fuente
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:
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:
Esa es una oración completa que contiene otras oraciones completas:
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
a
yb
. Para encontrar su mcd (suponiendo que ninguno sea 0), debe verificar sia
es divisible enb
. Si es así,b
es el mcd, de lo contrario, debe verificar el mcd deb
y el resto dea/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):
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 .
fuente
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:
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.
fuente
La recursión es una forma muy concisa de expresar algo que debe repetirse hasta que se alcance algo.
fuente
No es un inglés simple, no ejemplos de la vida real, sino dos formas de aprender la recursividad jugando:
fuente
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 :)
fuente