Está en la página 1de 13

Programaci on Modular. ETSIT.

1o C, Apuntes del profesor Juan Falgueras 2004

4 Recursividad
Contenido
4. Recursividad 4.1. Denici on y Tipos de recursividad . . . . . . . . . . . 4.1.1. La recursi on es como la iteraci on . . . . . . . . 4.1.2. Tipos de recursi on . . . . . . . . . . . . . . . . 4.1.3. Implementaci on interna en un ordenador . . . . 4.2. Vericaci on de la correcci on de un algoritmo recursivo 4.3. Conveniencia del uso de la recursividad . . . . . . . . 4.4. Ejemplos de programas recursivos . . . . . . . . . . . . 4.4.1. La funci on de Fibonacci . . . . . . . . . . . . . 4.4.2. D gitos binarios . . . . . . . . . . . . . . . . . . 4.4.3. La b usqueda binaria . . . . . . . . . . . . . . . 4.4.4. Quicksort . . . . . . . . . . . . . . . . . . . . . 4.4.5. Torres de Hanoi . . . . . . . . . . . . . . . . . 4.4.6. Ackerman . . . . . . . . . . . . . . . . . . . . . 4.4.7. B usqueda binaria . . . . . . . . . . . . . . . . . 4.5. Algoritmos de vuelta atr as . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 2 2 3 4 5 5 5 7 7 8 11 12 12 12

4.

Recursividad
Hasta ahora hemos visto formas de estructurar tanto datos como programas, mediante funciones. Las funciones resuelven cometidos concretos y llevan par ametros que las hacen ser u tiles para distintas situaciones. Las funciones que hemos visto hasta ahora se resolv an por s mismas o, a lo sumo pod an pedir ayuda otras funciones que terminaban resolvi endose sin m 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 c omo un ordenador, que es fundamentalmente iterativo, no recursivo, puede emular la recursi on; se dan reglas para comprobar que las solucines recursivas que se construyan tengan nal y se presentan diversos ejemplos importantes de recursi on.

4.1.

Denici 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 nal u til) cada vez que se llame internamente a s misma deber a hacerlo de una manera menos recursiva hasta que nalmente se llame a s misma de una manera no recursiva. La recursi on innita es in util. Lo u nico 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.

4.1

Denici on y Tipos de recursividad

4.1.1.

La recursi on es como la iteraci on

En muchas ocasiones se puede ver que la recursi on no es m as que la repetici on (iteraci on) de una serie de acciones. Esta iteraci on se da hasta llegar a un valor de una variable. Pero en la recursi on esta iteraci on se est a desarrollando mediante la llamada de la funci on a s misma con un par ametro que es la variable que determina el nal de la recursi on, en vez de ser un bucle dentro de una funci on normal. As pues, en la iteraci on, es la guarda del bucle la que determina cu ando acabar a la repetici on; en la recursi on es el par ametro. Por ejemplo: 1 x=0 f (x) = x f (x 1) x > 0 es una denici on formal de la funci on factorial en forma recursiva. que podr a corresponder al algoritmo recursivo :
1 2 3 4 5 6

int f(int n) { if (0==n) return 1; else return n * f(n-1); }

pero tambi en sabemos que la forma iterativa de factorial es: f (x) = x (x 1) (x 2) . . . 1 que est a denida mediante una repetici on de multiplicaciones empezando en el propio valor x y en las que el siguiente multiplicador disminuye hasta llegar al valor 1. El algoritmo ser a:
1 2

int f(int x) { int r, i; for (i=x, r=1; i>1; --i) r *= i; return r; }

4 5 6 7

En este sentido la recursividad es una nueva forma de ver las acciones repetitivas permitiendo que un subprograma se llame a s mismo para resolver una versi on m as peque na del problema original. Sin embargo, no siempre la recursi on es tan similar a la iteraci on. 4.1.2. Tipos de recursi on

