You are on page 1of 0

Algoritmos y Estructuras de Datos Apuntes tericos Prof. Lic.

Mara Gabriela Cerra


Ao 2009

1
RECURSION


Veremos un nuevo mecanismo para resolver problemas: RECURSIN.
La recursin es una alternativa a la iteracin o repeticin, y aunque en
tiempo de ejecucin y espacio de memoria ocupada la solucin recursiva es menos
eficiente que la iterativa, existen numerosas situaciones en las que la recursividad
provee una solucin ms simple y natural a un problema. La recursividad es una
herramienta potente y til en la resolucin de problemas que tengan naturaleza
recursiva.
Estudiaremos recursin desde un punto de vista conceptual, y nos va a
interesar discutir el mtodo para llegar a comprender el mecanismo de la
recursin. Lo vamos a hacer a travs de ejemplos.

SOLUCIONES RECURSIVAS: Soluciones elegantes y simples para
problemas de complejidad grande, muy bien estructurados y modulares.

Qu es recursin? Es una tcnica que realiza una tarea T, haciendo otra
tarea T.
Esta definicin parece semejante al diseo top-down, donde un problema
grande se descompone en pequeos problemas. La diferencia es que en recursin, la
tarea T es de la misma naturaleza que la tarea T.
La pregunta ahora es qu ganamos resolviendo tareas mediante otra
semejante? Lo importante es que en algn sentido T es ms pequea que T.

Veremos estos conceptos a travs de un ejemplo. Consideremos el problema
de buscar una palabra en un diccionario. Una bsqueda binaria se puede formular
como:

/*Buscar una palabra en el diccionario*/

Si diccionario tiene una sola pgina entonces
Ubicar la palabra en esa pgina
sino
Abrir diccionario en punto cercano a la mitad
Determinar a que mitad pertenece la palabra
Si la palabra pertenece a la primera mitad entonces
Buscar la palabra en la primera mitad del diccionario
sino
Buscar la palabra en la segunda mitad del diccionario
Finsi
finsi

Algoritmos y Estructuras de Datos Apuntes tericos Prof. Lic. Mara Gabriela Cerra
Ao 2009

2
La solucin que hemos planteado est a un alto nivel de desarrollo, no nos
interesa ahora entrar en detalles de implementacin. Lo que nos interesa es
examinar la estrategia de esta solucin.
Hemos reducido el problema de buscar una palabra en el diccionario a
buscarla en una mitad del mismo.









Dos caractersticas importantes:

- Una vez dividido el diccionario, est claro cual ser la mitad en la que
debemos buscar y se buscar utilizando la misma estrategia.
- Hay un caso especial, que se maneja distinto de los dems, es el caso en
que el diccionario ha sido dividido tantas veces que tiene slo una pgina. En este
punto el problema es suficientemente pequeo y se puede resolver directamente.
Este caso especial se llama caso base.

Podemos ver esta forma de resolver problemas como dividir y conquistar
(El problema se resolvi primero dividiendo el diccionario en dos mitades y luego
conquistando la mitad apropiada). El problema ms pequeo se resuelve aplicando la
misma estrategia.

Escribimos la solucin como un procedimiento para resaltar algunas
observaciones importantes.

Procedimiento Buscar (dic, pal)
Si diccionario tiene una sola pgina entonces
Ubicar la palabra en esa pgina
sino
Abrir diccionario en punto cercano a la mitad
Determinar a que mitad pertenece la palabra
Si la palabra pertenece a la primera mitad entonces
Buscar (primera mitad del dic, pal)
sino
Buscar (segunda mitad del dic, pal)

Finsi
Finsi

Buscar en el diccionario
Buscar en la primera
mitad del diccionario
Buscar en la segunda
mitad del diccionario
Algoritmos y Estructuras de Datos Apuntes tericos Prof. Lic. Mara Gabriela Cerra
Ao 2009

3

Observaciones:

1.- Una de las acciones de este procedimiento es llamarse a s mismo. Es
decir, el procedimiento BUSCAR es llamado desde adentro del procedimiento
BUSCAR. Esto es lo que hace una solucin recursiva.

