Sunteți pe pagina 1din 22

Laboratorio de Programacin.

_____________________________________________________________________________

  

INTRODUCCIN A LA RECURSIVIDAD.

Contenido _____________________________________________________________________________ 7.1.- Concepto de recursin. 7.2.- Ejemplos de programas recursivos. 7.3.- Bsqueda y ordenacin usando recursin. 2.3.1.- Bsqueda. 2.3.2.- Ordenacin. Ejercicios _____________________________________________________________________________

7.1.- CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir en trminos de si mismo, y a dicha definicin se le denomina definicin recursiva. La recursividad es una nueva forma de ver las acciones repetitivas permitiendo que un subprograma se llame a s mismo para resolver una versin ms pequea del problema original. La funcin factorial es una funcin que se puede definir recursivamente y cuyo dominio es el de los enteros positivos. La funcin factorial, que se representa con el smbolo de exclamacin, se define como: n! = n X (n - 1) X (n - 2) X ... X 1 lo cual significa que n! es igual al producto de todos los enteros no negativos entre n y 1, inclusivos. Consideremos los factoriales de los enteros no negativos del 1 al 5: 1! = 1 2! = 2 X 1 3! = 3 X 2 X 1 4! = 4 X 3 X 2 X 1 5! = 5 X 4 X 3 X 2 X 1

____________________________________________________________________________________ Introduccin a la Recursin. Pg 1

Laboratorio de Programacin. _____________________________________________________________________________



  

Si nos fijamos atentamente en las operaciones anteriores, podremos extraer una propiedad bastante interesante de la funcin factorial. Empecemos con el 5! , que es igual a: 5! = 5 X (4 X 3 X 2 X 1) pero lo que hay dentro del parntesis es 4!, es decir (5 - 1)!, lo cual significa que : 5! = 5 X 4! Similarmente, podemos ver que: 4! = 4 X 3! y que 3! = 3 X 2!, y as sucesivamente. Si definimos 0! = 1 entonces n! se puede definir como: 1 n! = n x (n 1)!

si n = 0 si n > 0

Esta es la definicin recursiva de la funcin factorial, ya que se define en trminos de si misma. La primera regla de la definicin, o caso base, establece la condicin de terminacin. Las definiciones recursivas nos permiten definir un conjunto infinito de objetos mediante una sentencia finita. La funcin factorial recursiva, se puede escribir como:
/************************************************** * Autor: * Fecha: Versin: ***************************************************/ #include <iostream.h> #include <stdlib.h> int factorial(int param) { if (param == 0) { return 1 ; } else { return param * factorial(param-1) ; } } int main() { int N; cout << "Introduzca numero:"; cin >> N << endl; ____________________________________________________________________________________ Introduccin a la Recursin. Pg 2

Laboratorio de Programacin. _____________________________________________________________________________



  

cout << factorial(N)<< endl; system("PAUSE"); return 0; }

Como puedes ver, la funcin Factorial se llama a si misma para hallar el valor de (N-1)!. A este tipo de recursin, donde un procedimiento se llama a s mismo, se le denomina recursin explcita o recursin directa. Si un procedimiento P llama a otro Q, Q llama a R, R llama a S, ... , y Z llama de nuevo a P, entonces tambin tenemos recursin, y a este tipo de recursin se le denomina recursin implcita o recursin indirecta.

Consideremos ahora la ejecucin de la funcin recursiva Factorial. En C++, cuando se llama a un procedimiento (o funcin), se guarda la direccin de la sentencia llamante como direccin de retorno, se asigna memoria a las variables locales del procedimiento, y al finalizar la ejecucin del procedimiento, se libera la memoria asignada a las variables locales y se devuelve la ejecucin al punto en que se hizo la llamada haciendo uso de la direccin de retorno. Pero qu es la direccin de retorno?. Cuando una sentencia escrita en un lenguaje de alto nivel se traduce a cdigo mquina, dicha sentencia suele representar varias lneas de cdigo mquina, es decir, la traduccin no es una a una sino una a muchas. Cuando nos referimos a la direccin de retorno, nos estamos refiriendo a la direccin de la instruccin que sigue a la instruccin de llamada al procedimiento. Esta direccin podra estar en medio de la traduccin de una sentencia de alto nivel (como en el caso de las llamadas a funciones) o podra ser la primera instruccin de la traduccin de la sentencia de alto nivel, as como la propia llamada a la funcin. Podemos extraer entonces las siguientes conclusiones:
1. Para poder resolver un problema de forma recursiva se debe poder definir en trminos de una versin ms pequea del mismo problema. 2. En cada llamada recursiva debe disminuir el tamao del problema. 3. El diseo de la solucin del problema ha de ser tal que asegure la ejecucin del caso base y por tanto, el fin del proceso recursivo.

Por otro lado, tenemos que estudiar dos conceptos ms. Uno es la pila (stack) y el otro son los registros de activacin de los procedimientos. Una pila es una forma especial de organizar la memoria en la que la informacin siempre se aade en la cima y la informacin que se necesita, tambin se coge de la cima de la pila, al igual que una pila de hojas de papel. Debido a este comportamiento a las pilas tambin se las conoce como estructuras ultimo-en-entrarprimero-en-salir (Last-In-First-Out=(LIFO)). El registro de activacin de un procedimiento es un bloque de memoria que contiene informacin sobre las constantes y variables declaradas en el procedimiento, junto con una direccin de retorno. Ahora estamos en disposicin de trazar la ejecucin del programa para el caso de N = 3. Para llevar a cabo la recursin, las computadoras usan pilas. Al comienzo de la ejecucin de un procedimiento, la pila est vaca (Figura 1.a). Cuando la ejecucin alcanza la sentencia:
cout << factorial(N);

