Tecnología de la Programación Título de Grado en Ingeniería Informática Curso 2009/10 Fernando Jiménez Barrionuevo Gracia Sánchez Carpena Mari Carmen Garrido Carrera Departamento de Ingeniería de la Información de las Comunicaciones Universidad de Murcia Temario Tema 1. Recursividad 1.1 1.2 1.3 1.4 1.5 Definición de recursividad Recursividad en C Esquemas recursivos Tiempo de ejecución de los algoritmos recursivos Recursión vs iteración Tecnología de la Programación Tema 1. Recursividad 2 1.1 Definición de Recursividad Se dice que un objeto es recursivo si forma parte de sí mismo o se define en función de sí mismo. Técnica particularmente potente en las definiciones matemáticas. La potencia de la recursión reside evidentemente en la posibilidad de definir un número infinito de objetos mediante un enunciado finito. De igual forma, un número infinito de operaciones de cálculo puede describirse mediante un programa recursivo finito. En general, un programa recursivo P puede expresarse como una composición ℘ de instrucciones básicas Si (que no contienen a P) y el propio P. P≡℘[Si,P] Tecnología de la Programación Tema 1. Recursividad 3 1.1 Definición de Recursividad Ejemplos: Números naturales: 1 es un número natural El siguiente de un número natural es un número natural Función factorial, n! (para números enteros no negativos): 0! = 1 Si n>0 entonces n! = n · (n–1)! 3! = = = = = = = = 3 3 3 3 3 3 3 6 · · · · · · · (3-1)! 2! 2 · (2-1)! 2 · 1! 2 · 1 · (1-1)! 2 · 1 · 0! 2 · 1 · 1 Tecnología de la Programación Tema 1. Recursividad 4 1.1 Definición de Recursividad Un algoritmo es recursivo si se invoca a sí mismo al menos una vez. Recursividad directa: El algoritmo contiene una llamada explícita a sí mismo, es decir, A invoca a A. Recursividad indirecta: El algoritmo se invoca a sí mismo de forma indirecta, es decir, A invoca a B y B invoca a A. Recursividad de cola o extremo final: La llamada recursiva es la última instrucción que ejecuta el algoritmo. Tecnología de la Programación Tema 1. Recursividad 5 1.1 Definición de Recursividad Un algoritmo recursivo está bien construido si: 1. Contiene, al menos, un caso base, en el cual el algoritmo no se llama a sí mismo. 2. Los valores de los parámetros del algoritmo en las sucesivas llamadas recursivas están más cerca del caso base que el valor con el que se llamó inicialmente al algoritmo, garantizando que el caso base se alcanza. Tecnología de la Programación Tema 1. Recursividad 6 1.2 Recursividad en C Multiplicación n · m (m ≥ 0) Caso Base: (m = 0) n · 0 = 0 Caso General: (m > 0) n · m = n + n · (m - 1) // m>=0 int multiplica(int n, int m) { if (m==0) return 0; return n+multiplica(n,m-1); } // m>=0 (version comprimida) int multiplica(int n, int m) { return(m==0?0:n+multiplica(n,m-1)); } Tecnología de la Programación Tema 1. Recursividad 7 1.2 Recursividad en C División entera n / m (n ≥ 0 y m> >0) Caso Base: (n < m) n / m = 0 Caso General: (n ≥ m) n / m = 1 + ( n – m ) / m // n>=0, m>0 int divide(int n, int m) { if (n<m) return 0; return 1+divide(n-m,m); } // n>=0 y m>0 (version comprimida) int divide(int n, int m) { return (n<m?0:1+divide(n-m,m)); } Tecnología de la Programación Tema 1. Recursividad 8 1.2 Recursividad en C Módulo n % m (n ≥ 0 y m > 0) Caso Base: (n < m) n % m = n Caso General: (n ≥ m) n % m = ( n – m ) % m // n>=0, m>0 int modulo(int n, int m) { if (n<m) return n; return modulo(n-m,m); } // n>=0 y m>0 (version comprimida) int modulo(int n, int m) { return (n<m?n:modulo(n-m,m)); } Tecnología de la Programación Tema 1. Recursividad 9 1.2 Recursividad en C Potencia nm (m ≥ 0) Caso Base: (m = 0) n 0 = 1 Caso General: (m > 0) n m = n · n m - 1 // m>=0 int potencia(int n, int m) { if (m==0) return 1; return n*potencia(n,m-1); } // m>=0 (version comprimida) int potencia(int n, int m) { return(m==0?1:n*potencia(n,m-1)); } Tecnología de la Programación Tema 1. Recursividad 10 1.2 Recursividad en C Factorial n! (n ≥ 0) Caso Base: (n = 0) 0! = 1 Caso General: (n > 0) n! = n · ( n – 1 )! // n>=0 int factorial(int n) { if (n==0) return 1; return n*factorial(n-1); } // n>=0 (version comprimida) int factorial(int n) { return(n==0?1:n*factorial(n-1)); } Tecnología de la Programación Tema 1. Recursividad 11 1.2 Recursividad en C Fibonacci fib(n) (n ≥ 0) Caso Base: (n ≤ 1) fib(n) = n Caso General: (n > 1) fib(n) = fib(n-2) + fib(n-1) // n>=0 int fibonacci(int n) { if (n<=1) return n; return fibonacci(n-2)+fibonacci(n-1); } // n>=0 (version comprimida) int fibonacci(int n) { return(((n<=1))?n:fibonacci(n-2)+fibonacci(n-1)); } Tecnología de la Programación Tema 1. Recursividad 12 1.2 Recursividad en C Hanoi h(n,a,b,c) (n ≥ 1) Caso Base: (n = 1) h(1,a,b,c): a → c Caso General: (n > 1) h(n,a,b,c): { h(n-1,a,c,b), a → c, h(n-1,b,a,c) } // n>=1 void hanoi(int n, char a, char b, char c) { if (n>1) hanoi(n-1,a,c,b); printf(“Mover de %d a %d\n”,a,c); if (n>1) hanoi(n-1,b,a,c); } Tecnología de la Programación Tema 1. Recursividad 13 1.3 Esquemas recursivos Divide y Vencerás función DV(x) devuelve solución si x es suficientemente pequeño o sencillo entonces (Caso Base): devolver ad hoc (x) en otro caso (Caso General): descomponer x en casos más pequeños x1,x2,...,xn para i=1 hasta n hacer yi = DV(xi) combinar los yi para obtener una solución y de x devolver y Tecnología de la Programación Tema 1. Recursividad 14 1.3 Esquemas recursivos Divide y Vencerás: Búsqueda Binaria Busca un elemento e en un vector v ordenado. Devuelve el índice de una ocurrencia del elemento e en del vector v. Si el vector v no contiene ninguna ocurrencia del elemento e, devuelve -1. // v está ordenado int buscabin(int v[], int ini, int fin, int e) { if (ini>fin) return -1; int med = (ini+fin)/2; if (v[med]==e) return med; if (v[med]>e) return buscabin(v,ini,med-1,e); if (v[med]<e) return buscabin(v,med+1,fin,e); } // v está ordenado, tam es el numero de elementos de v int buscabin(int v[], int tam, int e) { return buscabin(v,0,tam,e); } Tecnología de la Programación Tema 1. Recursividad 15 1.3 Esquemas recursivos Divide y Vencerás: Ordenación por Mezcla Ordena un vector v desde el índice ini hasta el índice fin. void mergesort(int v[], int ini, int fin) { if ((fin-ini)>0) { int med = (ini+fin)/2; mergesort(v,ini,med); mergesort(v,med+1,fin); mezcla(v,ini,fin,med); } } // tam es el numero de elementos de v void mergesort(int v[], int tam) { mergesort(v,0,tam); } Tecnología de la Programación Tema 1. Recursividad 16 1.3 Esquemas recursivos Vuelta Atrás: Una solución Devuelve cierto si encuentra una solución a partir de un paso, y falso en caso contrario. Si encuentra una solución la devuelve en el parámetro solucion. funcion VA(entrada: paso, salida: solución) devuelve booleano si solución completa entonces devolver cierto fin si para cada posibilidad de solución si posibilidad aceptable en solución entonces anotar posibilidad en solución si VA(siguiente paso, solución) entonces devolver cierto fin si cancelar posibilidad en solución fin si fin para devolver falso Tecnología de la Programación Tema 1. Recursividad 17 1.3 Esquemas recursivos Vuelta Atrás: Una solución Solución compuesta por n pasos y m posibilidades para cada paso. El parámetro solucion debe pasarse como apuntador porque es de salida. bool VA(int i, solucion * s) { if (i==n) return true; for(int j=0;j<m;j++) if (aceptable(s,i,j)) { anotar(s,i,j); if (VA(i+1,s)) return true; cancelar(s,i,j); } return false; } LLamada inicial al método VA: solucion * s = new solucion(); inicia(s); if (VA(0,s)) imprime(s); Tecnología de la Programación Tema 1. Recursividad 18 1.3 Esquemas recursivos Vuelta Atrás: Todas las soluciones Obtiene todas las soluciones posibles a partir de un paso. funcion VA(entrada: paso, salida: solución) si solución completa entonces anotar solución en otro caso para cada posibilidad de solución si posibilidad aceptable en solución entonces anotar posibilidad en solución VA(siguiente paso, solución) cancelar posibilidad en solución fin si fin para fin si Tecnología de la Programación Tema 1. Recursividad 19 1.3 Esquemas recursivos Vuelta Atrás: Todas las soluciones Solución compuesta por n pasos y m posibilidades para cada paso. void VA(int i, solucion * s) { if (i==n) imprime(s); else for(int j=0;j<m;j++) if (aceptable(s,i,j)) { anotar(s,i,j); VA(i+1,s); cancelar(s,i,j); } } LLamada inicial al método VA: solucion s = new solucion(); inicia(s); VA(0,s)); Tecnología de la Programación Tema 1. Recursividad 20 1.3 Esquemas recursivos Vuelta Atrás: Ocho Reinas n = 8 (8 pasos, un paso por cada reina) m = 8 (8 posibilidades para cada reina) x[i]: indica la posición en columna de la reina en la fila i-ésima a[i]: true si la columna i-ésima está libre b[i]: true si la diagonal derecha i-ésima está libre c[i]: true si la diagonal izquierda i-ésima está libre struct solucion { int x[8]; bool a[8]; bool b[15]; bool c[15]; }; Tecnología de la Programación Tema 1. Recursividad 21 1.3 Esquemas recursivos Vuelta Atrás: Ocho Reinas void inicia(solucion * s) { for(int j=0;j<8;j++) s->a[j] = true; for(int j=0;j<15;j++) { s->b[j] = true; s->c[j] = true; } } void imprime(solucion * s) { for(int j=0;j<8;j++) printf("%d ",s->x[j]); printf("\n"); } bool aceptable(solucion * s,int i,int j) { return s->a[j]&&s->b[i+j]&&s->c[i-j+7]; } void anotar(solucion * s, int i, int j) { s->x[i]=j; s->a[j]=false; s->b[i+j]=false; s->c[i-j+7]=false; } void cancelar(solucion * s, int i, int j) { s->a[j]=true; s->b[i+j]=true; s->c[i-j+7]=true; } Tecnología de la Programación Tema 1. Recursividad 22 1.4 Tiempo de ejecución de los algoritmos recursivos Expansión de recurrencias Utiliza la recurrencia misma para sustituir m<n por cualquier T(m) en la derecha, hasta que todos los términos T(m) para m>1 se hayan reemplazado por fórmulas que impliquen sólo T(1). Como T(1) siempre es constante, se tiene una fórmula para T(n) en función de n y de algunas constantes. A esta fórmula se le denomina “forma cerrada” para T(n). Tecnología de la Programación Tema 1. Recursividad 23 1.4 Tiempo de ejecución de los algoritmos recursivos Ejemplo: Factorial Ecuación de recurrencia: T(0) = c1 T(n) = T(n-1) + c2 si n>0 Expansión de recurrencias: T(n) = T(n-1)+c2 = [T(n-2)+c2]+c2 = T(n-2)+2c2 = [T(n-3)+c2]+2c2 = T(n-3)+3c2 Forma cerrada para T(n) (por inducción en i): T(n) = T(n-i) + i c2 La expansión terminará cuando se alcance T(0) en lado derecho de la forma cerrada, es decir, cuando n-i=0, por tanto i=n. Sustituyendo en la forma cerrada i por n se obtiene: T(n) = T(0) + n c2 = c1 + n c2 Esto demuestra que T(n) es O(n). Tecnología de la Programación Tema 1. Recursividad 24 1.4 Tiempo de ejecución de los algoritmos recursivos Ejemplo: Búsqueda Binaria Ecuación de recurrencia: T(1) = c1 T(n) = T(n/2) + c2 si n>1 Expansión de recurrencias: T(n) = T(n/2)+c2 = [T(n/4)+c2]+c2 = T(n/4)+2c2 = [T(n/8)+c2]+2c2 = T(n/8)+3c2 Forma cerrada para T(n) (por inducción en i): T(n) = T(n/2i) + ic2 La expansión terminará cuando se alcance T(1) en lado derecho de la forma cerrada, es decir, cuando n/2i=1, por tanto i=log n. Sustituyendo en la forma cerrada i por n se obtiene: T(n) = T(1) + log n c2 = c1 + log n c2 Esto demuestra que T(n) es O(log n). Tecnología de la Programación Tema 1. Recursividad 25 1.4 Tiempo de ejecución de los algoritmos recursivos Ejemplo: Mergesort Ecuación de recurrencia: T(1) = c1 T(n) = 2 T(n/2) + n c2 si n>1 Expansión de recurrencias: T(n) = 2T(n/2)+nc2 = 2[2T(n/4)+(n/2)c2]+nc2 = 4T(n/4)+2nc2 = 8T(n/8)+3nc2 Forma cerrada para T(n) (por inducción en i): T(n) = 2i T(n/2i) + i n c2 La expansión terminará cuando se alcance T(1) en lado derecho de la forma cerrada, es decir, cuando n/2i=1, por tanto i=log n. Sustituyendo en la forma cerrada i por n se obtiene: T(n) = nT(1) + (log n) n c2 = nc1 + n log n c2 Esto demuestra que T(n) es O(n log n). Tecnología de la Programación Tema 1. Recursividad 26 1.5 Recursión vs Iteración La recursión implica múltiples llamadas y el uso de la pila interna para almacenar, en cada llamada recursiva, los parámetros de llamada, variables locales y dirección de retorno, lo que hace que en general sea más ineficiente que el diseño iterativo. Siempre es posible sustituir un algoritmo recursivo por un algoritmo iterativo que utilice una pila. Casos en los que no se debe utilizar la recursión: 1. 2. 3. Recursividad de cola Árbol de recursión lineal (ejemplo, factorial) Árbol de recursión ramificado, pero con nodos repetidos (ejemplo Fibonacci) Conclusión: la recursión se deberá utilizar cuando, además de facilitar el diseño del algoritmo, genere un árbol de recursión ramificado y con nodos no repetidos. Tecnología de la Programación Tema 1. Recursividad 27 1.5 Recursión vs Iteración Multiplicación n · m (m ≥ 0) Caso Base: (m = 0) n · 0 = 0 Caso General: (m > 0) n · m = n + n · (m - 1) // m>=0 (versión recursiva) int multiplica(int n, int m) { if (m==0) return 0; return n+multiplica(n,m-1); } // m>=0 (versión iterativa) int multiplica(int n, int m) { int res = 0; for(;m>0;m--) res+=n; return res; } Tecnología de la Programación Tema 1. Recursividad 28 1.5 Recursión vs Iteración División entera n / m (n ≥ 0 y m > 0) Caso Base: (n < m) n / m = 0 Caso General: (n ≥ m) n / m = 1 + ( n – m ) / m // n>=0, m>0 (versión recursiva) int divide(int n, int m) { if (n<m) return 0; return 1+divide(n-m,m); } // n>=0, m>0 (versión iterativa) int divide(int n, int m) { int res = 0; for(;n>=m;n-=m) res++; return res; } Tecnología de la Programación Tema 1. Recursividad 29 1.5 Recursión vs Iteración Módulo n % m (n ≥ 0 y m > 0) Caso Base: (n < m) n % m = n Caso General: (n ≥ m) n % m = ( n – m ) % m // n>=0, m>0 (versión recursiva) int modulo(int n, int m) { if (n<m) return n; return modulo(n-m,m); } // n>=0, m>0 (versión iterativa) int modulo(int n, int m) { for(;n>=m;n-=m); return n; } Tecnología de la Programación Tema 1. Recursividad 30 1.5 Recursión vs Iteración Potencia nm (m ≥ 0) Caso Base: (m = 0) n 0 = 1 Caso General: (m > 0) n m = n · n m - 1 // m>=0 (versión recursiva) int potencia(int n, int m) { if (m==0) return 1; return n*potencia(n,m-1); } // m>=0 (versión iterativa) int potencia(int n, int m) { int res = 1; for(;m>0;m--) res*=n; return res; } Tecnología de la Programación Tema 1. Recursividad 31 1.5 Recursión vs Iteración Factorial n! (n ≥ 0) Caso Base: (n = 0) 0! = 1 Caso General: (n > 0) n! = n · ( n – 1 )! // n>=0 (versión recursiva) int factorial(int n) { if (n==0) return 1; return n*factorial(n-1); } // n>=0 (version iterativa) int factorial(int n) { int res = 1; for(;n>0;n--) res*=n; return res; } Tecnología de la Programación Tema 1. Recursividad 32 1.5 Recursión vs Iteración Fibonacci fib(n) (n ≥ 0) Caso Base: (n ≤ 1) fib(n) = n Caso General: (n > 1) fib(n) = fib(n-2) + fib(n-1) // n>=0 (versión recursiva) int fibonacci(int n) { if ((n<=1)) return n; return fibonacci(n-2)+fibonacci(n-1); } // n>=0 (version iterativa) int fibonacci(int n) { if (n<=1) return n; int * aux = (int *)malloc((n+1)*sizeof(int)); aux[0] = 0; aux[1] = 1; for(int i=2; i<=n; i++) aux[i] = aux[i-2] + aux[i-1]; int res = aux[n]; free(aux); return res; } Tecnología de la Programación Tema 1. Recursividad 33 1.5 Recursión vs Iteración Búsqueda Binaria // v está ordenado (versión recursiva) int buscabin(int v[], int ini, int fin, int e) { if (ini>fin) return -1; int med = (ini+fin)/2; if (v[med]==e) return med; if (v[med]>e) return buscabin(v,ini,med-1,e); if (v[med]<e) return buscabin(v,med+1,fin,e); } // v está ordenado (versión iterativa) int buscabin(int v[], int ini, int fin, int e) { while(ini<=fin) { int med = (ini+fin)/2; if (v[med]==e) return med; if (v[med]>e) fin = med-1; else ini = med+1; } return -1; } Tecnología de la Programación Tema 1. Recursividad 34 Ejercicios Propuestos Máximo Común Divisor mcd(n,m) (n ≥ 0, m ≥ 0) Caso Base: (m == 0) Caso General: (m > 0) mcd(n,m) = n mcd(n,m) = mcd(m,n%m) Monedas: Calcular el número mínimo de monedas necesarias para una determinada cantidad. Suponemos que tenemos monedas de 1, 5, 10, 20 y 50 céntimos y que la cantidad es menor de 1 euro. Tecnología de la Programación Tema 1. Recursividad 35