contenido de la lecciÓn 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · el...

43
MIGUEL Á. TOLEDO MARTÍNEZ FUNCIONES – LECCIÓN 11 11-1 CONTENIDO DE LA LECCIÓN 11 RECURSIVIDAD 1. Introducción 2 2. Ejemplos de recursividad 3 2.1 Ejemplos 11.1, 11.2, 11.3, 11.4, 11.5, 11.6, 3 3. Funcionamiento interno de la recursividad 11 3.1 Ejemplos 11.7, 11.8, 11.9, 11 4. Soluciones no recursivas (iterativas) 16 4.1 Ejemplos 11.10, 11.11, 11.12 16 5. El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular recursividad 23 7. El problema de las ocho reinas 26 7.1 Ejemplo 11.14 30 8. Casos interesantes 32 8.1 Ejemplo 11.15: Información genealógica 32 8.2 Ejemplo 11.16: Árboles similares 34 8.3 Ejemplo 11.17: Árboles no similares 36 9. Retroceso 36 10. Programación no determinista 36 11. Retroceso cronológico 37 12. Retroceso de dependencia dirigida 37 13. Uso de la recursividad 38 13.1 Examen breve 30 43 14. Lo que necesita saber 39 15. Preguntas y problemas 40 15.1 Preguntas 40 15.2 Problemas 40

Upload: others

Post on 22-Sep-2020

8 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-1

CONTENIDO DE LA LECCIÓN 11

RECURSIVIDAD 1. Introducción 2 2. Ejemplos de recursividad 3

2.1 Ejemplos 11.1, 11.2, 11.3, 11.4, 11.5, 11.6, 3 3. Funcionamiento interno de la recursividad 11

3.1 Ejemplos 11.7, 11.8, 11.9, 11 4. Soluciones no recursivas (iterativas) 16

4.1 Ejemplos 11.10, 11.11, 11.12 16 5. El problema de las Torres de Hanoi 20

5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular recursividad 23 7. El problema de las ocho reinas 26

7.1 Ejemplo 11.14 30 8. Casos interesantes 32

8.1 Ejemplo 11.15: Información genealógica 32 8.2 Ejemplo 11.16: Árboles similares 34 8.3 Ejemplo 11.17: Árboles no similares 36

9. Retroceso 36 10. Programación no determinista 36 11. Retroceso cronológico 37 12. Retroceso de dependencia dirigida 37 13. Uso de la recursividad 38

13.1 Examen breve 30 43 14. Lo que necesita saber 39 15. Preguntas y problemas 40

15.1 Preguntas 40 15.2 Problemas 40

Page 2: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-2

LECCIÓN 11

RECURSIVIDAD INTRODUCCIÓN Una función que se llama a sí mismo se dice que es recursiva. La recursividad en las funciones puede darse de dos maneras diferentes:

a. Directa. La función se llama directamente a sí misma. Por ejemplo, observe la figura 11.1, funcionA() es una función y en alguna parte de ella aparece una llamada a sí misma:

funcionA()

Figura 11.1. Recursividad directa

b. Indirecta. La función llama a otra función y esta a su vez llama a la primera. Por ejemplo, en la figura 11.2a la función funcionA() llama a la función funcionB() y esta a su vez invoca a la primera; es decir el control regresa a funcionA().

a) b)

Figura 11.2. Recursividad indirecta También se da la recursividad indirecta cuando una función llama a otra y ésta a una tercera. Una vez ejecutada, el control regresa a la función que la llamó, lo mismo sucede en todos los niveles. Por ejemplo, en la figura 11.2b la función funcionA() llama a la función funcionB(), y esta llama a la función funcionC() Cuando funcionC() concluye, el control regresa a funcionB(); y al terminar ésta, el control se transfiere a funcionA()

Llamada a funcionA()

Llamada a funcionB()

Llamada a funcionA()

Llamada a funcionC()

Llamada a funcionB()

funcionA()

funcionB()

funcionA()

funcionB()

funcionC()

Page 3: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-3

En toda definición recursiva de un problema se debe establecer un estado básico (estado primitivo, estado de salida o estado de terminación) Es decir, un estado en el cual la solución no se presente de manera recursiva sino directamente. Además la entrada (datos) del problema debe ir acercándose al estado básico.

Si dada la definición de un problema es posible determinar el estado básico y el

acercamiento paulatino al mismo, entonces se puede llegar a una solución. En otro caso se estaría en presencia de una definición circular del problema.

Es importante comprender que cada instanciación (copia activa) de una función

recursiva es totalmente peculiar y tiene sus propios argumentos, variables locales, direcciones de retorno, etc.

Objetivos de esta lección: •••• Comprender y manejar el concepto de recursividad. •••• Aprender como se escriben y utilizan funciones que se llaman a sí mismas. •••• Resolver problemas utilizando recursividad. Los ejemplos que aparecen a continuación, a pesar de que pueden resolverse de manera

no recursiva permiten ilustrar claramente el concepto de recursividad.

EJEMPLOS DE RECURSIVIDAD Ejemplo 11.1 Escriba una función recursiva C++ para encontrar la suma de todos los enteros desde 1 hasta N. Solución

Piense en esta operación por un minuto. ¿No es una operación recursiva clásica? Para encontrar la suma de enteros de 1 a 5, ¿podría adicionar 5 a la suma de enteros de 1 a 4? Entonces, para encontrar la suma de 1 a 4, adiciona 4 a la suma de enteros de 1 a 3, y así sucesivamente, ¿correcto? Expresada en símbolos:

sumaNumeros(5) = 5 + sumaNumeros(4) sumaNumeros(4) = 4 + sumaNumeros(3) sumaNumeros(3) = 3 + sumaNumeros(2) sumaNumeros(2) = 2 + sumaNumeros(1) sumaNumeros(1) = 1

Observe que sumaNumeros(1) es el estado básico, porque su valor es conocido. Ahora, traduciendo este proceso a una función recursiva, se obtiene:

