Presentación Recursividad

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 21

Estructuras de Datos

3. Recursividad
3.1. Definición de Recursividad
Recursión es, en ciencias de computación, una forma de atajar y solventar problemas. De hecho,
recursión es una de las ideas centrales de ciencia de computación. Resolver un problema mediante
recursión significa que la solución depende de las soluciones de pequeñas instancias del mismo
problema.

El poder de la recursión evidentemente se fundamenta en la posibilidad de definir un conjunto


infinito de objetos con una declaración finita. Igualmente, un número infinito de operaciones
computacionales puede describirse con un programa recursivo finito, incluso en el caso de que este
programa no contenga repeticiones explícitas."

La mayoría de los lenguajes de programación dan soporte a la recursión permitiendo a una función
llamarse a sí misma desde el texto del programa. Los lenguajes imperativos definen las estructuras
de loops como while y for que son usadas para realizar tareas repetitivas. Algunos lenguajes de
programación funcionales no definen estructuras de loops sino que posibilitan la recursión
llamando código de forma repetitiva. La teoría de la computabilidad ha demostrado que estos dos
tipos de lenguajes son matemáticamente equivalentes, es decir que pueden resolver los mismos
tipos de problemas, aunque los lenguajes funcionales carezcan de las típicas estructuras while y for.
Un algoritmo recursivo es un algoritmo que expresa la solución de un problema en términos de una
llamada a sí mismo. La llamada a sí mismo se conoce como llamada recursiva o recurrente.

Generalmente, si la primera llamada al subprograma se plantea sobre un problema de tamaño u


orden N, cada nueva ejecución recurrente del mismo se planteará sobre problemas, de igual
naturaleza que el original, pero de un tamaño menor que N. De esta forma, al ir reduciendo
progresivamente la complejidad del problema que resolver, llegará un momento en que su
resolución sea más o menos trivial (o, al menos, suficientemente manejable como para resolverlo de
forma no recursiva). En esa situación diremos que estamos ante un caso base de la recursividad.

Las claves para construir un subprograma recurrente son:

 Cada llamada recurrente se debería definir sobre un problema de menor complejidad (algo más
fácil de resolver).
 Ha de existir al menos un caso base para evitar que la recurrencia sea infinita.

Es frecuente que los algoritmos recurrentes sean más ineficientes en tiempo que los iterativos
aunque suelen ser mucho más breves en espacio.

Un método frecuente para simplificar es dividir un problema en problemas derivados de menor


tamaño del mismo tipo. Esto se conoce como dialecting. Como técnica de programación se
denomina divide y vencerás y es pieza fundamental para el diseño de muchos algoritmos de
importancia, así como parte esencial de la programación dinámica.
4
Virtualmente todos los lenguajes de programación modernos permiten la especificación directa de funciones
y subrutinas recursivas. Cuando se llama una función de este tipo, el ordenador, para la mayoría de los lenguajes en
casi todas las arquitecturas basadas en una pila (stack) o en la implementación del lenguaje, lleva la cuenta de las
distintas instancias de la función, en numerosas arquitecturas mediante el uso de un call stack, aunque no de forma
exclusiva. A la inversa, toda función recursiva puede transformarse en una función iterativa usando un stack.

La mayoría (aunque no todas) de las funciones y subrutinas que pueden ser evaluadas por un ordenador, pueden
expresarse en términos de una función recursiva (sin tener que utilizar una iteración pura); a la inversa, cualquier
función recursiva puede expresarse en términos de una iteración pura, dado que la recursión es, de por sí, también
iterativa. Para evaluar una función por medio de la recursión, tiene que definirse como una función de si misma (ej.
el factor n! = n * (n - 1)! , donde 0! se define como 1). Resulta evidente que no todas las evaluaciones de funciones
se prestan a un acercamiento recursivo. Por lo general, todas las funciones finitas pueden describirse directamente
de forma recursiva; las funciones infinitas (ej. las series de e = 1/1! + 2/2! + 3/3!...) necesitan un criterio extra para
detenerse, ej. el número de iteraciones, o el número de dígitos significativos, en caso contrario una iteración
recursiva resultaría en un bucle infinito.

Algunos lenguajes diseñados para programación lógica y programación funcional ofrecen la recursión como el
único medio de repetición directa disponible para el programador. Estos lenguajes suelen conseguir que
la recursión de cola sea tan eficiente como la iteración, permitiendo a los programadores expresar otras
estructuras repetitivas (tales como map y for de scheme) en términos de recursión.

