Você está na página 1de 13

Programacin Modular. ETSIT.

1o C, Apuntes o del profesor Juan Falgueras 2004

4 Recursividad
Contenido
4. Recursividad 4.1. Denicin y Tipos de recursividad . . . . . . . . . . . o 4.1.1. La recursin es como la iteracin . . . . . . . . o o 4.1.2. Tipos de recursin . . . . . . . . . . . . . . . . o 4.1.3. Implementacin interna en un ordenador . . . . o 4.2. Vericacin de la correccin de un algoritmo recursivo o o 4.3. Conveniencia del uso de la recursividad . . . . . . . . 4.4. Ejemplos de programas recursivos . . . . . . . . . . . . 4.4.1. La funcin de Fibonacci . . . . . . . . . . . . . o 4.4.2. D gitos binarios . . . . . . . . . . . . . . . . . . 4.4.3. La bsqueda binaria . . . . . . . . . . . . . . . u 4.4.4. Quicksort . . . . . . . . . . . . . . . . . . . . . 4.4.5. Torres de Hanoi . . . . . . . . . . . . . . . . . 4.4.6. Ackerman . . . . . . . . . . . . . . . . . . . . . 4.4.7. Bsqueda binaria . . . . . . . . . . . . . . . . . u 4.5. Algoritmos de vuelta atrs . . . . . . . . . . . . . . . . a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 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 parmetros que las hacen ser a utiles para distintas situaciones. Las funciones que hemos visto hasta ahora se resolv por an s mismas o, a lo sumo pod pedir ayuda otras funciones que terminaban resolvindose sin an e ms llamadas. Sin embargo, hay veces en que una funcin slo se puede resolver volvindose a o o e a llamar a s mismas. Se trata de funciones recursivas. Cundo tiene esto sentido? a En este tema se introduce el concepto de recursin, se examinan los posibles casos, se muestra o cmo un ordenador, que es fundamentalmente iterativo, no recursivo, puede emular la recuro sin; se dan reglas para comprobar que las solucines recursivas que se construyan tengan nal o y se presentan diversos ejemplos importantes de recursin. o

4.1.

Denicin y Tipos de recursividad o

Se dice que un proceso es recursivo si se resulve llamndose a s mismo. a Para que la recursin tenga sentido (un nal util) cada vez que se llame internamente a o s misma deber hacerlo de una manera menos recursiva hasta que nalmente se llame a s misma a de una manera no recursiva. La recursin innita es intil. o u Lo unico que determina el comportamiento de una funcin son los parmetros que son los o a que dan idea del tamao del problema. La funcin no es recursiva para algunos valores del n o parmetro que se denominan casos base. Toda funcin recursiva, pues, debe tener algn caso a o u base y toda llamada recursiva dentro de ella debe tender hacia el caso base.

4.1

Denicin y Tipos de recursividad o

4.1.1.

La recursin es como la iteracin o o

En muchas ocasiones se puede ver que la recursin no es ms que la repeticin (iteracin) o a o o de una serie de acciones. Esta iteracin se da hasta llegar a un valor de una variable. Pero en la o recursin esta iteracin se est desarrollando mediante la llamada de la funcin a s misma con un o o a o parmetro que es la variable que determina el nal de la recursin, en vez de ser un bucle dentro a o de una funcin normal. As pues, en la iteracin, es la guarda del bucle la que determina cundo o o a acabar la repeticin; en la recursin es el parmetro. a o o a Por ejemplo: 1 x=0 f (x) = x f (x 1) x > 0 es una denicin formal de la funcin factorial en forma recursiva. que podr corresponder al o o a algoritmo recursivo:
1 2 3 4 5 6

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

pero tambin sabemos que la forma iterativa de factorial es: e f (x) = x (x 1) (x 2) . . . 1 que est denida mediante una repeticin de multiplicaciones empezando en el propio valor x y a o 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 versin ms pequea del problema original. o a n Sin embargo, no siempre la recursin es tan similar a la iteracin. o o 4.1.2. Tipos de recursin o

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

4.1

Denicin y Tipos de recursividad o

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

Figura 1: En la recursin directa, la funcin se llama a s misma; en la indirecta llama o o a otras que terminan llamndola de nuevo a ella. a

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 despus de todas las operaciones. e intermedia implica la existencia de operaciones antes y despus de la llamada recursiva. e m ltiple se producen varias llamadas, recursivas en distintos puntos del procedimiento. u anidada la anidada o no primitiva es aquella en la que la recursin se produce en un parmetro o a de la propia llamada recursiva. Es decir, al hacer la llamada recursiva se utiliza un parmetro a 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 recursin se tienen cuatro formas, en general. o

4.1.3.

Implementacin interna en un ordenador o