Estructuralemente existen dos formas de recursi on: 1. la directa: es la que hemos visto hasta ahora en la que la funci on se llama a s misma. 2. Indirecta: es la recursi on que se produce cuando la funci on se llama no a s misma sino a otra funci on (y esta quiz as a otra) que termina llam ando a la funci on inicial. Se produce as una cadena: f (a) g (b) h(c) . . . f (a ) As la recursi on directa es una forma m as simple de recursi on en la que no existen g (), h(), etc. Existen distintos modos de hacer la recursi on: de cabeza la recursi on de cabeza se produce cuando la llamada recursiva se hace justo al principio del procedimiento recursivo, antes que ninguna otra operaci on. Por ejemplo:

4.1

Denici on y Tipos de recursividad

x? x directa f(x) final x''' x'' x' final indirecta f(x) x

Figura 1: En la recursi on directa, la funci on se llama a s misma; en la indirecta llama a otras que terminan llam andola de nuevo a ella.

1 2 3 4 5 6

char ultimaLetra(char *cadena) { if (*cadena != \0) { return ultimaLetra(cadena+1); } }

de cola por el contrario, en la de cola la llamada se hace al nal despu es de todas las operaciones. intermedia implica la existencia de operaciones antes y despu es de la llamada recursiva. m ultiple se producen varias llamadas, recursivas en distintos puntos del procedimiento. anidada la anidada o no primitiva es aquella en la que la recursi on se produce en un par ametro de la propia llamada recursiva. Es decir, al hacer la llamada recursiva se utiliza un par ametro que es el resultado de una llamada recursiva.
x'
f(x) f(x)

x'
f(x)

x'
f(x) f(x)

f(x')

A B C

A B C

A B C

A B C

Figura 2: Dependiendo del momento del desarrollo del algoritmo en el que se produce la recursi on se tienen cuatro formas, en general.

4.1.3.

Implementaci on interna en un ordenador

Consideremos ahora la ejecuci on de la funci on recursiva f (x). En un lenguaje de programaci on, cuando se llama a un procedimiento (o funci on), lo que se hace es que se guarda la direcci on de la sentencia llamante como direcci on de retorno; se asigna nueva memoria para las variables locales del procedimiento, y al nalizar la ejecuci on del procedimiento, se libera la memoria asignada a las variables locales y se vuelve la ejecuci on al punto en que se hizo la llamada haciendo uso de la direcci on de retorno, direcci on de la instrucci on que sigue a la instrucci on de llamada al procedimiento. Por otro lado, tenemos que estudiar dos conceptos m as. Uno es la pila (stack ) y el otro son los registros de activaci on de los procedimientos. Una pila es una estructura de datos en la que s olo cabe a nadir o quitar un dato cada vez, pero, tal y como se hace con una pila de libros, por ejemplo, para el dato a extraer s olo es accesible el u ltimo libro que se apil o. Debido a este comportamiento a las pilas tambi en se las conoce como estructuras ultimo que entra, primero que sale (LIFO).

4.2

Vericaci on de la correcci on de un algoritmo recursivo

return addr (x) int i;

f(x)

Figura 3: La pila de recursi on se va formando en cada llamada dejando encima los datos de la u ltima llamada para ser los inmediatamente pendientes de resolver.

El registro de activaci on de un procedimiento es un bloque de memoria que contiene informaci on sobre las constantes, variables locales declaradas en el procedimiento y los par ametros que se le pasen en esa llamada al procedimiento, junto con la direcci on de retorno que corresponde a esa llamada. Los lenguajes de programaci on actuales utilizan una pila especial que el sistema tiene para las llamadas a subrutinas. En esta pila el c odigo m aquina del programa cada vez que tiene que llamar a un procedimiento guarda su registro de activaci on. Si dentro de esta subrutina se llama de nuevo a s misma en forma recurrente, dado que la creaci on del registro de activaci on del propio procedimiento se hace sobre una pila sin borrar los registros de activaci on anteriores, se puede generar uno nuevo para cada llamada recursiva. Al nal nos encontramos con que con cada llamada recursiva se est an apilando registros de activaci on sobre los registros de activaci on de las llamadas anteriores. Cuando una de las llamadas recursivas se resuelve por n sin recursi on, su registro de activaci on se quita de la pila (se desapila) siguiendo la ejecuci on del programa por d onde iba cuando se llam o esta u ltima vez. Y por tanto siguiendo tambi en con las variables y par ametros que se ten an en aqu el momento. De nuevo, cuando esta llamada se resuelve sin recursi on, se repite la disminuci on de la pila y vuelta al estado anterior. Debido a la sobrecarga (overhead ) que producen las operaciones sobre la pila, la creaci on y borrado de los registros de activaci on, los procedimientos recursivos consumen m as tiempo y memoria que los programas no recursivos. S olo en determinadas ocasiones, debido a la estructura de datos usada en el problema o al planteamiento del mismo la recursi on es preferible, y evitar la recursi on es bastante m as dif cil que dar una soluci on recursiva al problema.

4.2.

Vericaci on de la correcci on de un algoritmo recursivo

Para comprobar que un algoritmo recursivo funciona correctamente es necesario que siga las tres reglas siguientes: 1. Existe un caso base (por cada llamada recursiva que se haga dentro del algoritmo debe haber un valor) para el cual el algoritmo nalice sin recursi on. Por ejemplo, fact(0) = 0 sin recursi on. En el algoritmo factorial se produce una llamada recursiva con lo cual es suciente con un caso base. 2. Todos los posibles argumentos de las llamadas recursivas se reenv an tendiendo sus valores hacia un caso base. En el caso del factorial, ya que la llamada recursiva que se hace es con fact(n-1) y n se supon a natural, tienden para valores de n > 0 al valor 0. 3. La funci on es correcta, para valores distintos del caso base. En el caso del factorial, por inducci on, tendr amos que si fact(n) = n!, entonces fact(n + 1) = n fact(n) = (n + 1)!

4.3

Conveniencia del uso de la recursividad

. En general la b usqueda de una soluci on recursiva de los algoritmos se facilita mediante la localizaci on de los casos bases. Una vez respondidos a ellos, se trata de que el argumento en general tienda a estos valores.

4.3.

Conveniencia del uso de la recursividad

La recursi on deber a de evitarse siempre que se pueda por motivos de eciencia, como se ha visto en el apartado 4.1.3. Sin embargo, ser a justicable emplearla cuando: 1. se sabe que la funci on no va a generar demasiada profundidad de llamadas recursivas; particularmante por que la pila del sistema donde se guardan todos las variables locales y par ametros, es relativamente peque na y podr a colapsar el sistema. 2. se sabe que la funci on no va a utilizar estructuras de datos locales demasiado grandes. Si as fuese el caso, aunque la soluci on se mantuviese como recursiva habr a que recurrir a otra forma de mantener los datos. 3. cada llamada no genera a su vez llamadas ya resueltas en otras llamadas que se generar an o se han generado antes. Este problema hay que analizarlo antes y es frecuente en recursi on. Es una fuente de ineciencia usual en recursi on el que una funci on se eval ue m ultiples veces con el mismo valor de par ametro en las llamadas recursivas generadas internamente, 4. La soluci on no es planteable de forma sencilla de otra manera. Ocurre en muchos casos que la forma recursiva es muy m as clara que la iterativa, como se ver a en los ejemplos. En otras palabras, si la forma iterativa est a ah y es sencilla, es preferible.

4.4.
4.4.1.

Ejemplos de programas recursivos


La funci on de Fibonacci 1 x2 b(x 2) + b(x 2) x > 2

b(x) =

La funci on de Fibonacci proviene del buc olico problema de la multiplicaci on de conejos. Supongamos que partimos de una pareja de conejos reci en nacidos, y queremos calcular cu antas parejas de 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 pareja macho-hembra, sexualmente madura, se reproduce en exactamente un par de conejos machohembra. Para un n peque no, por ejemplo 6, la soluci on se puede obtener f acilmente 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 suyos nacidos en el mes anterior. Mes 5: 5 parejas; la original, una pareja de hijos suyos nacidos ahora, las dos parejas nacidas en los meses 3 y 4, y una pareja de hijos de la pareja nacida en el mes 3.

4.4

Ejemplos de programas recursivos

Mes 6: 8 parejas; las 5 del mes anterior, una pareja de hijos de la original, una pareja de hijos de la nacida en el mes 3 y una pareja de hijos nacida en el mes 4. Si deseamos saber el n umero de parejas al cabo de n meses, para un n cualquiera, podemos construir un algoritmo recursivo f acilmente a partir de la siguiente relaci on: Parejas(n) = 1 n2 Parejas(n 1) + Parejas(n 2) n > 2

En esta relaci on Parejas(n 1) son las parejas vivas en el mes n 1, y Parejas(n 2) son las parejas que nacen en el mes n a partir de las que hab a en el mes n 2. La serie de n umeros Parejas(1), Parejas(2), Parejas(3),. . . es conocida como la Serie de Fibonacci, la cual modela muchos fen omenos naturales. El crecimiento de esta funci on, como demuestra la su aproximaci on: y (k ) 1/ 5( 1+2 5 )k , es exponencial. Los valores de yk coinciden (en su parte entera 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, 11 346,269 . . . son s olo los valores alcanzado hasta el t ermino 30. En forma de algoritmo recursivo la funci on de Fibonacci ser a:
1 2 3 4 5 6

int fib(int n) { if (n<=2) return 1; else return fib(n-1)+fib(n-2); }

es un ejemplo de sencillez y falta de eciencia recursiva, como se v e en el arbol de recursi on de la misma (Fig. 4).

f(6) f(5) f(4) f(3) f(2) f(1) f(2) f(2) f(3) f(1) f(2) f(3) f(1) f(4) f(2)

Figura 4: Arbol de recursi on del algoritmo recursivo Fibonacci. Obs erverse la cantidad de llamadas repetidas que se producen s olo para calcular el sexto valor de la serie.

Matem aticamente los n umeros de la serie de Fibonacci aparecen en muchas ocasiones. Una expresi on cerrada (no recurrente) para calcularlos es: 1 1+ 5 n 1 5 n n n b(n) = = 2 2 5 5 siendo = (1 + 5)/2 y = (1 5)/2 el n umero a ureo. Fibonacci crece exponencialmente ya que el t ermino que resta es menor que 1 ( 0 6180) mientras que 1 62. El n umero de d gitos del n-simo t ermino crece r apidamente, de manera lineal con n.

4.4

Ejemplos de programas recursivos

Programaci on din amica del algoritmo recursivo de Fibonacci . El problema de la soluci on recursiva de Fibonacci es que llama multitud de veces a los mismos c alculos que acaba de resolver. Para valores peque nos esto no tiene importancia, pero el crecimiento exponencial de este n umero de llamadas repetidas lo hacen realmente ineciente respecto a una sencilla soluci on iterativa mediante un simple bucle. La Programaci on Din amica consiste almacenar los valores que se van obteniendo durante el proceso completo como resultados parciales reaprovechables para subsiguientes c alculos, optimizando as el an alisis global. En nuestro caso, bastar a con almacenar, conforme los calculamos, los valores de los bs anteriores, de manera que cuando se pidan de nuevo esos valores se devuelvan directamente del almac en de los calculados. En el lenguaje de programaci on C, por ejemplo, se puede mantener la informaci on calculada en variables locales de una llamada a otra mediante variables locales est aticas. En este caso, nos interesa mantener un largo array de valores calculados pero tambi en, dado que tan s olo la primera vez necesitar a este array inicializarse, una variable que nos diga si es la primera vez o no que ejecutamos la funci on. Quedar a:
1 2 3 4 5 6 7 8 9 10 11 12 13 14

unsigned fib(int n) { static unsigned fibs[1000]; static bool primeravez=true; // s olo se inicializa una vez if (primeravez) { fibs[1]=fibs[2]=1; for(int i=3; i<1000; i++) fibs[i] = 0; primeravez = false; } if (fibs[n] != 0) return fibs[n]; fibs[n] = fib(n-1) + fib(n-2); return fibs[n]; }

4.4.2.

D gitos binarios

El siguiente algoritmo recursivo escribe secuencialmente (y de manera ordenada) los d gitos binarios del par ametro entero que recibe:
1 2 3 4 5 6 7 8 9

typedef unsigned long int base; void dec2bin(base n) { if (n >= 2){ dec2bin(n / 2); cout << n % 2; } else { cout << n; } }

Su especicaci on formal ser a: 0 dec2bin(x) = 1 dec2bin(x/2) x %2 x=0 x=1 x2

indicando la concatenaci on de cadenas de letras. La operaci on % ser a la operaci on resto que devuelve el resto de la divisi on entre sus operandos. 4.4.3. La b usqueda binaria

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

requiere que el array est e ordenado. Para responder recursivamente podemos lanzar una funci on recursiva en la que acotemos los l mites binarios de la b usqueda; lanzar amos la primera llamada con 0, N 1:

4.4

Ejemplos de programas recursivos

1 2

int buscarR(T ordenado[], int ini, int fin, T x) { int mitad; if (ini > fin) return -1; mitad = (fin + ini) / 2; if (ordenado[mitad] == x) return mitad; else if (x > ordenado[mitad]) return Buscar(ordenado, mitad+1, fin, x); else return Buscar(ordenado, ini, mitad-1, x); } int BusquedaBin(T ordenado[], T x) { return buscarR(ordenado, 0, N-1, x); }

4 5

7 8 9 10 11 12 13 14

16 17 18 19

4.4.4.

Quicksort

El algoritmo de ordenaci on de Hoare (apodado Quicksort ) es mucho m as sencillo en forma recursiva. C omo funciona? Se trata de separar en dos partes el array, a un lado los elementos m as peque nos y al otro los m as grandes (o iguales) a un elemento de referencia llamado pivote, que se toma del mismo array (ver la Figura 5). En esta divisi on o partici on est a el secreto de Quicksort. Para que la cosa funcione bien, se deber a elegir el pivote de manera que quedara m as o menos en medio del array repartido. Una vez hecha esta partici on podemos plantearnos recursivamente el mismo problema de ordenaci on pero con dos arrays la mitad de largos. Esto llevar a en el primer array a otros dos arrays tambi en separados por un nuevo pivote y as sucesivamente. Lo interesante es que cada subarray mitad est a ya en su sitio (r apidamente) desde el principio y, por ende, el array concatenaci on de todos ellos.1 Para que el array quede dividido por la mitad en cada partici on es necesario elegir muy bien el pivote o referencia. Para conseguir que el pivote quede enmedio, deber a tomarse la mediana de los elementos del array. De hecho algunas versiones de este algoritmo de ordenaci on buscan esta mediana primero entre los elementos del array, pero este proceso hay que descartarlo en general ya que lo que se gana en cuanto a la simetr a y eciencia de las particiones se pierde en la b usqueda de la mediana de los subarrays, necesariamente b usquedas de complejidades lineales. As pues, el pivote se elige al azar entre los elementos del array recibido. Al no haber criterio a priori se toma o bien el primero o bien el u ltimo, o, si se es supersticioso, el de enmedio. Tomando como referencia el u ltimo: x = a[N 1] Una vez tomado el pivote x empezamos la partici on. Para ello el siguiente paso es localizar (desde 0 hasta i) un punto hasta el cual todos los elementos del array sean menores que el pivote x elegido. Se para la b usqueda y se deja all una referencia i, as mismo se busca otro punto bajando desde el nal del array (desde N 1 hasta j ) hasta el cual todos los elementos del array sean mayores o iguales al pivote, ser a j . Tenemos pues dos puntos posiblemente separados i y j que contienen 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 el del j con el mismo criterio (de nuevo una vez localizados los nuevos i y j se intercambian sus contenidos) hasta que i j .
1 Una vez lograda esta divisi on el problema inicialmente de complejidad cuadr atica N 2 queda dividido en la suma de dos problemas de la cuarta parte de complejidad (N/2)2 . El resultado nal (N/2)2 + (N/2)2 1/2N 2 es de menor complejidad que si se trata siempre con todo el array de golpe, como se hace en los m etodos de intercambio directo (burbuja, selecci on o inserci on), que manejan todo el array en cada operaci on.

4.4

Ejemplos de programas recursivos

En el punto i = p en el que se encontr o un elemento mayor (o igual) que el pivote x se tienen aseguradas las condiciones: k < p, a[k ] < x y k > p, a[k ] x

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

< !
14 10 3 6 2 5 7 7 9 13

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

recursivamente m as peque nos. Con lo cual se abordan llamadas recursivas a cada parte del array para ordenarlos igualmente. Mientras m as iguales sean los tama nos de las partes menos llamadas recursivas surgir an. En este sentido se puede dar un caso degenerativo de este algoritmo que ser a el de tener como tama no de una de las partes un elemento cada vez (en la otra los restantes) y por lo tanto se produci endose N 1 llamadas recursivas que s olo sacan cada vez un elemento fuera del array a ordenar. El caso es que precisamente esta situaci on degenerativa se da con cierta facilidad. Basta con que nos den un array ordenado o invertido para que ocurra este desastre. Lo mejor para que no ocurra es que los elementos est en aleatoriamente repartidos. Se han hecho innidad de estudios y mejoras de este algoritmo (particularmente interesantes las iniciadas por Sedgewick), para tratar de paliar sus debilidades. N otese que las grandes cualidades de este algoritmo son las de hacer pocos intercambios y adem as haciendo viajar a los elementos grandes distancias cada vez que se hacen, lo que constituye las dos grandes cualidades m as buscadas de los algoritmos de ordenaci on. Un detalle m as a su favor, mucho m as a nivel t ecnico es que los bucles de recorrido que hacen el trabajo duro son especialmente f aciles de optimizar en cualquier procesador ya que involucran s olo dos punteros a bloques que se van incrementando/decrementando en el propio procesador.
1 2 3 4

int particion(char a[], int iz, int de) { char x, t; int i, j; x = a[de]; // pivote i = iz - 1; j = de; do { do ++i; while (a[i] < x); // x hace tambi en de centinela do --j; while (j >= 0 && a[j] >= x); if (i<j) intercambia(a[i], a[j]); } while (i < j); intercambia(a[i], a[de]); // asegura x en su sitio return i; } void quick(char a[], int iz, int de) { int p; if (de > iz) {

6 7 8 9 10 11 12 13 14 15 16

18 19 20

22

4.4

Ejemplos de programas recursivos

10

23 24 25 26 27

p = particion(a, iz, de); quick(a, iz, p-1); quick(a, p+1, de); } } void QuickSort(char a[]) { quick(a, 0, strlen(a)-1); }

29 30 31 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) (m)gaci(e)l our -i-> (e)gaci(m)l our egaci(m)(l) our -p-> egaci(l)(m) our egac(i)(i) lm our -p-> egac(i)(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 ace(g)(g) ilm our -p-> ace(g)(g) ilm our a ceg ilm o(u)(r) -p-> acegilmo(r)(u) acegilmoru

= [mgaciel]o[ur] = [egaci]l[m our] = [egac]i[lm our] = [a]c[eg ilm our] = [a c e]g[ilm our] = [acegilmo]r[u]

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

4.4

Ejemplos de programas recursivos

11

4.4.5.

Torres de Hanoi

Las Torres de Hanoi es un juego cuya soluci on se simplica mucho si se piensa como un problema recursivo. Se tienen 3 palos de madera, que llamaremos palo izquierdo, central y derecho. El palo izquierdo tiene ensartados un mont on de discos conc entricos de tama no decreciente, de manera que el disco mayor est a abajo y el menor arriba. El problema consiste en mover los discos del palo izquierdo al derecho respetando las siguientes reglas: S olo se pueden mover los discos de un palo a otro. S olo se puede mover un disco cada vez. No se puede poner un disco encima de otro m as peque no. Se quiere dise nar un programa que recibiendo un valor entero N y escriba la secuencia de pasos necesarios para resolver el problema de las torres de Hanoi para N discos. Soluci on Para ver el problema de forma recursiva tenemos que pensar que la soluci on para N = 1 es trivial, mientras que para un valor m as alto de N se puede reducir en cada ocasi on en un valor N 1, descomponiendo la soluci on en 3 fases: 1. Mover los N 1 discos superiores del palo izquierdo al palo central, utilizando el palo derecho como 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 como auxiliar. Si N = 1, el problema se resuelve inmediatamente sin m as que mover el disco del palo izquierdo al derecho. Para representar estos pasos en el ordenador, planteamos un procedimiento recursivo que recibe cuatro par ametros: El n umero 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 2 3 4 5 6 7 8 9 10

Algoritmo Mueve(N, origen, auxiliar, destino) Inicio SI N == 1 ENTONCES Mueve un disco del palo origen al destino EN OTRO CASO Mueve(N-1, origen, destino, auxiliar) Escribe("Mueve un disco del palo ", origen, " al ", destino) Mueve(N-1, auxiliar, origen, destino) FINSI Fin

4.5

Algoritmos de vuelta atr as

12

origen

auxiliar

destino

origen

auxiliar

destino

Figura 6: Mover n discos se reduce a mover n 1 al palo libre, mover despu es el que queda al destino, y volver a plantear recursivamente el mover, pero ahora, n 1 discos.

4.4.6.

Ackerman

La funci on Ackerman se dene: n + 1, A(m, n) = A(m 1, 1), A(m 1, A(m, n 1)),

m=0 n=0 m, n > 0

funci on recursiva no primitiva (anidada) (en la que como argumento aparece una recursi on). Probarla para A(1, 1), A(2, 1) Qu e ocurre para valores de m 5? No tiene especial inter es matem atico sino que apareci o como banco de pruebas de la capacidad de recursi on para los ordenadores. De hecho para valores muy peque nos de n y m se producen profundos niveles de anidamiento recursivo imposibles para los ordenadores usuales. 4.4.7. B usqueda binaria

Si se busca binariamente un valor dentro de un array ordenado, podemos descartar la mitad en la que seguro que no est a el valor y llamar recursivamente a la misma funci on con el subarray en la parte en la que puede estar. Se deja como ejercicio.

4.5.

Algoritmos de vuelta atr as

De entre los m etodos de resoluci on algor tmica de problemas el m etodo de vuelta atr as o de backtracking se caracteriza por plantear el mismo problema pero con la muestra de menor tama no, por lo tanto, en forma recursiva. Muchos problemas de juegos de tablero se resuelven mediante backtracking. Por ejemplo, si se necesita conocer si un camino en un laberinto es soluci on, se trata de recorrer hasta el nal, y si no se puede llegar, se vuelve a plantear el recorrido en una de las 7 tres direcciones restantes en el momento de partida. Se deja como ejercicio resolver el problema del salto del caballo recursivamente mediante la t ecnica de bactracking (Figura 7).

4.5

Algoritmos de vuelta atr as

13

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

Juan Falgueras Dpto. Lenguajes y Ciencias de la Computaci on Universidad de M alaga Despacho 3.2.32

También podría gustarte