He tenido algunos problemas con la recursividad desde hace un tiempo, desde que comencé a estudiar algoritmos. ¿Hay algún recurso / método en particular que te haya ayudado a entenderlo completamente y que puedas recomendar?

¿Te cuesta entenderlo conceptualmente o te desconcierta la mecánica de la recursividad? ¿O ambos?

Conceptualmente

The Little Schemer es un excelente libro de enseñanza sobre el tema de la recursividad. Es muy accesible, habiendo sido

basado en apuntes de una introducción de dos semanas “rápida” a Scheme para estudiantes sin experiencia previa en programación y una aversión admitida por cualquier cosa matemática.

El libro tiene un estilo muy fácil, no académico, enseñando con ejemplos y preguntas simples. La curva de aprendizaje es muy suave, con mucha repetición (cada vez, una pequeña variación o reutilización de algo que ya has aprendido). Se le enseña cómo resolver problemas simples y luego cómo juntar esas soluciones simples para resolver problemas más grandes. Te enseñan a analizar problemas y a encontrar patrones comunes en diferentes problemas.

El Capítulo 8 es más desafiante que los otros. No hay vergüenza en detenerse allí y volver más tarde, con una nueva perspectiva. En ese punto, ya has estado usando mucho la recursividad; Es posible que aún no lo entienda en un nivel fundamental, pero seguramente sabrá cómo usarlo.

Estructuras de datos recursivas

¿Puedes entender que algunas estructuras de datos son de naturaleza recursiva? Creo que puede ser útil porque los algoritmos recursivos funcionan particularmente bien con esas estructuras.

Por ejemplo, en un árbol de búsqueda binario, un nodo con dos descendientes es un nodo con dos árboles de búsqueda binarios. Cualquier nodo en un BST puede verse como la raíz de su propio árbol.

Imagine que el BST es una red de habitaciones vinculadas, cada una con un número diferente pintado en el piso. Se le pide que encuentre la suma de todos los números en el piso de todas las habitaciones.

Entras en la primera habitación; Tiene un seis en el piso y dos puertas por las que no has pasado. Te das cuenta de que el total es seis más la suma de todas las habitaciones más allá de la puerta de la izquierda y todas las habitaciones más allá de la puerta de la derecha. Entonces llamas a dos amigos para pedir ayuda, enviando uno por la puerta izquierda y el otro por la derecha.

Su primer amigo atraviesa la puerta de la izquierda, ve tres en el piso y dos puertas nuevas. Entonces llama a dos amigos … Su primer amigo pasa por la puerta izquierda, encuentra solo uno en el piso y ninguna otra puerta, así que regresa y dice “Uno”. Su segundo amigo vuelve igualmente rápido, diciendo “Dos”. Entonces agrega esas respuestas al número tres en su piso y vuelve a usted y dice “Seis”. Ahora sabes que el total es seis más seis más lo que sea que diga tu segundo amigo cuando finalmente regrese.

Eso es recursividad.

Tenías una red de amigos en constante expansión, cada uno con la misma tarea, cada uno resolviendo lo que podía resolver por sí mismo y confiando en que otros amigos hicieran el resto, cada uno usando el mismo plan pero encontrando una situación diferente. Finalmente, algunos amigos pudieron resolver su parte del problema sin ninguna ayuda. Lo que ayudó a los amigos que los llamaron a resolver su parte … y así sucesivamente hasta que sus dos amigos lo ayudaron a resolver su problema.

Eso es recursividad.

¿El plan para contar los números en todas las habitaciones se vuelve más confuso si todos tus amigos no solo siguen el mismo plan, sino que tienen el mismo nombre? No debería Después de todo, solo tiene que lidiar con dos de ellos directamente y creo que puede notar la diferencia entre usted y otras dos personas, incluso si todos tienen el mismo nombre. Uno atravesó la puerta izquierda, el otro atravesó la puerta derecha, usted se quedó en la habitación.

Eso es recursividad.

Mecánica