Consideremos ahora la ejecucin de la funcin recursiva f (x). En un lenguaje de programacin, o o o cuando se llama a un procedimiento (o funcin), lo que se hace es que se guarda la direccin de la o o sentencia llamante como direccin de retorno; se asigna nueva memoria para las variables locales o del procedimiento, y al nalizar la ejecucin del procedimiento, se libera la memoria asignada o a las variables locales y se vuelve la ejecucin al punto en que se hizo la llamada haciendo uso o de la direccin de retorno, direccin de la instruccin que sigue a la instruccin de llamada al o o o o procedimiento. Por otro lado, tenemos que estudiar dos conceptos ms. Uno es la pila (stack ) y el otro son los a registros de activacin de los procedimientos. Una pila es una estructura de datos en la que slo o o cabe aadir o quitar un dato cada vez, pero, tal y como se hace con una pila de libros, por ejemplo, n para el dato a extraer slo es accesible el ultimo libro que se apil. Debido a este comportamiento o o a las pilas tambin se las conoce como estructuras ltimo que entra, primero que sale (LIFO). e u

4.2

Vericacin de la correccin de un algoritmo recursivo o o

return addr (x) int i;

f(x)

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

El registro de activacin de un procedimiento es un bloque de memoria que contiene o informacin sobre las constantes, variables locales declaradas en el procedimiento y los o parmetros que se le pasen en esa llamada al procedimiento, junto con la direccin de a o retorno que corresponde a esa llamada. Los lenguajes de programacin actuales utilizan una pila especial que el sistema tiene para las o llamadas a subrutinas. En esta pila el cdigo mquina del programa cada vez que tiene que o a llamar a un procedimiento guarda su registro de activacin. Si dentro de esta subrutina se llama o de nuevo a s misma en forma recurrente, dado que la creacin del registro de activacin del o o propio procedimiento se hace sobre una pila sin borrar los registros de activacin anteriores, se o puede generar uno nuevo para cada llamada recursiva. Al nal nos encontramos con que con cada llamada recursiva se estn apilando registros de activacin sobre los registros de activacin de las a o o llamadas anteriores. Cuando una de las llamadas recursivas se resuelve por n sin recursin, su registro de activao cin se quita de la pila (se desapila) siguiendo la ejecucin del programa por dnde iba cuando o o o se llam esta ultima vez. Y por tanto siguiendo tambin con las variables y parmetros que se o e a ten en aqul momento. De nuevo, cuando esta llamada se resuelve sin recursin, se repite la an e o disminucin de la pila y vuelta al estado anterior. o Debido a la sobrecarga (overhead ) que producen las operaciones sobre la pila, la creacin o y borrado de los registros de activacin, los procedimientos recursivos consumen ms tiempo y o a memoria que los programas no recursivos. Slo en determinadas ocasiones, debido a la estructura de datos usada en el problema o al o planteamiento del mismo la recursin es preferible, y evitar la recursin es bastante ms dif que o o a cil dar una solucin recursiva al problema. o

4.2.

Vericacin de la correccin de un algoritmo recursivo o o

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 recursin. Por ejemplo, fact(0) = o 0 sin recursin. En el algoritmo factorial se produce una llamada recursiva con lo cual es o suciente con un caso base. 2. Todos los posibles argumentos de las llamadas recursivas se reenv tendiendo sus valores an 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 natural, tienden para valores de n > 0 al valor 0. a 3. La funcin es correcta, para valores distintos del caso base. En el caso del factorial, por o induccin, tendr o 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 bsqueda de una solucin recursiva de los algoritmos se facilita mediante la u o localizacin de los casos bases. Una vez respondidos a ellos, se trata de que el argumento en o general tienda a estos valores.

4.3.

Conveniencia del uso de la recursividad

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

4.4.
4.4.1.

Ejemplos de programas recursivos


La funcin de Fibonacci o 1 x2 b(x 2) + b(x 2) x > 2

b(x) =

La funcin de Fibonacci proviene del buclico problema de la multiplicacin de conejos. Supono o o gamos que partimos de una pareja de conejos recin nacidos, y queremos calcular cuntas parejas e a 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 pequeo, por ejemplo 6, la solucin se puede obtener fcilmente a mano: n o a Mes 1: 1 pareja, la inicial Mes 2: 1 pareja, ya que todav no es sexualmente madura. a 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 nmero de parejas al cabo de n meses, para un n cualquiera, podemos u construir un algoritmo recursivo fcilmente a partir de la siguiente relacin: a o Parejas(n) = 1 n2 Parejas(n 1) + Parejas(n 2) n > 2