se produce una llamada a la funcin Factorial con N = 3. Esto hace que se cree un registro de activacin, y este registro se inserta en la pila (Figura 1b). Cuando se ejecuta la parte ELSE de la funcin Factorial (cuando la sentencia IF no es TRUE), se produce una nueva llamada a Factorial, pero esta vez el argumento es 3 -1 =2.
____________________________________________________________________________________ Introduccin a la Recursin. Pg 3

Laboratorio de Programacin. _____________________________________________________________________________



  

RF

RF

RF N=1 A N=2 A N=2 A N=3 B (d)

RF

N=3 B (a) N=0 A N=1 A N=2 A N=3 B (e) RF 6 N=1 A N=2 A N=3 B (f) RF 6 RF 1 RF (b)

N=3 B (c)

RF 1 N=2 A N=3 B (g) N=3 B (h)

RF 2

(i)

(j)

Figura 1. Fases de la evolucin de la pila durante la ejecucin del programa Esta invocacin se insertar en la cima de la pila (Figura 1.c). Esta vez, la direccin de retorno es la A. Debido a esta nueva invocacin se tiene que volver a ejecutar el programa Factorial. La condicin de la sentencia if an es FALSE, y por tanto se volver a invocar a la parte ELSE, pero esta vez con 2-1 =1 como argumento. Se inserta en la cima de la pila el registro de activacin de esta nueva invocacin (Figura 1.d). Una vez ms se tiene que volver a ejecutar el procedimiento Factorial , en este caso el if tambin es FALSE y al ejecutarse la parte else se volver a llamar a Factorial pero esta vez con argumento 1-1=0 (Figura 1.e). En esta invocacin la condicin del if se evala a TRUE y se devuelve un 1. Lo primero que se hace es almacenar este valor en el registro de la funcin (RF), y usando la direccin de retorno del registro de activacin podremos volver a la sentencia que hizo la llamada. En este instante, como se ha completado la ejecucin del procedimiento Factorial(cuando N = 0), el computador no necesitar este ltimo registro de activacin, as que se puede eliminar (Figura 1.f). La sentencia a la que retornamos necesita el valor de la funcin Factorial(cuando N = 0), que se puede obtener del registro de la funcin (RF). Este valor se multiplicar por el valor de N que es 1. El resultado se copiar en el registro de la funcin y as concluir la ejecucin de Factorial(cuando N = 1) , adems su registro de activacin ser borrado (Figura 1.g). Los pasos anteriores se volvern a repetir dando lugar a un valor de 2 en el registro de la funcin y a un solo registro de activacin en la pila (Figura 1.h). Esta vez, el valor obtenido del registro de la
____________________________________________________________________________________ Introduccin a la Recursin. Pg 4

Laboratorio de Programacin. _____________________________________________________________________________ funcin, que es 2, ser multiplicado por N, cuyo valor actual es 3; el resultado, 6, se copiar en el registro de la funcin, y se usar la direccin B, con lo que retornaremos a la localizacin de la llamada original. Al final, la pila estar de nuevo vaca (Figura 1.j). Debido a la sobrecarga (overhead) que producen las operaciones sobre la pila, la creacin y borrado de los registros de activacin, los procedimientos recursivos consumen ms tiempo y memoria que los programas no recursivos. Pero, algunas veces, debido a la estructura de datos usada en el problema o al planteamiento del mismo, surge de forma natural, y evitar la recursin es bastante ms difcil que dar una solucin recursiva al problema.

  

7.2.- EJEMPLOS DE PROGRAMAS RECURSIVOS La recursin, si se usa con cuidado, nos permitir solucionar de forma elegante algunos problemas. En este apartado se van a resolver varios problemas usando procedimientos recursivos. En cada caso, sugerimos que antes de que veas el algoritmo no iterativo, lo intentes codificar tu mismo. Empezaremos con un problema simple. El trmino n-simo de la sucesin de Fibonacci se puede definir como: 1 F(N ) = F ( N 1) + F ( N 2)

si N = 1 N = 2 si N > 2

Puedes observar que en esta definicin ,al igual que en la definicin de la funcin factorial, tenemos un caso base que nos permite conocer el valor de la funcin. Esta regla es la condicin de terminacin. La otra regla es la relacin de recurrencia. El ejemplo 1 ilustra un programa que lee varios valores de N y calcula el nmero correspondiente de la sucesin de Fibonacci. Ejemplo1. Programa que computa el N-simo trmino de la sucesin de haciendo uso de una funcin recursiva .
/************************************************** * Autor: * Fecha: Versin: ***************************************************/ #include <iostream.h> #include <stdlib.h> double Fib(double N) { if (N<= 2) { return 1; } else { return Fib(N-1) + Fib(N - 2); } } ____________________________________________________________________________________ Introduccin a la Recursin. Pg 5 Fibonacci