>−+=1)1(

11)(

nSinssumaNumeronnSi

nssumaNumero

Esta expresión se puede expresar en seudocódigo como sigue:

Page 4: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-4

sumaNumeros(n) si (n == 1) entonces

regresar (1). si no

regresar ( n + sumaNumeros(n-1)).

La función C++ se codifica entonces directamente desde el algoritmo como:

/************************************************************* Esta función recursiva calculará la suma de los enteros entre 1 hasta N *************************************************************/ int sumaNumeros(int n)

{ if(n == 1) return (1) else

return (n + sumaNumeros(n – 1)) } // Fin de sumaNumeros()

Algoritmo 11.1. sumaNumeros

Ejemplo 11.2 El siguiente programa: SUMAENT.CPP, muestra el uso del algoritmo 11.1

/* Este programa: SUMAENT.CPP, calcula la suma de los números enteros positivos comprendidos entre 1 y el número dado. */ #include <iostream.h> //Para cin y cout int sumaNumeros(int n); void main(void) { int n; /* Tener en cuenta que el valor devuelto es de la clase int. Por lo tanto la suma esta limitada a un máximo de 32767, en caso contrario se producirá un sobreflujo. */ cout << "Dame un número entero mayor que cero: "; cin >> n; cout << "\nLa suma de los números enteros comprendidos entre 1 y " << n << " es: " << sumaNumeros(n) << endl; } int sumaNumeros(int n) { if(n == 1) return(1); else return(n + sumaNumeros(n-1)); }

Page 5: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-5

Ejemplo 11.3

Factorial de un número: La notación n! se lee factorial de n e indica el producto de los enteros positivos desde 1 hasta n. Por ejemplo:

3! = 1 ×××× 2 ×××× 3 4! = 1 ×××× 2 ×××× 3 ×××× 4 5! = 1 ×××× 2 ×××× 3 ×××× 4 ×××× 5 n! = 1 ×××× 2 ×××× 3 ××××... ×××× (n-2) ×××× (n-1) ×××× (n)

También definimos 1! = 1 y 0! = 1. Si se le pide que desarrolle una función que calcule el factorial de un número, ¿cómo le haría? Si invertimos la definición de la fórmula tenemos:

n! = n (n-1) (n-2) ... 3 ×××× 2 ×××× 1

Por lo tanto, 4! = 4 ×××× 3 ×××× 2 ×××× 1. Observe que 3 ×××× 2 ×××× 1 es 3!; entonces, podemos definir 4! de manera recursiva como:

4! = 4 ×××× 3!

En general, podemos definir n! como:

n! = n (n-1)!

(n-1)! = (n-1) (n-2)! (n-2)! = (n-2) (n-3)! . . .

Llegamos a definir entonces, el factorial de un número n en términos del factorial del número (n-1) de la siguiente manera:

>−×==

=1)!1(

101!

nSinnnonSi

n

En la definición recursiva del factorial se da un estado básico (si n = 0 o n = 1, entonces el factorial vale 1), en el cual el problema ya no se define en términos de sí mismo sino que se asigna un valor directamente. En el paso recursivo de la fórmula se utiliza el concepto de factorial aplicado a (n-1) Por ser un número entero positivo será (n-1) un valor más cercano al estado básico (n = 0 o n = 1) Una vez establecida la definición recursiva de los números factoriales, podemos comenzar a formular un algoritmo recursivo. Considere el siguiente seudocódigo:

factorial(n) INICIO

x = n -1. calcular x!. // (n-1)! regresar(n ×××× x!). // n! = n × (n -1)!

FIN.

La función factorial() estima el valor de n! calculando el valor de (n-1)! y luego multiplicando el resultado por n. Sin embargo, como ya habrá notado, la segunda instrucción no está definida de modo correcto: tenemos que encontrar una forma de calcular el valor de x!. Si lo piensa bien, verá que ya tenemos una: factorial() Esta función calcula el factorial de un número. Apliquemos este conocimiento y volvamos a escribir la rutina como:

Page 6: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-6

factorial(n) INICIO

x = factorial(n-1) . // (n-1)! regresar(n ×××× x). // n! = n × (n -1)!

FIN.

Ahora bien, cuando calcule el valor de n!, la función se llamará a sí misma de modo recursivo para estimar el valor de (n-1)!. Cabe preguntarnos si la función está completa. Mirémosla con detenimiento y analicemos su ejecución cuando calcula 2!. El procesamiento se inicia cuando se llama a la función con un argumento de 2. Entonces ésta calcula 2! recursivamente llamándose a sí misma con un argumento de 1; para estimar 1!, vuelve a llamarse a sí misma otra vez con un argumento de 0. La tercera copia (instancia) de la función se llamará a sí misma con un argumento de –1, la siguiente con –2, y así sucesivamente. El problema está más claro ahora: la función es recursiva hasta el infinito. Todas las funciones recursivas necesitan una forma de detenerse, a la que hemos denominado estado básico o primitivo. Se coloca, por lo común, en la parte superior de una función recursiva, y contiene instrucciones que al final terminan la recursividad y comienzan a desapilar todas las llamadas anidadas. Si se le omite o está incorrecta, como acabamos de ver, el funcionamiento puede convertirse en infinitamente recursivo. Volviendo a nuestro ejemplo, identifiquemos un estado básico para la función factorial(). Por definición, sabemos que 0! = 1 y que 1! = 1. Por lo tanto, podemos añadir revisiones para estos valores en la parte superior del procedimiento, de este modo:

factorial(n) INICIO si(n == 0 o n == 1)

regresar(1). regresar(n * factorial(n-1)). FIN.

Ahora, cuando se le llame con un argumento de 0 o 1, la función devolverá un valor explícito en lugar de realizar otra llamada recursiva. (Observe que hemos eliminado la innecesaria variable temporal x de nuestro algoritmo)

Pero aún tenemos otro problema. La función puede ser llamada al comienzo con un argumento negativo. Por lo tanto, debemos añadir una revisión más para asegurar que la función ha sido llamada de modo apropiado:

factorial(n) INICIO

si(n < 0) /* Revisa Argumentos erróneos */ regresar(-1). si(n == 0 || n == 1) regresar(1). regresar(n * factorial(n-1)). FIN.

A continuación se muestra la versión final escrita en C++.

int factorialRecurrente(int n) { if(n < 0) /* Revisa argumentos erróneos */

return(-1);

Page 7: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-7

if(n == 0 || n == 1) return(1);

return(n * factorialRecurrente(n-1)); }

Algoritmo 11.2: factorialRecurrente

Ejemplo 11.4

Serie de Fibonacci: Otro caso clásico de problemas definidos recursivamente es el cálculo de la serie de Fibonacci: 0, 1, 1, 2, 3, 5, 8, 13, 21, ..., etc. Recuérdese que el Fibonacci de un número se obtiene de la suma de los dos números Fibonacci anteriores. A continuación se ilustrará el concepto de números Fibonacci:

Por definición:

Fibonacci(0) = 0 Fibonacci(1) = 1

Los demás números de la serie en base a la definición son:

Fibonacci(2) = Fibonacci(1) + Fibonacci(0) = 1 + 0 = 1 Fibonacci(3) = Fibonacci(2) + Fibonacci(1) = 1 + 1 = 2 Fibonacci(4) = Fibonacci(3) + Fibonacci(2) = 2 + 1 = 3

La definición en forma recursiva de la serie de Fibonacci será:

>−+−==

=1)2()1(

)1()0()(

nSínfibonaccinfibonaccinonSin

nFibonacci

En este ejemplo el estado básico se presenta cuando n es cero o es uno. En el paso recursivo de la fórmula se utiliza el concepto de Fibonacci aplicado a (n-1) y (n-2) Por ser n un número entero positivo serán (n-1) y (n-2) valores más cercanos al estado básico.

Empecemos ahora a construir una solución recursiva:

fibonacciRecursivo(n)

INICIO si(n < 0) regresar(-1). /* Argumento erróneo */

si((n = 0) o (n == 1)) regresar(n). regresar(fibonacciRecursivo(n-1) + fibonacciRecursivo(n-2)).

FIN.

Esta solución tiene un error, la verificación n < 0, que busca un argumento erróneo, solo es válida la primera vez, en llamadas subsecuentes a la función no tiene sentido. Por lo tanto se hace necesario realizar algunas modificaciones al algoritmo anterior: fibonacciRecursivoInicial(n)

INICIO si(n < 0) regresar(-1). /* Argumento erróneo */

Page 8: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-8

regresar(fibonacciRecursivo(n)). FIN.

fibonacciRecursivo(n) INICIO

si((n == 0) o (n == 1)) regresar(n). regresar(fibonacciRecursivo(n-1) + fibonacciRecursivo(n-2)).

FIN.

La versión en C++ del algoritmo anterior es:

int fibonacciRecursivoInicial(n) { if(n < 0) return(-1); return(fibonacciRecursivo(n)); } int fibonacciRecursivo(n) { if((n == 0) || (n == 1)) return(n); return(fibonacciRecursivo(n - 1) + fibonacciRecursivo(n - 2)); }

Algoritmo 11.3: fibonacciRecursivo

Ejemplo 11.5 El siguiente programa: FIBONA1.CPP, ilustra el uso del algoritmo 11.3.

/* El siguiente programa: FIBONA1.CPP, calcula los n términos de la serie de Fibonacci */ #include <iostream.h> //Para cout y cin int fibonacciRecursivoInicial(int n); int fibonacciRecursivo(int n); void main(void) { int n; cout << "Dame el número de términos a calcular: "; cin >> n; cout << endl; while(n > 0) { cout << "El valor del término " << n << " de la serie de Fibonacci es: " << fibonacciRecursivoInicial(n - 1) << endl; n = n - 1; } }

Page 9: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-9

Ejemplo 11.6

En este ejemplo presentamos el caso de una inversión en una institución bancaria. Se ha depositado un capital m por el cual se recibe un x% de interés anual. El problema consiste en determinar el capital que se tendrá al cabo de n años. Supóngase que m = $5000.00 y x = 10%, entonces: C0 = 5000 [Capital Inicial] C1 = C0 + C0 * 10% [Capital al finalizar el primer año de inversión] C2 = C1 + C1 * 10% [Capital al finalizar el segundo año de inversión] En general, al cabo de n años se tendrá que:

Cn = Cn-1 + Cn-1 * 10% o Cn = 1.10 * Cn-1

Se llega a una definición recursiva del cálculo de inversiones.

>+=

=− 0*)1(

0

1 nSíCxnSím

Cn

n

Nuevamente se cuenta con un estado básico, para n = 0, y un acercamiento a él en la otra expresión de la fórmula. El siguiente algoritmo presenta una solución recursiva del cálculo de una inversión: balance(n, m, x)

INICIO si(n = 0) entonces

regresar(m). //Estado básico sino

regresar((1+x) * balance((n-1), m, x). FIN.

Para simplificar el ejemplo: sí definimos el capital inicial m y la tasa x como variables globales; y la recapitalización es mensualmente tendremos:

float balance(int m)

{ if (n == 0)

int fibonacciRecursivoInicial(n) { if(n < 0) return(-1); return(fibonacciRecursivo(n)); } int fibonacciRecursivo(n) { if((n == 0) || (n == 1)) return(n); return(fibonacciRecursivo(n - 1) + fibonacciRecursivo(n - 2)); }

Page 10: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-10

return (m); else

return((1 + x / 12 / 100) * balance(n – 1)); } //Fin de balance()

Algoritmo 11.4: balance

¡Esto es todo lo que hay que hacer! Esta función calculará al final de cualquier mes n que pase por la función. Observe cómo la función se llama a si misma en la cláusula else. Cuando la computadora encuentra la llamada recursiva en la cláusula else, deberá temporalmente retrasar el cálculo para evaluar la llamada de la función recursiva justo como se hizo el cálculo de interés compuesto. Cuando encuentra la cláusula else por segunda vez, la función se llama a sí misma otra vez y se mantiene llamándose cada vez que se ejecuta la cláusula else hasta que alcanza el estado primitivo. Cuando esto sucede, se ejecuta la cláusula if (porque n es cero), y la llamada recursiva termina. Ahora, insertaremos esta función en un programa llamado CAPITAL.CPP, para calcular el interés compuesto, como sigue:

// Este programa: CAPITAL.CPP, ilustra el cálculo del interés compuesto de un capital inicial determinado #include <iostream.h> // Para cin y cout // Funciones prototipo y variables globales float balance(int); // Regresa nuevo balance float deposito = 0.0; // Capital inicial float tasa = 0.0; // Tasa de interés anual void main(void)

{ // Define la variable argumento de la función int meses = 0; // Número de meses después del depósito // inicial para determinar el balance // Obtención del capital inicial, número de meses y tasa de interés cout << "Escriba el depósito inicial: $"; cin >> deposito; cout << "Escriba el número de meses (periodo): "; cin >> meses; cout << "Escriba la tasa de interés anual: "; cin >> tasa; // Establece la salida ajustada y la precisión cout.setf(ios::fixed); cout.precision(2); // Muestra resultados, haciendo la llamada a la función recursiva cout << "\n\nCon un depósito inicial de $" << deposito << " y una tasa de interés de " << tasa << "%\nel balance al final de " << meses << " meses deberá ser de $" << balance(meses) << endl; } // Fin de main()

Page 11: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-11

El programa anterior pide al usuario que escriba el depósito inicial, número de meses a calcular y la tasa de interés anual. Las variables deposito y tasa se definen en forma global para propósitos de ejemplo para que podamos concentrarnos en la función recursiva. El número de meses a calcular se pasa a la función como un parámetro por valor. Realmente las variables deposito y tasa podrían definirse en forma local para main() y pasar a la función como parámetros por valor. Esto se dejará como un ejercicio. Una vez que el usuario escribe los valores necesarios, se hace la llamada recursiva y el programa escribe el balance final. Aquí está una muestra de la salida del programa:

Escriba el depósito inicial: $1000↵↵↵↵ Escriba el número de meses para calcular: 4↵↵↵↵ Escriba la tasa de interés anual: 12↵↵↵↵ Con un depósito inicial de $1000 y una tasa de interés de 12% el balance al final de 4 meses deberá ser de $1040.60

FUNCIONAMIENTO INTERNO DE LA RECURSIVIDAD Internamente se utiliza una estructura tipo pila para guardar los valores de las variables y constantes locales de la función que efectúa la llamada. De esta manera, una vez concluida la ejecución, se puede seguir con los pasos que quedaron pendientes recuperando los valores necesarios de la pila. Con cada llamada recursiva se crea una copia de todas las variables y constantes que estén vigentes, y se guarda esa copia en la pila. Además se guarda una referencia a la siguiente instrucción a ejecutar. Al regresar se toma la imagen guardada en el tope de la pila y se continúa operando. Esta acción se repita hasta que la pila quede vacía. Se tratará de ilustrar estos conceptos con ayuda de los ejemplos anteriores. Ejemplo 11.7

Retomando el problema del cálculo del factorial de un número n (véase el algoritmo 11.2), para mostrar el funcionamiento interno de la recursión. En la tabla 11.1 se presentan los valores que van adquiriendo las variables y los valores que son guardados en la pila, en el transcurso del cálculo del factorial de un número n = 4.

PASO n PILA FactorialRecurrente

0 4 1 4 4*, 2 3 4*, 3*, 3 2 4*, 3*, 2*, 4 2 4*, 3*, 2* 1 5 2 4*, 3* 2(2*1) 6 3 4* 6(3*2) 7 4 24(4*6)

Tabla 11.1. Cálculo recursivo del factorial

/*********************************************************************** Esta función recursiva calculará un balance de cuenta con base en una tasa de interés compuesto mensual. ************************************************************************/ float balance(int n)

{ if(n == 0) return deposito; else return(1 + tasa / 12 / 100) * balance(n - 1); } //Fin de balance()

Page 12: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-12

En el primer paso n es igual a 4; por lo tanto, siguiendo el algoritmo 11.2 debe hacerse 4 * 3! Ante la imposibilidad de realizar esta operación (porque primero debe calcularse 3!) se guarda el valor de n en la pila y se continúa trabajando con n = 3.

Con el nuevo valor de n procedemos de manera similar que con n = 4. La diferencia radica en que la entrada, n, está más cercana al estado básico. En consecuencia, en el paso 2 se agrega el valor 3 a la pila para multiplicarlo posteriormente por 2! En el paso 3 se agrega el valor 2 a la pila. Una vez alcanzado el estado básico, paso 4, se comienza a extraer los valores almacenados en la pila y a operar con ellos. En el paso 5 se extrae de la pila el valor 2 y se multiplica por el factorial de 1, obteniendo 2! Luego se extrae el 3 y se multiplica por el factorial de 2, para obtener 3! Se continúa así hasta que la pila quede vacía, lo cual ocurre en el paso 7 donde se calcula el factorial de 4. En la figura 11.3 se presenta gráficamente lo descrito anteriormente.

Figura 11.3. Representación gráfica de la recursividad

Ejemplo 11.8

Retomando ahora el problema del cálculo de un número Fibonacci n (véase el algoritmo 11.3), para mostrar nuevamente el funcionamiento interno de la recursividad. En la tabla 11.2 se presentan los valores que van adquiriendo las variables y los valores almacenados en la pila, durante el cálculo del número Fibonacci n = 4.

PASO n PILA FibonacciRecursivo 0 4 1 4 fibonacciRecursivo(2) +, 2 3 fibonacciRecursivo(2) +, fibonacciRecursivo(1) +, 3 2 fibonacciRecursivo(2) +, fibonacciRecursivo(1) +,

fibonacciRecursivo(0) +,

4 1 fibonacciRecursivo(2) +, fibonacciRecursivo(1) +, fibonacciRecursivo(0) +,

1

5 0 fibonacciRecursivo(2) +, fibonacciRecursivo(1) +, 1(1+0) 6 1 fibonacciRecursivo(2) +, 2(1+1) 7 2 fibonacciRecursivo(0) +, 2 8 1 fibonacciRecursivo(0) +, 3(2+1) 9 0 3(3+0)

Tabla 11.2. Cálculo recursivo de un número Fibonacci

24

6

2 1 1 1

factorialRecurrente(4) =

4 * factorialRecurrente(3) = 24

3 * factorialRecurrente(2) = 6

2 * factorialRecurrente(1) = 2

Page 13: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-13

En la misma podemos observar que en este ejemplo se van guardando en la pila llamadas recursivas pendientes de ejecución. Para n igual a 4 se llama al proceso fibonacciRecursivo(3) y se guarda en la pila fibonacciRecursivo(2), paso 1. En el paso 2 se calcula fibonacciRecursivo(3), llamando al proceso fibonacciRecursivo(2) y almacenando en la pila fibonacciRecursivo(1) (una vez calculados, serán sumados) Se continúa de esta manera para los diferentes valores de n, hasta que éste sea 1 o 0. Cuando n es igual a 1, paso 4, se asigna directamente un valor a fibonacciRecursivo(1), ya que éste es un estado básico. Como se llegó a un estado básico, se comienzan a extraer sucesivamente de la pila los elementos dejados en ella. En el paso 5 se extrae de la pila a fibonacciRecursivo(0), el cual también tiene solución directa por ser un estado básico y se suma al número fibonacci obtenido en el paso anterior. Lo mismo sucede en el paso 6 al extraer fibonacciRecursivo(1) En el paso 7, al extraer fibonacciRecursivo(2) se procede de igual manera que en el paso 3. Es decir se llama a fibonacciRecursivo(1) y se almacena en la pila a fibonacciRecursivo(0) El proceso continúa hasta que la pila quede vacía, paso 9. En la figura 11.4 se presenta gráficamente lo descrito con anterioridad.

Page 14: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-14

Figura 11.4. Representación gráfica de la recursividad

fibonacciRecursivo(4) = fibonacciRecursivo(3) + fibonacciRecursivo(2) = 3

fibonacciRecursivo(3) = fibonacciRecursivo(2) + fibonacciRecursivo(1) = 2

fibonacciRecursivo(2) = fibonacciRecursivo(1) + fibonacciRecursivo(0) = 1

1 1

0 0

fibonacciRecursivo(1) = 1

fibonacciRecursivo(0) = 0

1 1

fibonacciRecursivo(1) = 1

fibonacciRecursivo(2) = fibonacciRecursivo(1) + fibonacciRecursivo(0) = 1

1 1

fibonacciRecursivo(1) = 1

0 0

fibonacciRecursivo(0) = 0

Page 15: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-15

Ejemplo 11.9

Para concluir esta serie de ejemplos sobre el funcionamiento interno de la recursividad, retomemos el problema del cálculo de una inversión en una institución bancaria (véase el algoritmo 11.4). En la tabla 11.3 se presentan los valores que van adquiriendo las variables y los valores almacenados en la pila, en el transcurso del cálculo de una inversión durante 4 años, con un capital inicial de $5000.00 y a un interés anual del 10%.(Nuevamente, para simplificar supondremos que los intereses se acumulan anualmente).

PASO n PILA balance 0 4 1 4 1.10 *, 2 3 1.10 *, 1.10 *, 3 2 1.10 *, 1.10 *, 1.10 *, 4 1 1.10 *, 1.10 *, 1.10 *, 1.10 *, 5 0 1.10 *, 1.10 *, 1.10 *, 1.10 *, 5000 6 1 1.10 *, 1.10 *, 1.10 *, 5500(5000 * 1.10) 7 2 1.10 *, 1.10 *, 6050(5500 * 1.10) 8 3 1.10 *, 6655(6050 * 1.10) 9 4 7320.5(6655 * 1.10)

Tabla 11.3. Cálculo recursivo de una inversión bancaria En el primer paso n es igual a 4, por lo tanto siguiendo el algoritmo debe hacerse 1.10 por el capital obtenido durante la inversión de los 3 años anteriores. Como no se dispone de este valor, se almacena 1.10 en la pila para operar con él posteriormente y se continúa con el cálculo de balance(3), paso 2. Como no se tiene aún el estado básico se procede de manera similar al paso anterior, es decir se almacena 1.10 en la pila y se llama a balance(2). Se continúa así hasta llegar al estado básico, paso 5, en el cual el capital a utilizar en el cálculo de la inversión es el inicial, y por lo tanto puede ser asignado directamente. En el paso 6 se extrae de la pila el valor 1.10 y se multiplica por el capital, calculando así la inversión obtenida al cabo de un año. Se continúa realizando esta operación mientras haya elementos en la pila. En el paso 9 la pila queda vacía y así se obtiene, finalmente, el monto total de la inversión al cabo de 4 años. En la figura 11.5 se presenta gráficamente lo antes descrito. En esta sección se ha presentado el funcionamiento interno de la recursividad. Aunque es un proceso transparente al usuario, es muy importante conocerlo para comprender debidamente la herramienta que se está utilizando. También se ha mencionado en los párrafos anteriores que la recursividad es un instrumento poderoso para definir objetos en términos de sí mismos. Usando recursividad es posible escribir un código más compacto y, en ciertos casos, de más fácil comprensión. Sin embargo, debe quedar claro que la recursividad no da mayor rapidez a la ejecución del código y tampoco permite ahorrar espacio de memoria. Con respecto a este último punto debe tenerse en cuenta que la recursividad, para su funcionamiento utiliza una pila interna la cual consume memoria.

En general, es recomendable usar recursividad cuando el problema por su naturaleza así lo exige. Es decir, cuando la solución simplifica y facilita de modo notable usando esta herramienta. En otro caso es más conveniente, computacionalmente hablando, utilizar alguna técnica iterativa para resolver el problema. Los casos que se presentaron antes, resueltos por algoritmos recursivos, son problemas que pueden resolverse fácilmente de manera iterativa.

Ahora es preciso ejemplificar cada uno de estos problemas, con su correspondiente solución iterativa.

Page 16: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-16

Figura 11.5. Representación gráfica de la recursividad

SOLUCIONES NO RECURSIVAS (ITERATIVAS) Ejemplo 11.10

Basándose en el algoritmo 11.2: factorialRecurrente antes diseñado, elaboraremos un algoritmo no recursivo.

factorialIterativo(n)

/* Este algoritmo calcula el factorial de un número n donde n es un valor numérico entero, positivo o nulo. */

INICIO si(n < 0) entonces // Valida datos erróneos.

regresar(-1).

factorial = 1. mientras (n > 0) hacer // Ciclo para calcular n!

Inicio factorial = n * factorial. n = n-1.

Fin.

regresar(factorial). FIN.

7320.50

6655.00

6050.00

balance(4) = 7320.50

1.10 * balance(3) = 7320.50

1.10 * balance(2) = 6655.00

5500.00 1.10 * balance(0) = 5500.00 5000.00 5000.00

1.10 * balance(1) =

Page 17: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-17

La tabla 11.4 representa los valores que van adquiriendo las variables, durante el cálculo del factorial de un número n = 4.

n factorial 4 1 3 4 2 12 1 24 0 24

Tabla 11.4. Cálculo iterativo del factorial

Ejemplo 11.11

A continuación se presenta un algoritmo no recursivo para el cálculo de los números de Fibonacci. fibonacciIterativo(n)

/* Este algoritmo calcula el número Fibonacci correspondiente a n, donde n es un valor numérico entero, positivo o nulo. */ INICIO si((n = 0) o (n == 1)) entonces

regresar(n).. si no

Inicio fiba = 0. fibb = 1. i = 2.

Fin.

mientras(i <=n) Inicio

fibo = fiba + fibb. fiba = fibb. fibb = fibo. i = i + 1.

Fin. regresar(fibo).

FIN.

La tabla 11.5 representa los valores que van adquiriendo las variables, durante el cálculo del número Fibonacci n = 4.

n fibo fiba fibb i 4 0 1 2 1 1 1 3 2 1 2 4 3 2 3 5

Tabla 11.5. Cálculo iterativo de un número Fibonacci

A continuación presentamos un algoritmo con algunas variaciones respecto a la solución antes propuesta.

Nuestro objetivo ahora es diseñar primero una función iterativa que acepte un número entero que no sea negativo como el argumento n y devuelva el valor F(n) La primera versión de nuestro seudocódigo puede ser así:

Page 18: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-18

fibonacci(n) INICIO

si (n = 0) regresar(0).

si(n = 1) regresar(1).

para i = 2 hasta n Inicio

elementoSiguiente = elementoAnteAnterior + elementoAnterior. actualizar elementoAnteAnterior y elementoAnterior.

Fin.

regresar(elementoSiguiente). FIN.

Observe que nuestro seudocódigo carece de algunos detalles relevantes: los valores iniciales de las variables, los incrementos para las variables que iteran (ciclos) y una prueba para detectar los argumentos válidos. Después de agregar esas instrucciones, el algoritmo queda así:

fibonacci(n) INICIO

si(n < 0) regresr(-1).

si (n = 0) regresar(0).

si(n = 1) regresar(1).

elementoAnteAnterior = 0. elementoAnterior = 1. para i = 2 hasta n Inicio

elementoSiguiente = elementoAnteAnterior + elementoAnterior. ElementoAnteAnterior = elementoAnterior. elementoAnterior = elementoSiguiente.

Fin.

regresar(elementoSiguiente). FIN.

Observe que hemos establecido la convención de devolver –1 para indicar un argumento erróneo. También observe que ya inicializamos y actualizamos las dos variables: elementoAnteAnterior y elementoAnterior. Finalmente escribiremos un programa en C++ que llame a esta función. El programa se llamará FIBONA2.CPP.

Page 19: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-19

Ejemplo 11.12

Algoritmo no recursivo para el cálculo del capital obtenido en una inversión bancaria durante n años. balanceIterativo(n, m, x)

/* Este algoritmo calcula el capital que se tendrá luego de invertir un capital inicial m, durante n años, con una tasa anual de x%. */ INICIO

capital = m. i = 1. mientras(i <= n) hacer

Inicio capital = capital + x * capital. i = i + 1.

/* Este programa: FIBONA2.CPP, resuelve en forma iterativa la serie de Fibonacci */ #include <iostream.h> //Para cout const int MAXIMO = 10; //Máximo n int fibonacci(int n); void main(void) { for(int i = -1; i <= MAXIMO; i++) cout << "\ti: " << i << " fib(" << i << "): " << fibonacci(i) << endl; } int fibonacci(int n) { int i; int elementoSiguiente, elementoAnteAnterior, elementoAnterior; if(n < 0) return(-1); if(n == 0) return(0); if(n==1) return(1); elementoSiguiente = 0; elementoAnteAnterior = 0; elementoAnterior = 1; for(i = 2; i <= n; i++) { elementoSiguiente = elementoAnterior + elementoAnteAnterior; elementoAnteAnterior = elementoAnterior; elementoAnterior = elementoSiguiente; } return(elementoSiguiente);

}

Page 20: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-20

Fin.

regresar(capital). FIN.

La tabla 11.6 presenta los valores que van adquiriendo las variables, en el transcurso del cálculo de una inversión durante 4 años con un capital inicial de $5000.00 a un interés anual del 10%. n m x capital i 4 5000 0.10 5000 1 5500 2 6050 3 6655 4 7320.50 5

Tabla 11.6. Cálculo iterativo de una inversión bancaria

EL PROBLEMA DE LAS TORRES DE HANOI El problema de las Torres de Hanoi es un problema clásico de recursividad, ya que pertenece a la clase de problemas cuya solución se simplifica notablemente al utilizar recursividad.

Tenemos tres torres: A, B y C, y un conjunto de cinco discos, todos de distintos tamaños. El enigma comienza con todos los discos colocados en la torre A de tal forma que ninguno de ellos cae sobre uno de menor tamaño; es decir, están apilados, uno sobre otro, con el más grande hasta abajo, encima de él el siguiente en tamaño y así sucesivamente (véase la figura 11.6, donde se muestra un ejemplo) El propósito del enigma es apilar los cinco discos, en el mismo orden, pero en la torre C. Durante la solución, puede colocar los discos en cualquier torre, pero debe apegarse a las siguientes reglas:

•••• Sólo puede mover el disco superior de cualesquiera de las torres. •••• Un disco más grande nunca puede estar encima de uno más pequeño.

Torre A Torre B Torre C

Figura 11.6 Enigma de Las torres de Hanoi

Intente resolver manualmente el enigma con una cantidad pequeña de discos, digamos cinco o seis, antes de proseguir con la solución algorítmica.

El problema que enfrentamos es cómo escribir un programa que resuelva el enigma para

cualquier cantidad de discos. Comencemos por considerar una solución general para n discos. Si tenemos la solución para n-1 discos, sería evidente que podemos resolver el problema para n discos: resolvemos el enigma para n-1 discos y luego movemos el disco n a la torre C. De modo

Page 21: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-21

similar, si pudiéramos resolver para n-2 discos, el caso n-1 sería más sencillo. Podemos continuar de esta manera hasta llegar al caso trivial donde n = 1: simplemente movemos el disco de la torre A a la torre C. Aunque no sea obvio, lo que acabamos de describir es una solución recursiva para el problema, es decir, resolvimos el problema para una n dada en términos de n-1.

Examinemos un ejemplo concreto y resolvamos el enigma con cinco discos. Supongamos

que sabemos cómo resolverlo con cuatro discos, moviéndolos de la torre A a la torre B (utilizando C como torre auxiliar) Luego, para terminar la solución, sólo debemos mover el disco más grande de la torre A a la torre C y mover los cuatro discos de la torre B a la torre C (usando A como auxiliar).

Podemos resumir la solución de un modo más preciso: 1. Si n = 1, movemos el disco de A a C y nos detenemos. 2. Movemos n-1 discos de A a B utilizando C como auxiliar. 3. Movemos el disco n de A a C. 4. Movemos n-1 discos de B a C usando A como auxiliar.

Observe que los pasos 2 y 4 son recursivos porque nos sugieren que repitamos la

solución para n-1 discos. También observe que las torres cambian su papel conforme avanza la solución.

Ahora que comprendemos la solución, debemos convertir estas reglas en un algoritmo. Diseñaremos una función, llamada torres(), que mostrará los movimientos necesarios para resolver el enigma de acuerdo con un número dado de discos. Su salida estará constituida por comandos de este tipo:

Mueve disco X de torre Y a la torre Z

La función torres() necesita cuatro argumentos. El primero indica la cantidad de discos que se van a usar. Los otros tres determinan el papel que tendrán las tres torres: origen, destino y auxiliar. A continuación se muestra el seudocódigo que soluciona el enigma de Las torres de Hanoi. Observe cómo la rutina cambia el papel de cada torre con cada llamada recursiva. Sin embargo, la función carece de un detalle importante: no revisa valores erróneos que le fueron pasados como argumentos. Dejaremos esto como ejercicio.

torres(entero n, carácter A, carácter B, carácter C) /* n: Cantidad de discos */ /* A: torre origen */ /* B: torre auxiliar */ /*C: torre destino */ INICIO

si(n == 1) Inicio

escribir(“mueve disco ”, n, “ de la torre ”, A, “ a la torre ”, C). regresa.

Fin. /* Mueve n-1 discos de la torre A a la torre B (C es e la torre auxiliar) */ torres(n-1, A, C, B). /* Mueve el enésimo aro de la torre A a la torre C */ escribir(“mueve disco ”, n, “ de la torre ”, A, “ a la torre ”, C).

Page 22: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-22

/* Mueve n-1 discos de la torre B a la torre C (A es la torre auxiliar) torres(n-1, B, A, C). regresar.

FIN. Algoritmo 11.5: Torres

El algoritmo recursivo anterior ofrece una solución clara y compacta al problema de las Torres de Hanoi. Es posible dar una solución iterativa a este problema, pero es conveniente mencionar que la misma es más complicada y extensa. Ejemplo 11.13 El siguiente programa: HANOI.CPP, ilustra el uso del algoritmo 11.5. Torres

/* Este programa: HANOI.CPP, muestra el algoritmo para resolver el enigma de Las torres de Hanoi. */ #include <iostream.h> //Para cout y cin void torres(int n, char A, char B, char C); void main(void) { int discos; char origen = 'A'; char auxiliar = 'B'; char destino = 'C'; cout << "Dame el número de discos: "; cin >> discos; cout << endl; torres(discos, origen, auxiliar, destino); } void torres(int n, char A, char B, char C) /* n: Cantidad de discos */ /* A: torre origen */ /* B: torre auxiliar */ /*C: torre destino */ { if(n == 1) { cout << "Mueve disco " << n << " de la torre " << A << " a la torre " << C << endl; return; } /* Mueve n-1 discos de la torre A a la torre B (C es la torre auxiliar) */ torres(n-1, A, C, B); /* Mueve el enésimo disco de la torre A a la torre C */ cout << "Mueve disco " << n << " de la torre " << A << " a la torre " << C << endl; /* Mueve n-1 discos de la torre B a la torre C (A es la torre auxiliar) */ torres(n-1, B, A, C); return; }

Page 23: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-23

Luego de realizar algunas pruebas, para distintos valores de n, se puede concluir que, para cualquier n, el número de movimientos está dado por la siguiente fórmula:

numeroMovimientos = 2n-1

En la siguiente sección se estudiará cómo pueden plantearse soluciones no recursivas a problemas definidos en forma recursiva, usando pilas.

USO DE LAS PILAS PARA SIMULAR RECURSIVIDAD Se ha presentado a la recursividad como una herramienta poderosa para crear algoritmos claros y concisos para problemas que se definen en términos de sí mismos. También se ha mencionado que el uso de la recursividad puede ser costoso en lo que a espacio de memoria se refiere. Por todo lo dicho y además, en consideración de que muchos lenguajes de programación no cuentan con esta facilidad, resulta conveniente contar con algún procedimiento que permita simular la recursividad. Una función puede tener variables locales y parámetros (argumentos) Cuando se hace una llamada recursiva a la función, los valores actuales de las variables y parámetros deben conservarse. Igualmente debe conservarse la dirección a la cual debe regresar el control una vez ejecutado la función llamada. En la función que se propone para simular recursividad sólo se conservan los valores de las variables y parámetros. Para ello se utilizan pilas. Nuevamente aparece el concepto de pila relacionado con recursividad. Una aplicación de éstas se estudió cuando se analizó la operación interna de la recursividad. Otra aplicación importante es el centro de atención de esta sección: simulación de recursividad mediante el uso de pilas. A continuación se presenta el problema de la Torres de Hanoi, pero con un enfoque no recursivo. Se verá cómo se puede modificar el algoritmo antes presentado para obtener una versión no recursiva del mismo. El algoritmo 11.5: Torres, trabaja con cuatro parámetros: n, origen, destino, auxiliar (el número de discos y los nombres de las tres torres que intervienen en el problema) Si se quieren conservar los valores de estos parámetros en cada llama, se deberá definir una pila para cada uno de ellos. (Otra alternativa sería definir una pila única, en la que cada elemento de la misma fuera capaz de almacenar los cuatro parámetros) Aquí se definirán cuatro pilas de trabajo:

pilaN: para almacenar imágenes de n. pilaO: para almacenar imágenes de origen. pilaD: para almacenar imágenes de destino. pilaX: para almacenar imágenes de auxiliar.

También será necesario manejar un tope para cada una de las pilas. El algoritmo siguiente es una solución iterativa al problema de las Torres de Hanoi.

hanoiIterativo(n, origen, destino, auxiliar) INICIO

/* Este algoritmo resuelve el problema de las torres de Hanoi de manera no recursiva. origen, destino y auxiliar son variables que almacenan los nombres de las tres torres, n representa el número de discos.

Page 24: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-24

pilaN, pilaO, pilaD y pilaX son estructuras de datos tipo pila. tope es una variable de tipo entero. varAux es una variable de tipo carácter. bandera es una variable de tipo booleano. */ tope = 0. bandera = falso. mientras(n > 0) Y (bandera = falso)

Inicio mientras(n > 1)

Inicio /* Guardar en las pilas los valores actuales de los parámetros. */ tope = tope + 1. pilaN[tope] = n. pilaO[tope] = origen. pilaD[tope] = destino. pilaX[tope] = auxiliar. /* Simular llamada a Hanoi con n-1, origen, auxiliar y destino. */ n = n – 1. varAux = destino. destino = auxiliar. auxiliar = varAux.

Fin.

escribir(“Mover un disco de “, origen, “ a “, destino). bandera = verdadero. si(tope > 0) entonces // Las pilas no están vacías.

Inicio // Se extraen los elementos de tope de las pilas. n = pilaN[tope]. origen = pilaO[tope]. destino = pilaD[tope]. auxiliar = pilaX[tope]. tope = tope – 1. escribir(“Mover un disco de “, origen, “ a “, destino). /* Simular llamada a Hanoi con n – 1, auxiliar, destino y origen. */ n = n – 1. varAux = origen. origen = auxiliar. auxiliar = varAux. bandera = falso.

Fin. Fin.

FIN. Algoritmo 11.6: hanoiIterativo

Page 25: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-25

A continuación se presenta un conjunto de tablas donde se analiza el algoritmo 11.6: hanoiIterativo para diferentes valores de n. Todas las tablas tienen las mismas columnas y éstas representan lo siguiente:

C1: representa el parámetro origen. C2: representa el parámetro destino. C3: representa el parámetro auxiliar. C4: representa la variable tipo pilaN. C5: representa la variable tipo pilaO. C6: representa la variable tipo pilaD. C7: representa la variable tipo pilaX C8: representa los movimientos de disco entre dos torres. Los escribiremos [x:y], y se

interpretarán como que un disco ha sido movido de la torre x a la torre y. C9: representa la variable de control bandera, la cual puede tomar los valores de v

(verdadero) y f (falso). En la tabla 11.7 se expone un seguimiento del algoritmo para n = 1.

Paso N C1 C2 C3 tope C4 C5 C6 C7 C8 C9 0 1 A B C 0 Falso 1 [A:B] Verdadero

Tabla 11.7. Solución iterativa del problema de las torres de Hanoi.

En el paso 1 se escribió el movimiento necesario para alcanzar el estado final (C8), y se asignó a la variable de control el valor verdadero (C9) Como una de las dos condiciones evaluadas en el algoritmo es falsa, hemos llegado a la solución del problema. En la tabla 11.8 se presenta un seguimiento del algoritmo 11.6: hanoiIterativo para n = 2.

Paso n C1 C2 C3 tope C4 C5 C6 C7 C8 C9 0 2 A B C 0 Falso 1 1 2 A B C 2 1 A C B 3 [A:C] Verdadero 4 2 A B C 0 [A:B] 5 1 C B A Falso 6 [C:B] Verdadero

Tabla 11.8. Solución iterativa del problema de las Torres de Hanoi

En el primer paso se guarda en las pilas (C4, C5, C6 y C7) una imagen de los datos (n, origen, destino y auxiliar) Se preparan las variables para realizar el movimiento de los (n – 1) discos superiores de la torre origen a la torre auxiliar (C1, C2 y C3), paso 2. En el paso 3 se escribe el movimiento de la torre origen a la torre destino (C8) En el paso 4 se extraen los elementos del tope de las pilas y se asignan a sus correspondientes variables (n, C1, C2 y C3), con lo que se recuperan los valores almacenados en ellas en el paso 1. Se escribe también el movimiento de la torre origen a la torre destino (C8) En el paso 5 se preparan las variables para realizar el movimiento de los (n – 1) discos superiores de la torre auxiliar a la torre destino (C1, C2 y C3) En el paso 6 se realizó el último movimiento de disco, necesario para alcanzar el estado solución (C8)

Page 26: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-26

En la tabla 11.9 se presenta un seguimiento del algoritmo 11.6: hanoiIterativo para n = 3.

Paso n C1 C2 C3 tope C4 C5 C6 C7 C8 C9 0 3 A B C 0 Falso 1 1 3 A B C 2 2 A C B 3 2 2 A C B 4 1 A B C 5 [A:B] Verdadero 6 2 A C B 1 [A:C] 7 1 B C A Falso 8 [B:C] Verdadero 9 3 A B C 0 [A:B]

10 2 C A B Falso 11 1 2 C B A 12 1 C A B 13 [C:A] Verdadero 14 2 C B A 0 [C:B] 15 1 A B C Falso 16 [A:B] Verdadero

Tabla 11.9. Solución iterativa del problema de las Torres de Hanoi Una simple comparación entre los métodos recursivos e iterativos permite concluir que la solución recursiva del problema de las Torres de Hanoi es mucho más compacta y más fácil de comprender que la solución no recursiva. De cualquier manera es importante contar con un formalismo para traducir procesos recursivos en procesos no recursivos, porque, como ya se mencionó más arriba, algunos lenguajes no tienen esta herramienta de programación y además en ciertos problemas, el costo de usar recursividad es significativo. EL PROBLEMA DE LAS OCHO REINAS

Otro ejemplo clásico de programación recursiva es el enigma de Las ocho reinas (o damas) El problema es colocar ocho reinas en un tablero de ajedrez de tal forma que ninguna ataque a las demás. En ajedrez una reina puede comerse a cualquier pieza desplazándose cualquier cantidad de casillas (escaques) por una fila, una columna o en diagonal( véase la figura 11.7). Por lo tanto, el dilema es colocar las ocho reinas en un tablero 8 x 8 sin que compartan la misma fila, columna o diagonal. Intente resolver manualmente el enigma antes de continuar.

Para comenzar nuestra solución, vamos a suponer que desarrollamos el procedimiento reina(), que intentará colocar una reina en una fila mediante su propio parámetro fila. De este modo la función analizará todos los escaques de la fila especificada y, tras localizar uno que no esté bajo ataque, colocará la reina ahí; luego ella misma se llamará de modo recursivo para colocar otra reina en la siguiente fila. Si puede colocar las ocho reinas en el tablero, la función devolverá el valor RESUELTO. Si todos los escaques de la fila indicada están bajo ataque, entonces devolverá FALLO. Comencemos a esbozar el algoritmo (observe que para facilitar la programación, las filas y las columnas se indexarán de 0 a 7)

Page 27: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-27

reina(fila) INICIO

para i = 0 hasta i < 8 i incrementado en 1 //Revisa cada columna si (aSalvo(fila, i) //Prueba escaque Inicio Colocar la reina en el tablero: fila, i. si(reina(fila+1) == RESUELTO) regresa RESUELTO. Fin. regresa(FALLO). //Todos los escaques están bajo ataque

FIN.

Figura 11.7 Filas, columnas y diagonales de ataque de la reina.

Es evidente que la descripción está incompleta. Primero, necesitamos una condición para detener la recursión. Pensemos en esto unos instantes. Sabemos que, por definición, hay un máximo de ocho reinas en el enigma. Por lo tanto, podemos revisar fila > 7 desde el comienzo de la función; Pero reflexionemos un momento en la importancia del valor que contiene el argumento fila. Si hacemos una llamada recursiva a reina() con fila equivalente a un valor n, significa que hemos resuelto de la fila 0 a la fila n-1. Por lo tanto, si debemos llamar a reina() cuando fila = 8, significa que todas las demás reinas (filas 0 a 7) ya fueron colocadas y la función debe devolver el valor RESUELTO.

Después necesitamos una forma de registrar la posición de las reinas a medida que

procede la función. Para hacerlo, utilizaremos un arreglo de caracteres de 8 x 8, que hará las veces de tablero. En cada posición, guardaremos (con propósito de mostrarlo) uno de los siguientes caracteres: - (guión) para indicar un escaque vacío; * (asterisco) para señalar un escaque donde se encuentra una reina.

Por último, debemos definir la función aSalvo(), que determina si un escaque específico

está bajo ataque. Pero pospongamos el análisis de aSalvo() hasta que no terminemos de definir reina()

Incorporemos los cambios que hemos sugerido y veamos cómo toma forma nuestra

función:

R

Page 28: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-28

reina(fila) INICIO

si(fila > 7) //Estado de salida regresa(RESUELTO).

//Revisa cada columna: de 0 a 7 para i = 0 hasta i < 8 en incrementos de uno en uno de i

//Verifica si el escaque esta o no bajo ataque si(aSalvo(fila, i))

Inicio //Coloca la reina en el tablero tablero[fila][i] = REINA. //Prueba la fila siguiente si(reina(fila + 1) = RESUELTO

regresa(RESUELTO). si no

tablero[fila][i] = VACIA. Fin. regresa(FALLO). FIN.

Algoritmo 11.7: reina

Observe que hemos añadido la instrucción:

tablero[fila][i] = VACIA.

porque si alguna llamada recursiva a reina() falla en encontrar una solución (con una reina ubicada en esa posición), esta instrucción restaura el tablero a su estado anterior; Luego la función queda en libertad para revisar el siguiente escaque disponible.

Nuestro algoritmo empieza a tomar forma, por lo que ahora debemos analizar cómo implementar la función aSalvo() El problema que debemos plantear es cómo determinará la función si un escaque específico está bajo ataque de una reina colocada con anterioridad. Primero, observe que, en realidad, no es necesario revisar posibles ataques a lo largo de las filas. Gracias a nuestra implementación, podemos tener la certeza de que la única reina que puede permanecer en una fila es la que intentamos colocar. Además, la revisión de ataques desde las columnas puede hacerse de modo directo, burdamente, indexando el tablero junto con la columna que vamos a revisar. Los ataques diagonales son los más difíciles de resolver. Como se ilustra en la figura 11.8, una reina colocada en cualesquiera de los escaques rojos sería atacada por la reina colocada en el escaque [3,3] ¿Qué tan fácil podemos determinar si un escaque está bajo ataque a lo largo de sus dos diagonales (ascendente y descendente)?

Page 29: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-29

Diagonal ascendente 0 1 2 3 4 5 6 7

Figura 11.8 Ataques diagonales Diagonal descendente Si mira con atención el tablero de la figura 11.8, observará que cada diagonal puede

identificarse de modo único como una función de sus índices. Por ejemplo, examinemos la figura 11.9a. La suma de los índices (fila + columna) de cada escape en la diagonal ascendente es igual a 6. Por lo tanto, cualquier reina que haya sido colocada antes en un escaque cuyos índices sumen 6 estará bajo el ataque de la casilla [3,3] De modo similar, podemos derivar un valor único para las diagonales descendentes (véase la figura 11.9b.) restando los índices (columna – fila). Observe que cada una de las 15 diagonales ascendentes tienen un intervalo de valores que va de 0 a 14, y las diagonales descendentes uno de –7 a 7.

0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7

Figura 11.9a Diagonal Ascendente Figura 11.9b Diagonal descendente Podemos integrar este concepto en nuestra función aSalvo() La idea es que cada vez que

coloquemos una reina en el tablero actualicemos dos arreglos: uno que registre la diagonal ascendente y otro la descendente. El índice de cada arreglo será el valor de índice de cada diagonal (por comodidad para programar, sumaremos 7 al valor del índice de la diagonal descendente) Así, aSalvo() sólo necesita revisar el arreglo de escaques apropiado para determinar si uno de éstos está bajo ataque desde alguna de sus diagonales. Debemos extender esta idea para rastrear ataques a lo largo de las columnas. En este caso, sólo usamos el valor de la columna como índice en el tercer arreglo.

R

6

6

6 9

R 9

6 9

6 9

6 9

9

0

0 -2 0

-2 0 -2 0

-2 0 -2 0

-2 0

0

1

2

3

4

5

6

7

0

1

2

3

4

5

6

7

0

1

2

3

4

5

6

7

Page 30: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-30

Ejemplo 11.14

A continuación se muestra la solución completa del enigma, mediante el programa REINAS.CPP. Este programa conduce la rutina; inicializa el tablero y los arreglos de las banderas; luego llama a reinaSiguiente() para que resuelva el enigma. Si esta última función devuelve RESUELTO, REINAS.CPP también llama a muestraTablero() para que imprima la solución.

/* Estre programa: REINAS.CPP, resuelve el problema de ubicar 8 reinas dentro de un tablero de ajedrez, sin que se ataquen la una con la otra. */ #include <iostream.h> #define FALLO 0 #define RESUELTO 1 #define VACIO '-' #define REINA '*' /* Banderas para revisar filas y diagonales */ char columnas[8]; char diagonalAscendente[15]; char diagonalDescendente[15]; char tablero[8][8]; int reinaSiguiente(int fila); void ponerBanderas(int fila, int columna); void restablecerBanderas(int fila, int columna); int aSalvo(int fila, int columna); void muestraTablero(); void main(void) { int i, j; /* Inicializa tablero y banderas */ for(i = 0; i < 8; i++) for(j = 0; j < 8; j++) tablero[i][j] = VACIO; for(i = 0; i < 15; i++) { diagonalAscendente[i] = VACIO; diagonalDescendente[i] = VACIO; } for(i = 0; i < 8; i++) columnas[i] = VACIO; /* Intenta resolver el problema de las ocho reinas */ if(reinaSiguiente(0) == RESUELTO) muestraTablero(); else cout << "¡No se hallaron soluciones!" << endl; }

Page 31: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-31

int reinaSiguiente(int fila) { int i; if(fila >7) return(RESUELTO); /*Revisa cada columna */ for(i = 0; i < 8; i++) if(aSalvo(fila, i) == 1) { tablero[fila][i] = REINA; ponerBanderas(fila, i); if(reinaSiguiente(fila + 1) == RESUELTO) return(RESUELTO); else { /* Restablece el tablero y revisa el siguiente escaque */ tablero[fila][i] = VACIO; restablecerBanderas(fila, i); } } /* No hay escaques salvos - regresa */ return(FALLO); } /* Pone banderas en columnas y diagonales */ void ponerBanderas(int fila, int columna) { columnas[columna] = REINA; diagonalAscendente[fila + columna] = REINA; diagonalDescendente[(fila - columna) + 7 ] = REINA; } /* Restablece banderas en columnas y diagonales */ void restablecerBanderas(int fila, int columna) { columnas[columna] = VACIO; diagonalAscendente[fila + columna] = VACIO; diagonalDescendente[(fila - columna) + 7 ] = VACIO; } int aSalvo(int fila, int columna) { if(columnas[columna] == REINA || diagonalAscendente[fila + columna] == REINA || diagonalDescendente[(fila - columna) + 7] == REINA) return(0); return(1); /* A salvo */ }

Page 32: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-32

La rutina ponerBanderas() calcula los valores de columna y diagonal y establece las

banderas apropiadas del arreglo; se le llama cada vez que se coloca una reina en el tablero. El procedimiento restablecerBanderas() restaura las banderas de columna y diagonal cada vez que quitamos una reina del tablero (es decir, después de FALLO) La función aSalvo() revisa las banderas asociadas con cada posición en el tablero; si devuelve 1, el escaque no sufre ataque alguno.

CASOS INTERESANTES Ejemplo 11.15. Información genealógica

En un árbol binario se ha guardado la información genealógica de una persona dada P. Para almacenar los datos se tuvo en cuenta el siguiente criterio:

• La rama izquierda almacena los datos de la madre de P y la rama derecha almacena los datos del padre de P.

Este criterio vuelve a aplicarse para guardar los datos genealógicos de la madre y del

padre de P. Lo que nos lleva a una formulación recursiva del problema.

Figura 11.10. Estructura de datos recursiva: árbol Supóngase que es necesario conocer los nombres de todos los antepasados masculinos de P. ¿Cómo obtenerlos? El primer ascendiente hombre de P es su padre y está guardado en la raíz del subárbol derecho. A su vez, el nombre del padre del padre de P (abuelo de P) está en el nodo raíz del subárbol

void muestraTablero() { int i, j; cout << endl; for(i = 0; i < 8; i++) { for(j = 0; j < 8; j++) cout << tablero[i][j]; cout << endl; } }

P

ANA

JOSÉ LINA

LUIS

JUAN ROSA

Page 33: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-33

derecho del padre de P. Así se podría seguir hasta que ya no se tenga información disponible. Ahora bien, el problema plantea la necesidad de conocer los nombres de todos los antepasados masculinos, lo cual incluye al padre de la madre de P y en general a todos los hombres de la familia materna de P. Dado el árbol genealógico de la figura 11.10, los nombres que deberán imprimirse son: Luis, Juan, ..., José, ... , etc. (no consideramos el orden en que se haga). Para conocer estos nombres debemos tener en cuenta lo siguiente: 1. Se deberá recorrer el árbol nodo por nodo, imprimiendo sólo los nombres que interesan. 2. Si el nodo analizado es raíz del subárbol derecho, entonces debe imprimirse la información

que contiene. 3. Si el nodo analizado es raíz del subárbol izquierdo, entonces no debe imprimirse la

información que contiene. 4. Tanto en el punto 2 como en el 3, se debe seguir recorriendo el árbol a través de sus dos

ramas. 5. El proceso termina cuando el árbol haya sido recorrido en su totalidad. El siguiente algoritmo presenta una solución recursiva a este problema.

paterno(nodo) INICIO

/* nodo es un dato tipo puntero. Inicialmente tiene la dirección de la raíz del árbol. */ si(nodo^.der ≠≠≠≠ NIL) entonces

Inicio q = nodo^.der. escribir(q^.info). llamar a paterno con q // Llamada recursiva // Recorre el árbol por su rama derecha.

Fin.

si(nodo^.izq ≠≠≠≠ NIL) entonces Inicio

Llamar a paterno con nodo^.izq //Llamada recursiva // Recorre el árbol por su rama izquierda.

Fin. FIN.

Algoritmo 11.8: paterno

En la tabla 11.10 se presenta el seguimiento del algoritmo 11.8: paterno para el árbol de la figura 11.10.

En el primer paso, como p^.der ≠≠≠≠ NIL, se imprime la información del nodo raíz del subárbol derecho y se llama nuevamente al algoritmo con la rama derecha. Queda pendiente la ejecución del punto 3 del algoritmo, el recorrido del subárbol izquierdo (se guarda en la pila) En el paso 3, el nodo dato es el que contiene a Juan. Como este nodo no tiene ni rama izquierda ni rama derecha, se termina el recorrido de ese subárbol. En el paso 4 se extrae de la pila el elemento del tope. Pero éste es un nodo terminal, por lo tanto no hay nodos que revisar. En cambio cuando se extrae de la pila el nodo Ana, se continúa con el recorrido del árbol porque éste no es un nodo terminal, paso 5. Se imprime la información del nodo raíz del subárbol derecho, y se sigue con el recorrido del árbol hasta que se hayan revisado todos los nodos.

Paso Nodo actual Nodo a visitar Impresión Pila

1 P p^.der→→→→LUIS LUIS p^.izq→→→→ANA 2 LUIS LUIS^.der→→→→JUAN JUAN LUIS^.izq→→→→LINA 3 JUAN 4 LINA 5 ANA ANA^.der→→→→JOSE JOSE ANA^.izq→→→→ROSA 6 JOSE 7 ROSA

Tabla 11.10. Recorrido de un árbol genealógico

Page 34: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-34

Ejemplo 11.16. Árboles similares

Dados los árboles binarios A y B, se desea determinar si los mismos son similares. Se dice que dos árboles binarios son similares si tienen la misma estructura. En la figura 11.11 se presentan dos árboles binarios similares.

Figura 11.11. Estructura de datos recursivo: árbol

Para determinar si dos árboles binarios A y B son similares, deben tenerse en cuenta los siguientes puntos:

1. Un nodo terminal es similar a otro nodo terminal 2. El subárbol izquierdo de A debe ser similar al subárbol izquierdo de B 3. El subárbol derecho de A debe ser similar al subárbol derecho de B 4. Si se pueden comprobar los puntos 2 y 3, entonces los árboles A y B son similares

El siguiente algoritmo presenta una solución recursiva a este problema, similares(A, B)

INICIO si(A = NIL) Y (B = NIL) entonces

similares = verdadero. si no

Inicio si((A ≠≠≠≠ NIL) Y (B = NIL)) O ((A = NIL) Y (B ≠≠≠≠ NIL)) entonces

similares = falso. si no

Inicio similares = (similares con A^.izq y B^.izq) y

(similares con A^.der y B^.der). // Llamada recursiva.

Fin. Fin.

FIN. Algoritmo 11.9:similares

En la tabla 11.11 se presenta un seguimiento del algoritmo 11.9 para los árboles de la figura 11.11.

A

A2 A1

A22 A21 A11

B222

B22 B21 B11

B2 B1

B

A222

Page 35: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-35

Paso Nodos actuales

A B Nodos a Visitar

Similares izq der

pila

1 A B A^.izq→→→→ A1

B^.izq→→→→ B1

A^.der→→→→ A2

B^.der→→→→ B2 2 A1

B1 A1^.izq→→→→ A11

B1^.izq→→→→ B11 A1^.der→→→→ NIL

B1^.der→→→→ NIL 3 A11 B11 A11^.izq→→→→ NIL

B11^.izq→→→→ NIL A11^.der→→→→ NIL

B11^.der→→→→ NIL 4 NIL NIL V 5 NIL NIL V 6 NIL NIL V 7 A2 B2 A2^.izq→→→→ A21

B2^.izq→→→→ B21 A2^.der→→→→ A22

B2^.der→→→→ B22 8 A21 B21 A21^.izq→→→→ NIL

B21^.izq→→→→ NIL A21^.der→→→→ NIL

B21^.der→→→→ NIL 9 NIL NIL V

10 NIL NIL V 11 A22

B22 A22^.izq→→→→ NIL

B22^.izq→→→→ NIL A22^.der→→→→ A22

B22^.der→→→→ B22 12 NIL NIL V 13 A222

B22 A222^.izq→→→→ NIL

B222^.izq→→→→ NIL A222^.der→→→→ NIL

B222^.der→→→→ NIL 14 NIL NIL V 15 NIL NIL V

Tabla 11.11. Similitud entre árboles binarios

En los pasos 1, 2 y 3 los nodos analizados no son iguales a NIL, por lo que se sigue el recorrido del árbol a través de las ramas izquierdas, almacenando en la pila el recorrido de las ramas derechas. En el paso 4 los nodos analizados son iguales a NIL, por lo que se asigna un valor booleano verdadero a la columna izq, indicando que existe similitud entre dos subárboles izquierdos (subárbol izquierdo de A11 y subárbol izquierdo B11) En el paso 5 los nodos analizados son iguales a NIL, por lo que se asigna un valor booleano verdadero a la columna der indicando que existe similitud entre dos subárboles derechos (subárbol derecho de A11 y subárbol derecho de B11). De los pasos 4 y 5 se puede afirmar que los subárboles A11 y B11 (subárboles izquierdos de A1 y B1, respectivamente) son similares. En el paso 6 se comprueba que los subárboles derechos de A1 y B1 son similares. Como ya se comprobó que sus subárboles izquierdos son similares, se puede afirmar que A1 y B1 son similares. En los siguientes pasos se comprueba que los subárboles A2 y B2 son similares, y se afirma finalmente que los árboles A y B son similares. A continuación se presenta un ejemplo sencillo de dos árboles binarios que no son similares.

Page 36: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-36

Ejemplo 11.17. Árboles no similares En la tabla 11.12 puede verse un seguimiento del algoritmo, para los árboles X y Y de la figura 11.12.

Paso Nodos actuales A B

Nodos a visitar

Similares izq der

pila

1 X Y X^.izq→→→→ X1

Y^.izq→→→→ Y1 X^.der→→→→ X2

Y^.der→→→→ Y2 2 X1

Y1 X1^.izq→→→→ NIL

Y1^.izq→→→→ NIL X1^.der→→→→ NIL

Y1^.der→→→→ NIL 3 NIL NIL V 4 NIL NIL V 5 X2

Y2 X2^.izq→→→→ NIL

Y2^.izq→→→→ NIL X2^.der→→→→ NIL

Y2^.der→→→→ Y22 6 NIL NIL V 7 NIL Y22 F

Tabla 11.12. Similitud entre árboles binarios

Figura 11.12. Estructura de datos recursiva: árbol

En el paso 4 se comprueba que los subárboles izquierdos de X y Y son similares(1), ya que los dos son nodos terminales (X1 y Y1) En el paso 7 se comprueba que los subárboles derechos de X y Y no son similares(2), debido a que sus ramas derechas no lo son. Considerando que los resultados parciales (1) y (2) están relacionados por el operador Y (AND), podemos concluir entonces que X y Y no son similares.

RETROCESO En el ejemplo anterior describimos un método de programación con el que se examinaron posibles vías de solución. Este método es una forma de retroceso (backtracking) El retroceso es una técnica de programación que avanza por una vía específica en busca de la meta. En cada bifurcación (fork) del camino, elige qué vía seguir. Si alguna elección resulta equivocada, retrocede; es decir, regresa a la bifurcación anterior y sigue la otra vía. La ejecución continúa de este modo hasta que se alcanza la meta o se terminan las posibilidades. Esto último significa que no existe solución y el programa debe terminar con una condición que lo indique. PROGRAMACIÓN NO DETERMINISTA El retroceso es una técnica para escribir el código que pertenece a una clase más general denominada programación no determinista (Non – Deterministic Programming, o NDP) En el diseño convencional de software se programan todos los pasos necesarios para obtener el resultado esperado; esto implica que, a priori, existe el conocimiento exacto de la solución y que el problema es, en sí mismo, resoluble de modo algorítmico. Por lo tanto, a medida que se

X

X2 X1

Y22

Y2 Y1

Y

Page 37: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-37

ejecuta adecuadamente cada instrucción, el programa se acerca poco a poco al resultado esperado. La programación no determinista es diferente porque no escribimos el código de la solución. En lugar de ello, se programa el método por el que obtendremos la solución (si existe alguna) De hecho, no presuponemos que existe una solución. El programa elige, literalmente, una y otra vez hasta que encuentra una solución o agota todas las posibilidades disponibles. Además, puede haber cero, una o muchas soluciones para un problema específico. Este método de programación tiene beneficios evidentes en aplicaciones de inteligencia artificial en el desarrollo de sistemas expertos . RETROCESO CRONOLÓGICO

Hay dos tipos de retroceso: el retroceso cronológico (Backtracking o CBT) y el

retroceso de dependencia dirigida (Dependency-Directed Bactracking o DDB) El primero es eficaz en búsquedas exhaustivas, similares a las del análisis anterior. Cada vía de solución se examina exhaustivamente hasta que se determinan una o dos soluciones. Por poner un caso, considere el siguiente seudocódigo:

1: retroceso(nodo) 2: INICIO 3: si(nodo = RESUELTO) entonces 4: regresar(EUREKA).

5: para (cadaPosibilidadEnEsteNodo) 6: Inicio 7: estadoRetroceso = retroceso(nodoHijo). 8: si(estadoRetroceso = RESUELTO) entonces 9: regresar(estadoRetroceso). 10: Fin. 11: regresar(FALLO). 12: FIN. Si en algún momento encuentra una solución (líneas 3-4, 7-9), la función devuelve un

valor que lo indica. De otro modo, debe probar otra posibilidad (líneas 5-10) Si agota todas las posibilidades (línea 5), devuelve un valor indicando que falló, forzando la llamada anterior de la función a que regrese a la vía anterior (línea 11) antes de que prosiga la búsqueda.

Hay dos puntos importantes en los que debemos reflexionar. Primero, siempre que

hagamos una copia de seguridad, o respaldo, debemos restaurar el entorno al estado anterior antes de probar la siguiente vía. Guardar y restaurar el estado de los datos puede llegar a ser muy costoso. Segundo, el retroceso comúnmente produce un algoritmo exponencial en cuanto a magnitud de ejecución. Las secciones siguientes analizan métodos para mejorar el rendimiento de esta técnica.

RETROCESO DE DEPENDENCIA DIRIGIDA En esencia, este tipo de dependencia funciona como describimos con anterioridad, pero intenta eliminar búsquedas innecesarias (y, por lo tanto, respaldos innecesarios), lo que se logra de dos formas. Primera, como lo indica su nombre, el retroceso de dependencia dirigida puede

Page 38: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-38

rastrear todas las posibilidades hasta llegar a un callejón sin salida, esto es, puede regresar hasta el punto donde se creó una dependencia, o rama, y continuar la búsqueda desde ahí. Como ejemplo de esta técnica, consideremos un caso en que buscamos una solución que necesita que se cumplan cuatro condiciones (A, B, C y D) para que el programa devuelva un valor de resuelto. Supongamos que hemos llegado a un punto del procesamiento en el que se satisfacen las condiciones A y B, pero no C y D. En lugar de que el rastreo regrese de modo automático hasta la siguiente rama, prosigue en el punto donde se cumplen A y B. Así, podemos omitir las vías, o ramas, que hay hasta ese punto. El segundo método para eliminar búsquedas innecesarias se denomina poda. Si llegamos a un punto, o rama, en donde es evidente que cualquier búsqueda por otras vías será infructuosa, podemos eliminar estas otras vías a partir de ese punto (es decir, forzar el retroceso) La poda es un procedimiento directo que a menudo se utiliza en simulaciones de práctica de juegos. Por ejemplo, podemos escribir un programa de ajedrez que determine su siguiente movimiento asignando un valor cuantificable a cada posición del tablero que examina. En algún punto específico elegirá el movimiento que le dé el valor más ventajoso (el mayor) Si el algoritmo recorriera una vía que representara los movimientos que tuviera que seguir la reina para comerse un peón, primero eliminaría cualquier otra vía (de búsqueda) que no la llevara a conseguir su cometido. Para completar este apartado, debemos abordar un tercer método para mejor el procedimiento de retroceso: el manejo de una pila explícita. Los procedimientos recursivos son costosos, cosa que puede atribuirse a la cantidad de procesamiento adicional inherente a cada llamada. El entorno de ejecución debe guardar registros, almacenar una dirección de retorno, reservar espacio de almacenamiento local, etc., para recibir el valor devuelto. Casi nada de esta información está relacionada de modo directo con el problema que se intenta resolver y, por consiguiente, el guardarla y restaurarla desperdicia ciclos de la unidad de procesamiento central (Central Processing Unit o CPU) Podríamos ahorrar tiempo y espacio si escribiéramos la pila explícitamente, circunstancia que podemos lograr transformando el algoritmo recursivo en iterativo y manteniendo la lista de pendientes en una pila controlada por aplicación. USO DE LA RECURSIVIDAD Una vez que se comprende la técnica de la recursión, la pregunta planteada con mayor frecuencia es cuándo emplearla. Comencemos por analizar cuándo no utilizarla. Por definición, todas las funciones recursivas tienen una solución iterativa correspondiente. Con pocas excepciones, las soluciones iterativas son más eficaces que sus contrapartes recursivas. Por lo tanto, no debe usar la recursividad cuando el desempeño del tiempo de ejecución sea vital. Pero esto no es todo. Empleada con propiedad, la recursividad no es menos eficaz que el uso de llamadas a procedimiento donde es adecuado. Por ejemplo, varios estudios han mostrado que para algunos algoritmos de clasificación, una solución recursiva es solo 2% más lenta que su contraparte iterativa, diferencia insignificante si consideramos, sobre todo, la velocidad de los procesadores actuales. Sin embargo, hay dos casos en los que el uso de la recursión puede causar un deterioro considerable en la ejecución:

Page 39: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-39

1. El algoritmo realiza cálculos redundantes. La implementación recursiva del algoritmo de Fibonacci es un ejemplo claro de este problema. Cuando llama a fibonacciRecursivo() para calcular F(n), estima el valor de F(n-2) dos veces: una durante la llamada inicial, y otra cuando hace una llamada recursiva para calcular F(n – 1) De modo similar, estima F(n-3), tres veces, F(n-4), cuatro veces y así sucesivamente.

2. La recursión llega a tener muchas anidaciones. Este problema está resaltado con claridad en la

función factorialRecurrente() Observe que al calcular n!, la profundidad de la recursión (es decir, la cantidad de llamadas anidadas) que tiene la función es 0(n). para valores muy grandes de n, esto puede producir demandas excesivas del entorno y tiempo de ejecución de la máquina. De hecho si obviamos todos los demás problemas (por ejemplo, el desbordamiento de los enteros), con un valor de n suficientemente grande, la función no podría acceder a recursos suficientes (por ejemplo, memoria y pila) para calcular la solución en algunos sistemas. Compare este comportamiento con el de la función reina(). La profundidad de su recursión nunca rebase el noveno periodo.

Además de cualquier otra consideración, no debe utilizar la recursividad cuando cada

llamada sucesiva produzca una mayor tarea. Cada llamada recursiva debe recibir una cantidad menor de trabajo.

Emplee la recursividad cuando el problema tenga, en sí mismo, una solución recursiva.

Esto es común en las fórmulas matemáticas (por ejemplo, en las relaciones recursivas). Úsela también cuando procese una estructura de datos definida recursivamente (árboles binarios, por decir algo) o cuando pueda resolver un problema con el método de arriba-abajo (top-down) Recuerde que la implementación recursiva de un algoritmo es casi siempre menor y, por lo tanto, casi siempre más económico desarrollarla y mantenerla.

LO QUE NECESITA SABER Antes de continuar con la siguiente lección, asegúrese de haber comprendido los siguientes conceptos:

!!!!""""Una función recursiva es aquella que se llama a si misma, sea directa o indirectamente. !!!!""""La recursividad es una poderosa técnica de programación. Si se le usa de modo apropiado produce

algoritmos simples y fáciles de mantener. !!!!""""Uno de los aspectos más importantes de un algoritmo recursivo es la terminación. Todas las

funciones recursivas necesitan un estado de terminación (o de salida) para detener la recursión y vaciar la pila.

!!!!""""Si la llamada a una función recursiva es mediante un caso base, dicha función simplemente devuelve un resultado. Si se le llama con un problema más complejo, lo divide en dos partes conceptuales; una que sabe cómo resolver y una versión ligeramente más pequeña del problema original. Debido a que este nuevo problema se parece al original, la función lanza una llamada recursiva que se encargará del problema más pequeño.

!!!!""""Para que termine una recursión, es necesario que cada vez que la función recursiva se llame a sí misma con una versión ligeramente más sencilla del problema original, la secuencia de problemas cada vez más pequeños termine por converger en el caso base. Cuando la función reconoce el caso base, el resultado se le devuelve a la llamada de función previa y sucede una secuencia de retornos que regresa hasta la primera llamada de la función, devolviendo el resultado final.

!!!!""""El estándar ANSI no especifica el orden en que deben evaluarse los operandos de la mayoría de los operadores. C++ especifica el orden de evaluación de los operandos de los operadores &&, ||, coma (,) y ? :. Los primeros tres son operadores binarios cuyos operandos se evalúan de izquierda a

EXAMEN BREVE 30

Page 40: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-40

derecha. El último es el único operador ternario de C++. Su operando de la izquierda se evalúa primero; si es diferente de cero, se evalúa el central y se ignora el último, si es cero, se evalúa el tercero y se ignora el central.

!!!!""""Tanto la iteración como la recursión se basan en una estructura de control: la iteración se vale de una estructura de repetición y la recursión de una estructura de selección.

!!!!""""Tanto en la iteración como en la recursión interviene la repetición: La iteración emplea explícitamente una estructura de repetición; la recursión logra la repetición a través de llamadas de función repetidas.

!!!!""""La iteración y la recursión comprenden una prueba de terminación: la iteración termina cuando falla la condición de continuación de ciclo; la recursión cuando se reconoce un caso base.

!!!!""""La iteración y la recursión pueden suceder infinitamente: sucede un ciclo infinito con la iteración si la prueba de continuación del ciclo nunca se vuelve falsa; sucede una recursión infinita si el paso recursivo no reduce el problema de manera que converja en el caso base.

!!!!""""La recursión invoca repetidamente el mecanismo, y en consecuencia la sobrecarga, de las llamadas de función. Esto puede tener un alto precio, tanto en tiempo de procesador como en espacio de memoria.

!!!!""""Una poderosa técnica de programación que emplea la recursividad es el retroceso (backtracking) Usted puede mejorar el desempeño de los algoritmos de retroceso mediante varias técnicas como la poda y el manejo explícito de la pila.

PREGUNTAS Y PROBLEMAS PREGUNTAS 1. Explique lo que es recursividad. 2. ¿Cuándo deberá considerar una solución recursiva para un problema? 3. Llene los siguientes espacios en blanco:

a) Las funciones que se llaman a sí mismas, directa o indirectamente, son funciones ________. b) Una función recursiva por lo general tiene dos componentes: uno que proporciona el medio para que

