recursividad...2009/08/04  · 4.1 definici´on y tipos de recursividad 2 4.1.1. la recursion es...

13
Programaci´ on Modular. ETSIT. 1 o C, Apuntes del profesor Juan Falgueras 2004 4 Recursividad Contenido 4. Recursividad 1 4.1. Definici´ on y Tipos de recursividad ........................... 1 4.1.1. La recursi´ on es como la iteraci´ on ........................ 2 4.1.2. Tipos de recursi´ on ................................ 2 4.1.3. Implementaci´ on interna en un ordenador .................... 3 4.2. Verificaci´ on de la correcci´ on de un algoritmo recursivo ................ 4 4.3. Conveniencia del uso de la recursividad ........................ 5 4.4. Ejemplos de programas recursivos ............................ 5 4.4.1. La funci´ on de Fibonacci ............................. 5 4.4.2. ıgitos binarios .................................. 7 4.4.3. La b´ usqueda binaria ............................... 7 4.4.4. Quicksort ..................................... 8 4.4.5. Torres de Hanoi ................................. 11 4.4.6. Ackerman ..................................... 12 4.4.7. usqueda binaria ................................. 12 4.5. Algoritmos de vuelta atr´ as ................................ 12 4. Recursividad Hasta ahora hemos visto formas de estructurar tanto datos como programas, mediante fun- ciones. Las funciones resuelven cometidos concretos y llevan par´ ametros que las hacen ser ´ utiles para distintas situaciones. Las funciones que hemos visto hasta ahora se resolv´ ıan por ı mismas o, a lo sumo pod´ ıan pedir ayuda otras funciones que terminaban resolvi´ endose sin as llamadas. Sin embargo, hay veces en que una funci´ on s´ olo se puede resolver volvi´ endose a llamar a s´ ı mismas. Se trata de funciones recursivas. ¿Cu´ ando tiene esto sentido? En este tema se introduce el concepto de recursi´ on, se examinan los posibles casos, se muestra omo un ordenador, que es fundamentalmente iterativo, no recursivo, puede emular la recur- si´ on; se dan reglas para comprobar que las solucines recursivas que se construyan tengan final y se presentan diversos ejemplos importantes de recursi´ on. 4.1. Definici´ on y Tipos de recursividad Se dice que un proceso es recursivo si se resulve llam´ andose a s´ ı mismo. Para que la recursi´ on tenga sentido (un final ´ util) cada vez que se llame internamente a ı misma deber´ a hacerlo de una manera “menos recursiva” hasta que finalmente se llame a s´ ı misma de una manera no recursiva. La recursi´ on infinita es in´ util. Lo ´ unico que determina el comportamiento de una funci´ on son los par´ ametros que son los que dan idea del “tama˜ no del problema”. La funci´ on no es recursiva para algunos valores del par´ ametro que se denominan casos base. Toda funci´ on recursiva, pues, debe tener alg´ un caso base y toda llamada recursiva dentro de ella debe tender hacia el caso base.

Upload: others

Post on 12-Aug-2020

11 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Recursividad...2009/08/04  · 4.1 Definici´on y Tipos de recursividad 2 4.1.1. La recursion es como la iteraci´on En muchas ocasiones se puede ver que la recursi´on no es mas

Programacion Modular. ETSIT. 1o C, Apuntesdel profesor Juan Falgueras 2004

4Recursividad

Contenido

4. Recursividad 14.1. Definicion y Tipos de recursividad . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

4.1.1. La recursion es como la iteracion . . . . . . . . . . . . . . . . . . . . . . . . 24.1.2. Tipos de recursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24.1.3. Implementacion interna en un ordenador . . . . . . . . . . . . . . . . . . . . 3

4.2. Verificacion de la correccion de un algoritmo recursivo . . . . . . . . . . . . . . . . 44.3. Conveniencia del uso de la recursividad . . . . . . . . . . . . . . . . . . . . . . . . 54.4. Ejemplos de programas recursivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

4.4.1. La funcion de Fibonacci . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54.4.2. Dıgitos binarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74.4.3. La busqueda binaria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74.4.4. Quicksort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84.4.5. Torres de Hanoi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114.4.6. Ackerman . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124.4.7. Busqueda binaria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

4.5. Algoritmos de vuelta atras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

4. Recursividad

Hasta ahora hemos visto formas de estructurar tanto datos como programas, mediante fun-ciones. Las funciones resuelven cometidos concretos y llevan parametros que las hacen serutiles para distintas situaciones. Las funciones que hemos visto hasta ahora se resolvıan porsı mismas o, a lo sumo podıan pedir ayuda otras funciones que terminaban resolviendose sinmas llamadas. Sin embargo, hay veces en que una funcion solo se puede resolver volviendosea llamar a sı mismas. Se trata de funciones recursivas. ¿Cuando tiene esto sentido?