Laboratorio de Programacin. _____________________________________________________________________________



  

int main() { double i, N; cout << "Teclea un entero positivo: "; cin>> N << endl; for (i=1; i<=N; i++) { cout << i<< " simo trmino de Fibonacci es: "; cout << Fib(i)<< endl; } system("PAUSE"); return 0; }

Analicemos ahora la ejecucin de este programa; cuando N = 6 vamos a hallar el nmero de veces que se invoca la funcin Fib. Para hallar Fib(6), previamente tenemos que hallar Fib(5) y Fib(4); para hallar Fib(4), tenemos que hallar Fib(3) y Fib(2); y as sucesivamente. Las invocaciones de la funcin Fib se pueden ilustrar de la siguiente forma:
Fib(6)

Fib(5)

Fib(4)

Fib(4) Fib(3) Fib(2)

Fib(3) Fib(1)

Fib(3) Fib(2) Fib(1)

Fib(2)

Fib(2)

Fib(1)

Como se puede observar en el diagrama anterior, para hallar F(6) tenemos que llamar a la funcin Fib 15 veces. De esas 15 llamadas, tres tienen como argumento el 1, cinco tienen como argumento el 2, tres tienen como argumento el 3, dos tienen el 4 como argumento y con argumentos 5 y 6 slo hay dos llamadas. Esto significa que el primer nmero de la serie de Fibonacci, se calcula tres veces, el segundo se calcula 5 veces, etc. Este anlisis demuestra el por qu una funcin recursiva puede llegar a ser una herramienta muy costosa para solucionar un problema. El Ejemplo 2 muestra la versin iterativa, la cual es mucho ms eficiente y fcil de escribir.

____________________________________________________________________________________ Introduccin a la Recursin. Pg 6

Laboratorio de Programacin. _____________________________________________________________________________ Ejemplo2. Programa que computa el N-simo trmino de la sucesin de Fibonacci sin usar recursin.

  

/************************************************** * Autor: * Fecha: Versin: ***************************************************/ #include <iostream.h> #include <stdlib.h> double Fib(double N) { double i, Primero, Segundo, Siguiente; Primero = 1; Segundo = 1; Siguiente = 1; for (i=3; i<=N ; i++) { Siguiente = Primero + Segundo; Primero = Segundo; Segundo = Siguiente; } return Siguiente; } int main() { double i,N; cout << "Teclea un entero positivo: " ; cin >> N; for (i=1; i<=N; i++) { cout << i << " i-esimo termino de Fibonacci es: "; cout << Fib(i); cout << endl; } system("PAUSE"); return 0; }

Aunque el programa del ejemplo anterior es ms eficiente, en este programa es ms difcil ver la relacin entre las sentencias de la funcin Fib y la definicin de la funcin Fibonacci. Una forma de hacer que el procedimiento recursivo sea ms eficiente es la de usar algn tipo de memoria que nos permita recordar los clculos que ya hemos realizado y que por tanto no tengamos que repetirlos. Dicha memoria podra ser un array que inicialicemos a cero ya que todos los nmeros que componen la sucesin de Fibonacci son mayores que cero. Podemos usar un array de tamao 10. Asumimos que el i-simo elemento de este array se corresponde con el isimo trmino de la sucesin de Fibonacci.

Al principio, conocemos los dos primeros trminos de la sucesin de Fibonacci. Durante el clculo del N-simo trmino de la sucesin de Fibonacci, si N es menor o igual que 10, comprobaremos el N-simo elemento de dicho array. Si dicho elemento es mayor que cero, simplemente devolveremos ese valor; en otro caso tendremos que calcular el N-simo trmino de la sucesin de Fibonacci, almacenarlo en la posicin correspondiente del array y devolverlo al programa principal. Si N es mayor que 10, tendremos que calcular su valor, pero eventualmente dicha computacin har uso de los valores ya precalculados y almacenados en el array. El Ejemplo 3 usa esta tcnica para calcular el N-simo trmino de Fibonacci. Esta aproximacin
____________________________________________________________________________________ Introduccin a la Recursin. Pg 7

Laboratorio de Programacin. _____________________________________________________________________________ reduce el nmero de llamadas al procedimiento; por tanto su ejecucin ahorra tiempo a expensas de hacer uso de ms espacio de memoria.

  

Ejemplo3. Programa que computa el N-simo trmino de la sucesin de usando un algoritmo recursivo con memoria .
/************************************************** * Autor: * Fecha: Versin: ***************************************************/ #include <iostream.h> #include <stdlib.h> const int Tamanyo = 10; typedef int TipoMemoria[Tamanyo]; TipoMemoria Memoria; void DefinirMemoria( TipoMemoria &M) { int i; M[1] = 1; M[2] = 1; for (i=3; i<=Tamanyo; i++) { M[i] = 0; } }

Fibonacci

int Fib(int N) { int K ; if (N < Tamanyo) { if (Memoria[N] != 0) { return Memoria[N]; } else { K = Fib(N-1) + Fib(N-2); Memoria[N] = K; return K; } } else { return (Fib(N-1) + Fib(N-2)); } } int main() { int N; DefinirMemoria(Memoria); Cout << "Termino Fibonacci?: "; Cin >> N ; Cout << endl; if (N > 0) { cout << N; cout << " simo trmino de Fibonacci es: "; cout << Fib(N); } else { cout<<"Error en la entrada. "; ____________________________________________________________________________________ Introduccin a la Recursin. Pg 8

Laboratorio de Programacin. _____________________________________________________________________________



  

cout << " N debera ser mayor que cero."; } system("PAUSE"); return 0; }