¿Realmente entiendes el alcance, la visibilidad y la vida útil? ¿Entiendes cómo se aplican cuando llamas a una función desde otra función? Es sorprendente cuántos programadores no lo hacen; se llevan bien buscándolo y copiando patrones de código estándar.

¿Le resulta confuso llamar a una función de otra? De lo contrario, no debería confundirse cuando la función que llama es la función desde la que llama.

¿Te confundiría si la función foo llama a la barra de funciones, y los parámetros de la barra y las variables locales tienen el mismo nombre que los parámetros y las variables locales de foo? No, porque sabes que tienen un alcance, visibilidad y vida útil completamente separados.

¿Te confundiría si el código de barras es una copia del código de foo? No debería Después de todo, la duplicación de código ocurre con demasiada frecuencia en la vida real.

Si no te confunde cuando foo y bar tienen los mismos nombres de variables / parámetros, y no te confunde cuando foo y bar contienen un código idéntico, ¿por qué debería confundirte cuando foo y bar tienen el mismo nombre? Todavía está llamando a una función desde otra función, cada una con su propio marco de referencia separado.

Puede ser útil si aprende lenguaje ensamblador y cómo se escriben las funciones en él. Eso le mostrará cómo se gestionan las pilas y los montones y cómo las pilas y los montones almacenan parámetros y variables locales. Eso puede darle una mejor comprensión de lo que quiero decir cuando digo que cada llamada a una función tiene su propio marco de referencia.

Realmente lo entendí una vez que comencé a tener que resolver problemas del mundo real. Y al resolver problemas del mundo real, ayuda fingir que estoy atravesando cosas de alguna manera.

Aquí hay un ejemplo:

Tienes un árbol de comentarios. Me gusta los comentarios de YouTube. Y desea saber cuál es el número total de comentarios realizados por una sola persona (llamémosle Sean. Es un comentarista particularmente travieso, por lo que debe seguirlo) en todo el árbol. Es un árbol porque cualquier comentario puede tener sub-comentarios, ramas.

Me gusta pensar que estoy enviando un robot a cada comentario. El robot envía sus robots hijos a los sub-comentarios. Y esos robots envían a sus hijos, y etc.

Cada robot padre espera a que todos sus hijos regresen antes de volver con su propio padre. Cada robot comprueba si Sean fue quien hizo su comentario. Si es así, se aferran a una bandera que indica que el robot es propietario del comentario travieso de Sean.

Cuando un robot no tiene más hijos para enviar, vuelve a su padre. Cuando todos los niños están de vuelta, el robot toma todas las banderas de sus hijos. Entonces el robot vuelve a su padre.

El robot padre final, cuando se devuelvan todos sus hijos, tendrá el total de banderas que indican el número de comentarios traviesos que Sean publicó.

El robot y el algoritmo:

Si lo piensas por un tiempo, rápidamente encontrarás que cada robot tiene que hacer algunas cosas. Y no hay robots especiales . Esa es una clave para la recursión.

La relación padre-hijo es otra clave. La recursión requiere replicación .

La forma en que el padre acumuló datos del niño es un método simple y elegante de contar. De nuevo, no es necesario que haya ningún caso especial. Los niños que no han encontrado ningún Seans tendrán cero banderas. Aquellos que lo hagan tendrán el total que han encontrado.

Finalmente, cuando un niño decide regresar con el padre es esencial, o la recursión no funcionará. Aquí, los niños volvieron cuando no tenían ningún comentario secundario para engendrar a sus propios hijos. Eso garantiza que todos los comentarios en el árbol serán atravesados ​​por un robot.

Imagine que tengo una pila de billetes de alguna moneda en particular. Podría hacerte imaginar una pila de billetes, pero sería más rentable para mí si tuviera los billetes.

Sé cómo agregar la factura más alta de la pila al valor del resto de la pila de facturas.

Entonces, saco la cuenta superior y la dejo a un lado. Entonces tengo un problema más simple. Solo necesito agregar esa factura más alta al valor del resto de la pila.

Tengo un algoritmo:

