8.1.
RECURSIVIDAD
Se dice que una función matemática es recursiva o recurrente cuando hay que definirla pa-
ra una serie de valores elementales y el resto de sus valores se puede obtener a partir de los valo-
res elementales.
Por ejemplo, la función FACTORIAL de un número entero N, que se denota N!, se adapta
a la definición de función recursiva: se define para los valores elementales 0 y 1:
0! = 1
1! = 1
y, para el resto de los valores se puede obtener utilizando estos:
2! = 2 * 1!
3! = 3 * 2!
4! = 4 * 3!
.................
N! = N * (N-1)!
Hay funciones matemáticas que no admiten un método de obtención recursivo; las hay que
admiten tanto un método recursivo como un método no recursivo (iterativo), y las hay que sólo
admiten un método recursivo.
8.2. RECURSIVIDAD EN TURBO PASCAL
Las funciones recursivas pueden codificarse en Turbo Pascal así como en otros lenguajes
de programación; aún más, la idea de la recursividad puede aplicarse también a procedimientos.
Un subprograma (procedimiento o función) Pascal es recursivo cuando tiene la capacidad
de llamarse a sí mismo. Piénsese que si un subprograma recursivo no se diseña bien, podemos
caer en anidamientos sucesivos del subprograma y no salir nunca de él. Para evitar esto, al escri-
bir un subprograma recursivo en Turbo Pascal, debemos asegurarnos de dos cosas:
1.) Que contiene una condición de salida que garantice el fin de los anidamientos.
2.) Que en cada llamada al subprograma recursivo, se 'esté más cerca' de la condición de salida.
Ejemplo: Construir una función recursiva que obtenga el factorial de cualquier número entero:
FUNCTION FACTORIAL (N:INTEGER) : REAL;
BEGIN
IF N<=1 THEN FACTORIAL := 1 {Condición de Salida}
ELSE
FACTORIAL := N * FACTORIAL (N-1); {Cada llamada a la función}
END; { nos acerca a la salida. }
Nótese la gran novedad con respecto a los subprogramas no recursivos: en la zona de ins-
trucciones del subprograma aparece una autollamada, en la que el dato (o datos) que se transfiere
al parámetro (o parámetros) varía, en cada ejecución, en el sentido de aproximación a la condi-
ción de salida, la cual viene controlada por un valor o valores concretos del parámetro (o pará-
metros).
Veamos qué sucede en la memoria cuando se utiliza la técnica de la recursión, examinando
una ejecución concreta de la función anterior. Supongamos que se ejecuta FACTORIAL(3). En
el momento de la llamada a la función, se toma una porción de memoria para la variable local N
(2 bytes por ser de tipo INTEGER), en la que se almacena el valor 3, y 6 bytes para la variable-
identificador FACTORIAL. Como no estamos en la condición de salida, se ejecuta la instrucción
FACTORIAL := 3 * FACTORIAL (2);
Aquí se produce la primera autollamada: se crean las variables locales de Nivel 2, N (en la
que se almacena el valor 2) y FACTORIAL (llamaremos a estas variables N2 y FACTORIAL2
para distinguirlas mejor). La variable de Nivel 1 FACTORIAL permanece en el stack pendiente
de resolución. Como tampoco ahora estamos en la condición de salida, se ejecuta la instrucción:
FACTORIAL := 2 * FACTORIAL (1);
Aquí se produce la segunda autollamada: se crean las variables locales de Nivel 3, N (en la
que se almacena el valor 1) y FACTORIAL (a las que llamaremos N3 y FACTORIAL3). La va-
riable de Nivel 2, FACTORIAL2, se queda también en el stack pendiente de resolución. Ahora
ya sí estamos en la condición de salida: se ejecuta la instrucción: FACTORIAL3 := 1; con lo
cual, la llamada a la función de Nivel 3 concluye: se libera la memoria ocupada por N3 y FAC-
TORIAL3, y se ejecuta la instrucción de Nivel 2 que quedó pendiente:
FACTORIAL2 := 2 * FACTORIAL3;
:= 2 * 1;
En este momento, concluye la llamada a la función de Nivel 2: se libera la memoria ocupa-
da por N2 y FACTORIAL2 y se ejecuta la instrucción de Nivel 1 que quedó pendiente:
FACTORIAL := 3 * FACTORIAL2;
:= 3 * 2;
Aquí concluye la llamada a la función de Nivel 1: se libera la memoria ocupada por N y
por FACTORIAL, y la variable-identificador entrega el valor 6 (3!) al programa principal.
En este ejemplo podemos observar que la cantidad de memoria utilizada por los subprogra-
mas recursivos es muy grande, por lo que deben utilizarse con precaución y sólo en los casos en
los que una solución no recursiva sea poco adecuada o impracticable.
Así, la función FACTORIAL se puede codificar de un modo iterativo más eficiente:
FUNCTION FACTORIAL (N:INTEGER):REAL;
VAR I:INTEGER; F:REAL;
BEGIN
F:=1;
FOR I:=2 TO N DO
F:=F*I;
FACTORIAL:=F;
END;
8.3. LA SENTENCIA FORWARD
Como ya sabemos, cuando un programa utiliza un subprograma (procedimiento o función)
éste debe de estar previamente descrito o declarado: si es un subprograma predefinido, en el pro-
grama debe declararse la utilización de la unidad en la que está incluido, mediante la sentencia
USES; si el subprograma debe incluirse en el programa desde un fichero externo, debe declarar-
se mediante el directivo de compilación {$I EspeciFich} y, por último si el subprograma está
descrito en el propio programa, debe estarlo en la zona de declaraciones del mismo, es decir, pre-
viamente a su utilización en la zona de instrucciones.
Igualmente, si un subprograma, Sub1, hace uso de otro subprograma, Sub2, éste último
subprograma debe estar descrito o declarado previamente a Sub1; en caso contrario, al compilar,
obtendremos un error: 'Identificador desconocido'.
PROGRAM PLASENCIA;
{$I SUMATRIZ}
USES CRT;
PROCEDURE SUB2;
BEGIN
.......
.......
END;
{ Continúa la codificación }
PROCEDURE SUB1 (A,B:BYTE; VAR C:CHAR);
BEGIN
.......
SUB2;
.......
END;
BEGIN
< Resto del Programa >
Esta situación no plantea ningún problema: basta con tener cuidado con el orden de decla-
ración de los distintos subprogramas.
Diferente es, sin embargo, la siguiente situación: el subprograma Sub1 utiliza el subprogra-
ma Sub2 y, a su vez, Sub2 utiliza el subprograma Sub1. Por ejemplo, si en la zona de declaracio-
nes de un programa nos encontramos con las declaraciones:
PROCEDURE SUB1 (A,B:BYTE; VAR C:CHAR);
BEGIN
.......
SUB2;
.......
END;
PROCEDURE SUB2;
BEGIN
.......
SUB1 (X,Y,Z);
.......
END;
¿ qué procedimiento debemos declarar primero ? Se trata de una recursividad a dos bandas: un
procedimiento llama a otro; éste a su vez llama al primero y así sucesivamente, hasta que desde
alguno de ellos se abandone la recursividad. Esta situación no se resuelve invirtiendo el orden de
descripción de los procedimientos, porque nos encontramos en el mismo caso.
Para resolver estos casos, Turbo Pascal dispone de la sentencia FORWARD, que debe es-
cribirse a continuación de la cabecera del primer procedimiento y que informa al compilador de
que el subprograma cuya cabecera ya se ha escrito, se describirá un poco más adelante en la mis-
ma zona de declaraciones del programa:
PROGRAM OLITE;
{$I SUMATRIZ}
USES CRT;
{ Continúa la Codificación }
PROCEDURE SUB1 (A,B:BYTE; VAR C:CHAR); FORWARD;
PROCEDURE SUB2;
BEGIN
.......
SUB1 (X,Y,Z);
.......
END;
PROCEDURE SUB1;
BEGIN
.......
SUB2;
.......
END;
BEGIN
< Resto del programa >
La sentencia FORWARD, a continuación de la cabecera de un procedimiento, avisa al
compilador de que el procedimiento se debe describir en esta zona de declaraciones pero que,
por problemas de recursividad encubierta, ésta descripción se realizará un poco después. La ca-
becera del subprograma que antecede a la sentencia FORWARD debe incorporar la descripción
de todos los parámetros que necesita, así como el tipo de resultado si se trata de una función.
Cuando se describa después el subprograma sólo hay que escribir el identificador del
mismo a continuación de las palabras PROCEDURE o FUNCTION, según el caso.
8.4. EJERCICIOS
1.- Programa que calcule la suma de los cuadrados de los N primeros números naturales,
utilizando una función recursiva.
2.- Programa que obtenga el término enésimo de la sucesión de FIBONACCI, mediante
una función recursiva.
3.- Programa que calcule la potencia A elevado a B (A real>1, B entero>=0), mediante una
función recursiva.
4.- Programa que calcule el factorial de un número entero, por medio de una función re-
cursiva.
5.- Programa que halle el Máximo Común Divisor de dos números dados por teclado por
medio de una función recursiva.
6.- Las TORRES DE HANOI es un juego que tiene orígenes en una tradición oriental. La
codificación en Pascal del juego es bastante difícil aplicando un proceso iterativo. Sin embargo,
es sencillo aplicando un proceso recursivo. El juego es el siguiente:
Se tienen tres palos A,B y C, y n discos con un agujero en el centro y de diferente tamaño
(evidentemente, se puede hacer con torres de monedas de diferentes tamaños). Al comienzo del
juego los discos están en el mismo palo A, de forma que los discos menores están sobre los ma-
yores. El objetivo del juego es llevar los discos al palo C desde el A y que queden de igual for-
ma. Las reglas a cumplir son las siguientes:
a) Sólo se puede desplazar un disco en cada movimiento.
b) Los discos deben estar colocados siempre en uno de los palos.
c) Nunca puede estar un disco mayor sobre uno menor.
A B C
Situación inicial para tres discos
A B C
Situación final para tres discos
El programa debe indicar cuales son todos los movimientos que hay que hacer, bien en for-
ma gráfica o textual. El número de discos (o monedas) se dará por teclado.
7.- Programa que sume los N primeros números enteros positivos por medio de una fun-
ción recursiva. (SUMAREC.PAS).
8.- Una de las funciones recursivas más conocidas es la llamada FUNCION DE ACKER-
MANN: Sean P y Q enteros positivos, entonces:
A ( P,Q ) = Q+1 si P = 0
A ( P,Q ) = A ( P-1,1 ) si Q = 0
A ( P,Q ) = A ( P-1, A ( P,Q-1 ) ) si P,Q>0
Construir un programa para poder calcular los diversos valores de esta función.
9.- En combinatoria y probabilidad aparece muy a menudo lo que se denominan números
combinatorios, que se escriben de la forma:
M
, para 0 < N < M , siendo N y M números enteros y cuyo valor es:
N
M M!
=
N N!(M − N)!
El número resultante nos da las combinaciones posibles de N elementos diferentes toma-
dos de entre M. Por ejemplo, con las 5 letras A,B,C,D,E, podemos formar los grupos de 3 si-
guientes: ABC, ABD, ABE, ACD, ACE, ADE, BCD, BCE, BDE y CDE. O sea, diez combina-
ciones diferentes de 3 elementos tomados de entre 5. Esto es lo que nos da el número combina-
torio:
5 5! 5 ⋅ 4 ⋅ 3 ⋅ 2 ⋅ 1 120
= = = = 10
3
3!( 5 − 3)! 3 ⋅ 2 ⋅ 1( 2 ⋅ 1) 6 ⋅ 2
Escribir un programa que nos calcule el valor de cualquier número combinatorio mediante
una función recursiva, sabiendo que:
M
= 1 si M = N o si N = 0.
N
M M M − 1
=
N N N − 1