termine la recursión, probando si sucede un caso _________ y otro que expresa el problema como llamada recursiva para un problema ligeramente más sencillo que el original.

4. Describa la técnica de programación llamada retroceso. PROBLEMAS 1. Escriba un programa con una función recursiva para resolver n!, para n ≥≥≥≥ 0. 2. Escriba un programa con una función recursiva que calcule el número Fibonacci n, para n ≥≥≥≥ 0. 3. La función de Ackermann se define de la siguiente manera:

−−=−=+

casootroEnNMAMANSIMAMSIN

NMA)1,(,1(

0)1,1(01

),(

Elabore un algoritmo recursivo para calcular A(M,N), para M y N ≥≥≥≥ 0. 5. El algoritmo de Euclides para el cálculo del máximo común divisor (MCD) se define de la siguiente manera:

>=0),(0

),(NSiNMODMNMCDNSiM

NMMCD

Escriba una función recursiva que calcule MCD(M,N), para M y N ≥≥≥≥ 0. 6. Escriba una función recursiva potencia(base, exponente) que, al llamarla, devuelva:

Page 41: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-41

baseexponente Por ejemplo, potencia(3,4) = 3 * 3 * 3 * 3. Suponga que exponente es un entero mayor o igual que 1. Sugerencia: el paso de recursión se valdría de la relación:

baseexponente = base⋅⋅⋅⋅ baseexponente - 1 y la condición de terminación sucedería cuando exponente fuera igual que 1, pues:

base1 = base 7. ¿Puede llamarse recursivamente a main()? Escriba un programa que contenga una función main() Incluya la

variable local static contador, inicializada a 1. Posincreméntela e imprima el valor de contador cada vez que se llame a main() Ejecute su programa. ¿Qué sucede?

8. ¿Qué hace el siguiente programa? //Problema 11.8 #include <iostream.h> int main(void)

{ int c; if( ( c = cin.get() ) != EOF )

{ main(); cout << c;

}//Fin de if return 0

}//Fin de main()

9. ¿Qué hace el siguiente programa? //Problema 11.9 #include <iostream.h> int misterio( int, int ); void main(void)

{ int x, y; cout << “Introduzca dos enteros: “; cin >> x >> y; cout << “El resultado es ” << misterio ( x, y ) << endl;

}//Fin de main()

// El parámetro b debe ser un entero positivo para evitar la recursión infinita int misterio( int a, int b)

{ if( b == 1)

return a; else

return a + misterio( a, b – 1 );

Page 42: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-42

10. Después de determinar lo que hace el problema 9, modifique el programa para que funcione correctamente tras eliminar la restricción ctra números no negativos del segundo argumento.

11. Encuentre los errores en el siguiente segmento de programa y explique la forma de corregirlos: a. int suma( int n )

{ if ( n == 0 )

return 0; else

return n + suma( n ); }//Fin de suma()

12. Defina un algoritmo no recursivo para el cálculo del MCD(M,N), para M y N >= 0. 13. El siguiente es un algoritmo recursivo para invertir una palabra:

si(la palabra tiene una sola letra) entonces Ésta no se invierte, solamente se la escribe.

si no Inicio

quitar la primera letra de la palabra. Invertir las restantes letras. Agregar la letra quitada..

Fin. Por ejemplo, si la palabra a invertir es ROMA, el resultado será AMOR.

14. Escriba un programa que utilice este algoritmo. 15. Escriba un programa que solucione el problema de las Torres de Hanoi. Pruébelo para distintos valores de N.

•••• Aplicando el algoritmo recursivo. •••• Aplicando el algoritmo no recursivo.

16. Teniendo en cuenta las recomendaciones dadas en la sección de casos interesantes formule un algoritmo recursivo para imprimir todos los nodos de un árbol binario.

•••• Aplicando el recorrido preorden. •••• Aplicando el recorrido inorden. •••• Aplicando el recorrido postorden.

17. Considere el árbol genealógico de la figura 11.11, y escriba un algoritmo recursivo que imprima todos los ascendientes femeninos de P.

18. Escriba una función recursiva que cuente de 1 a n, donde n es un argumento entero positivo que se pasa a la función.

19. Analice la ejecución de la función factorialRecurrente() cuando se le llama con un argumento de 10. 20. Analice la ejecución de la función fibonacciRecurrente() cuando se le llama con un argumento de 10. 21. Vuelva a escribir la función torres(), ahora como un algoritmo iterativo. 22. Implemente y analice la ejecución del programa reinas() 23. ¿Cuántas soluciones existen para el enigma de las ocho reinas? Modifique el programa para que indique

todas las soluciones existentes. 24. Convierta la función reina() en una solución iterativa. ¿Cuál versión es más fácil de mantener? ¿Cuál se

ejecuta más rápido? Explique las respuestas. 25. Coloque las funciones fibonacciRecurrente(), factorialRecurrente(), torres(), reinas() en un programa que

maneje menús que le permitan al usuario seleccionar una solución iterativa o recursiva. Ejecute cada opción para el mismo valor de N, y determine la cantidad de tiempo que toma ejecutar cada opción con un valor dado de N. Repita este proceso para valores crecientes de N. ¿Qué conclusiones se pueden obtener de soluciones iterativas en comparación con recursivas?

Page 43: CONTENIDO DE LA LECCIÓN 11azul2.bnct.ipn.mx/c/funciones/archivos (.pdf)/leccion 11.pdf · El problema de las Torres de Hanoi 20 5.1 Ejemplo 11.13 22 6. Uso de las pilas para simular

MIGUEL Á. TOLEDO MARTÍNEZ

FUNCIONES – LECCIÓN 11 11-43

EXAMEN BREVE 30 1. Las funciones que se llaman a sí mismas, directa o indirectamente, son funciones _________. 2. Una función recursiva por lo general tiene dos componentes: uno que proporciona el medio para que

termine la recursión, probando si sucede un estado ________ y otro que expresa el problema como llamada recursiva para un problema ligeramente más sencillo que el original.

3. Verdadero o falso: No hay forma de que una función C++ se puede llamar a sí misma. 4. Explique por qué podemos describir recursividad como un proceso de enrollado y desenrollado. 5. ¿Qué hace que una llamada de la función recursiva termine? 6. Una operación factorial (N!) encuentra el producto de todos los enteros desde 1 hasta algún entero

positivo N. De esta manera, 5! = 5 * 4 * 3 * 2 * 1. Escriba el seudocódigo necesario para encontrar N!, donde N es cualquier entero. (Nota: por definición, 0! = 1)

7. Verdadero o falso: Una ventaja de la recursividad es que no necesita mucha memoria para ejecutarse.

8. Verdadero o falso: Todos los problemas de recursividad se pueden solucionar también usando iteración.

RESPUESTA EXAMEN BREVE 30 1. Las funciones que se llaman a sí mismas, directa o indirectamente, son funciones recursivas. 2. Una función recursiva por lo general tiene dos componentes: uno que proporciona el medio para que

termine la recursión, probando si sucede un estado básico o primitivo y otro que expresa el problema como llamada recursiva para un problema ligeramente más sencillo que el original.

3. Falso: C++ soporta recursividad la cual permite que una función se llame a sí misma. 4. Podemos describir la recursividad como un proceso de enrollado y desenrollado, porque la

recursividad enrolla valores dentro de una pila y cuando se llega al estado primitivo, desenrolla los valores para calcular el resultado final.

5. Cuando se llega al estado primitivo, se termina el llamado a la función recursiva. 6. El seudocódigo para encontrar n!, en donde n es cualquier entero, es:

(a) if n == 0 then (i) factorial = 1

(b) else (i) factorial = n * factorial(n-1)

7. Falso: Porque la recursividad usa gran cantidad de memoria para mantener la pista de cada llamada recursiva.

Verdadero: Todos los problemas de recursividad se pueden solucionar también usando iteración.