¿Qué es la recursividad y cuándo debo usarla?

121

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:

  1. ¿Qué es la recursividad?
  2. ¿Cuándo usaría la recursión?
  3. ¿Por qué la gente no usa la recursividad?
Mike Minutillo
fuente
9
Y tal vez esto ayude: stackoverflow.com/questions/126756/…
kennytm
3
Esto puede ayudar a comprender el concepto: navegue hasta el enlace provisto en el segundo comentario de la pregunta en esta página y haga lo que dicen los comentarios: stackoverflow.com/questions/3021/…
dtmland

Respuestas:

86

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:

  1. el espacio está tallado en la pila para los argumentos de la función y las variables locales
  2. los argumentos de la función se copian en este nuevo espacio
  3. el control salta a la función
  4. se ejecuta el código de la función
  5. el resultado de la función se copia en un valor de retorno
  6. la pila se rebobina a su posición anterior
  7. el control vuelve a donde se llamó la 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.

Peter Burns
fuente
1
Es bueno ver una explicación de la sobrecarga inherente de la recursividad. Toqué eso en mi respuesta también. Pero para mí, la gran fortaleza con la recursividad es lo que puedes hacer con la pila de llamadas. Puede escribir un algoritmo sucinto con recursión que se ramifica repetidamente, lo que le permite hacer cosas como rastrear jerarquías (relaciones padre / hijo) con facilidad. Vea mi respuesta para un ejemplo.
Steve Wortham
77
Muy decepcionado de encontrar la respuesta principal a una pregunta titulada "¿Qué es la recurrencia y cuándo debo usarla?" en realidad no responde ninguno de esos, no importa la advertencia de parcialidad extrema contra la recursividad, a pesar de su uso generalizado en la mayoría de los idiomas que mencionó (no hay nada específicamente incorrecto en lo que dijo, pero parece que está exagerando el problema y subestimando) La utilidad).
Bernhard Barker
2
Probablemente tengas razón @Dukeling. Para el contexto, cuando escribí esta respuesta había muchas explicaciones excelentes de recursión ya escritas y escribí esto con la intención de ser un complemento de esa información, no la respuesta principal. En la práctica, cuando necesito caminar por un árbol o manejar cualquier otra estructura de datos anidados, generalmente recurro a la recursión y aún no he alcanzado un desbordamiento de pila de mi propia creación en la naturaleza.
Peter Burns
63

Inglés simple ejemplo de recursión.

A child couldn't sleep, so her mother told her a story about a little frog,
    who couldn't sleep, so the frog's mother told her a story about a little bear,
         who couldn't sleep, so the bear's mother told her a story about a little weasel... 
            who fell asleep.
         ...and the little bear fell asleep;
    ...and the little frog fell asleep;
...and the child fell asleep.
DMin
fuente
1
arriba + para tocar el corazón :)
Suhail Mumtaz Awan
Hay una historia similar como esta para los niños pequeños que no se duermen en los cuentos populares chinos, acabo de recordar esa, y me recuerda cómo funciona la recursión del mundo real.
Harvey Lin
49

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:

struct Node {
    Node* next;
};

Y desea saber cuánto tiempo dura una lista vinculada, puede hacer esto con recursividad:

int length(const Node* list) {
    if (!list->next) {
        return 1;
    } else {
        return 1 + length(list->next);
    }
}

(Por supuesto, esto también se puede hacer con un bucle for, pero es útil como una ilustración del concepto)