2.- Cada llamada BUSCAR (diccionario, pal) pasa un diccionario de la mitad
de tamao que el anterior. Es decir en cada llamada recursiva el tamao del
diccionario se reduce. El problema de la bsqueda est siendo resuelto, resolviendo
otra de igual naturaleza pero ms pequeo en tamao.

3.- Hay un problema de bsqueda que resuelve en forma diferente. Cuando
el diccionario tiene una sola pgina, se resuelve por otro mtodo (aqu se busca
directamente), este es el caso base. Cuando se alcanza el caso base, las llamadas
recursivas se detienen y el problema se resuelve directamente. Lo importante es
que la manera en la cual el tamao del problema disminuye, asegura que el caso
base ser alcanzado.

Se deben hacer cuatro preguntas para construir una solucin recursiva

1.- Cmo representar el problema en trminos de un problema del mismo
tipo, pero ms pequeo.

2.- Cmo reducir, en cada llamada recursiva, el tamao del problema.

3.- Qu instancia del problema sirve como caso base.

4.- Qu manera de reducir el problema nos asegura que siempre ser
alcanzado el caso base.



Ejemplos de aplicacin.

Ejemplo 1: Clculo del Factorial, se elige porque es fcil de entender y se
ajusta perfectamente al modelo dado.

Definicin iterativa del factorial (con n entero positivo):

FACT (n) = n * (n-1) * (n-2) * ....* 1

Por definicin FACT (0) = 1 y el factorial de un nmero negativo es
indefinido.

Algoritmos y Estructuras de Datos Apuntes tericos Prof. Lic. Mara Gabriela Cerra
Ao 2009

4
Todos sabemos construir una solucin iterativa para este problema
basndonos en esta definicin.
Tambin podemos construir un solucin recursiva del factorial:

FACT (n) = n * FACT (n - 1)

Esta definicin carece de un elemento importante, el caso base.
Como en el diccionario, un caso debe definirse diferente de todos los
dems, de lo contrario la recursin nunca se detiene.
El caso base en la recursin es el Factorial (0) el que se define simplemente
como 1.
Dado que n se asume positivo, decrementando en 1 cada vez que se llama al
factorial se sabe que siempre ser alcanzado el caso base.


1 si n = 0
Factorial (n)
n * Factorial (n -1) si n > 0


Estudiaremos los mecanismos de ejecucin de esta funcin recursiva.
Antes analizamos el Factorial = n* Factorial (n-1), esta sentencia tiene el
siguiente efecto:
Si calculamos el Factorial (4), usando esta definicin
Factorial (4) = 4 * Factorial (3)
Factorial (3) = 3 * Factorial (2)
Factorial (2) = 2 * Factorial (1)
Factorial (1) = 1 * Factorial (0)
Factorial (0) = 1
Se alcanz el caso base. La aplicacin de la definicin recursiva se detiene y
la informacin obtenida se puede usar para responder la pregunta original
factorial (4)?
Dado que:
Factorial (0) = 1 entonces
Factorial (1) = 1 * 1 = 1, entonces
Factorial (2) = 2 * 1 = 2, entonces
Factorial (3) = 3 * 2 = 6, entonces
Factorial (4) = 4 * 6 = 24

Esta definicin recursiva del factorial ilustra dos cosas:

- intuitivamente, Factorial (n) se puede definir en trminos del Factorial
(n-1)
- mecnicamente, la definicin se puede aplicar para determinar el valor
de un factorial dado.
Algoritmos y Estructuras de Datos Apuntes tericos Prof. Lic. Mara Gabriela Cerra
Ao 2009

5
Es fcil construir una funcin a partir de la definicin recursiva:


Function Factorial (n: entero) : entero
hacer
si (n=0) entonces Factorial :=1
sino Factorial := n * Factorial (n-1)
finsi
finhacer
finfuncin

Esta funcin responde al modelo de solucin recursiva.
1) La funcin Factorial se llama a s misma
2) En cada llamada recursiva el nmero cuyo factorial se calcula se
disminuye en 1.
3) El Factorial (0) se maneja en forma distinta. Este caso base no produce
una llamada recursiva.


Estudiaremos los mecanismos de ejecucin de esta funcin recursiva:
- Se evala la expresin a la derecha del smbolo : =
- Como es un producto se evala cada operando.
- El segundo operando es una llamada a la funcin Factorial, a pesar de ser
una llamada recursiva no hay nada especial en ella. Si fuera otra la funcin, el
principio es el mismo, se evala la funcin.