En este tema se introduce el concepto de recursion, se examinan los posibles casos, se muestracomo un ordenador, que es fundamentalmente iterativo, no recursivo, puede emular la recur-sion; se dan reglas para comprobar que las solucines recursivas que se construyan tengan finaly se presentan diversos ejemplos importantes de recursion.

4.1. Definicion y Tipos de recursividad

Se dice que un proceso es recursivo si se resulve llamandose a sı mismo.Para que la recursion tenga sentido (un final util) cada vez que se llame internamente a

sı misma debera hacerlo de una manera “menos recursiva” hasta que finalmente se llame a sı mismade una manera no recursiva.

La recursion infinita es inutil.Lo unico que determina el comportamiento de una funcion son los parametros que son los

que dan idea del “tamano del problema”. La funcion no es recursiva para algunos valores delparametro que se denominan casos base. Toda funcion recursiva, pues, debe tener algun casobase y toda llamada recursiva dentro de ella debe tender hacia el caso base.

Page 2: Recursividad...2009/08/04  · 4.1 Definici´on y Tipos de recursividad 2 4.1.1. La recursion es como la iteraci´on En muchas ocasiones se puede ver que la recursi´on no es mas

4.1 Definicion y Tipos de recursividad 2

4.1.1. La recursion es como la iteracion

En muchas ocasiones se puede ver que la recursion no es mas que la repeticion (iteracion)de una serie de acciones. Esta iteracion se da hasta llegar a un valor de una variable. Pero en larecursion esta iteracion se esta desarrollando mediante la llamada de la funcion a sı misma con unparametro que es la variable que determina el final de la recursion, en vez de ser un bucle dentrode una funcion normal. Ası pues, en la iteracion, es la guarda del bucle la que determina cuandoacabara la repeticion; en la recursion es el parametro.

Por ejemplo:

f(x) =