CountMyMoney (stackOfBills)
Empezar
Si el tamaño de la pila es 0
Devolución 0 euros.
De otra manera
Empezar
restOfTheStack = stackOfBills después de eliminar la factura principal;
stackValue = CountMyMoney (restOfTheStack)
Devolver stackValue + value of top bill
Fin
Fin

Entonces, tengo una pila de 3 x 500 euros

  1. CountMyMoney (llamada # 1) recibe una llamada con los 3 x 500 euros
  2. Descubre que la pila no está vacía, por lo que saca el billete de 500 euros superior
  3. CountMyMoney (llamada # 2) recibe una llamada con una pila de 2 x 500 euros
  4. Descubre que la pila no está vacía, por lo que saca el billete de 500 euros superior
  5. Se llama a CountMyMoney (llamada n.º 3) con una pila de 1 x 500 euros
  6. Descubre que la pila no está vacía, por lo que saca el billete de 500 euros superior
  7. CountMyMoney (llamada # 4) recibe una llamada con una pila de 0 x 500 euros
  8. Descubre que la pila está vacía, por lo que la llamada n. ° 4 devuelve el valor 0
  9. La llamada # 3 agrega el resultado de la llamada # 4, que es 0, a su factura de 500 euros
  10. Devuelve 500 euros
  11. La llamada # 2 agrega el resultado de la llamada # 3, que es 500, a su factura de 500 euros
  12. Devuelve 1000 euros
  13. La llamada # 1 agrega el resultado de la llamada # 2, que es 1000, a su factura de 500 euros
  14. Devuelve 1500 euros

Tienes el resultado y tengo 1500 euros imaginarios que no tenía antes de esta pregunta. Ahora para encontrar un banco que convierta euros imaginarios en dólares canadienses reales.

No trates de visualizarlo.

Soy un aprendiz visual, así que para mí, la visualización suele ser cómo entiendo algo. Esto es lo que me dio problemas de recursión cuando me topé por primera vez. En cambio, piense en ello como una forma de dividir un gran problema en una versión más pequeña de sí mismo.

Me pregunto si te estás preocupando demasiado por la implementación subyacente. Si es así, olvídalo y concéntrate en la definición que debería ser sencilla para cualquier persona con una educación matemática leve.

More Interesting

¿Hay algún algoritmo de ordenación que funcione en el orden de n?

¿Cuál es la mejor estructura de datos para almacenar y realizar una adición de dos números grandes de 512 bits?

F (n) E de O (g (n)) donde log (g (n))> 1 yf (n)> 1 para n grande?

¿Cuál es el número más pequeño [matemática] N [/ matemática] tal que [matemática] N \ equiv 2 \ mod 3, [/ matemática] [matemática] N \ equiv 1 \ mod 5, [/ matemática] [matemática] N \ equiv 4 \ mod 7 [/ matemáticas]?

¿Se introdujo la recursión a propósito?

¿Cómo determinan los algoritmos de creación de mercado qué tan agresivamente deberían salir de las posiciones?

¿Cuáles son los beneficios de usar la recursividad de la cola? ¿Es siempre posible?

¿Cuándo se usaría un algoritmo gráfico?

¿Qué son los pseudocódigos para GCD?

¿Existe un algoritmo ML para verificar qué tan bien coinciden 3 objetos de diferentes tipos?

¿Cuál es la forma más eficiente de detectar, si una cadena es un anagrama de un palíndromo?

¿Cuáles son algunas de las preguntas famosas al calcular los caminos más cortos (gráficos) usando Dijkstra's, DAG y Bellman-Ford?

¿Cuál es el algoritmo de clasificación más rápido para una matriz de números grandes (hasta 1,000,000,000,000)?

¿Cuáles son las aplicaciones del mundo real de algunas estructuras de datos avanzadas, y cuándo elegiría una estructura de datos sobre otra, en el caso de estructuras de datos similares?

¿Avanzar en CS en general hará que los algoritmos sean cada vez más complejos con el tiempo que las personas no pueden manejar? ¿Cuáles son las soluciones para ese caso?