• No se han encontrado resultados

Programación II Sesión 4: Inmersión, desplegado-plegado, transformación recursivo-iterativo

N/A
N/A
Protected

Academic year: 2021

Share "Programación II Sesión 4: Inmersión, desplegado-plegado, transformación recursivo-iterativo"

Copied!
5
0
0

Texto completo

(1)

Programación II

Sesión 4: Inmersión, desplegado-plegado,

transformación recursivo-iterativo

Diego R. Llanos Ferraris

UNED, Centro Asociado de Palencia

Nota:Estos apuntes son la guía utilizada por el Profesor Tutor para impartir el seminario correspondiente, y no sustituyen en ningún modo a los libros de la asignatura.

1.

Introducción

El primer objetivo de esta lección es ver cómo utilizar el diseño recursivo para resolver problemas que no están planteados de manera recursiva. Para ello se utiliza la técnica deinmersión.

En segundo lugar, se verá cómo convertir programas recursivos no fina-les en programas recursivos finafina-les (algo más eficientes), a través de la técnica de desplegado y plegado.

Finalmente, se harán algunas consideraciones respecto a la transforma-ción de programas recursivos a iterativos.

2.

Técnicas de inmersión

Si el problema no tiene una definición recursiva, encontrar una solución recursiva no es trivial. Las soluciones recursivas se basan en resolver el mismo problema sobre un conjunto “más pequeño” de datos: el problema es que no siempre es fácil descomponer los datos de entrada en partes más pequeñas.

Las técnicas de inmersión se basan en añadir parámetros de entrada o de salida, de modo que sirvan para definir un subconjunto de los datos de entrada, y así poder resolver el problema para un conjunto más pequeño. Problemas típicos: los que implican recorrer uno o varios vectores para realizar una tarea. La solución pasa por resolver el problema para vecto-res cada vez más pequeños hasta llegar al caso trivial: el problema es que los límites de los vectores son fijos, por lo que habrá que añadir uno o va-rios parámetros que nos permitan trabajar sólo con una parte del vector.

(2)

La nueva función así creada se llama “función inmersora”, mientras que la función original, cuya formalización no es recursiva, se llama “función sumergida”.

Hay dos tipos de inmersión: la inmersión no final y la inmersión final. En la inmersión no final, se invoca recursivamente a la función para datos cada vez más pequeños. Al llegar al caso trivial e ir deshaciendo la cadena de llamadas, se va construyendo la solución.

Un ejemplo clásico lo constituye el producto escalar de dos vectores de tamañon. La idea consiste en añadir un parámetroj para permitir que la función inmersora calcule el producto escalar de los dos subvectores entre el elementojy el elementon. La cadena de llamadas se repite hasta el caso trivial, y al ir deshaciéndolas el resultado se va acumulando hasta obtener el producto escalar. En este caso, la llamada inicial tendría un valor dej= 1.

Otra posibilidad sería calcular el producto escalar de dos subvectores en-tre1 y j. En este caso, cambiaría tanto el caso trivial como la llamada inicial. La elección de una solución u otra es indiferente, ya que la suma de los productos de cada elemento es conmutativa.

Por el contrario, lainmersión finalpermite obtener funciones recursivas finales. Para ello se añade un parámetro de entrada/salida que vaya pa-sando a las sucesivas llamadas los resultados parciales obtenidos hasta el momento. Cuando se llega al caso trivial, el parámetro ya contiene la solución: al deshacer la cadena de llamadas la solución “emerge” hasta la llamada inicial.

Inmersión por razones de eficiencia: se utiliza cuando ya se dispone de una solución recursiva, pero se descubre que esa solución repite innece-sariamente muchos cálculos. Un ejemplo típico es el de los números de Fibonacci, que se definen como sigue:

• fib(0) = 0

• fib(1) = 1

• fib(n+ 2) =fib(n+ 1) +fib(n)

Aquí, para calcular fib(8), hay que calcular fib(7)y fib(6). Sin embargo, para calcular fib(7)también hay que calcular fib(6), lo que obliga a repetir muchos cálculos. La solución en este caso pasa por definir una función inmersora con dos parámetros de salida: el valor de fib(n)y el de fib(n−

1). De esta manera, en lugar de invocar dos veces a la función original, bastará con invocar una vez a la función inmersora. Este ejemplo está muy bien desarrollado en el libro de Balcázar, capítulo V. Problema: hay que tener mucho cuidado con los casos triviales: corremos el riesgo de intentar calcular fib(−1).

