Recursión sin factorial, números de Fibonacci, etc.

48

Casi todos los artículos que puedo encontrar sobre la recursividad incluyen ejemplos de números factoriales o de Fibonacci, que son:

  1. Matemáticas
  2. Inútil en la vida real

¿Hay algunos ejemplos interesantes de códigos no matemáticos para enseñar la recursividad?

Estoy pensando en algoritmos de divide y vencerás, pero generalmente involucran estructuras de datos complejas.

lazyCrab
fuente
26
Si bien su pregunta es completamente válida, dudaría en llamar inútil a los números de Fibonacci en la vida real . Lo mismo vale para factorial .
Zach L
2
The Little Schemer es un libro completo sobre la recursión que nunca usa Fact o Fib. junix-linux-config.googlecode.com/files/…
Eric Wilson
55
@Zach: Aun así, la recursión es una forma horrible de implementar números de Fibonacci, debido al tiempo de ejecución exponencial.
dan04
2
@ dan04: La recursión es una forma horrible de implementar casi cualquier cosa debido a la posibilidad de desbordamiento de pila en la mayoría de los idiomas.
R ..
55
@ dan04 a menos que su idioma sea lo suficientemente inteligente como para implementar la optimización de llamadas de cola como la mayoría de los lenguajes funcionales, en cuyo caso funciona bien
Zachary K

Respuestas:

107

Las estructuras de directorio / archivo son el mejor ejemplo de uso para la recursividad, porque todos las entienden antes de comenzar, pero cualquier cosa que involucre estructuras en forma de árbol servirá.

void GetAllFilePaths(Directory dir, List<string> paths)
{
    foreach(File file in dir.Files)
    {
        paths.Add(file.Path);
    }

    foreach(Directory subdir in dir.Directories)
    {
        GetAllFilePaths(subdir, paths)
    }
}

List<string> GetAllFilePaths(Directory dir)
{
    List<string> paths = new List<string>();
    GetAllFilePaths(dir, paths);
    return paths;
}
pdr
fuente
1
Gracias, creo que iré con el sistema de archivos. Es algo concreto y puede usarse para muchos ejemplos del mundo real.
sinapsis
99
Nota: el comando unix a menudo excluye la opción -r (cp o rm por ejemplo). -r significa recursivo.
deadalnix
77
tienes que tener un poco de cuidado aquí, ya que en el mundo real los sistemas de archivos son en realidad un gráfico dirigido, no necesariamente un árbol, si el sistema de archivos lo admite, los enlaces duros, etc. pueden crear uniones y ciclos
jk.
1
@jk: los directorios no pueden estar vinculados de forma rígida, por lo que algunas de las hojas que pueden aparecer en más de una ubicación, y suponiendo que excluya los enlaces simbólicos, los sistemas de archivos del mundo real son árboles.
R ..
1
Existen otras peculiaridades en algunos sistemas de archivos para directorios, por ejemplo, puntos de análisis NTFS. mi punto es que el código que no es específicamente consciente de esto puede tener resultados inesperados en los sistemas de archivos del mundo real
jk.
51

Busque cosas que involucren estructuras de árboles. Un árbol es relativamente fácil de entender, y la belleza de una solución recursiva se hace evidente mucho antes que con estructuras de datos lineales como las listas.

Cosas para pensar:

  • sistemas de archivos: esos son básicamente árboles; una buena tarea de programación sería buscar todas las imágenes .jpg en un determinado directorio y todos sus subdirectorios
  • ancestral: dado un árbol genealógico, encuentre el número de generaciones que tiene que caminar para encontrar un antepasado común; o verifique si dos personas en el árbol pertenecen a la misma generación; o verifique si dos personas en el árbol pueden casarse legalmente (depende de la jurisdicción :)
  • Documentos tipo HTML: convierta entre la representación en serie (texto) de un documento y un árbol DOM; realizar operaciones en subconjuntos de un DOM (¿tal vez incluso implementar un subconjunto de xpath?); ...

Todos estos están relacionados con escenarios reales del mundo real, y todos pueden usarse en aplicaciones de importancia real.

tdammers
fuente
Por supuesto, debe tenerse en cuenta que siempre que tenga la libertad de diseñar su propia estructura de árbol, casi siempre es mejor mantener punteros / referencias al padre / próximo hermano / etc. en los nodos para que pueda recorrer el árbol sin recurrencia.
R ..
1
¿Qué tienen que ver los punteros con él? Incluso cuando tiene punteros para el primer hijo, el próximo hermano y el padre, todavía tiene que caminar a través de su árbol de alguna manera, y en algunos casos, la recurrencia es la forma más factible.
tdammers
41

