Recursividad Programación II trata del diseño y análisis formal de algoritmos abordándolo desde el punto de vista recursivo y el iterativo. La formación básica en programación (asignatura PROGRAMACIÓN I, por ejemplo) proporciona conceptos básicos de programación imperativa: secuencia de órdenes, sentencias condicionales, bucles. Usando el típico ejemplo del factorial, se propone como ejercicio el desarrollo de un procedimiento o función que calcule el factorial de un número no negativo (el factorial !n de un número n no negativo es igual a producto de todos los números no negativos iguales o inferiores que n teniendo en cuenta que el factorial de 0 es 1 por definición). Intuitivamente alguien con experiencia en MODULA-2 programaría la siguiente función para cálculo de factorial:
PROCEDURE Factorial(n : CARDINAL) : CARDINAL
BEGIN
VAR Resultado,i : CARDINAL ;
Resultado :=1 ;
FOR i :=1 TO n DO
Resultado :=Resultado*i ;
END ;
RETURN Resultado
END Factorial ;
Existe una vía alternativa para abordar el diseño de algoritmos: la recursiva. Sin embargo la mayoría de las personas que abordan la asignatura PROGRAMACION II por primera vez no han asimilado el concepto de recursividad. El objeto de este apartado es la introducción de ese concepto, dada su importancia para el dominio de la asignatura. Una aproximación diferente a la programación de una función que calcule el factorial de un número estaría basada en el siguiente razonamiento:
Siguiendo este razonamiento se puede llegar a programar el siguiente algoritmo en MODULA-2: PROCEDURE Factorial(n: CARDINAL): CARDINAL; BEGIN IF n=0 THEN RETURN 1 ELSE END END Factorial; La primera sorpresa reside en el hecho de que en la línea remarcada se ejecute una llamada a la propia función Factorial. ¿ Es esto posible ?. Conceptualmente si. En la práctica muchos lenguajes de alto nivel permiten la recursividad: Pascal, C, MODULA-2, etc. En el algoritmo iterativo la solución parece clara: se utiliza una variable en la que se va acumulando el producto de los n números. ¿ Cómo funciona el procedimiento recursivo?. Supongamos que se desea calcular el factorial del número 4. En el programa principal se ejecutará un sentencia análoga a: Resultado:= Factorial(4); En esta llamada, y puesto que 4 es distinto de 0, se ejecutará una segunda llamada a Factorial con parámetro 4-1=3 En la segunda llamada se efectuará una tercera llamada a Factorial con parámetro 3-1=2. En la tercera llamada se efectuará una cuarta llamada a Factorial con parámetro 2-1=1 En la cuarta llamada se efectuará una quinta llamada a Factorial con parámetro 1-1=0 En esta llamada se cumple la condición n=0, por lo tanto se retornará a la cuarta llamada el resultado 1. La cuarta llamada devolverá a la tercera el resultado 1*1=1 La tercera llamada devolverá a la segunda el resultado 2*1=2 La segunda llamada devolverá a la primera el resultado 3*2=6 La primera llamada devolverá al programa principal el resultado final 4*6=24 Cada llamada supone una nueva ejecución del procedimiento Factorial, en cada llamada se ha de mantener un contexto de ejecución diferente (parámetros, variables locales, punto de retorno), los programas compilados a partir de lenguajes de alto nivel que soportan la recursividad gestionan este proceso de forma transparente al programador. Esquemáticamente la ejecución de Factorial(5) se resuelve con el siguiente proceso:
El diseño y análisis recursivo de algoritmos es una técnica muy potente una vez dominada. Muchos algoritmos tienen una solución recursiva natural. En muchos casos la programación recursiva permite resolver problemas de forma elegante, con menos código y más fácil de entender. Sin embargo la necesidad de gestionar la pila en la que se suelen mantener los contextos de ejecución independientes hace que la solución recursiva resulte algo menos eficiente, en general, en cuanto a tiempo de ejecución que la iterativa. Siempre es posible obtener la solución iterativa equivalente a una recursiva dada, aunque en muchos casos el programa resultará menos claro y compacto. Especialmente cuando el algoritmo tiene una solución recursiva natural. Se presenta un ejemplo industrial en la NOTA. Para que una solución recursiva sea correcta se han de cumplir una serie de reglas. Estas reglas son materia importante en el estudio del Capítulo 4 de Peña. Se introducirán los criterios fundamentales utilizando el ejemplo del Factorial:
Es importante empezar cuanto antes a razonar en recursivo buscando la aproximación recursiva a problemas simples. Algunos ejemplos:
NOTA. La transformada Rápida de Fourier Posiblemente unos de los algoritmos con más trascendencia industrial ha sido el de Transformada Rápida de Fourier (Fast Fourier Transform o FFT). Ha permitido el cálculo rápido de transformadas de Fourier en aplicaciones relacionadas con la óptica, acústica, física cuántica, telecomunicaciones, teoría de sistemas y procesamiento de señales, reconocimiento del habla etc. Fue descubierto por Cooley y Turkey en 1965. La programación de FFTs en su forma más eficiente en cuanto a tiempo de ejecución da lugar a algoritmos iterativos más o menos complejos y muchas veces difíciles de leer. Sin embargo, cuando lo que prima es la claridad del código fuente, la codificación recursiva resulta más simple y clara. La FFT es un algoritmo naturalmente recursivo ya que el cálculo de la transformada de N puntos se basa en la combinación de las transformadas de dos secuencias de N/2 puntos. Una solución programada en C es (se presenta sólo a título de ejemplo, no es necesario comprenderla): void fft(double *x, double *y, int izda, int dcha)
{
double vr,vi,wr,wi; /* partes real e imaginaria de v y w */
int medio,i,j;
int n;
double fase, incfase;
if(izda<dcha) {
/* cálculo de las FFTs de las subsecuencias de N/2 puntos */
medio=(izda+dcha)/2;
fft(x,y,izda,medio);
fft(x,y,medio,dcha);
/* combinación del resultado */
n=dcha-izda+1;
incfase=2* M_PI/n;
for(i=izda; i<=medio; i++) {
j=i+n/2;
fase=(i-izda)*incfase;
vr=x[i];
vi=y[i];
wr=x[j]*cos(fase)+y[j]*sin(fase);
wi=-x[j]*sin(fase)*y[j]*cos(fase);
x[i]=vr+wr;
y[i]=vi+wi;
x[j]=vr-wr;
y[j]=vi-wi;
}
}
return;
}
Un programa más claro y simple todavía se obtiene en entornos que tienen definido o permiten definer el tipo número complejo ( C++, Java, Fortran, Ada ...) |
© Jerónimo Quesada 1998
Los contenidos de estas páginas pueden ser reproducidos para uso didáctico individual siempre que se cite el origen y no sean objeto de actividad de intercambio comercial de ningún tipo. En cualesquiera otras condiciones la copia, transmisión o almacenamiento por cualquier medio tipográfico, fotográfico, informático, telemático u otros requiere la autorización expresa del autor. Se agradecerá la comunicación de cualquier errata así como cualquier comentario o sugerencia.