150 likes | 261 Views
3. Recursividad. ¿Qué es y para qué se usa?. Al programar en forma recursiva, buscamos dentro de un problema otro sub-problema que posea su misma estructura Ejemplo : Calcular x n . . // Version 1, estrategia: x n = x * x n-1 public static float elevar ( float x, int n ) {
E N D
¿Qué es y para qué se usa? • Al programar en forma recursiva, buscamos dentro de un problema otro sub-problema que posea su misma estructura • Ejemplo: Calcular xn. // Version 1, estrategia: xn = x * xn-1 public static float elevar( float x, int n ) { if( n==0 ) return 1; else return x * elevar(x, n-1); } • // Version 2, estrategia xn = xn/2 * xn/2 • public static float elevar( float x, int n ) { • if( n==0 ) • return 1; • else if( n esimpar ) • return x * elevar( x, n-1 ); • else • return elevar( x*x, n/2 ); }
Ejemplo 2: Las torres de Hanoi • Pasar las argollas desde la estaca 1 a la 3 • Restricciones: • Mover una argolla a la vez • Nunca puede quedar una argolla mas grande sobre una más pequeña • public class TorresDeHanoi { • static void Hanoi( int n, int a, int b, int c ) { • if( n>0 ) { • Hanoi( n-1, a, c, b ); • System.out.println( a + " --> " + c ); • Hanoi( n-1, b, a, c ); • } • } • public static void main( String[] args ) { • Hanoi( Integer.parseInt(args[0]), 1, 2, 3 ); • } • }
Breve análisis • Cada invocación del método Hanoi genera a su vez dos llamadas recusrivas • Cada llamada recursiva se hace “achicando” el problema en una argolla • Cada ejecución toma tiempo constante • T(n) = 1 + 2T(n-1) • En cada nivel se tienen 21 ejecuciones • Σ 2i = 2n+1 - 1 i = 0..n • Se puede demostrar por inducción 20 T(n) 21 T(n-1) T(n-1) 22 T(n-2) T(n-2) T(n-2) T(n-2) N veces 2n T(1) T(1) T(1) T(1) T(1) . . .
Ejemplo 3: Generar permutaciones • Se tiene un arreglo a[0] . . a[n-1] • Se quieren generar (e imprimir) todas las permutaciones posibles • Estrategia: intercambiar el primer elemento con el i-esimo y generar todas las permutaciones para los n-1 siguientes, i = 0..n-1 • Ej 1,2,3 • 1 2,3 • 1 3,2 • 2 1,3 • 2 3,1 • 3 2,1 • 3 1,2
El programa public class PermutaArreglo { static void permutaciones( int[] x, intini, int fin) { if( ini == fin ) { imprimir(x); return;} for (inti = ini; i<= fin; i++) { intercambiar(x,ini,i); permutaciones(x, ini+1, fin); intercambiar(x,ini,i); } } public static void imprimir(int[] x) { for(inti = 0; i < x.length; i++) System.out.print(x[i]+" "); System.out.println(); } public static void main( String[] args ) { int[] a = {1,2,3,4,5}; permutaciones( a,0,4 ); } public static void intercambiar(int[] x, int y, int z) { intaux = x[y]; x[y] = x[z]; x[z] = aux; } }
Breve análisis • Cada invocación del método permutaciones genera a su vez n-1 llamadas recusrivas • Cada llamada recursiva se hace “achicando” el problema en un elemento • Cada ejecución toma orden n (por el for) • T(n) = n + nT(n-1) • En cada nivel se tienen n(n-1)(n-2)…(n-i+1) ejecuciones, cada una efectúa k(n-i) instrucciones • En el último nivel tenemos la n ejecuciones cada una con un elemento • n • n(n-1) • n(n-1)(n-2) • n! • Cota superior si en todos los niveles colocamos n! y tenemos n niveles tendriamosaprox (n+1)! • El resultado está entre n! y (n+1)! (IGUAL MUCHO)
El backtraking • Solucionar un problema por prueba y error • Se basa en generar todas las posibles soluciones a un problema y probarlas • Por esto mismo, el tiempo requerido para solucionar el problema puede explotar • Ejemplos típicos: las n-reinas, el caballo, el laberinto
Ejemplo 1: el laberinto • Se tiene una matriz de caracteres de dimensiones MxN que representa un laberinto. • Carácter ‘*’ significa pared, no se puede pasar • Carácter ‘ ‘ implica se puede pasar. • Carácter ‘&’ indica salida del laberinto • publicstaticboolean salida(char[][] x, int i, int j) retorna true si a desde la posición i,j se puede encontrar una salida. * * * * * * * * * * * * * * * * * & * * * * * * * * * * * * * * * * * * * * * * * * * *
Algoritmo backtraking • Recursivamente esto se puede programar de la siguiente manera probando todos los caminos posibles: • si en la posición donde estoy (i,j) hay un ‘*’ no hay salida y retorno false. • si en la posición donde estoy (i,j) hay un ‘&‘ entonces estoy fuera y retorno true. • si estoy en posición (i,j) y hay un espacio, pruebo recursivamente si hay salida por alguna de las 4 vecinas (i+1,j), (i-1,j), (i,j+1), (i,j-1). • si alguna de las llamadas retorna true, yo retorno true (suponemos que no se puede mover en diagonal). Si todas retornanfalse, retorno false.
Prgrama: version 1 publicstatic salida1(char[][] x,i,j) { if (x[i][j] == ‘&’) returntrue; if (salida1(x, i+1, j)) returntrue; if (salida1(x, i-1, j )) returntrue; if (salida1(x, i, j+1)) returntrue; if (salida1(x, i, j-1,)) returntrue; returnfalse; } • Esta solución tiene el problema que puede generar llamadas infinitas. Por ejemplo, si llamamos a salida(x, a, b, M,N) y esá vacía pero no es salida, esta llamará a salida(x,a+1,b,M,N). Si la celda (a+1,b) está vacía y no es salida, llamará a salida(x, a+1-1,b,M,N), generandose así un ciclo infinito.
Programa version 2 • Para evitar esto podemos ir “marcando” (por ejemplo, con una x) los lugares por donde hemos pasado para no pasar de nuevo por ahí: publicstaticboolean salida1( char[][] x, i, j) { if (x[i][j] == ‘&’) returntrue; if (x[i][j] == '*' || x[i][j] == '+') returnfalse; x[i][j] = ‘o'; if (salida1(x, i+1, j)) returntrue; if (salida1(x, i-1, j)) returntrue; if (salida1(x, i, j+1)) returntrue; if (salida1(x, i, j-1)) returntrue; returnfalse; • }
Rescatando el camino • Podemos retornar un string que contenga la secuencia de (i,j) por donde hay que pasar para llegar a la salida. Para eso debemos modificar el encabezado publicstatic String sailda(char[][] x, int i, int j) { if (x[i][j] == ‘&’) return "("+i+","+j+")"; String l = s.nextLine(); if (x[i][j] == '*' || x[i][j] == ‘o') returnnull; x[i][j] = '+'; String camino = (salida2(x, i+1, j)); if (camino != null) return "("+i+","+j+")"+camino; camino = (salida2(x, i-1, j)); if (camino != null) return "("+i+","+j+")"+camino; camino = (salida2(x, i, j+1)); if (camino != null) return "("+i+","+j+")"+camino; camino = (salida2(x, i, j-1)); if (camino != null) return "("+i+","+j+")"+camino; returnnull; }
Camino mas corto • Queremos saber cuánto mide el camino (de existir) entre la celda i,j y la salida más próxima. Para esto tenemos que probar todas las posibilidades y nos quedamos con la mejor (más corta): publicstaticintsailda(char[][] x, int i, intj) { if (x[i][j] == ‘&’) return 0; String l = s.nextLine(); if (x[i][j] == '*' || x[i][j] == ‘o') return -1; intmascorto = -1; x[i][j] = '+'; intcamino = (salida3(x, i+1, j)); if (camino != -1 && camino < mascorto) mascorto = camino; camino = (salida3(x, i-1, j)); if (camino != -1 && camino < mascorto) mascorto = camino; camino = (salida3(x, i, j+1)); if (camino != -1 && camino < mascorto) mascorto = camino; camino = (salida3(x, i, j-1)); if (camino != -1 && camino < mascorto) mascorto = camino; x[i][j] = ' '; if (mascorto == -1) return -1; returnmascorto +1; }
Ejemplo: mejor jugada del gato • función que evalúa qué tan buena es una jugada en el gato. • suponiendo que tanto mi contrincante como yo vamos a seguir escogiendo la mejor jugada posible en cada etapa. • retorno 1 si gano con la jugada x,y, 0 si empato, -1 si pierdo intgato(char[][] t, int x, int y, char z) { t[x][y] = z; if (gano(t, z)) return 1; if (empate(t,x,y,z)) return 0; char contrincante = 'O'; if (z == 'O')contrincante = 'X'; intmejorCont = -1; for (inti = 0; i <= 2; i++) for (int j = 0; j <= 2; j++) if (t[i][j] == ' ') { int c = gato(t,i,j,contrincante); if (c > mejorCont) mejorCont = c; } return -mejorCont: }