Introducción recursividad

7
Introducción. Un procedimiento o función recursiva es aquella que se llama así misma. Esta característica permite a un procedimiento recursivo repetirse valores diferentes de parámetros. La recursion es una alternativa a la iteración muy elegante en la solución de problemas, especialmente si estos tienen naturaleza recursiva. Normalmente, una solución recursiva es menos eficiente en términos de tiempo de computadora que una solución iterativa debido al tiempo adicional de llamada a procedimientos. En muchos casos, la recursion permite especificar una solución mas simple y natural para resolver un problema que en otro caso seria difícil. Por esta razón la recursion (recursividad) es una herramienta muy potente para la resolución de problemas de programación. Un objeto recursivo es aquel que forma parte de si mismo. Esta idea puede servir de ayuda para la definición de conceptos matematicos. Asi, la definición del conjunto de los numeros naturles es aque conjunto en el que se cumplen las siguientes caracteristicas: · 0 es un numero natural. · El siguiente numero de un numero natural es otro numero natural. Mediante una definición finita hemos representado un conjunto infinito. El concepto de la recursividad es muy importante en programación. La recursividad es una herramienta muy eficaz para resolver diversos tipos de problemas existen muchos algoritmos que se describiran mejor en terminos recursivos. Dentro de la teoría de la recursión, se tiene que existen diferentes tipos de recursión: 4.3.1.- Recursión directa. Cuando el código F tiene una sentencia que involucra a F.

Transcript of Introducción recursividad

Page 1: Introducción recursividad

Introducción.

Un procedimiento o función recursiva es aquella que se llama así misma. Esta característica permite a un procedimiento recursivo repetirse valores diferentes de parámetros. La recursion es una alternativa a la iteración muy elegante en la solución de problemas, especialmente si estos tienen naturaleza recursiva.

Normalmente, una solución recursiva es menos eficiente en términos de tiempo de computadora que una solución iterativa debido al tiempo adicional de llamada a procedimientos.

En muchos casos, la recursion permite especificar una solución mas simple y natural para resolver un problema que en otro caso seria difícil. Por esta razón la recursion (recursividad) es una herramienta muy potente para la resolución de problemas de programación.

Un objeto recursivo es aquel que forma parte de si mismo. Esta idea puede servir de ayuda para la definición de conceptos matematicos. Asi, la definición del conjunto de los numeros naturles es aque conjunto en el que se cumplen las siguientes caracteristicas:

· 0 es un numero natural.

· El siguiente numero de un numero natural es otro numero natural.

Mediante una definición finita hemos representado un conjunto infinito.

El concepto de la recursividad es muy importante en programación. La recursividad es una herramienta muy eficaz para resolver diversos tipos de problemas existen muchos algoritmos que se describiran mejor en terminos recursivos.

Dentro de la teoría de la recursión, se tiene que existen diferentes tipos de recursión:

4.3.1.- Recursión directa. Cuando el código F tiene una sentencia que involucra a F.

4.3.2.- Recursión indirecta o cruzada.- Cuando la función F involucra una función G que invoca a la ves una función H, y así sucesivamente, hasta que se involucra la función F. Por ejemplo el algoritmo de Par o impar.

int par (int n)

{ if (n==0) return n+1;

return impar(n-1);

int impar(int n)

if (n==0) return 0;

return par(n-1);}

Page 2: Introducción recursividad

A continuación se expone otro ejemplo de programa que utiliza recursión indirecta, y nos dice si un número es par o impar. Cabe mencionar que existen otros métodos mucho más sencillos para determinar la solución a este problema, basta con determinar el resto de la división entre dos. Por ejemplo: si hacemos par(2) devuelve 1 (cierto). Si hacemos impar(4) devuelve 0 (falso).

int par(int n);int impar(int n);

int par(int n){ if (n == 0) return 1;

return impar(n-1);}int impar(int n){ if (n == 0) return 0;

return par(n-1);}