Ahora resolveremos un problema donde la recursin simplifica nuestro programa. El programa consiste en encontrar el equivalente binario de los nmeros decimales. Como su nombre indica, los nmeros decimales se escriben usando diez dgitos (0 al 9) y los nmeros binarios se escriben usando dos dgitos (0,1). La representacin binaria de un nmero decimal se puede hallar fcilmente considerando el siguiente algoritmo: Sea N un nmero positivo decimal. Hallar N % 2 y N / 2, y almacenamos el primero. Reemplazamos N por N/2. Repetimos el proceso hasta que N sea igual a cero. Ahora, si reescribes los valores almacenados en sentido contrario a como los obtuviste, obtendrs el equivalente binario de N. Por ejemplo: supongamos que N es 23. N
23 11 5 2 1 0

N%2
1 1 1 0 1

N/2
11 5 2 1 0

La representacin binaria de 23 es 10111 Para resolver este problema sin usar recursin, necesitaremos un array para almacenar los dgitos binarios que vamos calculando, para despus poder escribirlos al revs. El problema a la hora de usar arrays es que no conocemos el nmero de elementos que vamos a usar del array. Veamos, ahora, como nos puede ayudar la recursin. El equivalente binario de 11, que es igual a 23 / 2, es 1011 As que, si podemos imprimir el equivalente binario de 11, ser fcil escribir el equivalente binario del 23; lo nico que tenemos que hacer es imprimir 23 % 2. Ahora observemos lo siguiente. El equivalente binario de 5, que es igual a 11 / 2, es 101. Si aadimos 11 % 2 = 1, nos da el equivalente binario del 11. Por tanto, si consideramos la representacin binaria de un nmero como una cadena de dgitos binarios, podemos llegar a la siguiente definicin: 0 si N = 0 Representacin Binaria de N = 1 si N = 1 Representacin binaria de( N / 2)||( N %2)

donde || representa la concatenacin. Esta aproximacin no requiere arrays y puede ser programada fcilmente. El programa se ilustra en el ejemplo 4.
____________________________________________________________________________________ Introduccin a la Recursin. Pg 9

Laboratorio de Programacin. _____________________________________________________________________________



  

Ejemplo4. Programa para hallar el equivalente binario de los nmeros decimales.


/************************************************** * Autor: * Fecha: Versin: ***************************************************/ #include <iostream.h> #include <stdlib.h> const int Base = 2; int N; void PrintBinario(int N) { if (N > 0) { PrintBinario(N/Base); Cout << N % Base; } }

// Imprime equivalente binario de N/2

int main() { cout << "Introduzca un entero positivo: "; cin >> N ; cout << endl; cout << " El Nmero Decimal "<< N <<" es igual a "; PrintBinario(N); Cout << " en binario " << endl; system("PAUSE"); return 0; }

Un ejemplo simple de la ejecucin de este programa sera: Entra un entero positivo: 256 El Nmero Decimal 26 es igual a 100000000 en binario. Entra un entero positivo: 32767 El Nmero Decimal 32767 es igual a 111111111111111 en binario. Entra un entero positivo: 23 El Nmero Decimal 23 es igual a 10111 en binario. Entra un entero positivo:

El programa del Ejemplo 4 se puede modificar para hallar la representacin de un nmero decimal en cualquier base B. Debemos notar que, si la nueva base B es mayor que 10, necesitaremos ms smbolos adems de los dgitos del 1 al 9. Tradicionalmente, se han usado los caracteres A, B, C, D, ..., y as sucesivamente para representar el 10,11, 12, 13, ... . Por ejemplo, el nmero 30 en decimal es equivalente en hexadecimal (B=16) a 1E, donde E representa el 14. Para manipular estos caracteres en este programa modificado, introducimos un array de caracteres llamado Dgitos. En este nuevo programa, en vez de imprimir el valor de
N % Base ____________________________________________________________________________________ Introduccin a la Recursin. Pg 10

Laboratorio de Programacin. _____________________________________________________________________________



  

usaremos el valor obtenido, para indexar el array Digitos e imprimir el carcter almacenado en dicho elemento (Ejemplo 5). Ejemplo5. Programa para convertir nmeros decimales a cualquier base hasta la
base 16
/************************************************** * Autor: * Fecha: Versin: ***************************************************/ #include <iostream.h> #include <stdlib.h> //Lmite superior de la base const int BaseMax = 16; typedef char TDigitos[BaseMax]; int N, NuevaBase; TDigitos Digitos; void DefinirDigitos(TDigitos &Digitos) { char C; int i; C = '0'; for (i=0; i<=9; i++) { Digitos[i] = C; C++; } C = 'A'; for (i=10; i<=BaseMax-1; i++) { Digitos[i] = C; C++; } } void PrintNuevaBase(int N,int NuevaBase) { if (N > 0) { PrintNuevaBase(N / NuevaBase, NuevaBase); cout<<Digitos[N % NuevaBase]; } } int main() { DefinirDigitos(Digitos); Cout << "Introduzca un entero positivo: "; Cin >> N; Cout << endl; Cout << "Entra la nueva base (2-16): "; Cin >> NuevaBase; Cout << endl; Cout << " El Nmero Decimal "<<N<<" es igual a "; PrintNuevaBase(N, NuevaBase); Cout << " en base "<<NuevaBase<<endl; system("PAUSE"); return 0; }