En principio la evaluacin de una funcin recursiva no es ms difcil que la
evaluacin de otra funcin. En la prctica, sin embargo, el seguimiento puede irse
de las manos, para ello introducimos un mtodo sistemtico, llamado mtodo de la
caja, para seguir la ejecucin de funciones o procedimientos recursivos.

Mtodo de la Caja

* Rotular con una letra el llamado recursivo en el cuerpo del subprograma
recursivo. En el ejemplo Factorial (n-1) lo llamamos A. Nos servir para saber
exactamente a donde se debe retornar despus que la llamada a la funcin se
complete.
* Cada llamada recursiva hecha al subprograma en el transcurso de la
ejecucin va a generar una nueva caja, que contendr el ambiente local del
subprograma. Esto es, las variables y parmetros que se crean en el llamado y se
destruyen cuando se termina la ejecucin.

Cada caja contendr entonces:
-El valor de los parmetros formales.
-Las variables declaradas localmente (no existen en el ejemplo).
-Un lugar para el valor a ser retornado por cada llamada recursiva generada
a partir de la caja corriente (marcada con el rtulo).
-El valor de la funcin misma.
Algoritmos y Estructuras de Datos Apuntes tericos Prof. Lic. Mara Gabriela Cerra
Ao 2009

6
* Cuando se crea una nueva caja se dibuja una fecha desde la caja donde se
hizo la llamada hacia la nueva. Sobre la flecha se pone el nombre de la llamada
(rtulo) para indicar a donde se debe retornar.
* Comenzar la ejecucin del cuerpo del subprograma con los valores
correspondientes al mbito local de la caja corriente. Cuando termina la ejecucin
de la caja corriente y se vuelve hacia atrs en las cajas, la anterior es ahora la
corriente y el nombre en la flecha indica el lugar a donde se debe retornar y
continuar la ejecucin del subprograma. El valor calculado se coloca en el tem
apropiado en la caja corriente.

Ejemplo 1: vamos a calcular el Factorial (3)

Llamada original: Factorial (3) comienza la ejecucin


rtulo

En el punto A, se hace una llamada recursiva, y la nueva invocacin de la funcin
Factorial comienza su ejecucin.


En el punto A, nuevamente se
hace una llamada recursiva, y la
nueva invocacin de la funcin
Factorial comienza su ejecucin.






Nuevamente, en el punto A, se hace una llamada recursiva, y la nueva invocacin de
la funcin Factorial comienza su ejecucin.







Se alcanza el caso base, por lo tanto la invocacin de Factorial se complet y
pueden comenzar a resolverse las cajas. Se vuelve a la caja anterior y se retorna el
valor pendiente al punto del llamado (marcado con el rtulo A)


n = 3
A: Factorial (n-1) = ?
Factorial = ?
n = 3
A: Factorial (n-1) = ?
Factorial = ?
n = 2
A: Factorial (n-1) = ?
Factorial = ?
A
n = 3
A: Factorial (n-1) = ?
Factorial (n-1) = ?
n = 3
A: Factorial (n-1) = ?
Factorial (n-1) = ?
n = 2
A: Factorial (n-1) = ?
Factorial = ?
A
n = 3
A: Factorial (n-1) = ?
Factorial = ?
n = 1
A: Factorial (n-1) = ?
Factorial = ?
A
n = 3
A: Factorial (n-1) = ?
Factorial (n-1) = ?
n = 3
A: Factorial (n-1) = ?
Factorial (n-1) = ?
n = 3
A: Factorial (n-1) = ?
Factorial (n-1) = ?
n = 2
A: Factorial (n-1) = ?
Factorial = ?
A
n = 3
A: Factorial (n-1) = ?
Factorial = ?
n = 1
A: Factorial (n-1) = ?
Factorial = ?
A
n = 0
Factorial = 1
A
Algoritmos y Estructuras de Datos Apuntes tericos Prof. Lic. Mara Gabriela Cerra
Ao 2009

7














Valor final retornado al programa principal: 6

Ejemplo 2:

Vamos a resolver de manera recursiva el problema de imprimir una cadena
de caracteres hacia atrs.
Para ello debemos responder las tres preguntas, es decir vamos a construir
la solucin al problema de imprimir una cadena de longitud n hacia atrs en
trminos de una cadena de longitud n -1. Esto indicara que en cada paso recursivo
la longitud de la cadena se hace ms chica. Entonces el problema de escribir una
cadena muy pequea hacia atrs puede servir como caso degenerado. Una cadena
muy pequea es la cadena vaca o nula, es decir la cadena de longitud cero.
Nuestro caso base es entonces imprimir una cadena nula y la solucin a este
problema es no hacer nada (no hay nada que imprimir).
Ahora hay que determinar de qu manera se puede usar la solucin de
imprimir hacia atrs un cadena de longitud n 1 para resolver el problema de
imprimir hacia atrs una cadena de longitud n. La cadena de longitud n 1 resulta
de quitar un carcter de la cadena original.

Veamos la siguiente solucin aproximada:
Imprimir_ hacia_ atrs (S)
Si (S es nula) entonces
no hacer nada (es el caso base)
sino
Imprimir el ltimo carcter de S
Imprimir_hacia_atrs (S menos el ltimo carcter)
finsi

Las llamadas recursivas a Imprimir _hacia _atrs pasan sucesivamente
cadenas de longitud ms chicas sacando siempre el ltimo carcter del anterior,
entonces seguro se alcanzar el caso base (es decir la cadena nula).
n = 3
A: Factorial (n-1) = ?
Factorial (n-1) = ?
n = 3
A: Factorial (n-1) = ?
Factorial (n-1) = ?
n = 3
A: Factorial (n-1) = ?
Factorial (n-1) = ?
n = 2
A: Factorial (n-1) = ?
Factorial = ?
A
n = 3
A: Factorial (n-1) = ?
Factorial = ?
n = 1
A: Factorial (n-1) = 1
Factorial = 1
A
n = 0
Factorial = 1
A
n = 3
A: Factorial (n-1) = ?
Factorial (n-1) = ?
n = 2
A: Factorial (n-1) = 1
Factorial = 2
A
n = 3
A: Factorial (n-1) = ?
Factorial = ?
n = 3
A: Factorial (n-1) = ?
Factorial (n-1) = ?
n = 1
A: Factorial (n-1) = 1
Factorial = 1
A
n = 3
A: Factorial (n-1) = ?
Factorial (n-1) = ?
n = 0
Factorial = 1
A
n = 3
A: Factorial (n-1) = ?
Factorial (n-1) = ?
n = 2
A: Factorial (n-1) = 1
Factorial = 2
A
n = 3
A: Factorial (n-1) = 2
Factorial = 6
n = 3
A: Factorial (n-1) = ?
Factorial (n-1) = ?
n = 1
A: Factorial (n-1) = 1
Factorial = 1
A
n = 3
A: Factorial (n-1) = ?
Factorial (n-1) = ?
n = 0
Factorial = 1
A
Algoritmos y Estructuras de Datos Apuntes tericos Prof. Lic. Mara Gabriela Cerra
Ao 2009

8
Traza de ejecucin usando el mtodo de la caja:

S = casa




Imprime a imprime s imprime a imprime c no hace nada

Resultado asac

Consideremos otra solucin al problema, quitar el primer carcter de la
cadena (en lugar del ltimo).

Analicemos la siguiente solucin:
Imprimir_ hacia_ atrs (S)
Si (S es nula) entonces
no hacer nada (es el caso base)
sino
Imprimir el primer carcter de S
Imprimir_hacia_atrs (S menos el primer carcter)
finsi

Hace lo que se espera? No, la recursin no es mgica, se debe formular
correctamente la solucin.
Veamos la solucin recursiva correcta:
Imprimir_ hacia_ atrs (S)
Si (S es nula) entonces
no hacer nada (es el caso base)
sino
Imprimir_hacia_atrs (S menos el primer carcter)
Imprimir el primer carcter de S
finsi

Esto significa que escribimos el primer carcter recin cuando todo el resto
ya ha sido escrito.

Traza de Escribir_ hacia_ atrs 2: en cada caja, luego del llamado recursivo
imprime el 1 carcter