4.3.1.3.- Recursión simple.- Aquella en cuya función solo aparece una llamada recursiva. Se puede transformar con facilidad en algoritmos iteractivos.

4.3.1.4.- Recursión múltiple.- Se da cuando hay más de una llamada a sí misma dentro del cuerpo de la función, resultando más difícil de transformar a iteractiva. Poe ejemplo el algoritmo de Fibonacci.

int fibo(int n){ if (n<=1) return 1

return fibo(n-1) + fibo(n-2)}

4.3.1.5.- Recursión anidada.- En algunos de los argumentos de la llamada hay una nueva llamada a sí misma. Por ejemplo la función de Ackerman:

int Ack(int m, int n){ if (m==0) return n+1

if (n=00) return Ack(n-1, 1)

return Ack(m-1, Ack(m, n-1));}

Un requisito para que un algoritmo recursivo sea correcto es que no genere una secuencia infinita de llamadas sobre sí mismo. Cualquier algoritmo que genere una secuencia de este tipo no terminará nunca. En consecuencia, la definición recursiva debe incluir un componente base (condición de salida ) en el que f(n) se define directamente (es decir, no recursivamente) para uno o más valores de n. Debe existir una <> de la secuencia de llamadas recursivas.

Al estar trabajando con recursividad, la memoria de la computadora hace uso de una pila de recursión, la cual se divide de manera lógica en 4 segmentos:

Page 3: Introducción recursividad

1.- Segmento de código.- Parte de la memoria donde se guardan las instrucciones del programa en código máquina.

2.- Segmento de datos.- Parte de la memoria destinada a almacenar las variables estáticas.

3.- Montículo.- Parte de la memoria destinada a las variables dinámicas.

4.- Pila de programa.- parte destinada a las variables locales y parámetros de la función que está siendo ejecutada.

4.3.2.- Funciones mutuamente recursivas

Cuando se trabaja llamados a la ejecución de las funciones, se provocan las siguientes operaciones:

1.- Reservar espacio en la pila para los parámetros de la función y sus variables locales.

2.- Guardar en la pila la dirección de la línea de código desde donde se ha llamado a la función.

3.- Almacenar los parámetros de la función y sus valores de pila.

4.- Al terminar la función, se libera la memoria asignada en la pila y se vuelve a la instrucción actual.

Pero cuando se trabaja con funciones recursivas, se provoca además lo siguiente:

1.- La función se ejecutará normalmente hasta la llamada a sí misma. En ese momento se crean en la pila nuevos parámetros y variables locales.

2.- El nuevo ejemplar de función comienza a ejecutarse.

3.- Se crean más copias hasta llegar a la primera llamada (ultima en cerrarse).

La ciencia de la computación hace tiempo que descubrió que se puede reemplazar a un método que utiliza un bucle para realizar un cálculo con un método que se llame repetidamente a sí mismo para realizar el mismo cálculo. El hecho de que un método se llame repetidamente a sí mismo se conoce como recursion.

La recursión trabaja dividiendo un problema en subproblemas. Un programa llama a un método con uno o más parámetros que describen un problema. Si el método detecta que los valores no representan la forma más simple del problema, se llama a sí mismo con valores de parámetros modificados que describen un subproblema cercano a esa forma.

Esta actividad continúa hasta que el método detecta la forma más simple del problema, en cuyo caso el método simplemente retorna, posiblemente con un valor, si el tipo de retorno del método no es void. La pila de llamadas a método empieza a desbobinarse como una llamada a método anidada para ayudar a completar una evaluación de

Page 4: Introducción recursividad

expresión. En algún punto, la llamada el método original se completa, y posiblemente se devuelve un valor.

4.3.3.- Recursión infinita

La iteración y la recursión pueden producirse infinitamente. Un bucle infinito ocurre si la prueba o test de continuación del bucle nunca se vuelve falsa.

Una recursión infinita ocurre si la etapa de recursión no reduce el problema en cada ocasión de modo que converja sobre el caso base o condición de la salida.