Un ejemplo simple de la ejecucin de este programa sera:


____________________________________________________________________________________ Introduccin a la Recursin. Pg 11

Laboratorio de Programacin. _____________________________________________________________________________



  

Entra un entero positivo: 123 Entra la nueva base (2-16): 16 El Nmero Decimal 123 es igual a 7B en base 16. Entra un entero positivo: 32676 Entra la nueva base (2-16): 8 El Nmero Decimal 32676 es igual a 77644 en base 8. Entra un entero positivo: 32676 Entra la nueva base (2-16): 10 El Nmero Decimal 32676 es igual a 32676 en base 8. Entra un entero positivo: 32676 Entra la nueva base (2-16): 12 El Nmero Decimal 32676 es igual a 16AB0 en base 12.

7.3.- BUSQUEDA Y ORDENACION USANDO RECURSION 7.3.1.- Bsqueda Asumimos que tenemos un array llamado Ordenado de N elementos y que est ordenado en orden creciente. Consideremos el problema de determinar si un elemento dado, Item, est presente en dicho array. Si Item est presente en la lista, nos gustara determinar su localizacin (Loc). Si Item no est presente en la lista, deberemos poner Loc a cero. Este problema se puede simplificar si reducimos el tamao del array. Una forma de hacer esto es dividir el array en dos partes cogiendo el elemento X-simo del array, donde X es un ndice seleccionado al azar entre 1 y N. Se nos presentan as tres posibilidades:. 1. Ordenado[X] = Item : En este caso, ya hemos encontrado el Item en el array, y ponemos Loc a X. 2. Ordenado[X] > Item : No podemos decir si el Item est o no en el array, pero ya que los elementos del array estn en orden creciente, podemos ignorar la segunda parte del array;la siguiente vez, consideraremos slo los elementos desde el primero al X-1. 3. Ordenado[X] < Item : No podemos decir si el Item est o no en el array, pero ya que los elementos del array estn en orden creciente, podemos ignorar la primera parte del array; la siguiente vez, consideraremos slo los elementos desde el X+1 al N. En los casos 2 y 3, el tamao del array se reduce, y el mismo proceso, seleccionar un ndice aleatorio X entre los valores del ndice vlidos (entre 1 y X-1 si se usa la primera parte; entre X +1 y N se usa la segunda parte) y comparar ese elemento con Item, se puede repetir sobre una porcin ms pequea del array Ordenado. Eventualmente el tamao del array bajo consideracin ser cero si el Item no est en el array; en otro caso se habr localizado previamente. En vez de usar un ndice X aleatorio , se suele coger la mitad del array. A este mtodo se le denomina bsqueda binaria porque el elemento intermedio de un array divide al array en dos partes iguales o casi iguales (Si N es par). El programa principal del Ejemplo 6 tiene un programa principal que primero lee el nmero de elementos que va a contener el array Ordenado y que despus lee los elementos. Dentro del bucle WHILE que sigue a la parte de entrada, el programa lee el Item a localizar y despus invoca al procedimiento Buscar, que
requiere cinco parmetros. Estos parmetros son, en orden: 2.Array Ordenado.

____________________________________________________________________________________ Introduccin a la Recursin. Pg 12

Laboratorio de Programacin. _____________________________________________________________________________ 3.Primero: Cota inferior de la parte del array Ordenado en el que estamos interesados. 4.Ultimo: Cota superior de la parte del array Ordenado en el que estamos interesados. 5.Item: El valor que estamos buscando. 6.Loc: Si se encuentra el Item, Loc contendr un ndice a la posicin dentro del array donde est el Item, sino contendr cero.

  

El procedimiento Buscar , despus de comparar con el elemento mitad de la porcin actual del array que estamos tratando, decide qu parte de la porcin actual hay que ignorar. Si el elemento intermedio es menor que el Item, se ignora la primera parte, y se llama otra vez a Buscar con las cotas inferiores y superiores configuradas con los valores Mitad+1 y Ultimo. En otro caso, se llaman con las cotas Primero y Mitad-1 como cotas inferior y superior. Ejemplo6. Bsqueda Binaria recursiva
/************************************************** * Autor: * Fecha: Versin: ***************************************************/ #include <iostream.h> #include <stdlib.h> // Tamao del array const int Tamanyo = 100; typedef int Lista[Tamanyo]; Lista Ordenado; int N, i, Loc,Item; void Buscar(Lista Ordenado, int Primero, int Ultimo, int Item, int &Loc) { int Mitad; if (Primero > Ultimo) { Loc = 0; } else { Mitad = (Ultimo + Primero) / 2; if (Ordenado[Mitad] == Item) { Loc = Mitad; } else { if (Ordenado[Mitad] < Item) { Buscar(Ordenado, Mitad+1, Ultimo, Item, Loc); } else { Buscar(Ordenado, Primero, Mitad-1, Item, Loc); } } } } int main() { // Lectura del array Ordenado cout << "Nmero de elementos en Ordenado: "; cin >> N; cout << endl; if (N > Tamanyo) { N = Tamanyo; // No se pueden leer ms de Tamanyo elementos ____________________________________________________________________________________ Introduccin a la Recursin. Pg 13

