introducciÓn a la recursividad. -...

22
Laboratorio de Programación. _____________________________________________________________________________ ____________________________________________________________________________________ Introducción a la Recursión. Pág 1 7 INTRODUCCIÓN A LA RECURSIVIDAD. Contenido _____________________________________________________________________________ 7.1.- Concepto de recursión. 7.2.- Ejemplos de programas recursivos. 7.3.- Búsqueda y ordenación usando recursión. 2.3.1.- Búsqueda. 2.3.2.- Ordenación. Ejercicios _____________________________________________________________________________ 7.1.- CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir en términos de si mismo, y a dicha definición se le denomina definición recursiva. La recursividad es una nueva forma de ver las acciones repetitivas permitiendo que un subprograma se llame a sí mismo para resolver una versión más pequeña del problema original. La función factorial es una función que se puede definir recursivamente y cuyo dominio es el de los enteros positivos. La función factorial, que se representa con el símbolo de exclamación, 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

Upload: dodieu

Post on 26-Apr-2018

225 views

Category:

Documents


3 download

TRANSCRIPT

Page 1: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 1

7 INTRODUCCIÓN A LA RECURSIVIDAD.

Contenido_____________________________________________________________________________

7.1.- Concepto de recursión.7.2.- Ejemplos de programas recursivos.7.3.- Búsqueda y ordenación usando recursión.

2.3.1.- Búsqueda.2.3.2.- Ordenación.

Ejercicios

_____________________________________________________________________________

7.1.- CONCEPTO DE RECURSION

Se dice que un proceso es recursivo si se puede definir en términos de si mismo, y a dichadefinición se le denomina definición recursiva. La recursividad es una nueva forma de ver lasacciones repetiti vas permitiendo que un subprograma se llame a sí mismo para resolver unaversión más pequeña del problema original.

La función factorial es una función que se puede definir recursivamente y cuyo dominioes el de los enteros positivos. La función factorial, que se representa con el símbolo deexclamación, 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! = 12! = 2 X 13! = 3 X 2 X 14! = 4 X 3 X 2 X 15! = 5 X 4 X 3 X 2 X 1

Page 2: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 2

Si nos fijamos atentamente en las operaciones anteriores, podremos extraer una propiedadbastante interesante de la función 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 paréntesis 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:

n

si n

nx n si n

!

( )!

==

− >

î

1 0

1 0

Esta es la definición recursiva de la función factorial, ya que se define en términos de simisma. La primera regla de la definición, o caso base, establece la condición de terminación. Lasdefiniciones recursivas nos permiten definir un conjunto infinito de objetos mediante unasentencia finita.

La función factorial recursiva, se puede escribir como:

/*************************************************** Autor:* Fecha: Versión:***************************************************/

#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;

Page 3: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 3

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

Como puedes ver, la función Factorial se llama a si misma para hallar el valor de (N-1)!.A este tipo de recursión, donde un procedimiento se llama a sí mismo, se le denomina recursiónexplícita o recursión 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 también tenemos recursión, y a este tipo de recursión se ledenomina recursión implícita o recursión indirecta.

Consideremos ahora la ejecución de la función recursiva Factorial. En C++, cuando sellama a un procedimiento (o función), se guarda la dirección de la sentencia ‘ llamante’ comodirección de retorno, se asigna memoria a las variables locales del procedimiento, y al finalizarla ejecución del procedimiento, se libera la memoria asignada a las variables locales y sedevuelve la ejecución al punto en que se hizo la llamada haciendo uso de la dirección de retorno.Pero ¿qué es la dirección de retorno?. Cuando una sentencia escrita en un lenguaje de alto nivelse traduce a código máquina, dicha sentencia suele representar varias líneas de código máquina,es decir, la traducción no es una a una sino una a muchas. Cuando nos referimos a la dirección deretorno, nos estamos refiriendo a la dirección de la instrucción que sigue a la instrucción dellamada al procedimiento. Esta dirección podría estar en medio de la traducción de una sentenciade alto nivel (como en el caso de las llamadas a funciones) o podría ser la primera instrucción dela traducción de la sentencia de alto nivel, así como la propia llamada a la función.

Podemos extraer entonces las siguientes conclusiones:

1. Para poder resolver un problema de forma recursiva se debe poder definiren términos de una versión más pequeña del mismo problema.

