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