Tema 5. Diseño de programas recursivos 5.1. INTRODUCCIÓN ............................................................................................................150 5.2. ETAPAS EN EL DISEÑO DE PROGRAMAS RECURSIVOS....................................159 5.3. EJEMPLOS DE PROGRAMAS RECURSIVOS ...........................................................164 EJERCICIOS ...........................................................................................................................170 150 Programación I 5.1. Introducción Un subprograma (procedimiento o función) es recursivo cuando en su definición aparece una (o más) llamada a sí mismo. - Números impares definidos en los naturales: El número 1 es impar El número 2 no es impar Cualquier número N> 2 es impar si el número (N – 2) es impar - Factorial de un entero: 0! = 1 n! = n * (n-1)! - Definición de una oración: Oracion Sujeto Verbo Sujeto él Verbo come Verbo corre Oracion Oracion y Oración Verbo cose Definición de una sentencia: Sentencia ::= if Condicion then Sentencia end if; | … Tema5- Diseño de programas recursivos Programación I 151 El fundamento de la construcción de subprogramas recursivos consiste en suponer que ya se dispone del propio subprograma que se quiere desarrollar, durante la definición de éste. “Principio de Inducción” Los casos triviales definen la solución directamente y los casos "más complicados" se definen utilizando una (o más) llamadas al subprograma que se define. Descomponer el problema en SUBproblemas MISMO TIPO pero MENOR TAMAÑO Tipo(PROBLEMA) == Tipo(SUBproblema) Tema5- Diseño de programas recursivos 152 Programación I Ejemplo 1: hacer un algoritmo que escriba todos los valores entre 1 y 10 Planteamiento iterativo: procedure Escribir_Hasta_10 begin for i in 1 .. 10 loop Put (I) end loop; end; Planteamiento recursivo: ahora vamos a tratar de plantearlo de una manera diferente. Escribir los valores de la secuencia desde 1 hasta el 10 Escribir 1; y luego Escribir los valores desde 2 hasta 10 Escribir 2; y luego Escribir los valores desde 3 hasta 10 ... Tema5- Diseño de programas recursivos Programación I 153 Para obtener una solución ejecutable deberemos seguir unos pasos: Definición del problema: escribir valores desde I hasta 10 Parametrización: procedure Escribir_Hasta_10( I: in Integer) -- pre: I expresa el número a partir del cual hay que escribir -- post: Se han escrito los valores desde I hasta 10 ¿Acción a repetir? escribir I ¿Cuándo acaba el proceso? I > 10 Tema5- Diseño de programas recursivos 154 Programación I procedure Escribir_Hasta_10(I: in Integer) is begin if I > 10 if I <= 10 then no hacer nada then Put(I); else Put(I); Escribir_Hasta_10 (I+1); Escribir_Hasta_10(I+1); end if; end if; end; De esta manera: Escribir_Hasta_10(1)put(1), Escribir_Hasta_10(2)put(2), Escribir_Hasta_10(3) put(3), Escribir_Hasta_10(4) put/4), Escribir_Hasta_10(5) put(5), Escribir_Hasta_10(6) put(6), Escribir_Hasta_10(7) put(7), Escribir_Hasta_10(8) put(8), Escribir_Hasta_10(9) put(9), Escribir_Hasta_10(10) put(10), Escribir_Hasta_10(11) ya no hace nada mas Terminación cuando I > 10 ¿Siempre termina? Tema5- Diseño de programas recursivos Programación I 155 Tema5- Diseño de programas recursivos 156 Programación I Ejemplo2. Función recursiva para el cálculo del factorial. Versión iterativa: function Factorial(N: Natural) return Natural is -- pos: devuelve N! F: Integer; begin F:=1 ; for I in 2..N loop F:= F * I ; end loop; return F; 1 n=0 o n=1 end Factorial; fac(n)= n * fac(n-1) e.o.c. Versión iterativa: Definición del problema: factorial de N Parametrización (igual que la versión iterativa) function Factorial( N: in natural) return natural; -- pos: devuelve N! Tema5- Diseño de programas recursivos Programación I 157 ¿Cuándo acaba el proceso? O ¿cuándo se resolvería el problema directamente sin recursión? N <= 1 function Factorial(N: in Integer) return Integer is F: Integer; begin if N <= 1 then F := 1; else F := N * Factorial (N-1); end if; return F; end Factorial; Tema5- Diseño de programas recursivos 158 Programación I Claves de la recursividad: 1.- La recursividad es otra alternativa para la solución de problemas (muchas veces existe una alternativa iterativa) 2.- En ADA se pueden definir funciones y procedimientos recursivos 3.- En todo subprograma recursivo debe haber al menos un caso no recursivo Casos no recursivos El resultado se obtiene sin hacer llamadas recursivas (casos triviales) Subprograma sin casos triviales (implícito o explícito) no acabaría jamás 4.- Los casos recursivos deben acercarse a los casos triviales, es decir, el subprograma debe ir hacia su finalización. 5.- Cuando se hacen llamadas recursivas se crean diferentes instancias de los parámetros formales y de las variables locales. Tema5- Diseño de programas recursivos Programación I 159 5.2. Etapas en el diseño de programas recursivos Vamos a ver cada uno de estos pasos con un ejemplo. Ejemplo 3: “Dado un entero positivo N, diseñar un algoritmo recursivo que escriba en pantalla el número con sus cifras en orden inverso. Por ejemplo, el inverso de 3456 es 6543, con los mismos dígitos en orden invertido (será útil el operador rem o mod para separar las cifras: 1234 rem 10 = 4).” 1. ESPECIFICACION/PARAMETRIZACIÓN - Determinar los parámetros (fundamental para el diseño). Parámetros IN, IN OUT y OUT - Especificar: Definir qué hace el algoritmo en función de esos parámetros (qué resuelve cada subproblema) - Establecer cuál será la llamada inicial en función de los parámetros definidos. procedure Escribir_Inverso (N: in Natural) -- Pre: N = d1d2...dn, donde d1, d2, ..., dn son dígitos de N -- Salida: un número (salida estándar) -- Pos: el número escrito es M= dndn-1...d1 primera llamada => Escribir_Inverso (Num) Tema5- Diseño de programas recursivos 160 Programación I 2. ANALISIS DE LOS CASOS. Todos los casos posibles deben estar contemplados en los casos triviales o casos recursivos o generales TRIVIALES: Determinar para qué valores de los parámetros (bajo qué condiciones), existe una solución directa y cuál es esa solución. Debe existir como mínimo 1 caso trivial, si no el algoritmo no parará de generar subproblemas. si N tiene un solo dígito, la solución es escribir N GENERALES: - Determinar para qué valores de los parámetros la solución se obtiene por descomposición en subproblemas del mismo tipo. - Establecer cómo se calculan esa solución general con las llamadas recursivas correspondientes. si N = 3456, entonces podemos escribir su última cifra (6), y a continuación se escribe el resto de los dígitos (345) en orden inverso: 6= N mod 10 y 345= N/10 si N tiene más de un dígito, la solución es escribir (N rem 10) y Escribir_Inverso(N/10); - Comprobar que las llamadas recursivas usan exactamente los parámetros definidos - Comprobar que el tamaño del problema se reduce o tiende hacia los casos triviales. Tema5- Diseño de programas recursivos Programación I 161 Tema5- Diseño de programas recursivos 162 Programación I 3. DISEÑO DEL ALGORITMO ( y quizás las estructuras de datos) Agrupar lo diseñado en pasos previos, procurando simplificar el algoritmo cuando sea posible. Eliminar redundancias y agrupar casos triviales cuando sea interesante. Procurar la eliminación de cálculos repetidos originada por el estudio separado de casos. if (N / 10 = 0) then -- también podría ser N<10 Put (N); else Put (N rem 10); Escribir_Inverso (N / 10); Put (N rem 10); end if; if (N/10 /= 0) then – N>9 Escribir_Inverso (N / 10); end if; Tema5- Diseño de programas recursivos Programación I 163 4. PRUEBA DE TERMINACIÓN Se debe comprobar que en las llamadas recursivas se “avanza” hacia un caso trivial, y que las llamadas recursivas acabarán alguna vez. N es un natural y tiene por ejemplo n dígitos N= d1d2...dn En cada llamada al hacer N/10 pierde un dígito, y por tanto llegará a ser un número de un sólo dígito. 5. IMPLEMENTACION Implementar las estructuras de datos, adecuadas a la representación de los mismos. Construir el subprograma (procedimiento o función) recursivo (en Ada en nuestro caso). procedure Escribir_Inverso (N : in Integer) -- Pre: N = d1d2...dn, donde d1, d2, ..., dn son dígitos de N -- Salida: un número (salida estándar) -- Pos: el número escrito es M= dndn-1...d1 begin Put (N rem 10); if (N / 10 /= 0) then Escribir_Inverso (N / 10); end if; end Escribir_Inverso; Tema5- Diseño de programas recursivos 164 Programación I 5.3. Ejemplos de Programas recursivos Ejemplo4: Búsqueda lineal type t_vector is array( integer range <>) of integer; 1. ESPECIFICACIÓN/PARAMETRIZACIÓN Entrada: un elemento e de tipo integer y un vector t de elementos de tipo integer Pre: Salida: un booleano Post: cierto sii el elemento e aparece en t function esta(t: t_ vector; e: integer) return boolean; -- pre: -- post: devuelve cierto si el elemento e esta en t Primera llamada => esta(t, e) Tema5- Diseño de programas recursivos Programación I 165 2. ANÁLISIS DE CASOS TRIVIALES: El vector es vacío (índice superior < índice inferior), falso. El elemento e coincide con el primero del vector, cierto. GENERALES: El elemento e NO COINCIDE con el central del vector, esta (e, siguientes_vector). 3. DISEÑO DE ALGORITMOS si es_vacío(t), falso sinosi t(primero)=e, true sino esta(siguientes(t), e) 4. PRUEBA DE TERMINACIÓN Cuando vamos llamando a los siguientes de un vector llega un momento que nos quedamos con un vector vacío, si es que antes no hemos encontrado ningún elemento igual al buscado. Tema5- Diseño de programas recursivos 166 Programación I 5. IMPLEMENTACIÓN function esta(t: t_vector; e: integer) return boolean; type t_tabla is array(integer range<>) of natural; -- pre: -- post: devuelve cierto si el elemento e esta en t. begin if t’last < t’first then return false; elsif t(t’first)=e then return true; else return esta(t(t’first +1.. t’last), e); end if; end esta; 7. Buscar en lista no ordenada Para reflexionar y diseñar: Dados un vector de enteros no ordenada y un valor entero, diseña un algoritmo recursivo que si el valor pertenece al vector devuelva la posición en la que aparece y si no pertenece al array, devuelva 0. Tema5- Diseño de programas recursivos Programación I 167 Ejemplo5. Búsqueda dicotómica, en un secuencia ordenada type t_vector is array( integer range <>) of integer; 1. ESPECIFICACIÓN/PARAMETRIZACIÓN Entrada: un elemento e de tipo integer y un vector t de elementos de tipo integer Pre: los elementos de t están ordenados crecientemente Salida: un booleano Post: cierto sii el elemento e aparece en t function esta(t: t_vector; e: integer) return boolean; -- pre: t(i)t(i+1) para cualquier valor i:1..(ultimo de t-1) -- post: devuelve cierto si el elemento e esta en t Primera llamada => esta(t, e) Tema5- Diseño de programas recursivos 168 Programación I 2. ANÁLISIS DE CASOS TRIVIALES: El vector es vacío (índice superior < índice inferior), falso. El elemento e coincide con el central del vector, cierto. GENERALES: El elemento e ES MENOR que el central de la vector, esta (e, mitad_izq_tabla). El elemento e ES MAYOR que el central de la vector, esta(e, mitad_dch_tabla). 3. DISEÑO DE ALGORITMOS si es_vacío(t), falso sinosi t(mitad)=e, cierto sinosi t(mitad)>e, esta(e, mitad_izq(t)) sino esta(e, mitad_dch(t)) 4. PRUEBA DE TERMINACIÓN Cuando vamos haciendo mitades de un vector llega un momento que nos quedamos con un vector vacío. Por ejemplo; tenemos el vector t(3..3) y t(3) =4, e= 2, cuando tengo que calcular la mitad izquierda lo haría: mitad_izq(A(i..j)) = A(i..m-1), siendo m la posición de la mitad entre i y j, m= (i+j)/2 Por tanto, quedaría en el ejemplo t(3..3-1)= t(3..2) (índice superior < al inferior) Tema5- Diseño de programas recursivos Programación I 169 5. IMPLEMENTACIÓN function esta(t: t_vector; e: integer) return boolean; t_tabla is array(integer range<>) natural; -- pre: t(i)type t(i+1) para cualquier valorofi:1..(longitud-1) -- post: devuelve cierto si el elemento e esta en t. mitad: integer; begin mitad:= (t’first + t’last)/2; if t’last < t’first then return false; elsif t(mitad)=e then return true; elsif t(mitad)> e then return esta(t(t’first..mitad –1), e); else return esta(t(mitad +1.. t’last), e); end if; end esta; Para reflexionar y diseñar: Dada un array de enteros ordenados (de menor a mayor) y un valor entero, diseña un algoritmo recursivo que devuelva la posición en la que aparece dicho elemento, si el valor pertenece al array, o la posición en que debería colocarse si el elemento no pertenece al array. Tema5- Diseño de programas recursivos 170 Programación I Ejercicios 1. Escribir una secuencia en orden inverso. Dada una secuencia en la entrada estándar acabada en punto, diseña un algoritmo recursivo que escriba los caracteres de la secuencia en orden inverso, sin escribir el punto. 2. Palíndromo. Tenemos palabras palíndromas “rapar”, “somos”, “allá”, “aléjela” o “reconocer”, un pueblo de nombre palindromo: Lolol. Hay nombres propios que son palabra palindroma como Ana, Odo o Natán; y también apellidos: Laval, Salas o Isasi…o frases como “ATALE DEMONIACO CAIN O ME DELATA” A) Dadas las siguientes definiciones: Max: constant Natural := 9; subtype t_Indice is Integer range 1..Max; type t_Tabla_de_Caracteres is array(t_Indice) of Character; Diseña el procedimiento Comprobar_Palindromo utilizando un algoritmo recursivo procedure Comprobar_Palindromo(T: in t_tabla_de_Caracteres; Palindromo: out Boolean; Izquierda, Derecha: in t_indice); -- Pre: -- Post: Palindromo será True si y solo si para todo i, -- Izquierda <= i <= Derecha, T(i) = T(Max-i+1) -- también lo podríamos expresar, T(i)= T(Izquierda-i+derecha Para verificar si un array es un palíndromo, la llamada inicial será de esta forma: T: t_Tabla_de_Caracteres:= (r,e,c,o,n,o,c,e,r); Es_Palindroma: boolean; Comprobar_Palindromo (Tabla, Es_Palindroma, 1, Max); Tema5- Diseño de programas recursivos Programación I 171 B) Para facilitar el proceso de verificación con distintos tamaños de palabras vamos a resolver el problema con el tipo de dato string. Recordar el proceso de recursión realizado en los problemas de búsqueda lineal y búsqueda dicotómica. funtion Comprobar_Palindromo(T: in string; Palindromo: out Boolean); -- Pre: -- Post: Palindromo será True si y solo si para todo i, primer índice de T -- <= i <= último índice de T, T(i) = T(último-i+primer) Para verificar si una palabra es palíndroma, las llamadas iniciales podrían ser: Comprobar_Palidroma(“rapar” , Es_Palindroma ); Comprobar_Palidroma(“somos”, Es_Palindroma ); Comprobar_Palidroma(“allá”, Es_Palindroma ); Comprobar_Palidroma(“aléjela”, Es_Palindroma ); Comprobar_Palidroma(“reconocer”, Es_Palíndroma); 3. Ejercicios con vector de enteros: type t_vector is array( integer range <>) of integer; Suma de los elementos de un vector. Dado un vector de enteros V, diseña un algoritmo recursivo que calcule la suma de todos los componentes del vector. Número de elementos de una lista menores que otro. Dado un vector de enteros y un entero, diseña un algoritmo recursivo que calcule cuántos elementos de la lista son menores que el entero dado. Escribir lista. Dado un vector de enteros, diseña un algoritmo recursivo que escriba en pantalla los elementos de la lista en el mismo orden. Prefijo. Dados dos vectores de enteros decir si el segundo es prefijo del primero. 4. Ejercicios con vector de enteros de tamaño variable en ejecución: type t_vector is array(integer range <>) of integer; Max: contant integer:= 25; type t_vector_variable is record Tema5- Diseño de programas recursivos 172 Programación I lista: t_vector(1..Max); cuantos: integer range 0..Max; end record; Insertar en lista ordenada. Dado un vector de enteros ordenada y un valor entero, diseña un algoritmo recursivo que inserte el valor en el lugar apropiado. Borrar la primera aparición. Dado un vector de enteros y un valor entero, diseña un algoritmo recursivo que borre ese elemento de la lista. Borrar en lista ordenada. Dada una lista de enteros ordenada y un valor entero, diseñar un algoritmo recursivo que borre ese elemento de la lista. 5. Implementa el subprograma Exponencial en Ada de forma recursiva. La exponencial son multiplicaciones sucesivas: NM = N*N*N*…*N (multiplicado M veces). function Exponencial(N,M:Natural) return Natural is --pre: --post: El resultado es NM procedure Exponencial(N,M:Natural; Resultado: out Natural) is --pre: --post: Resultado = NM 6. Diseña e Implementa un procedimiento en Ada con la siguiente especifiación: procedure Dar_la_vuelta ( S:String; Resultado: out String) is --post: Resultado es la palabra S dada la vuelta 7. Diseña e implementa un algoritmo recursivo que dados dos naturales N y M que verifican que N M, escriba en la salida externa (pantalla) los divisores de M mayores o iguales a N. 8. Diseña e implementa un algoritmo recursivo que dada una matriz cuadrada de tamaño NxN, decida si la matriz es simétrica o no con respecto a la diagonal ((1,1), (2,2), ..., (n,n)) Tema5- Diseño de programas recursivos Programación I 173 9. Se pide diseñar un algoritmo recursivo que, dado un natural N > 0, escriba en la salida externa todas las permutaciones con repetición de N elementos tomados de N en N. Ejemplo: si N = 2, aparecerán en pantalla los pares (1,1), (1,2), (2,1), (2,2). Se pide su implementación en Ada de forma recursiva. Tema5- Diseño de programas recursivos