En realidad, larecursión infinita significa que cada llamada recursiva produce otra llamada recursiva y esta a su vez otra llamada recursiva, y así para siempre. En la práctica, dicha función se ejecutará hasta que la computadora agote la memoria disponible y se produzca una terminación anormal del programa.

El flujo de control de una función recursiva requiere de tres condiciones para una terminación normal.

CUANDO NO UTILIZAR RECURSIVIDAD.

La solucion recursiva de ciertos problemas simplifica mucho la estructura de los programas. Como contrapartida, en la mayoria de los lenguajes de programación las llamadas recursivas a procedimientos o funciones tienen un coste de tiempo mucho mayor que sus homologos iterativos. Se puede, por lo tanto, afrimar que la ejecución de un programa recursivo va a ser mas lenta y menos eficiente que el programa iterativo que soluciona el mismo problema, aunque, a veces, la sencilles de la estructura recursiva justifica el mayor tiempo de ejecucion.

Los procedimientos recursivos se pueden convertir en no recursivos mediante la introducción de una pila y asi emular las llamadas recursivas. De esta manera se puede eliminar la recursion de aquellas partes de los programas que se ejecutan mas frecuentemente.

Ejemplo de mecánica de la recursividad

Las torres de Hanoi

Algoritmo para trasladar la torre 1…n del poste X al poste Z, usando como auxiliar el poste Y.

Si n = 1, lleva el disco 1 de X a Z y termina.

Traslada la torre 1…n−1 usando este mismo algoritmo, de X a Y, usando como auxiliar Z.

Lleva el disco n de X a Z.

Traslada la torre 1…n−1 usando este mismo algoritmo, de Y a Z, usando como auxiliar X. Este algoritmo siempre me ha parecido sorprendente, parece más la definición de un

Page 5: Introducción recursividad

problema que su resolución. Si eres como yo tendrás que implementarlo en un lenguaje de programación concreto para convencerte de que funciona. En realidad, lo único que hace es especificar unos pasos inevitables. Por ejemplo, como vimos antes, para resolver el puzzle es obligatorio llevar el disco n de A a C, y para ello es obligatorio llevar antes la torre 1…n a B, etc. La secuencia de movimientos que produce este algoritmo es la única solución óptima (o sea, de longitud mínima) posible. El número de movimientos es M3(n) = 2n−1, como se puede demostrar fácilmente por inducción sobre el número de discos.

Se cumple para n = 1

M3(1) = 1 = 21−1.

Si se cumple para n, se cumple para d+1

Al ejecutarse el algoritmo para n+1 se llama a sí mismo dos veces para n, más un movimiento del disco n+1. Así que M3(n+1) = 2 × M3(n) + 1 = 2 × (2n−1) + 1 = 2n+1−2+1 = 2n+1−1.

Para n = 8 el número de movimientos es de 28−1 = 255. Para n = 64, de 264−1 = 18.446.744.073.709.551.615. Si los bramanes de Benarés cambiaran un disco de sitio cada segundo necesitarían más de quinientos ochenta mil millones de años para terminar su tarea, unas cuarenta veces la edad del Universo.

Los algoritmos recursivos funcionan bien con ordenadores, pero son difíciles de aplicar para un ser humano. Si intentas resolver la torre de ocho discos aplicando el método descrito es fácil que te pierdas a no ser que vayas tomando notas de por dónde vas. Incluso para los ordenadores los algoritmos recursivos suelen ser «poco económicos», en el sentido de que consumen bastante memoria (y es que ellos también necesitan «tomar notas»). La alternativa a los algoritmos recursivos son los iterativos, en los que no hay llamadas a sí mismo, sino uno o varios bucles. Muy a menudo no existe o no se conoce una alternativa iterativa para un algoritmo recursivo, y cuando sí se conoce, suele ser mucho más complicada que la versión recursiva. Sin embargo, en el caso de la Torre de Hanoi, existen varios algoritmos iterativos muy simples.