Laboratorio de Programacin. _____________________________________________________________________________



  

} cout<< "Introduzca los elementos: "; for ( i = 0; i<=(N-1);i++) { cin >> Ordenado[i] ; } cout << endl; cout << "Item a Buscar: "; cin >> Item ; Buscar(Ordenado, 0, N-1, Item, Loc); cout << Item ; if (Loc == 0) { cout << " no est en la lista."; } else { cout << " es igual al "; cout << Loc; cout << "-esimo elemento de la lista."; } cout << endl; system("PAUSE"); return 0; }

7.3.2.- Ordenacin La ordenacin est muy relacionada con la mezcla. Mezclar es un trmino usado por un proceso que combina dos listas ordenadas preservando el orden. Examinemos la mezcla de dos listas ordenadas (asumiremos que nuestras listas contienen enteros y estn ordenadas de forma creciente) antes de ver el papel de la mezcla en la ordenacin. El proceso de mezcla se puede realizar seleccionando sucesivamente el valor ms pequeo que aparece en cualquiera de las dos listas y moviendo dicho valor a una nueva lista, creando as una lista ordenada. Por ejemplo, mientras mezclamos las siguientes dos listas: Lista 1: 13 27 58 Lista 2: 8 36 podemos obtener las siguientes trazas: Lista 1: 13 27 58 Lista 2:36 Nueva Lista : 8 Lista 1: 27 58 Lista 2:36 Nueva Lista : 8 13 Lista 1: 58 Lista 2:36 Nueva Lista : 8 13 27 Lista 1: 58 Lista 2 : Nueva Lista : 8 13 17 36

____________________________________________________________________________________ Introduccin a la Recursin. Pg 14

Laboratorio de Programacin. _____________________________________________________________________________ Lista 1: Lista 2: Nueva Lista : 8 13 17 36 58



  

Si estas dos listas se almacenan en el mismo array, dicho array tendr el siguiente aspecto:
Lista: 13 27 58 8 36

Primero

Mitad

Ultimo

donde los elementos Primero a Mitad-1 representan a la Lista1, mientras que los elementos Mitad a Ultimo representa a la Lista2. En el proceso de mezcla, se va a necesitar un rea temporal (Temp) ,del mismo tamao que la Lista, para almacenar los resultados. Al final, Temp se volver a copiar en la Lista. El procedimiento para realizar la operacin de mezcla se ilustra a continuacin:
void MezclaSimple(Lista &K, int Primero, int Mitad, int Ultimo) // Mezcla dos listas que estn ordenadas en el mismo array { Lista Temp; int Indice1, Indice2, IndiceMezcla, i; Indice1 = Primero; Indice2 = Mitad; // Inicializa el nmero de elementos en Temp IndiceMezcla = 0; /* Mientras haya elementos en cualquier parte de las listas ordenadas, mezclalos */ while((Indice1 < Mitad) && (Indice2<= Ultimo)) { IndiceMezcla++; if (K[Indice1] <= K[Indice2]) { Temp[IndiceMezcla] = K[Indice1]; Indice1++; } else { Temp[IndiceMezcla] = K[Indice2]; Indice2++; } } /* En este punto o la primera parte o la segunda parte est agotada. Copiamos cualquier parte remanente de la segunda parte a Temp. */ while (Indice2 <= Ultimo) { IndiceMezcla++; Temp[IndiceMezcla] = K[Indice2]; Indice2++; } // Copiamos cualquier parte que quede de la primera parte a Temp ____________________________________________________________________________________ Introduccin a la Recursin. Pg 15

Laboratorio de Programacin. _____________________________________________________________________________



  

while (Indice1 < Mitad) { IndiceMezcla++; Temp[IndiceMezcla] = K[Indice1]; Indice1++; } /* Copiamos Temp en el array K*/ for (i=1 ;i<=IndiceMezcla; i++) { K[Primero+i-1] = Temp[i]; } }

Este proceso de mezcla simple se puede generalizar para mezclar K listas ordenadas en una sola lista ordenada. A este proceso se le llama mezcla mltiple. La mezcla mltiple se puede llevar a cabo realizando una mezcla simple, repetidamente. Por ejemplo, si tenemos ocho listas para mezclar, podemos mezclarlas a pares para obtener as cuatro listas ordenadas. Estas listas, a su vez, se pueden mezclar a pares para obtener dos listas ordenas. Y una operacin de mezcla final nos dar la lista ordenada. Veamos ahora como se puede usar la mezcla para ordenar un array de ocho elementos. Asumimos que la lista contiene:
81 8 13 7 79 54 1 5

Dividamos esta lista en dos partes iguales (una barra vertical indica el punto de divisin). Obtenemos:
81 8 13 7 | 79 54 1 5

Como no estn ordenadas, no podemos mezclar estas dos listas. Dividamos la primera sublista en dos piezas:
81 | 8 13 7 | 79 54 1 5

Una vez ms:


81 | 8 | 13 7 | 79 54 1 5