Andreas Brinck
fuente
@ Christopher: Este es un buen y simple ejemplo de recursión. Específicamente, este es un ejemplo de recursión de cola. Sin embargo, como dijo Andreas, puede reescribirse fácilmente (de manera más eficiente) con un bucle for. Como explico en mi respuesta, hay mejores usos para la recursividad.
Steve Wortham
2
¿realmente necesitas una declaración else aquí?
Adrien Be
1
No, solo está ahí para mayor claridad.
Andreas Brinck
@SteveWortham: Esto no es recursivo de la cola como está escrito; length(list->next)todavía necesita volver a length(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 igual int length(const Node* list, int count=0) { return (!list) ? count : length(list->next, count + 1); }.
cHao
46

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:

int FloorByTen(int num)
{
    if (num % 10 == 0)
        return num;
    else
        return FloorByTen(num-1);
}

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 .

ingrese la descripción de la imagen aquí

Puede dibujar uno de esos de manera muy simple con recursividad, donde la pila de llamadas se ramifica en 3 direcciones:

private void BuildVertices(double x, double y, double len)
{
    if (len > 0.002)
    {
        mesh.Positions.Add(new Point3D(x, y + len, -len));
        mesh.Positions.Add(new Point3D(x - len, y - len, -len));
        mesh.Positions.Add(new Point3D(x + len, y - len, -len));
        len *= 0.5;
        BuildVertices(x, y + len, len);
        BuildVertices(x - len, y - len, len);
        BuildVertices(x + len, y - len, len);
    }
}

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.

Steve Wortham
fuente
27

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í:

public int Fact(int n)
{
  int fact = 1;

  for( int i = 2; i <= n; i++)
  {
    fact = fact * i;
  }

  return fact;
}

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 #:

public int FactRec(int n)
{
  if( n < 2 )
  {
    return 1;
  }

  return n * FactRec( n - 1 );
}

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í:

// In FactRec(5)
return 5 * FactRec( 5 - 1 );

// which is
return 5 * FactRec(4);

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:

// In FactRec(4)
return 4 * FactRec(3);

Si sustituimos este valor de retorno en el valor de retorno anterior, obtenemos

// In FactRec(5)
return 5 * (4 * FactRec(3));

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:

return 5 * (4 * FactRec(3));
return 5 * (4 * (3 * FactRec(2)));
return 5 * (4 * (3 * (2 * FactRec(1))));
return 5 * (4 * (3 * (2 * (1))));

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.

Wolfbyte
fuente
2
Buena explicación, pero creo que es importante tener en cuenta que esto es simplemente una recursión de cola y no ofrece ninguna ventaja sobre la solución iterativa. Es aproximadamente la misma cantidad de código y se ejecutará más lentamente debido a la sobrecarga de la llamada de función.
Steve Wortham
1
@ SteveWortham: Esto no es una recursión de cola En el paso recursivo, el resultado de FactRec()debe multiplicarse por nantes de regresar.
rvighne
12

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).

public int Factorial(int n)
{
    if (n <= 1)
        return 1;

    return n * Factorial(n - 1);
}
revs jkohlhepp
fuente
9

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 como X times the factorial of X-1. Por lo tanto, el método "recurre" para encontrar el factorial de X-1, y luego multiplica todo lo que obtuvo Xpara dar una respuesta final. Por supuesto, para encontrar el factorial de X-1, primero calculará el factorial de X-2, y así sucesivamente. El caso base sería cuando Xes 0 o 1, en cuyo caso sabe regresar 1desde entonces 0! = 1! = 1.

Ámbar
fuente
1
Creo que a lo que se está refiriendo no es a la recursión, sino al principio de diseño del algoritmo <a href=" en.wikipedia.org/wiki/… y Conquer</a>. Busque, por ejemplo, en <a href = " en.wikipedia. org / wiki / Ackermann_function "> Función de Ackermans </a>.
Gabriel Ščerbák
2
No, no me estoy refiriendo a D&C. D&C implica que existen 2 o más subproblemas, la recursión por sí misma no existe (por ejemplo, el ejemplo factorial dado aquí no es D&C, es completamente lineal). D&C es esencialmente un subconjunto de recursividad.
Ámbar
3
Citado del artículo exacto que vinculó: "Un algoritmo de divide y vencerás funciona dividiendo recursivamente un problema en dos o más subproblemas del mismo tipo (o relacionado)"
Ámbar
No creo que sea una gran explicación, ya que la recursión estrictamente hablando no tiene que resolver el problema en absoluto. Podrías llamarte a ti mismo (Y desbordarse).
UK-AL
Estoy usando su explicación en un artículo que estoy escribiendo para PHP Master, aunque no puedo atribuírselo. Espero que no te importe.
frostymarvelous
9

Considere un viejo problema bien conocido :

En matemáticas, el máximo común divisor (mcd) ... de dos o más enteros distintos de cero, es el mayor entero positivo que divide los números sin dejar resto.

La definición de gcd es sorprendentemente simple:

definición de mcd

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:

  1. mcd (10, 8)
  2. mcd (10, 10 mod 8)
  3. mcd (8, 2)
  4. mcd (8, 8 mod 2)
  5. mcd (2, 0)
  6. 2

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

sum l =
    if empty(l)
        return 0
    else
        return head(l) + sum(tail(l))

donde heades el primer elemento de una lista y tailes el resto de la lista. Tenga en cuenta que se sumrepite dentro de su definición al final.

Quizás prefiera el valor máximo en una lista:

max l =
    if empty(l)
        error
    elsif length(l) = 1
        return head(l)
    else
        tailmax = max(tail(l))
        if head(l) > tailmax
            return head(l)
        else
            return tailmax

Puede definir la multiplicación de enteros no negativos de forma recursiva para convertirla en una serie de adiciones:

a * b =
    if b = 0
        return 0
    else
        return a + (a * (b - 1))

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:

sort(l) =
    if empty(l) or length(l) = 1
        return l
    else
        (left,right) = split l
        return merge(sort(left), sort(right))

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 !