https://stackoverflow.com/questions/105838/real-world-examples-of-recursion

  • modelando una infección contagiosa
  • generando geometría
  • gestión de directorios
  • clasificación

https://stackoverflow.com/questions/2085834/how-did-you-practically-use-recursion

  • trazado de rayos
  • ajedrez
  • análisis del código fuente (gramática del lenguaje)

https://stackoverflow.com/questions/4945128/what-is-a-good-example-of-recursion-other-than-generating-a-fibonacci-sequence

  • Búsqueda BST
  • Torres de Hanoi
  • búsqueda de palíndromo

https://stackoverflow.com/questions/126756/examples-of-recursive-functions

  • Da una bonita historia en inglés que ilustra la recurrencia de un cuento antes de dormir.
mskfisher
fuente
10
Si bien esto puede responder teóricamente la pregunta, sería preferible incluir aquí las partes esenciales de esas preguntas y respuestas, y proporcionar los enlaces para referencia. Si alguna vez se eliminan las preguntas de SO, su respuesta será completamente inútil.
Adam Lear
@ Anna Bueno, los usuarios no pueden eliminar sus preguntas, entonces, ¿qué tan probable es que eso suceda?
vemv
44
@vemv Eliminar votos, moderadores, reglas sobre lo que está cambiando el tema ... puede suceder. De cualquier manera, tener una respuesta más completa aquí sería preferible que enviar un visitante a cuatro páginas diferentes de inmediato.
Adam Lear
@Anna: Siguiendo esta línea de pensamiento, cada pregunta cerrada como "duplicado exacto" debe tener la respuesta del original pegada. Esta pregunta ES un duplicado exacto (de las preguntas sobre SO), ¿por qué debería recibir un tratamiento diferente al exacto? duplicados de preguntas sobre programadores?
SF.
1
@SF Si pudiéramos cerrarlo como un duplicado, lo haríamos, pero los duplicados entre sitios no son compatibles. Los programadores son un sitio separado, por lo que idealmente las respuestas aquí usarían SO como cualquier otra referencia, no delegarían por completo. No es diferente a decir simplemente "su respuesta está en este libro", técnicamente cierto, pero no se puede usar de inmediato sin consultar la referencia.
Adam Lear
23

Aquí hay algunos problemas más prácticos que me vienen a la mente:

  • Ordenar fusión
  • Búsqueda binaria
  • Recorrido, inserción y eliminación en árboles (ampliamente utilizado en aplicaciones de bases de datos)
  • Generador de permutaciones
  • Solucionador de Sudoku (con retroceso)
  • Corrector ortográfico (de nuevo con retroceso)
  • Análisis de sintaxis (.eg, un programa que convierte el prefijo a notación postfix)
Daniel Scocco
fuente
11

QuickSort sería el primero que salta a la mente. La búsqueda binaria también es un problema recursivo. Más allá de eso, hay toda una serie de problemas por los que las soluciones se caen casi gratis cuando comienzas a trabajar con la recursividad.

Zachary K
fuente
3
La búsqueda binaria a menudo se formula como un problema recursivo, pero es trivial (y a menudo preferible) implementar de manera imperativa.
esponjoso
Dependiendo del idioma que esté utilizando, el código puede o no ser explícitamente recursivo o imperativo o recursivo. Pero sigue siendo un algoritmo recursivo en el sentido de que está dividiendo el problema en partes cada vez más pequeñas para llegar a la solución.
Zachary K
2
@Zachary: los algoritmos que se pueden implementar con la recursión de cola (como la búsqueda binaria) se encuentran en una clase de espacio fundamentalmente diferente de aquellos que requieren una recursión real (o sus propias estructuras de estado con requisitos de espacio igualmente costosos). No creo que sea beneficioso que se les enseñe juntos como si fueran lo mismo.
R ..
8

Ordenar, definido recursivamente en Python.

