Professional Documents
Culture Documents
Captulo 2
Recursividad
2.3 Definicin
63
Apuntes de Estructuras de Datos
public Nodo ( ) {
/* Crea un Nuevo objeto nodo */
}
}
64
Apuntes de Estructuras de Datos
Esto pareciera un crculo vicioso, sin embargo la clave est en que el mtodo recursivo se
llama a s mismo o a otro, pero con instancias diferentes. Para que esto pueda darse, deben
establecerse las siguientes consideraciones:
Dependiendo del problema, cualquiera de estos casos puede ser trivial o complejo.
Se dice tambin que una estructura de datos que est parcialmente compuesta por otras
instancias de la estructura de dato, es recursiva. Por ejemplo un rbol est compuesto de
rboles ms pequeos, los subrboles y los nodos hojas, y una lista puede tener otras listas
como elementos. Las estructuras de datos recursivas son a menudo mejor manejadas con un
algoritmo recursivo.
Se puede considerar que en la ejecucin de una recursin, cada mtodo no ocupa una parte
diferente de la misma computadora, sino que est corriendo en otra mquina. As, cuando un
mtodo invoca a otro, activa la mquina correspondiente, y cuando el otro termina su trabajo,
enva la respuesta a la primera mquina, la cual puede entonces reanudar su tarea.
65
Apuntes de Estructuras de Datos
Java al igual que C++, implementa los mtodos recursivos mediante una pila de registros de
activacin o pila de marcos activos, en donde cada registro o marco contiene informacin
relevante sobre el mtodo como los valores de los parmetros y las variables locales, como se
muestra en el siguiente esquema.
Se utiliza una pila (estructura lifo) ya que la terminacin de los mtodos se produce en sentido
inverso a la invocacin de los mismos. El tema de la pilas ser visto a mayor detalle en la
siguiente unidad.
Por la cercana relacin entre recursin y pilas se dice que los procesos recursivos siempre se
pueden implementar en forma iterativa con una pila explcita.
La recursividad a final de cuentas es un ciclo, slo que en este ciclo no se utiliza ninguna
estructura de control cclica como el for, el while o el do while existentes en la mayora de
los lenguajes de programacin. Sin embargo se deben considerar los mismos principios que en
estas estructuras de control. Esto se ejemplificar de la siguiente manera, comparando uno con
otro.
66
Apuntes de Estructuras de Datos
La diferencia, adems del uso del if en el proceso recursivo, es que en un proceso iterativo, si
nunca se cumple la condicin, el programa se cicla y ste tiene que ser abortado a travs del
teclado. En el proceso recursivo, el programa se aborta automticamente al consumirse la
memoria.
Se pueden encontrar muchos ejemplos del uso de recursividad sobre todo en los problemas
matemticos, ya que muchos de ellos tienen una definicin matemtica recursiva, en estos
casos, la escritura de la rutina se simplifica, es decir, la recursividad es una herramienta muy
potente para la solucin de problemas complejos ya que estos se reducen a problemas mas
simples del mismo tipo. En algunos casos, los algoritmos que se expresan en forma natural en
modo recursivo, deben reescribirse sin recursividad para conseguir una mayor eficiencia.
Dentro de las funciones matemticas que pueden ser definidas recursivamente estn:
El factorial de un nmero
La serie de Fibonacci
El mximo comn divisor con el algoritmo de Euclides (mcd): O((log a)(log b))
La transformada de Fourier
Clculo de la suma de todos los nmeros desde 1 hasta n
Clculo de 2 a la potencia de un nmero entero positivo
Clculo de cualquier nmero a la potencia de un nmero entero positivo
Etc.
67
Apuntes de Estructuras de Datos
Adems, muchos problemas de juegos pueden ser resueltos recursivamente, como el conocido
como las Torres de Hanoi, que emplea en su solucin la estructura de datos Pila, o algn otro
ms complejo como el ajedrez. Algunos algoritmos de bsqueda o de ordenacin tambin
pueden ser definidos recursivamente. Otra de las estructuras de datos ms importantes que
utiliza recursividad son los rboles que se revisarn ms adelante.
Uno de los ejemplos ms simples de una definicin recursiva es el clculo del factorial de un
nmero, que puede definirse as:
Por lo que una forma natural de escribir un mtodo para este clculo sera:
Note que este mtodo se llama a s mismo para evaluar el siguiente trmino. Eventualmente
asumir la condicin de terminacin y cesar la ejecucin. Sin embargo, antes de llegar a la
condicin de terminacin, habr introducido n registros o marcos de activacin en la pila. Por
lo que la complejidad del mtodo es O(n). Se hablar en la ltima unidad del programa acerca
de cmo determinar la complejidad de los algoritmos.
68
Apuntes de Estructuras de Datos
Otro ejemplo comnmente utilizado como mtodo recursivo es el clculo de los nmeros de
Fibonacci, que puede definirse de la siguiente manera:
En este caso, aunque la rutina es corta y elegante, tiene un problema subyacente, ya que lleva a
cabo una multitud de clculos repetidos, es decir, para calcular Fib(n), se calcula
primeramente en forma recursiva Fib(n1), cuando la llamada recursiva termina, se calcula
entonces Fib(n2) recursivamente, aunque ya se haba calculado Fib(n2) al calcular Fib(n
1), entonces la llamada a Fib(n2) es un clculo repetido. Como ste, hay muchos clculos
repetidos, por lo que el tiempo de este algoritmo es exponencial O(2n).
69
Apuntes de Estructuras de Datos
Fib(6)
_______________________________________
Fib(5) Fib(4)
__________________________ ______________
Fib(4) Fib(3) Fib(3) Fib(2)
________________ __________ __________
Fib(3) Fib(2) Fib(2) Fib(1) Fib(2) Fib(1)
____________
Fib(2) Fib(1) etc.
Se revisar ahora un mtodo de bsqueda: la bsqueda binaria, para encontrar algn elemento
en particular en un arreglo.
Caractersticas:
Definicin Matemtica:
70
Apuntes de Estructuras de Datos
x = elemento a buscar
a = arreglo
LI = lmite inferior del arreglo, LS = lmite superior del arreglo
pm = punto medio del arreglo
Si LI > LS entonces DIR = 1 punto de terminacin
BUSBIN pm = (LI + LS) / 2
Si x = a[pm] entonces DIR = pm punto de terminacin
Si x est en parte derecha punto de reactivacin
LI = pm + 1
Si x est en parte izquierda punto de reactivacin
LS = pm 1
public static int BusBin ( int [ ] a, int li, int ls, int x ) {
int pm;
if ( li > ls ) // caso base
return 1; // punto de terminacin
else {
pm = ( li + ls ) / 2;
if ( x == a[pm] ) // caso base
return pm; // punto de terminacin
else if ( x < a[pm] )
return BusBin ( a, li, pm1, x ); // buscar a la izquierda
else return BusBin ( a, pm+1, ls, x ); // buscar a la derecha
}
}
71
Apuntes de Estructuras de Datos
La bsqueda binaria recursiva tiene la misma estrategia que la versin iterativa, por lo que
para encontrar la complejidad, lo primero es identificar cuntas veces se va a ejecutar el ciclo
(o llamadas). Ya que cada paso a travs del ciclo decrementa el arreglo a la mitad, entonces
dado un tamao n cuntas veces necesita partir a la mitad el arreglo hasta encontrarlo o llegar
a 1? n/2, n/4, n/8, n/16, , n/2k, qu ocurre primero? Si se resuelve la ecuacin n/2k < 1
se obtiene que k > log n, por lo que se puede fijar a k = log n (techo de x x, es el entero
ms pequeo que es mayor o igual que x, por ejemplo, 6.1 = 7), entonces se puede decir que
despus de muchas iteraciones en el ciclo, se encontrar el valor o se concluir que no est en
el arreglo, entonces la complejidad del algoritmo es O(log n), lo que es muy rpido.
Ahora se revisar un mtodo de ordenacin: por mezcla o MERGE/SORT.
Definicin Matemtica:
Si LI = LS elemento ordenado punto de terminacin
pm = (LI + LS) /2 dividir en 2 el arreglo
MERGE/SORT SORT ordenar parte izquierda: LS = pm punto de reactivacin
SORT ordenar parte derecha: LI = pm + 1 punto de reactivacin
MERGE intercalar parte izquierda y derecha ya ordenada
72
Apuntes de Estructuras de Datos
}
}
La complejidad de este mtodo es O(n log n) porque siempre divide el arreglo a la mitad. Los
dos factores involucrados en el mtodo son el nmero de llamadas recursivas y el tiempo que
toma la mezcla.
Cada llamada recursiva podr ser un caso base o resultar en dos llamadas recursivas. La
primera llamada empieza haciendo dos llamadas, cada una de ellas hace cuatro y as
sucesivamente, como en las ramificaciones de un rbol binario. Por ejemplo, para ordenar un
arreglo m de 4 elementos (posiciones de: 0 3), las divisiones quedaran:
m(0 3)
m(0 1) m(2 3)
m(0 0) m(1 1) m(2 2) m(3 3)
Ahora, qu sucede con la cantidad de trabajo hecho por la llamada recursiva. Primero se puede
pensar que cada mezcla en el rbol es en tiempo O(n), aunque esto es incorrecto, ya que en
cada nivel, el nmero de elementos se reduce dramticamente; al final de la rama cada llamada
al Merge/Sort es mezclado exactamente la mitad de la lista. En el nodo raz es la nica vez que
la lista entera se mezcl junta en un solo nodo.
73
Apuntes de Estructuras de Datos
74
Apuntes de Estructuras de Datos
Las reglas del lenguaje estn compuestas de dos partes conocidas como la sintaxis y la
semntica del lenguaje. Las reglas de sintaxis definen cmo las palabras (o vocabulario) del
lenguaje pueden ser puestas juntas para formar frases. Las reglas de semntica atribuyen
sentido y significado a estas combinaciones de palabras. Las reglas de semntica son
usualmente establecidas con menos formalidad que las reglas de sintaxis.
Todo lenguaje de programacin tiene sus propias reglas de sintaxis, las cuales determinan si el
cdigo especificado por el programador puede ser traducido por el compilador del lenguaje al
cdigo mquina.
Bajo esta notacin un smbolo se define dando las reglas para reemplazarlo:
Lo que puede ser interpretado como sigue: lo que est a la izquierda del operador := es un
smbolo no terminal que puede ser reemplazado por las alternativas sealadas a la derecha, las
cuales utilizan el separador | y que stas a su vez pueden ser tambin no terminales. Los
smbolos no terminales son aquellos que requieren ser definidos y deben ir encerrados entre
los operadores < >. Por otro lado, un smbolo terminal es aquel que ya est definido.
Todo aquello que est encerrado entre corchetes [] indica que es opcional, y lo encerrado
entre llaves {} indica que se puede repetir ms de una vez.
75
Apuntes de Estructuras de Datos
En este caso, el proceso que se sigue para verificar que un identificador est correctamente
escrito, consiste en dividir el identificador en dos partes: "el primer carcter" del
identificador, el cual es evaluado; y el "resto" del identificador. El proceso se repite de la
misma manera para el "resto" hasta terminar con el ltimo carcter del identificador.
Esta regla es leda como: Un identificador est definido como una letra, o una letra seguida
de un resto.
Por medio de otras reglas se van definiendo letra y resto, que son tambin smbolos no
terminales:
<letra> ::= A | B | C | ... | Z | a | b | c | ... | z
<resto> ::= <letra> | <dgito> | <subrayado> | <letra> <resto> | <dgito> <resto> |
<subrayado> <resto>
<dgito> ::= 0 | 1 | 2 | ... | 9
<subrayado> ::= _
76
Apuntes de Estructuras de Datos
77