imprime c imprime a imprime s imprime a no hace nada
S=casa S=cas S=ca S=c S=
S=casa S=asa S=sa S=a S=
Algoritmos y Estructuras de Datos Apuntes tericos Prof. Lic. Mara Gabriela Cerra
Ao 2009

9
Resultado asac

El resultado es el mismo que con la otra solucin, imprime asac pero la
secuencia de cadenas que se va generando es diferente. La diferencia en la
secuencia de valores se compensa por el carcter que se imprime y en el momento
en que se imprime. En la primera solucin se imprime antes de generar una nueva
caja y en la segunda al salir de la caja antes de retornar de una llamada recursiva.
El objetivo de este ejemplo es demostrar que podemos tener dos
estrategias diferentes (o ms) para realizar las mismas tareas.

Consideremos un pequeo cambio al problema y supongamos que la cadena
est almacenada en un arreglo. Si desarrollamos las dos soluciones vistas
tendramos:

Tipos estructurados
vec = arreglo [n]: carcter 1

Procedimiento Imprimir_HACIA_ATRS (s: vec; dim: entero 2);
Hacer
Si dim >0 entonces
Imprimir: s[dim]
Imprimir_HACIA_ATRS (s, dim-1)
Finsi
Finhacer
Finprocedimiento /* el caso base, cadena nula no se escribe pues no se
hace nada*/

La segunda solucin vista, llamar al procedimiento recursivo con la cadena
sin su primer carcter y tambin se puede resolver sin inconvenientes si la cadena
est almacenada en un arreglo de caracteres.
Qu ocurre si la cadena est almacenada en una lista de caracteres en
lugar de un arreglo?

Tipos estructurados
lista = ^nodo
Nodo = registro
Letra: carcter 1
Prox: lista
finregistro
variables
str: lista

La variable str de tipo lista apunta al primer nodo de la lista que contiene la
cadena que se debe imprimir.
Algoritmos y Estructuras de Datos Apuntes tericos Prof. Lic. Mara Gabriela Cerra
Ao 2009

10
Si consideramos la primera solucin cuando llamamos recursivamente con la
cadena sin su ltimo carcter, se hace difcil de implementar ya que lo que
conocemos es la direccin del primer nodo. Para obtener la direccin del ltimo
nodo y eliminarlo hay que recorrer toda la lista. Otra opcin sera mantener
tambin un puntero al ltimo nodo, pero sera complicado direccionar al ante-ltimo
nodo para eliminar el ltimo. Esto hace ms ineficiente la solucin.
La segunda solucin vista requiere solo conocer la direccin de acceso al
primer nodo de la lista:

Procedimiento imprimir_HACIA_ATRS2 (str: lista)
hacer
si str <> nil entonces
imprimir_HACIA_ATRS2 (str^. prox)
imprimir: str^. letra
finsi
finhacer
finprocedimiento


Ejemplo 3: Resolver X elevado a la n

Ahora veremos como podemos encontrar una solucin recursiva, es decir
cmo podemos definir X a la n-sima potencia en trminos de X a una potencia ms
pequea. La respuesta est dada en las reglas de exponenciacin:

X
N
= X * X
N-1
Esto es, podemos calcular X a la N, calculando X a la N-1
y multiplicando su resultado por X.

Nuestra solucin recursiva tiene un solo caso base: X
0
= 1

Entonces podemos formular nuestra solucin recursiva de la siguiente
forma:
X
0
= 1
X
N
= X * X
N-1
, si N > 0 El caso base (N=0) siempre se alcanzar.

Funcin potencia (x : entero 2 ; N : entero 1);
Hacer
Si N=0 entonces
potencia :=1
sino
potencia := X * potencia (X, N-1)
finsi
finhacer
finfuncin

Algoritmos y Estructuras de Datos Apuntes tericos Prof. Lic. Mara Gabriela Cerra
Ao 2009

11
Bibliografa de referencia:
- Fundamentos de Programacin. Algoritmos y estructuras de datos. Luis Joyanes
Aguilar.
- Programacin en Turbo/Borland Pascal 7.0, Luis Joyanes Aguilar
- Algoritmos, datos y programas. Conceptos bsicos, De Giusti, Madoz y otros.

You might also like