(3)

* fact * fact v a) b) c) n fact n−1 n *

Figura 1: Generalización para la función factorial. a) Árbol sintáctico original; b) Subárbol que une la función con la raíz del término; c) Relleno con paráme-tros originales y parámeparáme-tros de inmersión.

3.

Técnica de desplegado y plegado

Es una técnica de transformación que permite convertir programas recur-sivos no finales en recurrecur-sivos finales, que son algo más eficientes pero menos legibles. Se trata de un algoritmo dividido en tres partes:

Generalización: Se basa en definir una nueva funcióngbasada en el caso no trivial de la función recursiva no finalf. Para ello se construye el árbol sintáctico de dicho caso no trivial, y se reemplazan algunos elementos:

Las ramas que parten de la función original f se rellenan con los parámetros originales.

El resto de ramas se rellenan con parámetros de inmersión.

Una vez hecho esto, se determinan los elementos neutros de las operacio-nes que involucran los parámetros de inmersión.

Ejemplo:Sea el algoritmo recursivo para el cálculo del factorial (ejercicio 3.23 libro de Peña):

funfact (n:entero)dev (p: entero)

cason= 1→1

2n >1→fact(n−1).n

fcaso

Se escribe el árbol sintáctico de la expresión para el caso no trivial de la función (figura 3 a). A continuación se toma el camino que une al sub-árbol de la funciónfactcon la raíz del árbol (figura 3 b). Finalmente se rellenan las hojas del subárbol de la función con los parámetros origina-les, y el resto con parámetros de inmersión.

Una vez hecho esto, la nueva funcióng, en nuestro casoifact, será la si-guiente:

(4)

El elemento neutro del producto es el uno, por lo que la función original

factpuede calcularse como

fact(n) =ifact(n,1)

Desplegado: Consiste en sustituir la funciónf(que en nuestro ejemplo esfact, tal como aparece en la generalización hecha arriba) por su definición en términos deg (en nuestro ejemplo,ifact). De esa forma se obtiene una definición degen función de casos triviales y no triviales.

Ejemplo:En nuestro caso, la reescritura deifacten función defactquedaría como sigue: ifact(n, v)= (cason= 1→1 2n >1→fact(n−1).n fcaso).v o, lo que es lo mismo, ifact(n, v)= cason= 1→1.v 2n >1→fact(n−1).n.v fcaso

Plegado: Consiste en reemplazar el caso no trivial del desplegado hecho arri-ba porg. De esa forma se obtiene una definición degen función de casos triviales y de casos no triviales a través deg, es decir, una definición re-cursiva.

Ejemplo:En nuestro caso, reemplazando el caso no trivial de la definición deifactdada más arriba por la definición de la ecuación 1, obtenemos lo siguiente:

ifact(n, v)=

cason= 1→v

2n >1→ifact(n−1, n.v)

fcaso

De este modo, la funciónifactes recursiva final. Compárese esta función confact.

Al final, el cálculo que se realizaba al volver de la cadena de llamadas de la función recursiva no final queda desplazado a la invocación de la función recursiva final.

4.

Transformación de recursivo a iterativo

La transformación de algoritmos de recursivo a iterativo es diferente para las funciones recursivas finales y no finales.

En el caso de las recursivas finales, la correspondiente versión iterati-va recorre todos los subproblemas calculando los resultados parciales: cuando llega al caso trivial la solución ya está calculada. Por lo tanto, la

(5)

Las recursivas no finales obligan a realizar dos bucles: uno que avance hasta el problema trivial, y otro que, a partir de dicho caso trivial, com-ponga las soluciones para los problemas intermedios entre el caso trivial y el caso que se pretende resolver. Esto lleva a la realización de dos bu-cles.

Por otra parte, el segundo bucle utilizará elinversode la función sucesora para obtener el “predecesor” correspondiente. Si dicha función no existe, no queda más remedio que ir almacenando la cadena de sucesores al ir ejecutando el primer bucle, para luego recuperarlos y poder ejecutar el segundo bucle con ellos. Para esto se suele usar una estructura de pila.

Referencias

Documento similar