La recursión está profundamente anclada en la teoría de computación, con la equivalencia teórica de función
microrecursiva y máquinas de Turing en la cimentación de ideas sobre la universalidad del ordenador moderno.
5
Recursividad directa vs indirecta.

Cuando en una subrutina hay llamadas a ella misma se habla de recursividad directa,
en contraposición, cuando se tienen varias subrutinas y estas se llaman unas a otras
formando ciclos se dice que la recursión es indirecta.

Subrutina_A → Subrutina_A → Subrutina_A

Subrutina_A → Subrutina_B → Subrutina_C → Subrutina_D → Subrutina_A

6
3.2. Encontrar caso Base
Crear una subrutina recursiva requiere principalmente la definición de un "caso base", y
entonces definir reglas para subdividir casos más complejos en el caso base. Para una
subrutina recursiva es esencial que con cada llamada recursiva, el problema se reduzca
de forma que al final llegue al caso base.

Algunos expertos clasifican la recursión como "generativa" o bien "estructural". La


distinción se hace según de donde provengan los datos con los que trabaja la subrutina.
Si los datos proceden de una estructura de datos similar a una lista, entonces la
subrutina es "estructuralmente recursiva"; en caso contrario, es "generativamente
recursiva".

Muchos algoritmos populares generan una nueva cantidad de datos a partir de los datos
aportados y recurren a partir de ahí. HTDP (How To Design Programs), al español, "Cómo
diseñar programas", se refiere a esta variante como recursión generativa. Ejemplos de
recursión generativa incluyen: máximo común divisor, quicksort, búsqueda
binaria, mergesort, Método de Newton, fractals e integración adaptiva.
Diseño de algoritmos recursivos

1. Resolución de problema para los casos base:

 Sin emplear recursividad.


 Siempre debe existir algún caso base.

2. Solución para el caso general.

 Expresión de forma recursiva.


 Pueden incluir pasos adicionales (Para combinar las soluciones parciales).

Siempre se debe avanzar a un caso base: Las llamadas recursivas simplifican el problema y, en
ultima instancia, los casos base nos sirven para obtener la solución.

 Los casos base corresponden a situaciones que se pueden resolver con facilidad.

 Los demás casos se resuelven recurriendo, antes o después, a alguno(s) de los casos base.

 De esta forma, podemos resolver problemas complejos que serian muy difíciles de resolver
directamente.
8
3.3. Gráficamente como funciona la
Recursividad
Un fractal natural es un elemento de la naturaleza que puede ser descrito mediante la geometría fractal. Las
nubes, las montañas, el sistema circulatorio, las líneas costeras o los copos de nieve son fractales naturales. Las
propiedades atribuidas a los objetos fractales ideales, como el detalle infinito, tienen límites en el mundo natural.

Pues bien, a partir de ahí puede concluirse que los árboles, por ejemplo, son fractales. Cumplen con las
características antes mencionadas. Como los fractales pueden programarse a través de un algoritmo recursivo
(aunque esto no es estrictamente necesario), hay muchos programas que simulan estos objetos matemáticos,
que se encuentran de alguna manera en el mundo real.
Definición por algoritmos recursivos

Podemos destacar tres técnicas comunes para generar fractales:

Sistemas de funciones iteradas (IFS)


Unos conjuntos se reemplazan recursivamente por su imagen bajo un sistema de
aplicaciones: el conjunto de Cantor, la alfombra de Sierpinski, el triángulo de Sierpinski,
la curva de Peano, la curva del dragón, el copo de nieve de Koch o la Esponja de
Menger, son algunos ejemplos.

Fractales de algoritmos de Escape


Definidos por una relación de recurrencia en cada punto del espacio (por ejemplo, el
plano complejo): el conjunto de Mandelbrot, conjunto de Julia, y el fractal de Lyapunov.

Fractales aleatorios
Generados por procesos estocásticos, no deterministas: el movimiento browniano,el
vuelo de Lévy, los paisajes fractales o los árboles brownianos. Estos últimos son
producidos por procesos de agregación por difusión limitada..

10
3.4. La función factorial como
operación recursiva

Factorial
Un ejemplo clásico de una subrutina recursiva es la función usada para calcular
el factorial de un entero.

Definición de la función:
Una relación recurrente es una ecuación que relaciona términos posteriores en la
secuencia con términos previos.

Relación recurrente de un factorial:

12
Esta función factorial también puede describirse sin usar recursión haciendo uso de
típicas estructuras de bucle que se encuentran en lenguajes de programación
imperativos:

13
El código en C++ que representa la función Factorial es el siguiente:

#include <iostream>
#include <cstdlib>
using namespace std;
int Factorial(int n);
int main(){
int valor;
system("clear");
cout << "Introduzca numero a calcular: ";
cin >> valor;
cout << "\nEl Factorial de " << valor << " es: " << Factorial(valor) << endl;
return 0;
}
int Factorial(int n){
if (n < 0){
cout << “No existe el factorial de un numero negativo.\n”;
}else if(n < 2){
return 1;
}else
return n * Factorial(n-1);
}
}
14
Generalmente, si la primera llamada al subprograma se plantea sobre un problema de
tamaño u orden N, cada nueva ejecución recurrente del mismo se planteara sobre
problemas, de igual naturaleza que el original, pero de un tamaño menor que N. De
esta forma, al ir reduciendo progresivamente la complejidad del problema a resolver,
llegara un momento en que su resolución sea mas o menos trivial (o, al menos,
suficientemente manejable como para resolverlo de forma no recursiva). En esa
situación diremos que estamos ante un caso base de la recursividad.

Es frecuente que los algoritmos recurrentes sean mas ineficientes en tiempo que los
iterativos aunque suelen ser mucho mas breves en espacio.

Es mucho mas difícil desarrollar una solución recursiva en un lenguaje determinado


para resolver un problema especifico cuando no se tiene un algoritmo. No es solo el
programa sino las definiciones originales y los algoritmos los que deben desarrollarse.
En general, cuando encaramos la tarea de escribir un programa para resolver un
problema no hay razón para buscar una solución recursiva. La mayoría de los
problemas pueden resolverse de una manera directa usando métodos no recursivos.
Sin embargo, otros pueden resolverse de una manera mas lógica y elegante mediante
la recursión.
15
Volviendo a examinar la función factorial. El factor es, probablemente, un ejemplo
fundamental de un problema que no debe resolverse de manera recursiva, dado que su
solución iterativa es directa y simple. Sin embargo, examinaremos los elementos que
permiten dar una solución recursiva. Antes que nada, puede reconocerse un gran
numero de casos distintos que se deben resolver. Es decir, quiere escribirse un
programa para calcular 0!, 1!, 2! Y así sucesivamente.

Puede identificarse un caso "trivial" para el cual la solución no recursiva pueda


obtenerse en forma directa. Es el caso de 0!, que se define como 1. El siguiente paso es
encontrar un método para resolver un caso "complejo" en términos de uno mas
"simple", lo cual permite la reducción de un problema complejo a uno mas simple. La
transformación del caso complejo al simple resultaría al final en el caso trivial. Esto
significaría que el caso complejo se define, en lo fundamental, en términos del mas
simple.

16
3.5. Representación recursiva de los
números de Fibonacci

Otra popular secuencia recursiva es el Número de Fibonacci. Los primeros elementos


de la secuencia son: 0, 1, 1, 2, 3, 5, 8, 13, 21...

Definición de la función:
18
Relación recurrente para Fibonacci:
bn = bn-1 + bn-2
b1 = 1, b0 = 0

Este algoritmo de Fibonacci es especialmente malo pues cada vez que se ejecuta la
función, realizará dos llamadas a la función a si misma, cada una de las cuales hará a la
vez dos llamadas más y así sucesivamente hasta que terminen en 0 o en 1. El ejemplo
se denomina "recursión de árbol", y sus requisitos de tiempo crecen de forma
exponencial y los de espacio de forma lineal.
19
El código en C++ que representa la función Fibonacci es el siguiente:
#include <iostream>
#include <cstdlib>
using namespace std;
int Fibonacci(int n);

int main(){
int valor;
system("clear");
cout << "Introduzca numero a calcular: ";
cin >> valor;
cout << "\nEl Fibonacci de " << valor << " es: " << Fibonacci(valor) << endl;
return 0;
}
int Fibonacci(int n){
if (n < 0){
cout << “No existe Fibonacci para números negativos.”;
} else if (n == 0) {
return 0;
} else if (n == 1) {
return 1;
}else
return Fibonacci(n-2) + Fibonacci(n -1);
}
20
Bibliografía

 “Programación en C++”, del autor Luis Joyanes Aguilar, editorial Mc Graw Hill.

 https://es.wikipedia.org/wiki/Recursión_(ciencias_de_computación).

 https://es.wikipedia.org/wiki/Recursión.

 https://www.uaeh.edu.mx/docencia/P_Presentaciones/icbi/asignatura/Cap4Recursi
on.pdf.

 http://elvex.ugr.es/decsai/java/pdf/7b-recursividad.pdf.

También podría gustarte