revs gbacon
fuente
8
  1. Una función que se llama a sí misma
  2. Cuando una función puede descomponerse (fácilmente) en una operación simple más la misma función en una porción más pequeña del problema. Debo decir, más bien, que esto lo convierte en un buen candidato para la recursividad.
  3. ¡Ellas hacen!

El ejemplo canónico es el factorial que se ve así:

int fact(int a) 
{
  if(a==1)
    return 1;

  return a*fact(a-1);
}

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).

Louis Brandy
fuente
6

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):

function cmdCheckAllClick {
    checkRecursively(TreeView1.RootNode);
}

function checkRecursively(Node n) {
    n.Checked = True;
    foreach ( n.Children as child ) {
        checkRecursively(child);
    }
}

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.

Blorgbeard está fuera
fuente
5

La recursión es una expresión que hace referencia directa o indirectamente a sí misma.

Considere las siglas recursivas como un ejemplo simple:

  • GNU significa GNU's Not Unix
  • PHP significa PHP: preprocesador de hipertexto
  • YAML significa YAML no es lenguaje de marcado
  • WINE significa vino no es un emulador
  • VISA son las siglas de Visa International Service Association

Más ejemplos en Wikipedia

Konstantin
fuente
4

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:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

Joey deVilla
fuente
4

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:

factorial(6) = 6*5*4*3*2*1

Pero es fácil ver factorial (6) también es:

6 * factorial(5) = 6*(5*4*3*2*1).

Entonces generalmente:

factorial(n) = n*factorial(n-1)

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:

factorial(6) = 6*factorial(5)
                   = 6*5*factorial(4)
                   = 6*5*4*factorial(3) = 6*5*4*3*factorial(2) = 6*5*4*3*2*factorial(1) = 6*5*4*3*2*1

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...

Gregory Brown
fuente
4

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.

No use la recursividad para factoriales o números de Fibonacci

Un problema con los libros de texto de informática es que presentan ejemplos tontos de recursión. Los ejemplos típicos son calcular un factorial o calcular una secuencia de Fibonacci. La recursión es una herramienta poderosa, y es realmente tonto usarla en cualquiera de esos casos. Si un programador que trabajara para mí usara la recursividad para calcular un factorial, contrataría a alguien más.

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

drelihan
fuente
66
La mayor parte de la razón por la que se usan ejemplos o secuencias de fibonacci como ejemplos es porque son elementos comunes que se definen de manera recursiva y, por lo tanto, se prestan naturalmente a ejemplos de recursividad para calcularlos, incluso si ese no es realmente el mejor método desde un punto de vista de CS.
Ámbar
Estoy de acuerdo - que acabo de encontrar cuando estaba leyendo el libro que era un punto interesante para elevar en medio de una sección sobre la recursividad
Robben_Ford_Fan_boy
4

1.) Un método es recursivo si puede llamarse a sí mismo; ya sea directamente:

void f() {
   ... f() ... 
}

o indirectamente:

void f() {
    ... g() ...
}

void g() {
   ... f() ...
}

2.) Cuándo usar la recursividad

Q: Does using recursion usually make your code faster? 
A: No.
Q: Does using recursion usually use less memory? 
A: No.
Q: Then why use recursion? 
A: It sometimes makes your code much simpler!

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.

Vinisha Vyasa
fuente
¿Qué pasa con la reducción de la complejidad al dividir y conquistar con respecto a los perfs?
mfrachet
4

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:

  1. Si el conjunto está vacío, el recuento de elementos en el conjunto es cero (¡duh!).
  2. Si el conjunto no está vacío, el recuento es uno más el número de elementos del conjunto después de que se elimine un elemento.

Supongamos que tiene un conjunto como este: [xxx]. Vamos a contar cuántos artículos hay.

  1. el conjunto es [xxx] que no está vacío, por lo que aplicamos la regla 2. el número de elementos es uno más el número de elementos en [xx] (es decir, eliminamos un elemento).
  2. el conjunto es [xx], por lo que aplicamos la regla 2 nuevamente: uno + número de elementos en [x].
  3. el conjunto es [x], que aún coincide con la regla 2: uno + número de elementos en [].
  4. Ahora el conjunto es [], que coincide con la regla 1: ¡el recuento es cero!
  5. Ahora que sabemos la respuesta en el paso 4 (0), podemos resolver el paso 3 (1 + 0)
  6. Del mismo modo, ahora que sabemos la respuesta en el paso 3 (1), podemos resolver el paso 2 (1 + 1)
  7. Y finalmente, ahora que sabemos la respuesta en el paso 2 (2), podemos resolver el paso 1 (1 + 2) y obtener el recuento de elementos en [xxx], que es 3. ¡Hurra!