La primeras dos sublistas contienen slo un elemento, y obviamente una lista que contiene un solo elemento est ordenada(!). Esto significa que se pueden mezclar estas dos obteniendo
8 81 | 13 7 | 79 54 1 5

Ahora, si la parte B de esta lista se divide en dos partes y las listas resultantes formadas por elementos simples se mezclan, obtenemos:
8 81 | 7 13 | 79 54 1 5

Como las dos primeras listas estn ahora ordenadas, podemos mezclarlas :
7 8 13 81 | 79 54 1 5

Como puedes ver, la primera mitad del array ya est ordenada. Si aplicamos la misma tcnica a la segunda sublista y despus las mezclamos con la primera, estar ordenado todo el array. En el
____________________________________________________________________________________ Introduccin a la Recursin. Pg 16

Laboratorio de Programacin. _____________________________________________________________________________ Ejemplo 7, el procedimiento OrdenacionPorMezcla divide el array de entrada en sublistas ms y ms pequeas hasta que alcancen el tamao de un elemento. Entonces, usando el procedimiento MezclaSimple, se mezclan esas sublistas, obtenendose la lista ordenada.

  

Ejemplo7. Programa recursivo para la ordenacin por mezcla.


/************************************************** * Autor: * Fecha: Versin: ***************************************************/ #include <iostream.h> #include <stdlib.h> // Tamao del array const int Tamanyo = 10; typedef int Lista[Tamanyo]; void MezclaSimple(Lista &K, int Primero,int Mitad,int Ultimo) // Mezcla dos listas que estn ordenadas en el mismo array // El cuerpo del procedimiento anterior void OrdenacionPorMezcla(Lista &K,int Primero, int Ultimo) { // Ordena el array K usando ordenacin por mezcla recursiva int Tam, Mitad; // Determina el tamao de la porcin actual Tam = Ultimo - Primero + 1; if (Tam <= 1) // No ms divisiones { return; } else { // Divide en dos y ordena Mitad = Primero + Tam / 2 -1; OrdenacionPorMezcla(K, Primero, Mitad); OrdenacionPorMezcla(K, Mitad+1, Ultimo); // Mezcla ambas mitades ordenadas MezclaSimple(K, Primero, Mitad+1,Ultimo); } } int main() { Lista Desordenado; int Elemento ,Primero, Segundo, Tercero, i, K; // Entrada de datos desordenados i = 0; cin >> K; while(i < Tamanyo-1) { i++; Desordenado[i] = K; Cin >> K; } Tercero = i; OrdenacionPorMezcla(Desordenado, 1, Tercero); // Imprime el array ordenado for( i = 1; i<=Tercero; i++) { cout<<Desordenado[i]<<endl; } system("PAUSE"); return 0; }

____________________________________________________________________________________ Introduccin a la Recursin. Pg 17

Laboratorio de Programacin. _____________________________________________________________________________ EJERCICIOS



  

1.- Cual ser la salida del siguiente programa ?. Intenta resolverlo primero sobre papel.
#include <iostream.h> #include <stdlib.h> void ImprimeResto() { const char Punto='.'; char X; cin >> X; if (X != Punto) { ImprimeResto(); } cout << X; } int main() { ImprimeResto(); cout << endl; system("PAUSE"); return 0; }

si la entrada es :
Si algo puede salir mal, saldr mal.

2.- Cual ser la salida del siguiente programa ?. Intenta resolverlo primero sobre papel.
#include <iostream.h> #include <stdlib.h> int Uno(int A, int B) { if (B != 0) { A++; B--; return Uno(A,B); } else { return A ; } } int main() { cout << Uno(5,6) << endl; system("PAUSE"); return 0; }

____________________________________________________________________________________ Introduccin a la Recursin. Pg 18

Laboratorio de Programacin. _____________________________________________________________________________



  

3.- La relacin recurrente 1 x n = x * x n 1 n +1 x /x si n = 0 si n > 0 si n < 0

define x como la potencia n-sima de todos los enteros. Escribir un procedimiento recursivo llamado Potencia que calcule x a la n-sima potencia para un entero x.

____________________________________________________________________________________ Introduccin a la Recursin. Pg 19

Laboratorio de Programacin. _____________________________________________________________________________ 4.- El mximo comn divisor de dos enteros positivos M y N se puede definir como:

  

N MCD( M , N ) = MCD( N , M ) MCD( N , M mod N )

si N M y M mod N = 0 si M < N en otro caso

Escribir una funcin recursiva que permita calcular el mximo comn divisor de dos enteros positivos.

7.- En general, el N-simo trmino de la sucesin de Fibonacci, se define como: A F ( A, B, N ) = B F ( B, A + B, N 1)

si N = 1 si N = 2 si N > 2

Donde A y B son los nmeros que originan la secuencia. Escribir un programa que compute el Nsimo trmino de la sucesin de Fibonacci usando diferentes nmeros para originar la secuencia. 8.- La funcin Q se define como: 1 Q( N ) = Q ( N Q ( N 1))

si N 2 si N > 2

Escribir dos procedimientos, uno recursivo y otro no recursivo, para computar el valor de esta funcin para varios valores de N. Puedes escribir una funcin que haga uso de ms memoria para as aumentar la velocidad de los clculos recursivos?.