2. En cada llamada recursiva debe disminuir el tamaño del problema.3. El diseño de la solución del problema ha de ser tal que asegure la

ejecución del caso base y por tanto, el fin del proceso recursivo.

Por otro lado, tenemos que estudiar dos conceptos más. Uno es la pila (stack) y el otroson los registros de activación de los procedimientos. Una pila es una forma especial deorganizar la memoria en la que la información siempre se añade en la cima y la información quese necesita, también se coge de la cima de la pila, al igual que una pila de hojas de papel. Debidoa este comportamiento a las pilas también se las conoce como estructuras ultimo-en-entrar-pr imero-en-sali r (Last-In-First-Out=(LIFO)).

El registro de activación de un procedimiento es un bloque de memoria que contieneinformación sobre las constantes y variables declaradas en el procedimiento, junto con unadirección de retorno.

Ahora estamos en disposición de trazar la ejecución del programa para el caso de N = 3.Para llevar a cabo la recursión, las computadoras usan pilas. Al comienzo de la ejecución de unprocedimiento, la pila está vacía (Figura 1.a). Cuando la ejecución alcanza la sentencia:

cout << factorial(N);

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

Page 4: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 4

Figura 1. Fases de la evolución de la pila durante la ejecución del programa

Esta invocación se insertará en la cima de la pila (Figura 1.c). Esta vez, la dirección deretorno es la “A” . Debido a esta nueva invocación se tiene que volver a ejecutar el programaFactorial . La condición de la sentencia if aún es FALSE, y por tanto se volverá a invocar a laparte ELSE, pero esta vez con 2-1 =1 como argumento. Se inserta en la cima de la pila el registrode activación de esta nueva invocación (Figura 1.d). Una vez más se tiene que volver a ejecutarel procedimiento Factorial , en este caso el if también es FALSE y al ejecutarse la parte else sevolverá a llamar a Factorial pero esta vez con argumento 1-1=0 (Figura 1.e). En estainvocación la condición del if se evalúa a TRUE y se devuelve un 1. Lo primero que se hace esalmacenar este valor en el registro de la función (RF), y usando la dirección de retorno delregistro de activación podremos volver a la sentencia que hizo la llamada. En este instante, comose ha completado la ejecución del procedimiento Factorial(cuando N = 0) , el computador nonecesitará este último registro de activación, así que se puede eliminar (Figura 1.f). La sentenciaa la que retornamos necesita el valor de la función Factorial(cuando N = 0), que se puedeobtener del registro de la función (RF). Este valor se multiplicará por el valor de N que es 1. Elresultado se copiará en el registro de la función y así concluirá la ejecución deFactorial(cuando N = 1) , además su registro de activación será borrado (Figura 1.g). Lospasos anteriores se volverán a repetir dando lugar a un valor de 2 en el registro de la función y aun solo registro de activación en la pila (Figura 1.h). Esta vez, el valor obtenido del registro de la

RF RF

B

N = 3

B

N = 3

A

N = 2

RF

B

N = 3

A

N = 2

RF

A

N = 1

B

N = 3

A

N = 2

RF

A

N = 1

A

N = 0

B

N = 3

A

N = 2

RF

A

N = 1 1

B

N = 3

A

N = 2

RF

1

RF

B

N = 3

2

RF

6

RF

6

(a) (b) (c) (d)

(e) (f) (g) (h)

(i) (j)

Page 5: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 5

función, que es 2, será multiplicado por N, cuyo valor actual es 3; el resultado, 6, se copiará en elregistro de la función, y se usará la dirección “B” , con lo que retornaremos a la localización de lallamada original. Al final, la pila estará de nuevo vacía (Figura 1.j).

Debido a la sobrecarga (overhead) que producen las operaciones sobre la pila, la creacióny borrado de los registros de activación, los procedimientos recursivos consumen más tiempo ymemoria que los programas no recursivos. Pero, algunas veces, debido a la estructura de datosusada en el problema o al planteamiento del mismo, surge de forma natural, y evitar la recursiónes bastante más difícil que dar una solución recursiva al problema.

7.2.- EJEMPLOS DE PROGRAMAS RECURSIVOS

La recursión, si se usa con cuidado, nos permitirá solucionar de forma elegante algunosproblemas. En este apartado se van a resolver varios problemas usando procedimientosrecursivos. En cada caso, sugerimos que antes de que veas el algoritmo no iterativo, lo intentescodificar tu mismo. Empezaremos con un problema simple. El término n-ésimo de la sucesión deFibonacci se puede definir como:

F N

si N ó N

F N F N si N

( )

( ) ( )

== =

− + − >

î

1 1 2

1 2 2

Puedes observar que en esta definición ,al igual que en la definición de la función factorial,tenemos un caso base que nos permite conocer el valor de la función. Esta regla es la condiciónde terminación. La otra regla es la relación de recurrencia. El ejemplo 1 ilustra un programaque lee varios valores de N y calcula el número correspondiente de la sucesión de Fibonacci.

Ejemplo1. Programa que computa el N-ésimo término de la sucesión de Fibonacci

haciendo uso de una función recursiva .

/*************************************************** Autor:* Fecha: Versión:***************************************************/

#include <iostream.h>#include <stdlib.h>

double Fib( double N){ if (N<= 2) { return 1; } else { return Fib(N-1) + Fib(N - 2); }}

Page 6: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 6

int main(){ double i, N; cout << "Teclea un entero positivo: "; cin>> N << endl; for (i=1; i<=N; i++) { cout << i<< " ésimo término de Fibonacci es: "; cout << Fib( i)<< endl; }

system("PAUSE"); return 0;}

Analicemos ahora la ejecución de este programa; cuando N = 6 vamos a hallar el númerode veces que se invoca la función 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. Lasinvocaciones de la función Fib se pueden ilustrar de la siguiente forma:

Como se puede observar en el diagrama anterior, para hallar F(6) tenemos que llamar a lafunción Fib 15 veces. De esas 15 llamadas, tres tienen como argumento el 1, cinco tienen comoargumento el 2, tres tienen como argumento el 3, dos tienen el 4 como argumento y conargumentos 5 y 6 sólo hay dos llamadas. Esto significa que el primer número de la serie deFibonacci, se calcula tres veces, el segundo se calcula 5 veces, etc. Este análisis demuestra el porqué una función recursiva puede llegar a ser una herramienta muy costosa para solucionar unproblema. El Ejemplo 2 muestra la versión iterativa, la cual es mucho más eficiente y fácil deescribir.

Fib(6)

Fib(5)

Fib(4) Fib(3)

Fib(4)

Fib(2)

Fib(2)

Fib(1)

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

Fib(2)Fib(3)

Fib(1)

Page 7: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 7

Ejemplo2. Programa que computa el N-ésimo término de la sucesión de Fibonacci

sin usar recursión .

/*************************************************** Autor:* Fecha: Versión:***************************************************/#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 más eficiente, en este programa es más difícilver la relación entre las sentencias de la función Fib y la definición de la función Fibonacci.

Una forma de hacer que el procedimiento recursivo sea más eficiente es la de usar algúntipo de memoria que nos permita ‘recordar’ los cálculos que ya hemos realizado y que por tantono tengamos que repetirlos. Dicha memoria podría ser un array que inicialicemos a cero ya quetodos los números que componen la sucesión de Fibonacci son mayores que cero. Podemos usarun array de tamaño 10. Asumimos que el i-ésimo elemento de este array se corresponde con el i-ésimo término de la sucesión de Fibonacci.

Al principio, conocemos los dos primeros términos de la sucesión de Fibonacci. Durante elcálculo del N-ésimo término de la sucesión 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 término dela sucesión de Fibonacci, almacenarlo en la posición correspondiente del array y devolverlo alprograma principal. Si N es mayor que 10, tendremos que calcular su valor, pero eventualmentedicha computación hará uso de los valores ya precalculados y almacenados en el array. ElEjemplo 3 usa esta técnica para calcular el N-ésimo término de Fibonacci. Esta aproximación

Page 8: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 8

reduce el número de llamadas al procedimiento; por tanto su ejecución ahorra tiempo a expensasde hacer uso de más espacio de memoria.

Ejemplo3. Programa que computa el N-ésimo término de la sucesión de Fibonacci

usando un algoritmo recursivo con memoria .

/*************************************************** Autor:* Fecha: Versión:***************************************************/#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; }}

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 término de Fibonacci es: "; cout << Fib(N); } else { cout<<"Error en la entrada. ";

Page 9: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 9

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

