Recursión e iteración Se exponen en este apartado diversos ejemplos simples de definiciones de procedimientos que generan procesos recursivos o iterativos. Se comienza precisando algoritmos y sus correspondientes procedimientos para el cálculo de potencias de exponente entero no negativo, primero en el monoide multiplicativo de los enteros y, posteriormente, potencias en un monoide cualquiera. Mediante estos ejemplos sencillos se ilustran definiciones de procedimientos recursivos e iterativos, ası́ como sus correspondientes procesos recursivos e iterativos. También se ponen de manifiesto hechos cruciales relativos al consumo de tiempo y de memoria en los procesos computacionales. [1] Comencemos considerando el concepto de potencia de exponente entero no negativo (se tomará, por simplificar, el monoide multiplicativo (Z, ×) de los enteros como dominio base). Deberá tenerse en cuenta que no van a servir, en principio, definiciones “para andar por casa” al estilo de bn = b × b × . . . × b ; en lugar de este tipo de definiciones deberemos acudir al concepto matemáticamente correcto de potencia. Fijado un número entero b, sea pb : N → Z la función tal que pb (0) = 1, pb (n + 1) = b × pb (n), (n ∈ N) (1) Se escribe bn = pb (n), para todo entero b y todo número natural n, y se dice que bn es la potencia de base b y exponente n; queda ası́ definida una aplicación p:Z×N→Z p(b, n) = bn , (b ∈ Z, n ∈ N) que, de acuerdo con (1), verifica: b0 bn = 1 = b × bn−1 (b ∈ Z, n ∈ N, n > 0) (1 ) Esta definición matemática, recursiva, proporciona el procedimiento computacional Programación en Matemáticas. Carlos Ruiz de Velasco y Bellas, Universidad de Cantabria, Santander. 10 de octubre de 2003 1 (define (potencia base exponente) (if (zero? exponente) 1 (* base (potencia base (− exponente 1))))) (2) Ejemplo de uso: > (potencia 3 5) 243 El proceso generado en el cálculo de (potencia 3 5) se puede esquematizar en la forma: (potencia 3 5) (* 3 (potencia 3 4)) (* 3 (* 3 (potencia 3 3))) (* 3 (* 3 (* 3 (potencia 3 2)))) (* 3 (* 3 (* 3 (* 3 (potencia 3 1))))) (* 3 (* 3 (* 3 (* 3 (* 3 (potencia 3 0)))))) (* 3 (* 3 (* 3 (* 3 (* 3 1))))) (* 3 (* 3 (* 3 (* 3 3)))) (* 3 (* 3 (* 3 9))) (* 3 (* 3 27)) (* 3 81) 243 El procedimiento potencia genera procesos recursivos que requieren un número de pasos (tiempo de ejecución) que crece de modo lineal en la talla del exponente y usan memoria también lineal en la talla del exponente. ¿Es posible mejorar estas cotas? [2] Atendamos, en primer lugar, al tiempo de ejecución. De la definición (1) se derivan propiedades de las potencias que pueden ayudar a disminuir el número de pasos; por ejemplo bn+m = bn × bm , de donde, si se toma m = n, 2 b2×n = bn × bn = (bn ) Por tanto, el cálculo de b2×n requiere exactamente una multiplicación más que el cálculo de bn . De ahı́ que, si e > 0 es par, entonces be = (be/2 )2 (3) El cálculo directo de be según (1) conlleva una multiplicación más que el cálculo de be−1 , mientras que usando (3) el número de multiplicaciones requeridas es una más que el cálculo de be/2 . Ahora, si e/2 es par se vuelve a usar (3); mientras que si e/2 es impar, se procede como en (1): e be/2 = b × b( 2 −1) Programación en Matemáticas. Carlos Ruiz de Velasco y Bellas, Universidad de Cantabria, Santander. 10 de octubre de 2003 2 y e 2 − 1 es par, con lo que se le puede aplicar nuevamente la reducción (3), etc. Estas consideraciones conducen a la formulación siguiente: Dado un entero b, para todo entero no negativo n se tiene b0 bn bn = 1 = (bn/2 )2 , si n > 0 y n es par = b × bn−1 , si n > 0 y n es impar (4) que en términos de procedimientos se expresa (define (pot base exponente) (cond ((zero? exponente) 1) ((even? exponente) (cuadrado (pot base (/ exponente 2)))) (else (* base (pot base (− exponente 1)))))) (5) (define (cuadrado x) (* x x)) Ejemplo de uso: > (pot 2 21) 2097152 El proceso generado en el cálculo de (pot 2 21) se puede esquematizar en la forma: (pot 2 21) (* 2 (pot 2 20)) (* 2 (cuadrado (pot 2 10))) (* 2 (cuadrado (cuadrado (pot (* 2 (cuadrado (cuadrado (* 2 (* 2 (cuadrado (cuadrado (* 2 (* 2 (cuadrado (cuadrado (* 2 (* 2 (cuadrado (cuadrado (* 2 (* 2 (cuadrado (cuadrado (* 2 (* 2 (cuadrado (cuadrado (* 2 (* 2 (cuadrado (cuadrado (* 2 (* 2 (cuadrado 1024)) (* 2 1048576) 2097152 2 5)))) (pot 2 4))))) (cuadrado (pot 2 2)))))) (cuadrado (cuadrado (pot 2 1))))))) (cuadrado (cuadrado (* 2 (pot 2 0)))))))) (cuadrado (cuadrado (* 2 1))))))) (cuadrado 4))))) 16)))) El procedimiento pot genera procesos recursivos que requieren un número de pasos que crece logarı́tmicamente con el exponente. El paso de un tiempo lineal a un tiempo logarı́tmico es crucial: el cálculo de b elevado a 1048576 (= 220 ) requerirı́a 1048575 multiplicaciones según el procedimiento potencia y solamente 20 = log2 (1048576) multiplicaciones según el procedimiento pot . Programación en Matemáticas. Carlos Ruiz de Velasco y Bellas, Universidad de Cantabria, Santander. 10 de octubre de 2003 3 [3] Expongamos ahora otro punto de vista, radicalmente distinto al anterior, para el cálculo de potencias de acuerdo con el siguiente esquema adaptado al caso concreto 35 ; se comienza con una variable a con valor inicial 1, una variable b con valor la base (3 en nuestro caso) y una variable n con valor inicial el exponente (5 en nuestro caso), de modo repetitivo se multiplica a por la base b a la vez que se disminuye n en una unidad: a b n 1 3 5 1×3 = 3 3 4 3×3 = 9 3 3 9×3 = 27 3 2 27×3 = 81 3 1 81×3 = 243 3 0 cuando n alcance 0, el valor de a es la potencia buscada. Formalicemos esta forma de proceder. Dado un número entero b, consideremos la función Pb : Z × N → Z tal que Pb (a, 0) = a, Pb (a, n + 1) = Pb (a × b, n), para todo a ∈ Z para todo a ∈ Z, n ∈ N (6) Se tiene Pb (a, n) = a × bn (7) Demostración: Por inducción sobre n. Pb (a, 0) = a = a × b0 , y Pb (a, n + 1) = Pb (a × b, n) = (a × b) × bn = a × bn+1 En particular, para a = 1: Pb (1, n) = bn (8) Queda definida una función P : Z×Z×N (a, b, n) → → Z Pb (a, n) y se cumple P (a, b, n) = a × bn . Teniendo en cuenta (6), la función P se expresa en la forma P (a, b, 0) = a P (a, b, n) = P (a × b, b, n − 1) (a, b ∈ Z, n ∈ N, n > 0) Programación en Matemáticas. Carlos Ruiz de Velasco y Bellas, Universidad de Cantabria, Santander. 10 de octubre de 2003 (9) 4 y se tiene bn = P (1, b, n) (10) Pasemos a definir los correspondientes procedimientos computacionales. La función P se implementa, teniendo en cuenta (9), como el procedimiento pot2Iter: (define (pot2Iter a b n) (if (zero? n) a (pot2Iter (* a b) b (− n 1)))) (11) y de (10) se obtiene (12) (define (pot2 base exponente) (pot2Iter 1 base exponente)) Ejemplo de uso: > (pot2 3 5) 243 El proceso generado en la evaluación de (pot2 3 5) es como sigue: (pot2 3 5) llama a pot2Iter con los parámetros a, b, n reemplazados por 1, 3, 5, respectivamente. Posteriormente, y de modo repetitivo (i.e. iterativo), se evalúan a * b, b y n − 1, después estos valores pasan a ser (en paralelo) los nuevos valores de a, b y n, respectivamente: (a, b, n) ← (a * b, b, n − 1) hasta que el valor de n sea 0 en cuyo momento se para el proceso y devuelve 243 como valor de (pot2Iter 1 3 5); esto es, como valor de (pot2 3 5). El siguiente esquema puede ayudar a entender el proceso: Programación en Matemáticas. Carlos Ruiz de Velasco y Bellas, Universidad de Cantabria, Santander. 10 de octubre de 2003 5 (pot2 3 5) = . (pot2Iter 1 3 5) = . . (pot2Iter 3 3 4) = . . . (pot2Iter 9 3 3) = . . . . (pot2Iter 27 3 2) = . . . . . (pot2Iter 81 3 1) = . . . . . . (pot2Iter 243 3 0) = . . . . . . 243 = . . . . . 243 = . . . . 243 = . . . 243 = . . 243 = . 243 = 243 Los procedimientos potencia y pot2 calculan potencias de exponente natural de números enteros, pero son esencialmente distintos en cuanto a la forma de calcularlos o, con mayor precisión, los procesos generados son diferentes: – La definición de potencia es recursiva en el sentido de que potencia se llama a sı́ mismo [en la definición (2)], y el proceso generado por (potencia b n) es estrictamente recursivo en el sentido de que en cada paso queda aplazada una evaluación [el producto (* base (potencia base (− exponente 1))) ] – La definición de pot2Iter es recursiva, pot2Iter se llama a sı́ mismo [en la definición (11)], pero el proceso generado por (pot2Iter a b n) es esencialmente iterativo en el sentido de que la respuesta se va construyendo mientras el proceso avanza y no quedan (o no deberı́an quedar) evaluaciones aplazadas. Una de las ventajas de esta forma recursivo-iterativa de pensar es que el par de definiciones (11), (12) se traduce automáticamente a una definición de procedimiento que generará procesos puramente iterativa; esto se consigue mediante el uso de alguna de las primitivas de iteración explı́cita que proporcione el lenguaje o el sistema, por ejemplo, do en Scheme. Veamos cómo llevar a cabo esta traducción automática mediante una sucesión de pasos según se expone seguidamente: 1. la entrada (los datos) es un par (base, exponente) ∈ Z × N (primera lı́nea del procedimiento (12)). 2. se invoca pot2Iter con sus parámetros a, b, n inicializados como 1, base, exponente, respectivamente (segunda lı́nea de (12)). 3. cuando n alcance el valor 0 se devuelve el valor de a (segunda lı́nea de (11)). Programación en Matemáticas. Carlos Ruiz de Velasco y Bellas, Universidad de Cantabria, Santander. 10 de octubre de 2003 6 4. si n no es 0 se efectúa el cambio (a, b, n) ← (a * b, b, n − 1) y se repite la evaluación de pot2Iter con estos nuevos valores (tercera lı́nea de (11)). Estamos en condiciones de definir un procedimiento que genere procesos puramente iterativo para el cálculo de potencias según el esquema indicado: (define (pot3 base exponente) (do ((a 1 (* a b)) (b base) (n exponente (− n 1))) ((zero? n) a))) [4] Combinemos ahora dos puntos de vista tratados previamente: – proceso iterativo del apartado anterior [3] y – tiempo logarı́tmico del apartado [2]. Para ello debemos tener en cuenta que si n es par, entonces bn = (bn/2 )2 = (b2 )n/2 Volvamos a usar las funciones Pb y P del apartado [3]. En la situación allı́ contemplada, y si n es un entero positivo par, se tiene: P (a, b, n) = Pb (a, n) = a × bn = a × (b2 )n/2 = Pb2 (a, n/2) = P (a, b2 , n/2) Por lo tanto las expresiones de (9) se escriben P (a, b, 0) = a P (a, b, n) = P (a, b2 , n/2), si n es par P (a, b, n) = P (a × b, b, n − 1), si n es impar (a, b ∈ Z, n ∈ N, n > 0) (13) y se tiene, véase (10), bn = P (1, b, n) Esto proporciona la siguiente definición de procedimiento: (define (pot4Iter a b n) (cond ((zero? n) a) ((even? n) (pot4Iter a (* b b) (/ n 2))) (else (pot4Iter (* a b) b (− n 1))))) (define (pot4 base exponente) (pot4Iter 1 base exponente)) Programación en Matemáticas. Carlos Ruiz de Velasco y Bellas, Universidad de Cantabria, Santander. 10 de octubre de 2003 7 Ejemplo de uso: > (pot4 5 87) 6462348535570528709932880406796584793482907116413116455078125 El procedimiento pot4 iter (recursivo-iterativo) genera procesos (que deberı́an ser) iterativos con un número de pasos que crece logarı́tmicamente con el tamaño de exponente. Ahora, y como traducción directa del procedimiento anterior, veamos la versión puramente iterativa: (define (pot5 base exponente) (do ((a 1 (if (even? (b base (if (even? (n exponente (if (even? ((zero? n) a))) n) a (* a b))) n) (* b b) b)) n) (/ n 2) (− n 1)))) Programación en Matemáticas. Carlos Ruiz de Velasco y Bellas, Universidad de Cantabria, Santander. 10 de octubre de 2003 8 [5] Generalización mediante procedimientos de orden superior. La definición de potencia de exponente natural en el dominio Z de los enteros, vista en (1), se generaliza de modo obvio al caso de un monoide arbitrario M = (M, •). Recordemos que • es una operación binaria en M asociativa y con elemento unidad (necesariamente único), digamos e. La definición (1) se expresa ahora: Fijado un elemento b ∈ M , sea pb : M → M la función tal que pb (0) = e, pb (n + 1) = b • pb (n), (n ∈ N) (14) El resto de la discusión es igual con los cambios: la unidad: 1 por e y la operación: × por • ; en particular, las relaciones (13) se escriben: P (a, b, 0) P (a, b, n) P (a, b, n) = a = P (a, b • b, n/2), si n es par = P (a • b, b, n − 1), si n es impar (a, b ∈ M, n ∈ N, n > 0) (15) y se tiene pb (n) = P (e, b, n), (b ∈ M, n ∈ N) Para implementar (15) es preciso incluir, además de la base y el exponente, la operación binaria • (como un procedimiento de dos argumentos) y la unidad e. Por lo demás el procedimiento Potencia es una copia casi exacta del procedimiento pot5 de la sección previa: (define (potenciaGeneral base exponente operador unidad) (do ((a unidad (if (even? n) a (operador a b))) (b base (if (even? n) (operador b b) b)) (n exponente (if (even? n) (/ n 2) (− n 1)))) ((zero? n) a))) Programación en Matemáticas. Carlos Ruiz de Velasco y Bellas, Universidad de Cantabria, Santander. 10 de octubre de 2003 9 Ejemplos de uso: 1 En el monoide multiplicativo de los enteros: > (potenciaGeneral 5 7 * 1) 78125 > (expt 5 7) 78125 2 En el monoide aditivo de los enteros (aquı́ se dice múltiplo en lugar de potencia): > (potenciaGeneral 123 9876 + 0) 1214748 > (* 123 9876) 1214748 3 En el monoide multiplicativo de los enteros modulo 37. La operación es multiplicación ordinaria de enteros seguida de reducción con respecto al módulo, 37 en este caso: > (potenciaGeneral 22 13 (lambda (u v) (remainder (* u v) mod) 1) 17 (define (potenciaModular base exponente mod) (potenciaGeneral base exponente (lambda (u v) (remainder (* u v) mod)) 1)) > (potenciaModular 234 567 890) 594 > (potenciaModular −234 567 890) -594 Obsérvese que en el último ejemplo, utilizando nuestra función Potencia, se obtiene un resultado correcto pero negativo: (−234)567 ≡ −594(mod 890) Esta aparente anomalı́a se debe al funcionamiento de la primitiva remainder de Scheme: Programación en Matemáticas. Carlos Ruiz de Velasco y Bellas, Universidad de Cantabria, Santander. 10 de octubre de 2003 10 > (remainder 7 5) 2 > (remainder 7 −5) 2 > (remainder −7 5) -2 > (remainder −7 −5) -2 Como puede verse remainder devuelve el resto con el signo del dividendo. Si se quiere que los restos sean no negativos, definamos un procedimiento resto que devuelva restos no negativos: (define (resto a b) (let ((r (remainder a b))) (if (>= r 0) r (+ (abs b) r)))) (define (potenciaModular base exponente mod) (potenciaGeneral base exponente (lambda (u v) (resto (* u v) mod)) 1)) Ejemplo de uso: (potenciaModular −22 13 37) 20 Teorema de Fermat. Si p es un entero primo y a es un entero, 0 ≤ a < p, entonces ap ≡ a (mod p) Por tanto, dado un entero n, n > 1, si para cualquier entero a, 1 < a < n, ocurre que an ≡ a (mod n) entoces n es compuesto. Ejercicio. Compruébese que 87669562583239363359710599 es compuesto. Programación en Matemáticas. Carlos Ruiz de Velasco y Bellas, Universidad de Cantabria, Santander. 10 de octubre de 2003 11