9.- El valor del N-simo trmino del polinomio de Legendre se puede calcular usando las siguientes frmulas: P0(x) = 1 P1(x) = x 2* N 1 N 1 * x * PN 1 ( x ) * PN 2 ( x ) N N

PN ( x ) =

Escribir una furncin recursiva que compute el N-simo trmino del polinomio de Legendre.

____________________________________________________________________________________ Introduccin a la Recursin. Pg 20

Laboratorio de Programacin. _____________________________________________________________________________ 10.- Escribir una funcin recursiva para computar la funcin de Ackermann, la cual se define como: N + 1 si M = 0 A(M, N) = A(M 1, N) si M 0 and N = 0 A(M -1, A(M, N -1)) si M 0 and N 0

  

donde M y N son nmeros cardinales. 11.- Para cualesquiera enteros no negativos N y K, donde 0<=K<=N, el coeficiente binomial C(N,K) se define como: C( N , K ) = N! K !* ( N K )!

Esta frmula verifica que: 1. C(N,K) = C(N, N-K); es decir, la lista de coeficientes binomiales es simtrica con respecto a su valor central. 2. C(N,N) = 1 y por simetra C(N,0) = 1. 3. C(N,N-1)=N y por simetra C(N,1) = N. El problema para computar C(N,K) haciendo uso de la frmula anterior estriba en que N! crece muy rpidamente.Por lo que necesitamos un mtodo alternativo para calcular dichos coeficientes. Para ello podemos hacer uso del hecho de que para cualquier K que satisfaga 1<=K<=N-1: C( N , K ) = C ( N , K 1) * ( N K + 1) K

Escribir un programa que calcule el coeficiente binomial C(N,K) para varios N y K. 12.- Un programa bastante popular es hacer que un ratn encuentre un queso de cabrales en un laberinto. Vamos a suponer que el laberinto es un recinto rectangular dividido en cuadrados, estando cada cuadrado ocupado por un obstculo o libre. El permetro del rectngulo est ocupado por obstculos, excepto en una o ms salidas. Comenzamos en algn lugar dentro del laberinto, y tenemos que encontrar el camino de salida. Podemos movernos de cuadrado en cuadrado en cualquier direccin (excepto diagonalmente), pero no podemos atravesar un obstculo. Podemos representar el laberinto en un ordenador mediante un array de caracteres ( o booleano) bidimensional, por ejemplo:
BBBBBBBBBBBB B..BBB....BB BB...B.BBB.B B..B.B.B...B BBB....BBBBB B...BBB....B B.B.....BB.B BBBBBBBBBB.B

Construir un programa recursivo que tenga como entrada la posicin del ratn dentro del laberinto y que determine una de sus posibles salidas.
____________________________________________________________________________________ Introduccin a la Recursin. Pg 21

Laboratorio de Programacin. _____________________________________________________________________________ (* El siguiente ejercicio no se debe incluir en la relacin de problemas *)



  

12.- El mtodo de ordenacin rpida (QuickSort) es un mtodo de ordenacin rpido y relativamente moderno desarrollado por Hoare. Es esencialmente un esquema de insercin/intercambio que, en cada etapa, coloca al menos un elemento en su sitio adecuado. Saber que este elemento est bien posicionado se usa para reducir el nmero de comparaciones que se necesitan para posicionar el resto de elementos. La filosofa del QuickSort consiste en dividir la lista de elementos tomando como referencia un determinado elemento, llamado pivote, de forma que, en las listas resultantes de la divisin, todas los elementos que precedan al pivote sean menores o iguales al mismo y todos los elementos que estn despus del pivote sean mayores o iguales que el mismo.

Izqda

IFin

Dinicio

Dcha

. . .
pivote pivote

pivote

Como resultado de la particin, el pivote est correctamente colocado. El mismo proceso de particin se aplica entonces a las dos sublistas a ambos lados del pivote. De esta forma la lista original es sistemticamente reducida a una serie de sublistas, cada una de longitud uno, que estn, por supuesto, ordenadas y, ms importante, correctamente posicionadas en relacin a las otras. La caracterstica ms importante de este algoritmo es la particin. La entrada seleccionada como el pivote suele ser la primera entrada. Habiendo elegido el pivote, el algoritmo busca en la lista ,desde dicho pivote hasta el final, el primer elemento que no pertenezca a ese lado del pivote. Estos dos elementos se intercambian, y se reanuda la bsqueda con los elementos adyacentes. Cuando coinciden dos bsquedas, el pivote est posicionado entre dos sublistas en orden para mantener la ordenacin relativa. Dados los ndices de los elementos ms a la izquierda (Izquierda) y a la derecha (Derecha) de la sublista a ordenar, el algoritmo queda como:
SI Izquierda < Derecha parte Lista[Izquierda ] a Lista[Derecha] en dos secciones ms pequeas de forma que: Lista[Izquierda ] ...Lista[Final] <= Lista[Pivote] <= Lista[DechaInicio] ... Lista[Derecha] ordena Lista[Izquierda]...Lista[Final] ordena Lista[DechaInicio]...Lista[Derecha]

Escribir un programa que ordene una lista de enteros usando este mtodo.

____________________________________________________________________________________ Introduccin a la Recursin. Pg 22

S-ar putea să vă placă și