Ahora resolveremos un problema donde la recursión simpli fica nuestro programa. El programaconsiste en encontrar el equivalente binario de los números decimales. Como su nombre indica,los números decimales se escriben usando diez dígitos (0 al 9) y los números binarios se escribenusando dos dígitos (0,1). La representación binaria de un número decimal se puede hallarfácilmente considerando el siguiente algoritmo:

Sea N un número 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, sireescribes los valores almacenados en sentido contrario a como los obtuviste, obtendrás elequivalente binario de N. Por ejemplo: supongamos que N es 23.

N N % 2 N / 2

23 1 1111 1 55 1 22 0 11 1 00

La representación binaria de 23 es

10111

Para resolver este problema sin usar recursión, necesitaremos un array para almacenar los dígitosbinarios que vamos calculando, para después poder escribirlos al revés. El problema a la hora deusar arrays es que no conocemos el número de elementos que vamos a usar del array. Veamos,ahora, como nos puede ayudar la recursión. El equivalente binario de 11, que es igual a 23 / 2, es1011

Así que, si podemos imprimir el equivalente binario de 11, será fácil escribir elequivalente binario del 23; lo único que tenemos que hacer es imprimir 23 % 2. Ahoraobservemos lo siguiente. El equivalente binario de 5, que es igual a 11 / 2, es 101. Si añadimos11 % 2 = 1, nos da el equivalente binario del 11. Por tanto, si consideramos la representaciónbinaria de un número como una cadena de dígitos binarios, podemos llegar a la siguientedefinición:

î

==