En esta relacin Parejas(n 1) son las parejas vivas en el mes n 1, y Parejas(n 2) son las o parejas que nacen en el mes n a partir de las que hab en el mes n 2. a La serie de nmeros Parejas(1), Parejas(2), Parejas(3),. . . es conocida como la Serie de Fibou nacci, la cual modela muchos fenmenos naturales. El crecimiento de esta funcin, como demuestra o o la su aproximacin: y(k) 1/ 5( 1+2 5 )k , es exponencial. Los valores de yk coinciden (en su parte o 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 slo los valores alcanzado hasta el trmino 30. o e En forma de algoritmo recursivo la funcin de Fibonacci ser o 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 en el rbol de recursin de la e a o 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 recursin del algoritmo recursivo Fibonacci. Obsrverse la cantidad o e de llamadas repetidas que se producen slo para calcular el sexto valor de la o serie.

Matemticamente los nmeros de la serie de Fibonacci aparecen en muchas ocasiones. Una a u expresin cerrada (no recurrente) para calcularlos es: o 1 1+ 5 n 1 5 n n n b(n) = = 2 2 5 5 siendo = (1 + 5)/2 y = (1 5)/2 el nmero areo. Fibonacci crece exponencialmente ya u u que el trmino que resta es menor que 1 ( 0 6180) mientras que 1 62. El nmero de e u d gitos del n-simo trmino crece rpidamente, de manera lineal con n. e a

4.4

Ejemplos de programas recursivos

Programacin dinmica del algoritmo recursivo de Fibonacci . El problema de la soo a lucin recursiva de Fibonacci es que llama multitud de veces a los mismos clculos que acaba o a de resolver. Para valores pequeos esto no tiene importancia, pero el crecimiento exponencial de n este nmero de llamadas repetidas lo hacen realmente ineciente respecto a una sencilla solucin u o iterativa mediante un simple bucle. La Programacin Dinmica consiste almacenar los valores que se van obteniendo durante el o a proceso completo como resultados parciales reaprovechables para subsiguientes clculos, optimia zando as el anlisis global. En nuestro caso, bastar con almacenar, conforme los calculamos, los a a valores de los bs anteriores, de manera que cuando se pidan de nuevo esos valores se devuelvan directamente del almacn de los calculados. e En el lenguaje de programacin C, por ejemplo, se puede mantener la informacin calculada o o en variables locales de una llamada a otra mediante variables locales estticas. En este caso, nos a interesa mantener un largo array de valores calculados pero tambin, dado que tan slo la primera e o vez necesitar este array inicializarse, una variable que nos diga si es la primera vez o no que a ejecutamos la funcin. Quedar o 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; // slo se inicializa una vez o 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 parmetro entero que recibe: a
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 especicacin formal ser o a: 0 dec2bin(x) = 1 dec2bin(x/2) x %2 x=0 x=1 x2

indicando la concatenacin de cadenas de letras. La operacin % ser la operacin resto o o a o que devuelve el resto de la divisin entre sus operandos. o 4.4.3. La b squeda binaria u

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

requiere que el array est ordenado. Para responder recursivamente podemos lanzar una funcin e o recursiva en la que acotemos los l mites binarios de la bsqueda; lanzar u 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 ordenacin de Hoare (apodado Quicksort) es mucho ms sencillo en forma o a recursiva. Cmo funciona? Se trata de separar en dos partes el array, a un lado los elementos ms o a pequeos y al otro los ms grandes (o iguales) a un elemento de referencia llamado pivote, que se n a toma del mismo array (ver la Figura 5). En esta divisin o particin est el secreto de Quicksort. o o a Para que la cosa funcione bien, se deber elegir el pivote de manera que quedara ms o menos a a en medio del array repartido. Una vez hecha esta particin podemos plantearnos recursivamente el mismo problema de o ordenacin pero con dos arrays la mitad de largos. Esto llevar en el primer array a otros dos arrays o a tambin separados por un nuevo pivote y as sucesivamente. Lo interesante es que cada subarray e mitad est ya en su sitio (rpidamente) desde el principio y, por ende, el array concatenacin a a o de todos ellos.1 Para que el array quede dividido por la mitad en cada particin es necesario elegir muy bien o el pivote o referencia. Para conseguir que el pivote quede enmedio, deber tomarse la mediana de a los elementos del array. De hecho algunas versiones de este algoritmo de ordenacin buscan esta o 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 y eciencia de las particiones se pierde en la bsqueda a u de la mediana de los subarrays, necesariamente bsquedas de complejidades lineales. As pues, el u 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 ultimo, o, si se es supersticioso, el de enmedio. Tomando como referencia el ultimo: x = a[N 1] Una vez tomado el pivote x empezamos la particin. Para ello el siguiente paso es localizar (desde o 0 hasta i) un punto hasta el cual todos los elementos del array sean menores que el pivote x elegido. Se para la bsqueda y se deja all una referencia i, as u 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 j. Tenemos pues dos puntos posiblemente separados i y j que a 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 divisin el problema inicialmente de complejidad cuadrtica N 2 queda dividido en la o a 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 mtodos de e intercambio directo (burbuja, seleccin o insercin), que manejan todo el array en cada operacin. o o o

4.4

Ejemplos de programas recursivos

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

Despus de esto, dado que el elemento que queda en p puede ser mayor que el pivote x que e est situado en a[N 1], se intercambia por l, dejando el pivote en p y garantizando un array con a e 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 iteracin se han intercambiado los elementos de manera que quedan o los menores a un lado y las mayores al otro del valor elegido de referencia.

recursivamente ms pequeos. Con lo cual se abordan llamadas recursivas a cada parte del array a n para ordenarlos igualmente. Mientras ms iguales sean los tamaos de las partes menos llamadas a n recursivas surgirn. En este sentido se puede dar un caso degenerativo de este algoritmo que ser a a el de tener como tamao de una de las partes un elemento cada vez (en la otra los restantes) y por n lo tanto se producindose N 1 llamadas recursivas que slo sacan cada vez un elemento fuera del e o array a ordenar. El caso es que precisamente esta situacin degenerativa se da con cierta facilidad. o 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 estn aleatoriamente repartidos. e Se han hecho innidad de estudios y mejoras de este algoritmo (particularmente interesantes las iniciadas por Sedgewick), para tratar de paliar sus debilidades. Ntese que las grandes o cualidades de este algoritmo son las de hacer pocos intercambios y adems haciendo viajar a a los elementos grandes distancias cada vez que se hacen, lo que constituye las dos grandes cualidades ms buscadas de los algoritmos de ordenacin. Un detalle ms a su favor, mucho ms a a o a a nivel tcnico es que los bucles de recorrido que hacen el trabajo duro son especialmente fciles e a de optimizar en cualquier procesador ya que involucran slo dos punteros a bloques que se van o 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 tambin de centinela e 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 ordenacin in situ de un array. Las o -i-> son reordenaciones para repartir entre los dos subarrays. Las -p-> la reubicacin del pivote. o

4.4

Ejemplos de programas recursivos

11

4.4.5.

Torres de Hanoi

Las Torres de Hanoi es un juego cuya solucin se simplica mucho si se piensa como un o problema recursivo. Se tienen 3 palos de madera, que llamaremos palo izquierdo, central y derecho. El palo izquierdo tiene ensartados un montn de discos concntricos de tamao decreciente, de manera que o e n el disco mayor est abajo y el menor arriba. a El problema consiste en mover los discos del palo izquierdo al derecho respetando las siguientes reglas: Slo se pueden mover los discos de un palo a otro. o Slo se puede mover un disco cada vez. o No se puede poner un disco encima de otro ms pequeo. a n Se quiere disear un programa que recibiendo un valor entero N y escriba la secuencia de pasos n necesarios para resolver el problema de las torres de Hanoi para N discos. Solucin o Para ver el problema de forma recursiva tenemos que pensar que la solucin para N = 1 es trivial, o mientras que para un valor ms alto de N se puede reducir en cada ocasin en un valor N 1, a o descomponiendo la solucin en 3 fases: o 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 ms que mover el disco del palo izquierdo a al derecho. Para representar estos pasos en el ordenador, planteamos un procedimiento recursivo que recibe cuatro parmetros: a El nmero de discos a mover. u 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 atrs a

12

origen

auxiliar

destino

origen

auxiliar

destino

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

4.4.6.

Ackerman

La funcin Ackerman se dene: o n + 1, A(m, n) = A(m 1, 1), A(m 1, A(m, n 1)),

m=0 n=0 m, n > 0

funcin recursiva no primitiva (anidada) (en la que como argumento aparece una recursin). Proo o barla para A(1, 1), A(2, 1) Qu ocurre para valores de m 5? No tiene especial inters matemtico e e a sino que apareci como banco de pruebas de la capacidad de recursin para los ordenadores. De o o hecho para valores muy pequeos de n y m se producen profundos niveles de anidamiento recursivo n imposibles para los ordenadores usuales. 4.4.7. B squeda binaria u

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

4.5.

Algoritmos de vuelta atrs a

De entre los mtodos de resolucin algor e o tmica de problemas el mtodo de vuelta atrs o de e a backtracking se caracteriza por plantear el mismo problema pero con la muestra de menor tamao, n 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 solucin, se trata o 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 tcnica de bactracking (Figura 7). e

4.5

Algoritmos de vuelta atrs a

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 de pasar a por todas las N N casillas de un tablero.

Juan Falgueras Dpto. Lenguajes y Ciencias de la Computacin o Universidad de Mlaga a Despacho 3.2.32

Você também pode gostar