def sort( a ):
    if len(a) == 1: return a
    part1= sort( a[:len(a)//2] )
    part2= sort( a[len(a)//2:] )
    return merge( part1, part2 )

Fusionar, definido de forma recursiva.

def merge( a, b ):
    if len(b) == 0: return a
    if len(a) == 0: return b
    if a[0] < b[0]:
        return [ a[0] ] + merge(a[1:], b)
    else:
        return [ b[0] ] + merge(a, b[1:]) 

Búsqueda lineal, definida recursivamente.

def find( element, sequence ):
    if len(sequence) == 0: return False
    if element == sequence[0]: return True
    return find( element, sequence[1:] )

Búsqueda binaria, definida recursivamente.

def binsearch( element, sequence ):
    if len(sequence) == 0: return False
    mid = len(sequence)//2
    if element < mid: 
        return binsearch( element, sequence[:mid] )
    else:
        return binsearch( element, sequence[mid:] )
S.Lott
fuente
6

En cierto sentido, la recursión se trata de dividir y conquistar soluciones, es decir, dividir el espacio del problema en uno más pequeño para ayudar a encontrar la solución para un problema simple, y luego volver a reconstruir el problema original para componer la respuesta correcta.

Algunos ejemplos que no involucran matemáticas para enseñar la recursividad (al menos esos problemas que recuerdo de mis años universitarios):

Estos son ejemplos del uso de Backtracking para resolver el problema.

Otros problemas son los clásicos del dominio de Inteligencia Artificial: uso de la búsqueda en profundidad, búsqueda de caminos, planificación.

Todos esos problemas involucran algún tipo de estructura de datos "compleja", pero si no desea enseñarla con matemáticas (números), entonces sus opciones pueden ser más limitadas. Es posible que desee comenzar a enseñar con una estructura de datos básica, como una Lista vinculada. Por ejemplo representando los números naturales usando una Lista:

0 = lista vacía 1 = lista con un nodo. 2 = lista con 2 nodos. ...

luego defina la suma de dos números en términos de esta estructura de datos como esta: Vacío + N = N Nodo (X) + N = Nodo (X + N)

Gabriel
fuente
5

Towers of Hanoi es bueno para ayudar a aprender la recursividad.

Hay muchas soluciones en la web en muchos idiomas diferentes.

Oded
fuente
3
Esto es en mi opinión otro mal ejemplo. En primer lugar, no es realista; No es un problema que la gente realmente tenga. En segundo lugar, hay soluciones fáciles no recursivas. (Una es: numerar los discos. Nunca mueva un disco a un disco de la misma paridad y nunca deshaga el último movimiento que hizo. Si sigue esas dos reglas, resolverá el rompecabezas con la solución óptima. No se requiere recurrencia. )
Eric Lippert
5

Un detector de palíndromo:

Comience con una cadena: "ABCDEEDCBA" Si los caracteres iniciales y finales son iguales, luego repita y marque "BCDEEDCB", etc.

NWS
fuente
66
Eso también es trivial de resolver sin recurrencia y, en mi humilde opinión, mejor resuelto sin él.
Blrfl
3
De acuerdo, pero OP solicitó específicamente ejemplos de enseñanza con un uso mínimo de estructuras de datos.
NWS
55
No es un buen ejemplo de enseñanza si sus estudiantes pueden pensar inmediatamente en una solución no recursiva. ¿Por qué alguien prestaría atención cuando su ejemplo es "Aquí hay algo trivial que hacer con un bucle. Ahora voy a mostrar una forma más difícil sin razón aparente".
Vuelva a instalar Mónica
5

Un algoritmo de búsqueda binaria suena como lo que quieres.

Sardathrion
fuente
4

En los lenguajes de programación funcionales, cuando no hay funciones de orden superior disponibles, se utiliza la recursión en lugar de los bucles imperativos para evitar el estado mutable.

F # es un lenguaje funcional impuro que permite ambos estilos, así que compararé ambos aquí. La siguiente suma todos los números en una lista.

Bucle imperativo con variable mutable

let xlist = [1;2;3;4;5;6;7;8;9;10]
let mutable sum = 0
for x in xlist do
    sum <- sum + x

Bucle recursivo sin estado mutable

let xlist = [1;2;3;4;5;6;7;8;9;10]
let rec loop sum xlist = 
    match xlist with
    | [] -> sum
    | x::xlist -> loop (sum + x) xlist
let sum = loop 0 xlist

Tenga en cuenta que este tipo de agregación se captura en la función de orden superior List.foldy podría escribirse como, List.fold (+) 0 xlisto incluso más, simplemente con la función de conveniencia List.sumcomo tal List.sum xlist.

Stephen Swensen
fuente
merecerías más puntos que solo +1 de mi parte. F # sin recursión sería muy tedioso, ¡esto es aún más cierto para Haskell que no tiene construcciones de bucle SOLO recursividad!
schoetbi
3

He usado mucho la recursividad en el juego de IA. Escribiendo en C ++, hice uso de una serie de aproximadamente 7 funciones que se llaman entre sí en orden (con la primera función que tiene una opción para omitirlas y llamar a una cadena de 2 funciones más). La función final en cualquiera de las cadenas llamó a la primera función nuevamente hasta que la profundidad restante que quería buscar fue a 0, en cuyo caso la función final llamaría a mi función de evaluación y devolvería el puntaje de la posición.

Las múltiples funciones me permitieron ramificar fácilmente en función de las decisiones del jugador o eventos aleatorios en el juego. Hacía uso de pasar por referencia cada vez que podía, porque estaba pasando estructuras de datos muy grandes, pero debido a cómo estaba estructurado el juego, era muy difícil tener un "movimiento de deshacer" en mi búsqueda, así que Usaría el paso por valor en algunas funciones para mantener mis datos originales sin cambios. Debido a esto, cambiar a un enfoque basado en bucles en lugar de un enfoque recursivo resultó demasiado difícil.

Puede ver un resumen muy básico de este tipo de programa, consulte https://secure.wikimedia.org/wikipedia/en/wiki/Minimax#Pseudocode

David Stone
fuente
3

Un buen ejemplo de la vida real en los negocios es algo llamado "Lista de materiales". Estos son los datos que representan todos los componentes que conforman un producto terminado. Por ejemplo, usemos una bicicleta. Una bicicleta tiene manubrios, ruedas, un cuadro, etc. Y cada uno de esos componentes puede tener subcomponentes. por ejemplo, la rueda puede tener radios, un vástago de válvula, etc. Por lo general, estos se representan en una estructura de árbol.

Ahora, para consultar cualquier información agregada sobre la lista de materiales o para cambiar elementos en una lista de materiales, a menudo recurre a la recursividad.

    class BomPart
    {
        public string PartNumber { get; set; }
        public string Desription { get; set; }
        public int Quantity { get; set; }
        public bool Plastic { get; set; }
        public List<BomPart> Components = new List<BomPart>();
    }

Y una muestra de llamada recursiva ...

    static int ComponentCount(BomPart part)
    {
        int subCount = 0;
        foreach(BomPart p in part.Components)
            subCount += ComponentCount(p);
        return part.Quantity * Math.Max(1,subCount);

    }

Obviamente, la clase BomPart tendría muchos más campos. Es posible que necesite averiguar cuántos componentes plásticos tiene, cuánto trabajo se necesita para construir una pieza completa, etc. Sin embargo, todo esto vuelve a la utilidad de Recursion en una estructura de árbol.

deepee1
fuente
Una lista de materiales puede ser una densidad dirigida en lugar de un árbol, por ejemplo, la misma especificación de tornillo puede ser utilizada por más de un componente.
Ian
-1

Las relaciones familiares son buenos ejemplos porque todos las entienden intuitivamente:

ancestor(joe, me) = (joe == me) 
                    OR ancestor(joe, me.father) 
                    OR ancestor(joe, me.mother);
Caleb
fuente
¿en qué idioma está escrito este código?
törzsmókus
@ törzsmókus No hay un lenguaje específico. La sintaxis es bastante parecida a C, Obj-C, C ++, Java y muchos otros lenguajes, pero si desea un código real, es posible que deba sustituir un operador apropiado como ||el OR.
Caleb
-1

Una recursividad bastante inútil pero que muestra un buen funcionamiento interno es recursivo strlen():

size_t strlen( const char* str )
{
    if( *str == 0 ) {
       return 0;
    }
    return 1 + strlen( str + 1 );
}

Sin matemáticas: una función muy simple. Por supuesto, no lo implementa de manera recursiva en la vida real, pero es una buena demostración de recursividad.

diente filoso
fuente
-2

Otro problema de recursión del mundo real con el que los estudiantes pueden relacionarse es crear su propio rastreador web que extraiga información de un sitio web y siga todos los enlaces dentro de ese sitio (y todos los enlaces de esos enlaces, etc.).

GregW
fuente
2
En general, eso es mejor servido por una cola de proceso en lugar de recurrencia en el sentido tradicional.
esponjoso
-2

Resolví un problema con un patrón de caballero (en un tablero de ajedrez) usando un programa recursivo. Se suponía que debías mover al caballero para que tocara cada cuadro, excepto unos pocos cuadros marcados.

Tu simplemente:

mark your "Current" square
gather a list of free squares that are valid moves
are there no valid moves?
    are all squares marked?
        you win!
for each free square
    recurse!
clear the mark on your current square.
return.    

Se pueden trabajar muchos tipos de escenarios de "anticipación" probando las posibilidades futuras en un árbol como este.

Bill K
fuente