Podemos representar esto como:

count of [x x x] = 1 + count of [x x]
                 = 1 + (1 + count of [x])
                 = 1 + (1 + (1 + count of []))
                 = 1 + (1 + (1 + 0)))
                 = 1 + (1 + (1))
                 = 1 + (2)
                 = 3

Al aplicar una solución recursiva, generalmente tiene al menos 2 reglas:

  • La base, el caso simple que establece lo que sucede cuando ha "agotado" todos sus datos. Esto suele ser alguna variación de "si no tiene datos para procesar, su respuesta es X"
  • La regla recursiva, que establece qué sucede si todavía tiene datos. Esto suele ser algún tipo de regla que dice "haz algo para que tu conjunto de datos sea más pequeño y vuelve a aplicar tus reglas al conjunto de datos más pequeño".

Si traducimos lo anterior a pseudocódigo, obtenemos:

numberOfItems(set)
    if set is empty
        return 0
    else
        remove 1 item from set
        return 1 + numberOfItems(set)

Hay muchos más ejemplos útiles (atravesando un árbol, por ejemplo) que estoy seguro de que otras personas cubrirán.

Mark Harrison
fuente
3

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.

Dave Markle
fuente
3

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)

Ralph
fuente
2

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.

Peter Burns
fuente
2

En inglés simple: suponga que puede hacer 3 cosas:

  1. Toma una manzana
  2. Escriba las marcas de conteo
  3. Cuenta las marcas de conteo

Tienes muchas manzanas frente a ti en una mesa y quieres saber cuántas manzanas hay.

start
  Is the table empty?
  yes: Count the tally marks and cheer like it's your birthday!
  no:  Take 1 apple and put it aside
       Write down a tally mark
       goto start

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!

Bastiaan Linders
fuente
1
Espera, tengo muchas marcas de conteo frente a mí en una mesa, y ahora quiero saber cuántas marcas de conteo hay. ¿De alguna manera puedo usar las manzanas para esto?
Christoffer Hammarström
Si toma una manzana del suelo (cuando la puso allí durante el proceso) y la coloca en la mesa cada vez que rasca una marca de la lista hasta que no queden marcas, estoy bastante seguro de que terminar con una cantidad de manzanas en la mesa igual a la cantidad de marcas de conteo que tenía. ¡Ahora solo cuenta esas manzanas para el éxito instantáneo! (nota: este proceso ya no es recurrencia, sino un bucle infinito)
Bastiaan Linders
2

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.

Mcdon
fuente
2

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.

fajrian
fuente
1

Desea usarlo cada vez que tenga una estructura de árbol. Es muy útil para leer XML.

Nick Berardi
fuente
1

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.

bennybdbc
fuente
1

"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?

  1. Dividir: Extiende todas las hojas, para que tengas solo una hoja en cada "pila".
  2. Conquistar:
    1. Dé la vuelta, colocando cada hoja encima de otra hoja. Ahora tienes pilas de 2.
    2. Dé la vuelta, colocando cada pila 2 encima de otra pila 2. Ahora tienes pilas de 4.
    3. Dé la vuelta, colocando cada 4 pilas encima de otras 4 pilas. Ahora tienes pilas de 8.
    4. ... incesantemente ...
    5. ¡Ahora tiene una gran pila de 1024 hojas!

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.

Andres Jaan Tack
fuente
66
Estás describiendo divide y vencerás. Si bien este es un ejemplo de recursión, de ninguna manera es el único.
Konrad Rudolph
Esta bien. No estoy tratando de capturar [el mundo de la recursión] [1] en una oración, aquí. Quiero una explicación intuitiva. [1]: facebook.com/pages/Recursion-Fairy/269711978049
Andres Jaan Tack
1

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.

Shivam
fuente
1

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.

Luka Ramishvili
fuente
1

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:

public int fact(int n)
{
    if (n==0) return 1;
    else return n*fact(n-1)
}
Indigo Praveen
fuente
1
En inglés simple, repetir algo una y otra vez se llama iteración.
toon81
1

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

  tree = null 
       | leaf(value:integer) 
       | node(left: tree, right:tree)

un algoritmo estructural recursivo tendría la forma

 function computeSomething(x : tree) =
   if x is null: base case
   if x is leaf: do something with x.value
   if x is node: do something with x.left,
                 do something with x.right,
                 combine the results

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

 integer = 0 | succ(integer)

ves que un algoritmo estructural recursivo en enteros se ve así

 function computeSomething(x : integer) =
   if x is 0 : base case
   if x is succ(prev) : do something with prev

La función factorial demasiado conocida es el ejemplo más trivial de esta forma.

mfx
fuente
1

La función se llama a sí misma o utiliza su propia definición.

Syed Tayyab Ali
fuente