=)2%(||)2 / (debinariaciónRepresenta

11

00

N de Binariación Representa

NN

Nsi

Nsi

donde || representa la concatenación. Esta aproximación no requiere arrays y puede serprogramada fácilmente. El programa se ilustra en el ejemplo 4.

Page 10: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 10

Ejemplo4. Programa para hallar el equivalente binario de los números decimales .

/*************************************************** Autor:* Fecha: Versión:***************************************************/#include <iostream.h>#include <stdlib.h>

const int Base = 2;int N;

void PrintBinario( int N) { if (N > 0) { PrintBinario(N/Base); // Imprime equivalente binario de N/2 Cout << N % Base; } }

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

Un ejemplo simple de la ejecución de este programa sería:

Entra un entero positivo: 256El Número Decimal 26 es igual a 100000000 en binario.Entra un entero positivo: 32767El Número Decimal 32767 es igual a 111111111111111 en binario.Entra un entero positivo: 23El Número Decimal 23 es igual a 10111 en binario.Entra un entero positivo:

El programa del Ejemplo 4 se puede modificar para hallar la representación de un númerodecimal en cualquier base B. Debemos notar que, si la nueva base B es mayor que 10,necesitaremos más símbolos además de los dígitos del 1 al 9. Tradicionalmente, se han usado loscaracteres A, B, C, D, ..., y así sucesivamente para representar el 10,11, 12, 13, ... . Por ejemplo,el número 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 decaracteres llamado Dígitos. En este nuevo programa, en vez de imprimir el valor de

N % Base

Page 11: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 11

usaremos el valor obtenido, para indexar el array Digitos e imprimir el carácter almacenado endicho elemento (Ejemplo 5).

Ejemplo5. Programa para convertir números decimales a cualquier base hasta labase 16

/*************************************************** Autor:* Fecha: Versión:***************************************************/#include <iostream.h>#include <stdlib.h>const int BaseMax = 16; //Límite superior de la base

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 Número Decimal "<<N<<" es igual a "; PrintNuevaBase(N, NuevaBase); Cout << " en base "<< NuevaBase<< endl; system("PAUSE"); return 0;}

Un ejemplo simple de la ejecución de este programa sería:

Page 12: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 12

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

7.3.- BUSQUEDA Y ORDENACION USANDO RECURSION

7.3.1.- Búsqueda

Asumimos que tenemos un array llamado Ordenado de N elementos y que está ordenado enorden creciente. Consideremos el problema de determinar si un elemento dado, Item , estápresente en dicho array. Si Item está presente en la li sta, nos gustaría determinar su localización(Loc ). Si Item no está presente en la li sta, deberemos poner Loc a cero.

Este problema se puede simpli ficar si reducimos el tamaño del array. Una forma de haceresto 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 posibili dades:.

1. Ordenado[X ] = Item : En este caso, ya hemos encontrado el Item en el array, yponemos Loc a X.

2. Ordenado[X ] > Item : No podemos decir si el Item está o no en el array, pero ya quelos elementos del array están en orden creciente, podemos ignorar la segunda parte delarray;la siguiente vez, consideraremos sólo 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 quelos elementos del array están en orden creciente, podemos ignorar la primera parte delarray; la siguiente vez, consideraremos sólo los elementos desde el X+1 al N.

En los casos 2 y 3, el tamaño del array se reduce, y el mismo proceso, seleccionar uníndice aleatorio X entre los valores del índice válidos (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 repetirsobre una porción más pequeña del array Ordenado . Eventualmente el tamaño del array bajoconsideración será cero si el Item no está en el array; en otro caso se habrá localizadopreviamente. En vez de usar un índice X aleatorio , se suele coger la mitad del array. A estemétodo se le denomina búsqueda binar ia porque el elemento intermedio de un array divide alarray en dos partes iguales o casi iguales (Si N es par). El programa principal del Ejemplo 6 tieneun programa principal que primero lee el número de elementos que va a contener el arrayOrdenado y que después lee los elementos. Dentro del bucle WHILE que sigue a la parte deentrada, el programa lee el Item a localizar y después invoca al procedimiento Buscar , querequiere cinco parámetros. Estos parámetros son, en orden:

2.Array Ordenado .

Page 13: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 13

3.Primero : Cota inferior de la parte del array Ordenado en el que estamosinteresados.

4.Ultimo : Cota superior de la parte del array Ordenado en el que estamosinteresados.

5.Item : El valor que estamos buscando.6.Loc : Si se encuentra el Item, Loc contendrá un índice a la posición dentro del

array donde está el Item, sino contendrá cero.

El procedimiento Buscar , después de comparar con el elemento mitad de la porción actual delarray que estamos tratando, decide qué parte de la porción actual hay que ignorar. Si el elementointermedio es menor que el Item, se ignora la primera parte, y se llama otra vez a Buscar con lascotas inferiores y superiores configuradas con los valores Mitad+1 y Ultimo. En otro caso, sellaman con las cotas Primero y Mitad-1 como cotas inferior y superior.

Ejemplo6. Búsqueda Binaria recursiva

/*************************************************** Autor:* Fecha: Versión:***************************************************/#include <iostream.h>#include <stdlib.h>const int Tamanyo = 100; // Tamaño del arraytypedef 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 << "Número de elementos en Ordenado: "; cin >> N; cout << endl; if (N > Tamanyo) { N = Tamanyo; // No se pueden leer más de Tamanyo elementos

Page 14: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 14

} 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.- Ordenación

La ordenación está muy relacionada con la mezcla. Mezclar es un término usado por un procesoque combina dos listas ordenadas preservando el orden. Examinemos la mezcla de dos listasordenadas (asumiremos que nuestras listas contienen enteros y están ordenadas de formacreciente) antes de ver el papel de la mezcla en la ordenación.

El proceso de mezcla se puede realizar seleccionando sucesivamente el valor máspequeño 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 58Lista 2: 8 36

podemos obtener las siguientes trazas:

Lista 1: 13 27 58Lista 2:36Nueva Lista : 8

Lista 1: 27 58Lista 2:36Nueva Lista : 8 13

Lista 1: 58Lista 2:36Nueva Lista : 8 13 27

Lista 1: 58Lista 2 :Nueva Lista : 8 13 17 36

Page 15: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 15

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

donde los elementos Primero a Mitad-1 representan a la Lista1, mientras que los elementosMitad a Ultimo representa a la Lista2. En el proceso de mezcla, se va a necesitar un áreatemporal (Temp) ,del mismo tamaño que la Lista, para almacenar los resultados. Al final, Tempse volverá a copiar en la Lista. El procedimiento para realizar la operación de mezcla se ilustra acontinuación:

void MezclaSimple(Lista &K, int Primero, int Mitad, int Ultimo)// Mezcla dos listas que están ordenadas en el mismo array{ Lista Temp; int Indice1, Indice2, IndiceMezcla, i; Indice1 = Primero; Indice2 = Mitad; // Inicializa el número 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

Primero Mitad Ultimo

Page 16: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 16

while (Indice1 < Mitad) {

IndiceMezcla++; Temp[ IndiceMezcla] = K[Indice1];

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

Este proceso de mezcla simple se puede generalizar para mezclar K listas ordenadas enuna sola lista ordenada. A este proceso se le llama mezcla múltiple. La mezcla múltiple se puedellevar a cabo realizando una mezcla simple, repetidamente. Por ejemplo, si tenemos ocho listaspara mezclar, podemos mezclarlas a pares para obtener así cuatro listas ordenadas. Estas listas, asu vez, se pueden mezclar a pares para obtener dos listas ordenas. Y una operación de mezclafinal nos dará la li sta ordenada.

Veamos ahora como se puede usar la mezcla para ordenar un array de ocho elementos.Asumimos que la li sta contiene:

81 8 13 7 79 54 1 5

Dividamos esta lista en dos partes iguales (una barra vertical indica el punto de división).Obtenemos:

81 8 13 7 | 79 54 1 5

Como no están ordenadas, no podemos mezclar estas dos listas. Dividamos la primera sublista endos piezas:

81 | 8 13 7 | 79 54 1 5

Una vez más:

81 | 8 | 13 7 | 79 54 1 5

La primeras dos sublistas contienen sólo un elemento, y obviamente una lista que contiene unsolo 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 porelementos simples se mezclan, obtenemos:

8 81 | 7 13 | 79 54 1 5

Como las dos primeras listas están 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 técnica a lasegunda sublista y después las mezclamos con la primera, estará ordenado todo el array. En el

Page 17: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 17

Ejemplo 7, el procedimiento OrdenacionPorMezcla divide el array de entrada en sublistasmás y más pequeñas hasta que alcancen el tamaño de un elemento. Entonces, usando elprocedimiento MezclaSimple, se mezclan esas sublistas, obteníendose la li sta ordenada.

Ejemplo7. Programa recursivo para la ordenación por mezcla.

/*************************************************** Autor:* Fecha: Versión:***************************************************/#include <iostream.h>#include <stdlib.h>const int Tamanyo = 10; // Tamaño del arraytypedef int Lista[ Tamanyo]; void MezclaSimple(Lista &K, int Primero,int Mitad,int Ultimo)

// Mezcla dos listas que están ordenadas en el mismo array // El cuerpo del procedimiento anterior

void OrdenacionPorMezcla(Lista & K, int Primero, int Ultimo) { // Ordena el array K usando ordenación por mezcla recursiva int Tam, Mitad; // Determina el tamaño de la porción actual

Tam = Ultimo - Primero + 1; if ( Tam <= 1) // No más 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; i = 0; // Entrada de datos desordenados cin >> K; while( i < Tamanyo-1) { i++; Desordenado[i] = K;

Cin >> K; } Tercero = i; OrdenacionPorMezcla(Desordenado, 1, Tercero); for( i = 1; i<= Tercero; i++) // Imprime el array ordenado { cout<< Desordenado[ i]<< endl;

} system("PAUSE"); return 0;}

Page 18: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 18

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;}

Page 19: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 19

3.- La relación recurrente

x

si n

x x si n

x x si n

n n

n

=

=

>

<

î

+

1 0

0

0

1

1

*

/

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

Page 20: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 20

4.- El máximo común divisor de dos enteros positivos M y N se puede definir como:

MCD M N

N si N M y M N

MCD N M si M N

MCD N M N en otro caso

( , )

mod

( , )

( , mod )

=≤ =<

î

0

Escribir una función recursiva que permita calcular el máximo común divisor de dos enterospositivos.

7.- En general, el N-ésimo término de la sucesión de Fibonacci, se define como:

F A B N

A si N

B si N

F B A B N si N

( , , )

( , , )

===

+ − >

î

1

2

1 2

Donde A y B son los números que originan la secuencia. Escribir un programa que compute el N-ésimo término de la sucesión de Fibonacci usando diferentes números para originar la secuencia.

8.- La función Q se define como:

Q N

si N

Q N Q N si N

( )

( ( ))

=≤

− − >

î

1 2

1 2

Escribir dos procedimientos, uno recursivo y otro no recursivo, para computar el valor de estafunción para varios valores de N. ¿Puedes escribir una función que haga uso de más memoriapara así aumentar la velocidad de los cálculos recursivos?.

9.- El valor del N-ésimo término del polinomio de Legendre se puede calcular usando lassiguientes fórmulas:

P0(x) = 1P1(x) = x

P xN

Nx P x

N

NP xN N N( )

** * ( ) * ( )=

−−

−− −

2 1 11 2

Escribir una furnción recursiva que compute el N-ésimo término del polinomio de Legendre.

Page 21: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 21

10.- Escribir una función recursiva para computar la función de Ackermann, la cual se definecomo:

A(M,N)

N si M 0

A(M 1,N) si M 0 and N = 0

A(M -1,A(M,N -1)) si M 0 and N 0

=+ =

− ≠≠ ≠

î

1

donde M y N son números cardinales.

11.- Para cualesquiera enteros no negativos N y K, donde 0<=K<=N, el coeficiente binomialC(N,K) se define como:

C N KN

K N K( , )

!

!* ( )!=

Esta fórmula verifica que:1. C(N,K) = C(N, N-K); es decir, la li sta de coeficientes binomiales es simétrica con

respecto a su valor central.2. C(N,N) = 1 y por simetría C(N,0) = 1.3. C(N,N-1)=N y por simetría C(N,1) = N.

El problema para computar C(N,K) haciendo uso de la fórmula anterior estriba en que N! crecemuy rápidamente.Por lo que necesitamos un método 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 KC N K N K

K( , )

( , ) * ( )=

− − +1 1

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 ratón encuentre un queso de cabrales en unlaberinto. Vamos a suponer que el laberinto es un recinto rectangular dividido en cuadrados,estando cada cuadrado ocupado por un obstáculo o libre. El perímetro del rectángulo estáocupado por obstáculos, excepto en una o más salidas. Comenzamos en algún lugar dentro dellaberinto, y tenemos que encontrar el camino de salida. Podemos movernos de cuadrado encuadrado en cualquier dirección (excepto diagonalmente), pero no podemos atravesar unobstáculo.

Podemos representar el laberinto en un ordenador mediante un array de caracteres ( obooleano) bidimensional, por ejemplo:

BBBBBBBBBBBBB..BBB....BBBB...B.BBB.BB..B.B.B...BBBB....BBBBBB...BBB....BB.B.....BB.BBBBBBBBBBB.B

Construir un programa recursivo que tenga como entrada la posición del ratón dentro dellaberinto y que determine una de sus posibles salidas.

Page 22: INTRODUCCIÓN A LA RECURSIVIDAD. - lcc.uma.esafdez/apuntes/laboratorio/apuntes/apuntesrecursivi... · CONCEPTO DE RECURSION Se dice que un proceso es recursivo si se puede definir

� � � � � � � � � � � � � � � � � � � � � � � � � Laboratorio de Programación.

_____________________________________________________________________________

____________________________________________________________________________________Introducción a la Recursión. Pág 22

(* El siguiente ejercicio no se debe incluir en la relación de problemas * )

12.- El método de ordenación rápida (QuickSort) es un método de ordenación rápido yrelativamente moderno desarrollado por Hoare. Es esencialmente un esquema deinserción/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 número de comparacionesque se necesitan para posicionar el resto de elementos.

La filosofía del QuickSort consiste en dividir la li sta de elementos tomando comoreferencia un determinado elemento, llamado pivote, de forma que, en las listas resultantes de ladivisión, todas los elementos que precedan al pivote sean menores o iguales al mismo y todos loselementos que estén después del pivote sean mayores o iguales que el mismo.

Como resultado de la partición, el pivote está correctamente colocado. El mismo procesode partición se aplica entonces a las dos sublistas a ambos lados del pivote. De esta forma la li staoriginal es sistemáticamente reducida a una serie de sublistas, cada una de longitud uno, queestán, por supuesto, ordenadas y, más importante, correctamente posicionadas en relación a lasotras.

La característica más importante de este algoritmo es la partición. La entrada seleccionadacomo el pivote suele ser la primera entrada. Habiendo elegido el pivote, el algoritmo busca en lalista ,desde dicho pivote hasta el final, el primer elemento que no pertenezca a ese lado delpivote. Estos dos elementos se intercambian, y se reanuda la búsqueda con los elementosadyacentes. Cuando coinciden dos búsquedas, el pivote está posicionado entre dos sublistas enorden para mantener la ordenación relativa.

Dados los índices de los elementos más a la izquierda (Izquierda) y a la derecha(Derecha) de la sublista a ordenar, el algoritmo queda como:

SI Izquierda < Derechaparte Lista[Izquierda ] a Lista[Derecha ] en dos seccionesmás pequeñas 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 método.

≤pivote

Izqda Dcha

≥pivote

pivote

IFin Dinicio

. . .