Recursión. - Departamento de Electrónica

Anuncio
UNIVERSIDAD TECNICA FEDERICO SANTA MARIA
DEPARTAMENTO DE ELECTRONICA
Programación en Pascal
Capítulo 19. Recursión.
19. RECURSION
19.1. Conceptos.
Cualquier procedimiento o función pueden contener, en su bloque de acciones ejecutables,
llamados a otros procedimientos que sean accesibles.
Si puede llamarse a cualquier procedimiento accesible, entonces un procedimiento puede
llamarse a sí mismo.
Esta autoreactivación se denomina recursión.
Una cadena de llamados recursivos debe terminar en algún momento; por esta razón los
subprogramas recursivos deben colocar dentro de una instrucción condicionada el llamado
recursivo.
El empleo de un identificador de subprograma dentro del texto del mismo subprograma
implica la ejecución recursiva del mismo.
Más específicamente, la ocurrencia de un llamado a una función, dentro de una expresión
perteneciente al bloque asociado a la misma función, implica la ejecución recursiva de la
función.
Su aplicación es natural en aquellos algoritmos definidos recursivamente; y también cuando
se emplean estructuras de datos que hayan sido definidas recursivamente. Sin embargo,
siempre es posible desarrollar un algoritmo repetitivo en lugar del recursivo; lo cual tendrá
ventajas en menor ocupación de memoria y mayor velocidad de ejecución. Cada vez que se
realiza una autoinvocación, se crea espacio para variables y parámetros valor; es decir, a
medida que aumentan los llamados recursivos existen varias instancias (o encarnaciones) de
las variables. Así también habrá que considerar todas las instrucciones que permitan retornar a
los diferentes puntos de llamado. Esto también implica que el programador debe asegurarse
que los niveles de recursión no sean excesivamente altos; ya que esto podría copar la memoria
disponible.
Un objeto se dice recursivo si está definido en términos de sí mismo.
19.2. Ejemplos de definiciones recursivas.
Prof. Leopoldo Silva Bijit.
07-07-2003
242
UNIVERSIDAD TECNICA FEDERICO SANTA MARIA
DEPARTAMENTO DE ELECTRONICA
Programación en Pascal
Capítulo 19. Recursión.
El concepto de recursión es particularmente poderoso en definiciones matemáticas; veamos
algunos ejemplos:
a) Número natural:
i) 1 es un número natural.
ii) el sucesor de un número natural es un número natural.
b) La función factorial, para enteros no negativos:
i) 0! = 1
ii) Si n>0 entonces n! = n*(n-1)!
c) Máximo común divisor, para enteros positivos. mcd(m, n)
i) mcd(m, 0) = m
ii) Si n>0 entonces mcd(m, n) = mcd(n, m mod n)
d) Números de Fibonacci:
i) fib(0) = 1; fib(1) = 1
ii) fib(n) = fib(n-1)+fib(n-2) para n>1
En Pascal, pueden diseñarse, casi directamente las siguientes funciones:
a) function fact(i:integer):integer;
begin
if i>0
then fact:=i*fact(i-1)
else fact:=1
end;
b) function mcd(m,n:integer):integer;
begin
if n=0
then mcd:=m
else mcd:=mcd(n,m mod n)
end;
c) function fib(n:integer):integer;
begin
if n=0
then fib:=0
Prof. Leopoldo Silva Bijit.
07-07-2003
243
UNIVERSIDAD TECNICA FEDERICO SANTA MARIA
DEPARTAMENTO DE ELECTRONICA
Programación en Pascal
Capítulo 19. Recursión.
else if n=1
then fib:=1
else fib:=fib(n-1)+fib(n-2)
end;
Algunos de estos ejemplos han sido desarrollados antes por algoritmos repetitivos.
Nótese que los llamados recursivos están dentro de estructuras de control condicional.
La potencia de la recursión se basa en la posibilidad de definir un conjunto infinito de
objetos por una sentencia finita. Del mismo modo un número infinito de computaciones puede
describirse por un programa finito recursivo. Las etapas previas de compilación, el análisis
léxico y sintáctico pueden describirse recursivamente; por esta razón los programas que los
implementan suelen ser recursivos.
Se han ilustrado algunas funciones recursivas, pero también es posible desarrollar
procedimientos recursivos.
19.3. Ejemplos.
19.3.1. Permutaciones.
Una permutación de una secuencia de objetos es una redisposición de los mismos.
Por ejemplo las permutaciones de 123 son:
123, 132, 213, 231, 312 y 321.
Permutando las letras de una palabra se obtiene un anagrama.
Por ejemplo: roma, amor, omar, mora, ramo.
El número de permutaciones de n objetos es n!.
En la primera posición es posible colocar cualesquiera de los n objetos; en la segunda
existen n-1 posibilidades, ya que se asume utilizado un objeto en la primera posición. En la
última posición sólo es posible colocar el que queda.
El siguiente programa genera las permutaciones de 123.
program permute(output);
var i,j,k : integer;
Prof. Leopoldo Silva Bijit.
07-07-2003
244
UNIVERSIDAD TECNICA FEDERICO SANTA MARIA
DEPARTAMENTO DE ELECTRONICA
Programación en Pascal
Capítulo 19. Recursión.
begin
for i:=1 to 3 do
for j:=1 to 3 do
for k:=1 to 3 do
if (i<>j) and (i<>k) and (j<>k)
then writeln(i,j,k);
end.
El programa efectúa 3*3*3 iteraciones; es decir, 27. Mientras que el número de
permutaciones es 3! ; es decir, 6.
Una forma de disminuir las iteraciones es por ejemplo, no realizar el tercer lazo (el más
interno) si las dos variables anteriores son iguales. Pero si se desea obtener las permutaciones
de un número de objetos mayor (y que no sean necesariamente números) debe pensarse en un
algoritmo diferente, que sea más eficiente.
Si se escriben las permutaciones de 1234 y se analiza en detalle la secuencia, podremos
plantear para las permutaciones de n objetos distintos a[1], a[2], ..., a[n] el siguiente algoritmo
recursivo.
Sea permute(n) la acción que obtiene las permutaciones de los n objetos.
implementación se descompone en:
Su
a) Se mantiene a[n] en su lugar; y se generan todas las permutaciones de los n-1 objetos
restantes. Es decir se invoca a permute(n-1).
b) Se repite a) previo cambio de a[n] con a[1].
c) Se sigue repitiendo a) efectuando el cambio a[n] con a[i] para i=2 hasta n-1.
Debe observarse que el procedimiento recursivo permute emplea un parámetro valor.
También cuando el número de objetos a permutar sea igual a uno, se tendrá una permutación
lista para ser impresa.
procedure permute(k:integer);
var i : integer;
begin
if k=1
then salida
else
begin
{se mantiene a[k] en su lugar}
Prof. Leopoldo Silva Bijit.
07-07-2003
245
UNIVERSIDAD TECNICA FEDERICO SANTA MARIA
DEPARTAMENTO DE ELECTRONICA
Programación en Pascal
Capítulo 19. Recursión.
permute(k-1); {se generan las permutaciones de los k-1 objetos restantes}
for i:=1 to k-1 do
begin
cambio de a[k] con a[i]; {pasos b y c}
permute(k-1);
se vuelve a[k] a posicion original;
end
end
end;
El siguiente programa permite generar permutaciones de caracteres.
program permutaciones(input,output);
{$A- Esta es una orden para el Compilador Turbo-Pascal}
var n : integer;
a : array[1..5] of char;
procedure lea(var ch:char);
begin
read(kbd,ch)
{lee sin eco; depende del compilador}
end;
procedure entrada;
{llena arreglo de largo variable}
var ch : char;
begin
write('->Entre una secuencia de caracteres, terminada en punto: ')
n:=0; lea(ch);
while ch <> '.' do
begin
n:=n+1; a[n]:=ch; write(ch); lea(ch)
end;
writeln;
end;
procedure salida;
var i : integer;
begin
for i:=1 to n do write(a[i]);
Prof. Leopoldo Silva Bijit.
07-07-2003
246
UNIVERSIDAD TECNICA FEDERICO SANTA MARIA
DEPARTAMENTO DE ELECTRONICA
Programación en Pascal
Capítulo 19. Recursión.
writeln
end;
procedure permute(k:integer);
procedure con_el_i_en_k;
procedure cambio_k_por_i;
var t : char;
begin
t:=a[i]; a[i]:=a[k]; a[k]:=t;
end;
{La llamada a permute (k-1) equivale a ir decrementando k}
var i : integer;
begin {con el i en k}
for i:=1 to k-1 do
begin
cambio_k_por_i; permute(k-1); cambio_k_por_i
end
end;
begin {permute}
if k=1
then salida
else
begin
permute(k-1); {cona[k]fijo,genera las
permutaciones de los k1 objetos
precedentes.}
con_el_i_en_k {repite con a[i] en
el lugar de
a[k], para i desde 1 a k1. }
end
end;
Permutaciones
Entrada
Salida
Permute
begin {principal}
con_el_i_en_k
Cambio
Prof. Leopoldo Silva Bijit.
07-07-2003
247
UNIVERSIDAD TECNICA FEDERICO SANTA MARIA
DEPARTAMENTO DE ELECTRONICA
Programación en Pascal
Capítulo 19. Recursión.
entrada;
permute(n)
end.
El diagrama anterior muestra la estructura de los bloques.
SALIDA debe estar antes de PERMUTE, para ser accesible desde este último. CAMBIO
es accesible sólo desde con_el_i_en_k.
19.3.2. Coeficientes Binomiales.
Los coeficientes del binomio, suelen describirse por el triángulo de Pascal.
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
.....
Emplearemos la notación:
n
C (n ,k ) =  
k 
Se tienen:
Prof. Leopoldo Silva Bijit.
07-07-2003
248
UNIVERSIDAD TECNICA FEDERICO SANTA MARIA
DEPARTAMENTO DE ELECTRONICA
Programación en Pascal
Capítulo 19. Recursión.
C(n,0) = 1
n >=0
C(n,k) = 0
n<k
C(n,k) = C(n-1,k)+C(n-1,k-1) 0<=k<=n
Las relaciones anteriores definen recursivamente al coeficiente del binomio; es inmediata
entonces, la codificación:
function C(n,k:integer):integer;
begin
if k=0
then C:=1
else
if n<k
then C:=0
else C:=C(n-1,k)+C(n-1,k-1)
end;
Pero también:
C(n,k) = n! / k!(n-k)!
Que puede escribirse:
C(n,k) = (n-k+1)(n-k+2)..(n) / 1*2*3*..*k
La siguiente función desarrolla el coeficiente binomial en forma no recursiva:
function C(n,k:integer):integer;
var num,den : integer;
begin
num:=1; den:=1;
for j:=1 to k do
begin
num:=num*(n+1-j);
den:=den*j
end;
C:=num div den
end;
Prof. Leopoldo Silva Bijit.
07-07-2003
249
UNIVERSIDAD TECNICA FEDERICO SANTA MARIA
DEPARTAMENTO DE ELECTRONICA
Programación en Pascal
Capítulo 19. Recursión.
19.3.3. Generador de Combinaciones.
Analizar el algoritmo y colocar comentarios.
program combinaciones(kbd,output);
var m,n : integer;
a : array[1..10] of integer;
b : array[1..10] of char;
procedure entrada;
var ch : char;
begin
write('->Entre secuencia de caracteres, terminada en punto: ');
m:=0; read(kbd,ch);
while ch <> '.' do
begin
m:=m+1; b[m]:=ch; write(ch); read(kbd,ch); a[m]:=m
end;
writeln;
repeat
write('->Elementos de la combinacion: ');
read(n);
if n>m then write('<-ERROR debe ser menor que: ',m+1);
writeln
until n<=m;
end;
procedure salida;
var i : integer;
begin writeln; for i:=1 to n do write(b[a[i]]) end;
procedure combine;
var p,b : integer;
begin
salida; {escribe la inicial}
p:=1;
while p<=n do
Prof. Leopoldo Silva Bijit.
07-07-2003
250
UNIVERSIDAD TECNICA FEDERICO SANTA MARIA
DEPARTAMENTO DE ELECTRONICA
Programación en Pascal
Capítulo 19. Recursión.
if a[n+1-p] < m+1-p
then
begin
b:=a[n+1-p];
while p>=1 do
begin
a[n+1-p]:=b+1;
b:=b+1;
p:=p-1
end;
salida;
p:=1
end
else p:=p+1
end;
begin
writeln('Generacion de combinaciones');
entrada;
combine
end.
Prof. Leopoldo Silva Bijit.
07-07-2003
251
UNIVERSIDAD TECNICA FEDERICO SANTA MARIA
DEPARTAMENTO DE ELECTRONICA
Programación en Pascal
Capítulo 19. Recursión.
19.3.4. Análisis sintáctico.
Se desea saber si una determinada secuencia de caracteres cumple las reglas sintácticas
siguientes :
<expresión> ::= <término>{'+' | '-'<término>}
<término> ::= <factor>{ '*' | '/'<factor>}
<factor>
::= <letra> | '('<expresión> ')' | '['<expresión>']'
Nótese que <expresión> está definida en forma recursiva.
Además se desea transformar la expresión a notación polaca inversa.
describirse por las siguientes reglas:
Esta puede
t1 + t2 --> t1t2+
t1 - t2 --> t1t2f1 * f2 --> f1f2*
f1 / f2 --> f1f2/
(e) --> e
[e] --> e
Donde : t1 y t2 son términos;
f1 y f2 son factores;
y e es una expresión.
Nótese que la secuencia de salida no contiene paréntesis.
Se aprovecha el programa que permite recorrer la sintaxis para generar código; en este
caso, se produce una secuencia en notación polaca inversa que podría posteriormente ser
evaluada en una pila.
En el programa se lee sin eco; se detectan errores de sintaxis, no aceptando caracteres
ilegales, de acuerdo a las producciones.
La secuencia de salida se almacena temporalmente en un arreglo; luego se la escribe en la
misma línea que la secuencia aceptada de entrada. Se toman providencias para mantener
alineadas las columnas de resultado, esto a través de una variable que contabiliza los pares de
paréntesis.
Prof. Leopoldo Silva Bijit.
07-07-2003
252
UNIVERSIDAD TECNICA FEDERICO SANTA MARIA
DEPARTAMENTO DE ELECTRONICA
Programación en Pascal
Capítulo 19. Recursión.
El programa ilustra el diseño de procedimientos locales. Factor es local a término; y término
es local a expresión. En este caso, debe cuidarse la definición de las variables locales; ya que
debido al carácter recursivo es necesario disponer de las encarnaciones adecuadas de las
variables locales.
Se emplea la técnica de leer un carácter por adelantado; y al mismo tiempo se valida que el
carácter recién leído pertenezca al conjunto válido de caracteres siguientes.
Nótese que el texto del programa refleja las producciones. La generación de la secuencia
polaca inversa se logra, no pasando los paréntesis hacia el arreglo de salida; y posponiendo el
paso de los operadores, hasta haber encontrado el siguiente factor o término.
Se emplean conjuntos, los que son tratados en el capítulo siguiente.
program polaca;
const largo=20;
var ch : char;
salida : array[1..largo] of char;
cursor : integer;
cpar : integer;
procedure leach;
begin
read(kbd,ch) {no estándar}
end;
procedure error;
begin write(chr(07)); leach(ch) end;
procedure wrt(ch : char);
begin
cursor:=cursor+1; salida[cursor]:=ch
end;
procedure wrtpol;
var i : integer;
begin
for i:=1 to largo+10-cursor-cpar do write(' ');
Prof. Leopoldo Silva Bijit.
07-07-2003
253
UNIVERSIDAD TECNICA FEDERICO SANTA MARIA
DEPARTAMENTO DE ELECTRONICA
Programación en Pascal
Capítulo 19. Recursión.
{compensa los parentesis con cpar}
for i:=1 to cursor do write(salida[i])
end;
procedure expresion;
var sumop : char;
procedure termino;
var mulop : char;
procedure factor;
begin {factor}
if ch='('
then
begin
write(ch);
leach(ch); while not (ch in ['a'..'z','(','[']) do error;
expresion;
while ch <> ')' do error; cpar:=cpar+2
end
else
if ch='['
then
begin
write(ch);
leach(ch);whilenot(ch in['a'..'z','(','['])do error;
expresion;
while ch <> ']' do error; cpar:=cpar+2
end
else
begin
while (ch<'a') or (ch>'z') do error;
wrt(ch)
end;
write(ch);
leach(ch);
whilenot (ch in ['*','/','-','+',')',']','.']) do error;
end; {factor}
begin {termino}
Prof. Leopoldo Silva Bijit.
07-07-2003
254
UNIVERSIDAD TECNICA FEDERICO SANTA MARIA
DEPARTAMENTO DE ELECTRONICA
Programación en Pascal
Capítulo 19. Recursión.
factor;
while (ch='*') or (ch='/') do
begin
write(ch); mulop:=ch;
leach(ch); while not (ch in ['a'..'z','(','[']) do error;
factor; wrt(mulop)
end
end; {termino}
begin {expresion}
termino;
while (ch='+') or (ch='-') do
begin
write(ch); sumop:=ch;
leach(ch); while not (ch in ['a'..'z','(','[']) do error;
termino; wrt(sumop)
end
end; {expresion}
begin {polaca}
clrsrc; {no estándar}
writeln('Entre <expresion>"." Y la paso a polaca inversa.');
write('- >');
leach(ch); while not (ch in ['a'..'z','(','[','.']) do error;
while ch <> '.' do
begin
cursor:=0; cpar:=0;
expresion;
wrtpol; writeln;
write('->');
leach(ch);whilenot(ch in['a'..'z','(','[','.']) do error;
end
end.
Prof. Leopoldo Silva Bijit.
07-07-2003
255
Descargar