Algorítmica y Lenguajes de Programación Recursividad Recursividad. ! ! ! ! ! Introducción La recursividad es una característica de los lenguajes de programación que permite que un subprograma se invoque a sí mismo. La recursividad es útil para resolver problemas definibles en sus propios términos. La recursividad es, en cierta medida, análoga al principio de inducción. No existen problemas intrínsecamente recursivos o iterativos; cualquier proceso iterativo puede expresarse de forma recursiva y viceversa. La recursividad aunque da lugar a algoritmos más simples y compactos resulta más lenta y consume más recursos al ejecutarse sobre el ordenador. 2 1 Recursividad. ! ! Para desarrollar algoritmos recursivos hay que partir del supuesto de que ya existe un algoritmo que resuelve una versión más sencilla del problema. A partir de esta suposición debe hacerse lo siguiente: 1. 2. 3. Identificar subproblemas atómicos de resolución inmediata (casos base). Descomponer el problema en subproblemas resolubles mediante el algoritmo pre-existente; la solución de estos subproblemas debe aproximarnos a los casos base. Probar de manera informal que tanto los casos base como los generales pueden solucionarse con el algoritmo desarrollado. 3 Recursividad. ! Algoritmos recursivos (i) Algoritmos recursivos (ii) Factorial de un número, n! ! ! ! ! Algoritmo pre-existente: (n-1)! Caso base: 0! = 1 Subproblemas resolubles mediante el algoritmo pre-existente y que aproximen al caso base: n! = n · (n-1)! Prueba informal: Conociendo las dos expresiones anteriores es posible calcular 0!, 1!, 4!, etc. 4 2 Recursividad. ! Algoritmos recursivos (iii) Potencia de un número, an ! ! ! ! Algoritmo pre-existente: an-1 Caso base: a0 = 1 Subproblemas resolubles mediante el algoritmo pre-existente y que aproximen al caso base: an = a · an-1 Prueba informal: Conociendo las dos expresiones anteriores es posible calcular 20, 43, 34, etc. 5 Recursividad. ! ! ! Sintaxis notación algorítmica (i) La sintaxis para el desarrollo de funciones y subrutinas en la notación algorítmica es totalmente transparente. Basta con hacer la llamada correspondiente dentro del cuerpo del subprograma. Tan sólo hay que tener la precaución de no iniciar una llamada recursiva para los casos base. 6 3 Recursividad. ! Sintaxis notación algorítmica (ii) Ejemplos: entero funcion factorial (n ∈ entero) inicio si n=0 entonces factorial " 1 si no factorial " n · factorial(n-1) fin si fin ! entero funcion potencia (a,n ∈ entero) inicio si n=0 entonces potencia " 1 si no potencia " a · potencia(a, n-1) fin si fin Estas funciones no tienen validación de datos de entrada. 7 Recursividad. ! ! Sintaxis FORTRAN 90 (i) FORTRAN 77 no soporta recursividad. FORTRAN 90 lo hace pero de una forma bastante artificiosa: ! ! Hay que utilizar la palabra reservada RECURSIVE para indicar que el subprograma es recursivo. Las funciones precisan, además, utilizar una variable de retorno (indicada mediante la palabra reservada RESULT) puesto que FORTRAN 90 no admite la aparición del nombre de una función simultáneamente en la parte derecha e izquierda de una asignación. 8 4 Recursividad. ! Sintaxis FORTRAN 90 (ii) Ejemplos: integer recursive function factorial(n) result(f) integer n if (n==0) then f = 1 else f = n * factorial(n-1) end if end function integer recursive function potencia(a,n) result (p) integer a,n if (n==0) then p = 1 else p = a * potencia(a,n-1) end if end function recursive subroutine asteriscos (n) Funciones integer n if (n==1) then print *, '*' else Subrutina print *, '*' call asteriscos(n-1) end if end subroutine Recursividad. ! ! ! ! ! Invocación recursiva (i) Al ejecutarse un programa existe una zona de memoria, pila, donde se almacena información por cada subprograma activo. Esta información consiste en: ! ! 9 Los argumentos de la función o subrutina. Las variables locales del subprograma. La “dirección” de retorno, es decir el punto del programa que debe pasar a ejecutarse cuando el subprograma retorne. Cuando un subprograma es invocado toda esa información es apilada. Al finalizar la ejecución del subprograma es retirada y en el caso de una función sustituida por el valor de retorno. 10 5 Recursividad. Invocación recursiva (ii) Ejemplo (i) 01 program prueba 02 implicit none 03 04 integer valor 05 valor = factorial (3) 06 print *, valor 07 08 09 contains 10 11 integer recursive function factorial (n) result (f) 12 implicit none 13 integer n 14 15 if (n==0) then 16 f = 1 17 else 18 f = n*factorial(n-1) 19 end if 20 end function 21 end Recursividad. 11 Invocación recursiva (iii) Ejemplo (ii) Argumentos: Variables locales: valor (¿?) Dirección retorno: Sistema n (3) Argumentos: Variables locales: f (¿?) Dirección retorno: programa (06) Argumentos: Variables locales: valor (¿?) Dirección retorno: Sistema Al comenzar a ejecutarse el programa la única variable del mismo, valor, carece de un valor determinado. En la línea 05 se invoca la función factorial, para lo cual se apilan sus argumentos, 3, y la dirección de retorno. Al ser una función al retornar debe desapilarse esta información y colocarse en la pila el valor de retorno, correspondiente a la línea 06 del programa principal. n (2) Argumentos: Variables locales: f (¿?) Dirección retorno: factorial (19) n (3) Argumentos: Variables locales: f (¿?) Dirección retorno: programa (06) Argumentos: Variables locales: valor (¿?) Dirección retorno: Sistema La función factorial invocada debe, a su vez, invocarse a sí misma con lo cual debe apilar los argumentos de esa nueva llamada, 2, y la dirección de retorno que se corresponderá con la línea 19 asociada a la primera llamada de la función. 12 6 Recursividad. n (1) Argumentos: Variables locales: f (¿?) Dirección retorno: factorial (19) Invocación recursiva (iv) Ejemplo (iii) n (2) Argumentos: Variables locales: f (¿?) Dirección retorno: factorial (19) n (3) Argumentos: Variables locales: f (¿?) Dirección retorno: programa (06) Argumentos: Variables locales: valor (¿?) Dirección retorno: Sistema La segunda función factorial invocada debe invocar también otra copia de sí misma, apilando los argumentos, 1, y la nueva dirección de retorno. n (0) Argumentos: Variables locales: f (¿?) Dirección retorno: factorial (19) n (1) Argumentos: Variables locales: f (¿?) Dirección retorno: factorial (19) n (2) Argumentos: Variables locales: f (¿?) Dirección retorno: factorial (19) n (3) Argumentos: Variables locales: f (¿?) Dirección retorno: programa (06) Argumentos: Variables locales: valor (¿?) Dirección retorno: Sistema La función factorial es invocada, otra vez, y se apilan los datos necesarios, otra vez. 13 Recursividad. Invocación recursiva (v) Ejemplo (iv) 1 n (1) Argumentos: Variables locales: f (¿?) Dirección retorno: factorial (19) n (2) Argumentos: Variables locales: f (¿?) Dirección retorno: factorial (19) Argumentos: n (3) Variables locales: f (¿?) Dirección retorno: programa (06) Como la última invocación de factorial recibe como argumento el 0, caso base, detiene la serie de invocaciones recursivas y retorna el valor 1 desapilando los datos sobre estado y apilando el valor de retorno. Argumentos: Variables locales: valor (¿?) Dirección retorno: Sistema Este valor de retorno es recibido por la penúltima invocación de factorial, pudiendo finalizar y retornar, es decir, desapilar su información de estado y apilando su valor de retorno, 1. 1 Argumentos: n (2) Variables locales: f (¿?) Dirección retorno: factorial (19) n (3) Argumentos: Variables locales: f (¿?) Dirección retorno: programa (06) Argumentos: Variables locales: valor (¿?) Dirección retorno: Sistema Dicho valor de retorno es recibido por la segunda llamada a factorial que puede también finalizar, desapilar su información de estado y apilar su valor de retorno, 2. 14 7 Recursividad. Invocación recursiva (vi) Ejemplo (v) 2 n (3) Argumentos: Variables locales: f (¿?) Dirección retorno: programa (06) Argumentos: Variables locales: valor (¿?) Dirección retorno: Sistema Este valor de retorno es extraído de la pila por la primera invocación de factorial que finaliza, desapila su información y coloca en la pila el valor de retorno final de la serie de llamadas recursivas, 6. Argumentos: Variables locales: valor (¿?) Dirección retorno: Sistema Este valor de retorno es desapilado por el programa principal y asignado a la variable valor. 6 Argumentos: Variables locales: valor (6) Dirección retorno: Sistema ! ! El programa ha finalizado y puede retornar al sistema operativo. Como se puede ver, una serie de llamadas recursivas supone un gran número de operaciones con la pila de ejecución lo cual resulta más lento que una serie de operaciones iterativas y, además, requiere un espacio considerable. Por eso se dice que la recursividad es más lenta y consume más recursos que una solución iterativa. 15 Recursividad. ! ! ! ! ! Un subprograma es recursivo cuando se invoca a sí mismo. La recursividad es análoga al principio de inducción. Todo proceso recursivo puede convertirse en iterativo y viceversa. Los algoritmos recursivos son más simples y compactos que sus correspondientes iterativos, sin embargo, su ejecución en un ordenador es más lenta y requiere más recursos. Para diseñar un algoritmo recursivo hay que tener en cuenta lo siguiente: ! ! ! ! ! ! Resumen Suponer que ya existe un algoritmo para resolver una versión reducida del problema. Identificar casos base. Descomponer el problema en subproblemas que se puedan resolver con el algoritmo pre-existente y cuya solución vaya aproximándose a los casos base. Probar, de manera informal, que los casos base y los generales encuentran solución con el algoritmo diseñado. La sintaxis de funciones y subrutinas recursivas en la notación algorítmica es inmediata. La sintaxis en FORTRAN 90 resulta un tanto artificiosa. 16 8