{1 x = 0x · f(x− 1) x > 0

es una definicion formal de la funcion factorial en forma recursiva. que podrıa corresponder alalgoritmo recursivo:

1 int f(int n) {

2 if (0==n)

3 return 1;

4 else

5 return n * f(n-1);

6 }

pero tambien sabemos que la forma iterativa de factorial es:

f(x) = x · (x− 1) · (x− 2) · . . . · 1

que esta definida mediante una repeticion de multiplicaciones empezando en el propio valor x yen las que el siguiente multiplicador disminuye hasta llegar al valor 1. El algoritmo serıa:

1 int f(int x) {

2 int r, i;

4 for (i=x, r=1; i>1; --i)

5 r *= i;

6 return r;

7 }

En este sentido la recursividad es una nueva forma de ver las acciones repetitivas permitiendo queun subprograma se llame a sı mismo para resolver una version mas pequena del problema original.Sin embargo, no siempre la recursion es tan similar a la iteracion.

4.1.2. Tipos de recursion

Estructuralemente existen dos formas de recursion:

1. la directa: es la que hemos visto hasta ahora en la que la funcion se llama a sı misma.

2. Indirecta: es la recursion que se produce cuando la funcion se llama no a sı misma sinoa otra funcion (y esta quizas a otra) que termina llamando a la funcion inicial. Se produceası una cadena:

f(a) → g(b) → h(c) → . . . f(a′)

Ası la recursion directa es una forma mas simple de recursion en la que no existen g(), h(), etc.Existen distintos modos de hacer la recursion:

de cabeza la recursion de cabeza se produce cuando la llamada recursiva se hace justo al principiodel procedimiento recursivo, antes que ninguna otra operacion.

Por ejemplo:

Page 3: Recursividad...2009/08/04  · 4.1 Definici´on y Tipos de recursividad 2 4.1.1. La recursion es como la iteraci´on En muchas ocasiones se puede ver que la recursi´on no es mas

4.1 Definicion y Tipos de recursividad 3

x'''f(x)

final

xdirecta

f(x)

final

xindirecta

x?

x''

x'

Figura 1: En la recursion directa, la funcion se llama a sı misma; en la indirecta llamaa otras que terminan llamandola de nuevo a ella.

1 char ultimaLetra(char *cadena)

2 {

3 if (*cadena != ’\0’) {

4 return ultimaLetra(cadena+1);

5 }

6 }

de cola por el contrario, en la de cola la llamada se hace al final despues de todas las operaciones.

intermedia implica la existencia de operaciones antes y despues de la llamada recursiva.

multiple se producen varias llamadas, recursivas en distintos puntos del procedimiento.

anidada la anidada o no primitiva es aquella en la que la recursion se produce en un parametrode la propia llamada recursiva. Es decir, al hacer la llamada recursiva se utiliza un parametroque es el resultado de una llamada recursiva.

x'

f(x)

ABC

f(x)

A

x'

BC

f(x)

AB

x'

C

f(x)

ABC

f(x)f(x')

Figura 2: Dependiendo del momento del desarrollo del algoritmo en el que se producela recursion se tienen cuatro formas, en general.

4.1.3. Implementacion interna en un ordenador

Consideremos ahora la ejecucion de la funcion recursiva f(x). En un lenguaje de programacion,cuando se llama a un procedimiento (o funcion), lo que se hace es que se guarda la direccion de lasentencia ‘llamante’ como direccion de retorno; se asigna nueva memoria para las variables localesdel procedimiento, y al finalizar la ejecucion del procedimiento, se libera la memoria asignadaa las variables locales y se vuelve la ejecucion al punto en que se hizo la llamada haciendo usode la direccion de retorno, direccion de la instruccion que sigue a la instruccion de llamada alprocedimiento.

Por otro lado, tenemos que estudiar dos conceptos mas. Uno es la pila (stack) y el otro son losregistros de activacion de los procedimientos. Una pila es una estructura de datos en la que solocabe anadir o quitar un dato cada vez, pero, tal y como se hace con una pila de libros, por ejemplo,para el dato a extraer solo es accesible el ultimo libro que se apilo. Debido a este comportamientoa las pilas tambien se las conoce como estructuras “ultimo que entra, primero que sale” (LIFO).

Page 4: Recursividad...2009/08/04  · 4.1 Definici´on y Tipos de recursividad 2 4.1.1. La recursion es como la iteraci´on En muchas ocasiones se puede ver que la recursi´on no es mas

4.2 Verificacion de la correccion de un algoritmo recursivo 4

f(x)

return addr(x)

int i;

Figura 3: La pila de recursion se va formando en cada llamada dejando encima los datosde la ultima llamada para ser los inmediatamente pendientes de resolver.

El registro de activacion de un procedimiento es un bloque de memoria que contieneinformacion sobre las constantes, variables locales declaradas en el procedimiento y losparametros que se le pasen en esa llamada al procedimiento, junto con la direccion deretorno que corresponde a esa llamada.

Los lenguajes de programacion actuales utilizan una pila especial que el sistema tiene para lasllamadas a subrutinas. En esta pila el codigo maquina del programa cada vez que tiene quellamar a un procedimiento guarda su registro de activacion. Si dentro de esta subrutina se llamade nuevo a sı misma en forma recurrente, dado que la creacion del registro de activacion delpropio procedimiento se hace sobre una pila sin borrar los registros de activacion anteriores, sepuede generar uno nuevo para cada llamada recursiva. Al final nos encontramos con que con cadallamada recursiva se estan apilando registros de activacion sobre los registros de activacion de lasllamadas anteriores.

Cuando una de las llamadas recursivas se resuelve por fin sin recursion, su registro de activa-cion se ‘quita’ de la pila (se desapila) siguiendo la ejecucion del programa por donde iba cuandose llamo esta ultima vez. Y por tanto siguiendo tambien con las variables y parametros que setenıan en aquel momento. De nuevo, cuando esta llamada se resuelve sin recursion, se repite ladisminucion de la pila y vuelta al estado anterior.

Debido a la sobrecarga (overhead) que producen las operaciones sobre la pila, la creaciony borrado de los registros de activacion, los procedimientos recursivos consumen mas tiempo ymemoria que los programas no recursivos.

Solo en determinadas ocasiones, debido a la estructura de datos usada en el problema o alplanteamiento del mismo la recursion es preferible, y evitar la recursion es bastante mas difıcil quedar una solucion recursiva al problema.

4.2. Verificacion de la correccion de un algoritmo recursivo

Para comprobar que un algoritmo recursivo funciona correctamente es necesario que siga lastres reglas siguientes:

1. Existe un caso base (por cada llamada recursiva que se haga dentro del algoritmo debehaber un valor) para el cual el algoritmo finalice sin recursion. Por ejemplo, fact(0) =0 sin recursion. En el algoritmo factorial se produce una llamada recursiva con lo cual essuficiente con un caso base.

2. Todos los posibles argumentos de las llamadas recursivas se reenvıan tendiendo sus valoreshacia un caso base. En el caso del factorial, ya que la llamada recursiva que se hace es confact(n-1) y n se suponıa natural, tienden para valores de n > 0 al valor 0.

3. La funcion es correcta, para valores distintos del caso base. En el caso del factorial, porinduccion, tendrıamos que si fact(n) = n!, entonces fact(n+1) = n ·fact(n) = (n+1)! �

Page 5: Recursividad...2009/08/04  · 4.1 Definici´on y Tipos de recursividad 2 4.1.1. La recursion es como la iteraci´on En muchas ocasiones se puede ver que la recursi´on no es mas

4.3 Conveniencia del uso de la recursividad 5

.

En general la busqueda de una solucion recursiva de los algoritmos se facilita mediante lalocalizacion de los casos bases. Una vez respondidos a ellos, se trata de que el argumento engeneral tienda a estos valores.

4.3. Conveniencia del uso de la recursividad

La recursion deberıa de evitarse siempre que se pueda por motivos de eficiencia, como se havisto en el apartado 4.1.3. Sin embargo, serıa justificable emplearla cuando:

1. se sabe que la funcion no va a generar demasiada profundidad de llamadas recursivas; par-ticularmante por que la pila del sistema donde se guardan todos las variables locales yparametros, es relativamente pequena y podrıa colapsar el sistema.

2. se sabe que la funcion no va a utilizar estructuras de datos locales demasiado grandes. Siası fuese el caso, aunque la solucion se mantuviese como recursiva habrıa que recurrir a otraforma de mantener los datos.

3. cada llamada no genera a su vez llamadas ya resueltas en otras llamadas que se generaran ose han generado antes. Este problema hay que analizarlo antes y es frecuente en recursion.Es una fuente de ineficiencia usual en recursion el que una funcion se evalue multiples vecescon el mismo valor de parametro en las llamadas recursivas generadas internamente,

4. La solucion no es planteable de forma sencilla de otra manera. Ocurre en muchos casos quela forma recursiva es muy mas clara que la iterativa, como se vera en los ejemplos. En otraspalabras, si la forma iterativa esta ahı y es sencilla, es preferible.

4.4. Ejemplos de programas recursivos

4.4.1. La funcion de Fibonacci

fib(x) =

{1 x ≤ 2fib(x− 2) + fib(x− 2) x > 2

La funcion de Fibonacci proviene del bucolico problema de la multiplicacion de conejos. Supon-gamos que partimos de una pareja de conejos recien nacidos, y queremos calcular cuantas parejasde conejos forman la familia al cabo de n meses si:

Los conejos nunca mueren.

Un conejo puede reproducirse al comienzo del tercer mes de vida.

Los conejos siempre nacen en parejas macho-hembra. Al comienzo de cada mes, cada parejamacho-hembra, sexualmente madura, se reproduce en exactamente un par de conejos macho-hembra.

Para un n pequeno, por ejemplo 6, la solucion se puede obtener facilmente a mano:

Mes 1: 1 pareja, la inicial

Mes 2: 1 pareja, ya que todavıa no es sexualmente madura.

Mes 3: 2 parejas; la original y una pareja de hijos suyos.

Mes 4: 3 parejas; la original, una pareja de hijos suyos nacidos ahora y la pareja de hijos suyosnacidos en el mes anterior.

Mes 5: 5 parejas; la original, una pareja de hijos suyos nacidos ahora, las dos parejas nacidas en losmeses 3 y 4, y una pareja de hijos de la pareja nacida en el mes 3.

Page 6: Recursividad...2009/08/04  · 4.1 Definici´on y Tipos de recursividad 2 4.1.1. La recursion es como la iteraci´on En muchas ocasiones se puede ver que la recursi´on no es mas

4.4 Ejemplos de programas recursivos 6

Mes 6: 8 parejas; las 5 del mes anterior, una pareja de hijos de la original, una pareja de hijos de lanacida en el mes 3 y una pareja de hijos nacida en el mes 4.

Si deseamos saber el numero de parejas al cabo de n meses, para un n cualquiera, podemosconstruir un algoritmo recursivo facilmente a partir de la siguiente relacion:

Parejas(n) =

{1 n ≤ 2Parejas(n− 1) + Parejas(n− 2) n > 2

En esta relacion Parejas(n − 1) son las parejas vivas en el mes n − 1, y Parejas(n − 2) son lasparejas que nacen en el mes n a partir de las que habıa en el mes n− 2.

La serie de numeros Parejas(1), Parejas(2), Parejas(3),. . . es conocida como la Serie de Fibo-nacci, la cual modela muchos fenomenos naturales. El crecimiento de esta funcion, como demuestrala su aproximacion: y(k) ≈ 1/

√5( 1+

√5

2 )k, es exponencial. Los valores de yk coinciden (en su parteentera redondeada desde k = 1 con los de f(k)).

1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1.597, 2.584, 4.181, 6.765,10.946, 17.711, 28.657, 46.368, 75.025, 121.393, 196.418, 317.811, 514.229, 832.040,11346,269 . . .

son solo los valores alcanzado hasta el termino 30.En forma de algoritmo recursivo la funcion de Fibonacci serıa:

1 int fib(int n) {

2 if (n<=2)

3 return 1;

4 else

5 return fib(n-1)+fib(n-2);

6 }

es un ejemplo de sencillez y falta de eficiencia recursiva, como se ve en el arbol de recursion de lamisma (Fig. 4).

f(6)

f(5)

f(4)

f(3)

f(2) f(1)

f(2) f(3)

f(2) f(1)

f(4)

f(3)

f(2) f(1)

f(2)

Figura 4: Arbol de recursion del algoritmo recursivo Fibonacci. Observerse la cantidadde llamadas repetidas que se producen solo para calcular el sexto valor de laserie.

Matematicamente los numeros de la serie de Fibonacci aparecen en muchas ocasiones. Unaexpresion cerrada (no recurrente) para calcularlos es:

fib(n) =1√5

((1 +√

52

)n

−(1−

√5

2

)n)

=φn − φ∗n

√5

siendo φ = (1 +√

5)/2 y φ∗ = (1−√

5)/2 el numero aureo. Fibonacci crece exponencialmente yaque el termino que resta es menor que 1 (φ∗ ≈ −0′6180) mientras que φ ≈ 1′62. El numero dedıgitos del n-simo termino crece rapidamente, de manera lineal con n.

Page 7: Recursividad...2009/08/04  · 4.1 Definici´on y Tipos de recursividad 2 4.1.1. La recursion es como la iteraci´on En muchas ocasiones se puede ver que la recursi´on no es mas

4.4 Ejemplos de programas recursivos 7

Programacion dinamica del algoritmo recursivo de Fibonacci . El problema de la so-lucion recursiva de Fibonacci es que llama multitud de veces a los mismos calculos que acabade resolver. Para valores pequenos esto no tiene importancia, pero el crecimiento exponencial deeste numero de llamadas repetidas lo hacen realmente ineficiente respecto a una sencilla solucioniterativa mediante un simple bucle.

La Programacion Dinamica consiste almacenar los valores que se van obteniendo durante elproceso completo como resultados parciales reaprovechables para subsiguientes calculos, optimi-zando ası el analisis global. En nuestro caso, bastarıa con almacenar, conforme los calculamos, losvalores de los fibs anteriores, de manera que cuando se pidan de nuevo esos valores se devuelvandirectamente del almacen de los calculados.

En el lenguaje de programacion C, por ejemplo, se puede mantener la informacion calculadaen variables locales de una llamada a otra mediante variables locales estaticas. En este caso, nosinteresa mantener un largo array de valores calculados pero tambien, dado que tan solo la primeravez necesitara este array inicializarse, una variable que nos diga si es la primera vez o no queejecutamos la funcion. Quedarıa:

1 unsigned fib(int n) {

2 static unsigned fibs[1000];

3 static bool primeravez=true; // solo se inicializa una vez

4 if (primeravez) {

5 fibs[1]=fibs[2]=1;

6 for(int i=3; i<1000; i++)

7 fibs[i] = 0;

8 primeravez = false;

9 }

10 if (fibs[n] != 0)

11 return fibs[n];

12 fibs[n] = fib(n-1) + fib(n-2);

13 return fibs[n];

14 }

4.4.2. Dıgitos binarios

El siguiente algoritmo recursivo escribe secuencialmente (y de manera ordenada) los dıgitosbinarios del parametro entero que recibe:

1 typedef unsigned long int base;

2 void dec2bin(base n) {

3 if (n >= 2){

4 dec2bin(n / 2);

5 cout << n % 2;

6 } else {

7 cout << n;

8 }

9 }

Su especificacion formal serıa:

dec2bin(x) =

0 x = 01 x = 1dec2bin(x/2) ∼ x%2 x ≥ 2

indicando ∼ la concatenacion de cadenas de letras. La operacion “ %” serıa la operacion “resto”que devuelve el resto de la division entre sus operandos.

4.4.3. La busqueda binaria

Z BusquedaBin(E T a[N], E T x)

requiere que el array este ordenado. Para responder recursivamente podemos lanzar una funcionrecursiva en la que acotemos los lımites “binarios” de la busqueda; lanzarıamos la primera llamadacon 0, N − 1:

Page 8: Recursividad...2009/08/04  · 4.1 Definici´on y Tipos de recursividad 2 4.1.1. La recursion es como la iteraci´on En muchas ocasiones se puede ver que la recursi´on no es mas

4.4 Ejemplos de programas recursivos 8

1 int buscarR(T ordenado[], int ini, int fin, T x) {

2 int mitad;

4 if (ini > fin)

5 return -1;

7 mitad = (fin + ini) / 2;

8 if (ordenado[mitad] == x)

9 return mitad;

10 else if (x > ordenado[mitad])

11 return Buscar(ordenado, mitad+1, fin, x);

12 else

13 return Buscar(ordenado, ini, mitad-1, x);

14 }

16 int BusquedaBin(T ordenado[], T x)

17 {

18 return buscarR(ordenado, 0, N-1, x);

19 }

4.4.4. Quicksort

El algoritmo de ordenacion de Hoare (apodado Quicksort) es mucho mas sencillo en formarecursiva.

¿Como funciona? Se trata de separar en dos partes el array, a un lado los elementos maspequenos y al otro los mas grandes (o iguales) a un elemento de referencia llamado pivote, que setoma del mismo array (ver la Figura 5).

En esta division o particion esta el secreto de Quicksort.Para que la cosa funcione bien, se deberıa elegir el pivote de manera que quedara mas o menos

en medio del array repartido.Una vez hecha esta particion podemos plantearnos recursivamente el mismo problema de

ordenacion pero con dos arrays la mitad de largos. Esto llevarıa en el primer array a otros dos arraystambien separados por un nuevo pivote y ası sucesivamente. Lo interesante es que cada subarraymitad esta ya en su sitio (“rapidamente”) desde el principio y, por ende, el array concatenacionde todos ellos.1

Para que el array quede dividido por la mitad en cada particion es necesario elegir muy bienel pivote o referencia. Para conseguir que el pivote quede enmedio, deberıa tomarse la mediana delos elementos del array. De hecho algunas versiones de este algoritmo de ordenacion buscan estamediana primero entre los elementos del array, pero este proceso hay que descartarlo en general yaque lo que se gana en cuanto a la simetrıa y eficiencia de las particiones se pierde en la busquedade la mediana de los subarrays, necesariamente busquedas de complejidades lineales. Ası pues, elpivote se elige al azar entre los elementos del array recibido. Al no haber criterio a priori se toma obien el primero o bien el ultimo, o, si se es supersticioso, el de enmedio. Tomando como referenciael ultimo:

x = a[N − 1]

Una vez tomado el pivote x empezamos la particion. Para ello el siguiente paso es localizar (desde0 hasta i) un punto hasta el cual todos los elementos del array sean menores que el pivote xelegido. Se para la busqueda y se deja allı una referencia i, asımismo se busca otro punto bajandodesde el final del array (desde N − 1 hasta j) hasta el cual todos los elementos del array seanmayores o iguales al pivote, serıa j. Tenemos pues dos puntos posiblemente separados i y j quecontienen valores “desordenados”, uno mayor (o igual) (en i) y otro menor que el pivote (en j).Se intercambian esos valores y se repite este proceso subiendo el valor del ındice i y bajando eldel j con el mismo criterio (de nuevo una vez localizados los nuevos i y j se intercambian suscontenidos) hasta que i ≥ j.

1Una vez lograda esta division el problema inicialmente de complejidad cuadratica ∝ N2 queda dividido en lasuma de dos problemas de la cuarta parte de complejidad ∝ (N/2)2. El resultado final ∝ (N/2)2+ ∝ (N/2)2 ∝1/2N2 es de menor complejidad que si se trata siempre con todo el array de golpe, como se hace en los metodos deintercambio directo (burbuja, seleccion o insercion), que manejan todo el array en cada operacion.

Page 9: Recursividad...2009/08/04  · 4.1 Definici´on y Tipos de recursividad 2 4.1.1. La recursion es como la iteraci´on En muchas ocasiones se puede ver que la recursi´on no es mas

4.4 Ejemplos de programas recursivos 9

En el punto i = p en el que se encontro un elemento mayor (o igual) que el pivote x se tienenaseguradas las condiciones:

∀k < p, a[k] < x y ∀k > p, a[k] ≥ x

Despues de esto, dado que el elemento que queda en p puede ser mayor que el pivote x queesta situado en a[N −1], se intercambia por el, dejando el pivote en p y garantizando un array condos partes a[0..p] < r y a[p + 1..N − 1] ≥ r. Se tienen pues ahora problemas como el inicial pero

36

25 7 7 9

131410

< ≥

Figura 5: Tras cada iteracion se han intercambiado los elementos de manera que quedanlos menores a un lado y las mayores al otro del valor elegido de referencia.

recursivamente mas pequenos. Con lo cual se abordan llamadas recursivas a cada parte del arraypara ordenarlos igualmente. Mientras mas iguales sean los tamanos de las partes menos llamadasrecursivas surgiran. En este sentido se puede dar un caso degenerativo de este algoritmo que serıael de tener como tamano de una de las partes un elemento cada vez (en la otra los restantes) y porlo tanto se produciendose N −1 llamadas recursivas que solo sacan cada vez un elemento fuera delarray a ordenar. El caso es que precisamente esta situacion degenerativa se da con cierta facilidad.Basta con que nos den un array ordenado o invertido para que ocurra este desastre. Lo mejor paraque no ocurra es que los elementos esten aleatoriamente repartidos.

Se han hecho infinidad de estudios y mejoras de este algoritmo (particularmente interesan-tes las iniciadas por Sedgewick), para tratar de paliar sus debilidades. Notese que las grandescualidades de este algoritmo son las de hacer pocos intercambios y ademas haciendo viajar alos elementos grandes distancias cada vez que se hacen, lo que constituye las dos grandes cua-lidades mas buscadas de los algoritmos de ordenacion. Un detalle mas a su favor, mucho mas anivel tecnico es que los bucles de recorrido que hacen el trabajo duro son especialmente facilesde optimizar en cualquier procesador ya que involucran solo dos punteros a bloques que se vanincrementando/decrementando en el propio procesador.

1 int particion(char a[], int iz, int de)

2 {

3 char x, t;

4 int i, j;

6 x = a[de]; // pivote

7 i = iz - 1;

8 j = de;

9 do {

10 do ++i; while (a[i] < x); // x hace tambien de centinela

11 do --j; while (j >= 0 && a[j] >= x);

12 if (i<j) intercambia(a[i], a[j]);

13 } while (i < j);

14 intercambia(a[i], a[de]); // asegura x en su sitio

15 return i;

16 }

18 void quick(char a[], int iz, int de)

19 {

20 int p;

22 if (de > iz) {

Page 10: Recursividad...2009/08/04  · 4.1 Definici´on y Tipos de recursividad 2 4.1.1. La recursion es como la iteraci´on En muchas ocasiones se puede ver que la recursi´on no es mas

4.4 Ejemplos de programas recursivos 10

23 p = particion(a, iz, de);

24 quick(a, iz, p-1);

25 quick(a, p+1, de);

26 }

27 }

29 void QuickSort(char a[])

30 {

31 quick(a, 0, strlen(a)-1);

32 }

murcielago

m(u)rciela(g)o -i-> m(g)rciela(u)o

mg(r)ciel(a)uo -i-> mg(a)ciel(r)uo

mgaciel(r)u(o) -p-> mgaciel(o)u(r) = [mgaciel]o[ur]

(m)gaci(e)l our -i-> (e)gaci(m)l our

egaci(m)(l) our -p-> egaci(l)(m) our = [egaci]l[m our]

egac(i)(i) lm our -p-> egac(i)(i) lm our = [egac]i[lm our]

(e)g(a)c ilm our -i-> (a)g(e)c ilm our

a(g)e(c) ilm our -p-> a(c)e(g) ilm our = [a]c[eg ilm our]

ace(g)(g) ilm our -p-> ace(g)(g) ilm our = [a c e]g[ilm our]

a ceg ilm o(u)(r) -p-> acegilmo(r)(u) = [acegilmo]r[u]

acegilmoru

Cuadro 1: Ejemplo de pasos intermedios en la ordenacion in situ de un array. Las-i-> son reordenaciones para repartir entre los dos subarrays. Las -p-> lareubicacion del pivote.

Page 11: Recursividad...2009/08/04  · 4.1 Definici´on y Tipos de recursividad 2 4.1.1. La recursion es como la iteraci´on En muchas ocasiones se puede ver que la recursi´on no es mas

4.4 Ejemplos de programas recursivos 11

4.4.5. Torres de Hanoi

“Las Torres de Hanoi” es un juego cuya solucion se simplifica mucho si se piensa como unproblema recursivo.

Se tienen 3 palos de madera, que llamaremos palo izquierdo, central y derecho. El palo iz-quierdo tiene ensartados un monton de discos concentricos de tamano decreciente, de manera queel disco mayor esta abajo y el menor arriba.

El problema consiste en mover los discos del palo izquierdo al derecho respetando las siguientesreglas:

– Solo se pueden mover los discos de un palo a otro.

– Solo se puede mover un disco cada vez.

– No se puede poner un disco encima de otro mas pequeno.

Se quiere disenar un programa que recibiendo un valor entero N y escriba la secuencia de pasosnecesarios para resolver el problema de las torres de Hanoi para N discos.

SolucionPara ver el problema de forma recursiva tenemos que pensar que la solucion para N = 1 es trivial,mientras que para un valor mas alto de N se puede reducir en cada ocasion en un valor N − 1,descomponiendo la solucion en 3 fases:

1. Mover los N−1 discos superiores del palo izquierdo al palo central, utilizando el palo derechocomo palo auxiliar.

2. Mover el disco que queda del palo izquierdo al derecho.

3. Mover los N − 1 discos del palo central al palo derecho, utilizando el palo izquierdo comoauxiliar.

Si N = 1, el problema se resuelve inmediatamente sin mas que mover el disco del palo izquierdoal derecho.

Para representar estos pasos en el ordenador, planteamos un procedimiento recursivo querecibe cuatro parametros:

El numero de discos a mover.

El nombre del palo a tomar como origen desde donde moverlos.

El nombre del palo a tomar como destino hacia el que moverlos.

El nombre del palo a usar como auxiliar.

1 Algoritmo Mueve(N, origen, auxiliar, destino)

2 Inicio

3 SI N == 1 ENTONCES

4 Mueve un disco del palo origen al destino

5 EN OTRO CASO

6 Mueve(N-1, origen, destino, auxiliar)

7 Escribe("Mueve un disco del palo ", origen, " al ", destino)

8 Mueve(N-1, auxiliar, origen, destino)

9 FINSI

10 Fin

Page 12: Recursividad...2009/08/04  · 4.1 Definici´on y Tipos de recursividad 2 4.1.1. La recursion es como la iteraci´on En muchas ocasiones se puede ver que la recursi´on no es mas

4.5 Algoritmos de vuelta atras 12

origen destinoauxiliar origen destinoauxiliar

Figura 6: Mover n discos se reduce a mover n − 1 al palo libre, mover despues el quequeda al destino, y volver a plantear recursivamente el mover, pero ahora,n− 1 discos.

4.4.6. Ackerman

La funcion Ackerman se define:

A(m,n) =

n + 1, m = 0A(m− 1, 1), n = 0A(m− 1,A(m,n− 1)), m, n > 0

funcion recursiva no primitiva (anidada) (en la que como argumento aparece una recursion). Pro-barla para A(1, 1), A(2, 1) ¿Que ocurre para valores de m ≥ 5? No tiene especial interes matematicosino que aparecio como banco de pruebas de la capacidad de recursion para los ordenadores. Dehecho para valores muy pequenos de n y m se producen profundos niveles de anidamiento recursivoimposibles para los ordenadores usuales.

4.4.7. Busqueda binaria

Si se busca binariamente un valor dentro de un array ordenado, podemos descartar la mitaden la que seguro que no esta el valor y llamar recursivamente a la misma funcion con el subarrayen la parte en la que puede estar. Se deja como ejercicio.

4.5. Algoritmos de vuelta atras

De entre los metodos de resolucion algorıtmica de problemas el metodo de vuelta atras o debacktracking se caracteriza por plantear el mismo problema pero con la muestra de menor tamano,por lo tanto, en forma recursiva. Muchos problemas de juegos de tablero se resuelven mediantebacktracking. Por ejemplo, si se necesita conocer si un camino en un laberinto es solucion, se tratade recorrer hasta el final, y si no se puede llegar, se vuelve a plantear el recorrido en una de las7 tres direcciones restantes en el momento de partida. Se deja como ejercicio resolver el problemadel salto del caballo recursivamente mediante la tecnica de bactracking (Figura 7).

Page 13: Recursividad...2009/08/04  · 4.1 Definici´on y Tipos de recursividad 2 4.1.1. La recursion es como la iteraci´on En muchas ocasiones se puede ver que la recursi´on no es mas

4.5 Algoritmos de vuelta atras 13

Figura 7: Posibles movimientos de salto que puede realizar un caballo (no importarıaque las celdas del camino estuviesen o no ya ocupadas). Se tratarıa de pasarpor todas las N ×N casillas de un tablero.

Juan FalguerasDpto. Lenguajes y Ciencias de la Computacion

Universidad de MalagaDespacho 3.2.32