You are on page 1of 54

Diseo y Anlisis de Algoritmos con Java Tcnicas Algortmicas.

Dr.Eric Jeltsch F.

A continuacin estudiaremos algunas tcnicas generales para la construccin de algoritmos. Cada una de estas tcnicas tienen caractersticas especiales que las hacen apropiados para resolver ciertos tipos de problemas. Desde luego una tcnica siempre podr tener alguna ventaja o desventaja respecto de otra. Lo importante, en todo caso, es enfrentar medolgicamente la construccin de algoritmos que obedecen a ciertas estrategias. Sin embargo, debo insistir en que no se deberan aplicar estas tcnicas en forma indiscriminada a cualquier problema. En general, la estructura del problema determina habitualmente la posibilidad de aplicacin de una tcnica especfica. Sin embargo, considerar que muchos problemas pueden resolverse buscando una solucin fcil y directa pero, a la vez bastante ineficiente. Este mtodo, llamado de fuerza bruta, puede ser muy directo, pero con un poco de anlisis puede encontrarse algoritmos ms eficientes. El esquema mas sencillo quizs sea el llamado divide y vencers, basado en la descomposicin de un problema en subproblemas. Otros esquemas requieren un anlisis minucioso del problema de forma que la solucin se vaya construyendo en etapas. Si puede preverse que decisin conviene en cada etapa para producir cierto tipo de mejor resultado, tenemos una solucin voraz, si la decisin en una etapa, solo puede tomarse tras considerar varias soluciones de otras etapas mas simples, la solucin es dinmica. Aun as, hay problemas cuya solucin no puede hallarse sino mediante un proceso de bsqueda, a pesar de lo complejas que son las operaciones de bsqueda, su uso adecuado mediante el esquema de bsqueda con retroceso (o backtracking) permite ganar gran eficiencia respecto a soluciones de fuerza bruta. Por ultimo, conviene conocer otros mtodos de diseo de algoritmos que tambin resultan de utilidad prctica. Otro no menos importante, por la diversidad de campos de aplicacin es la programacin dinmica, muy til en la asignatura de Investigacin de Operaciones, por ejemplo, en donde se utiliza el empleo de tablas o arreglos matriciales como estructura auxiliar para la resolucin eficiente del problema. Las tcnicas que se abordan son: a) Fuerza Bruta, b) Divide y Vencers, c) Programacin Dinmica y d) Algoritmos Avidos (Greedy). e) Backtracking a) Fuerza Bruta Los algoritmos de Fuerza Bruta se caracterizan por una falta de sofisticacin en la solucin. En general, se toma la ruta ms directa, sin ningn intento de minimizar el nmero de operaciones necesarias para calcular la solucin. Ejemplo: (Exponenciacin). Considrese el problema de calcular xn, n entero positivo.
Pot1 (real x, entero positivo n) resultado = x; for i=1 to n 1 do resultado = resultado* x return resultado

Si 1(n) es el nmero de multiplicaciones requeridas, evidentemente para calcular xn en funcin de n, ser 1(n)= n-1 nmero de multiplicaciones. Es decir 1(n)= (n). Ejemplo:(Bsqueda Exhaustiva). Buscar un producto especfico en el supermercado en donde no conocemos la distribucin de sus productos. Si el producto es un alimento congelado, de inmediato nos dirijimos al pasillo o sector donde se encuentran los alimentos 1 ____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

congelados, sin recorrer otro pasillo. Sin embargo, los algoritmos de Fuerza Bruta ignoran esta alternativa e ingenuamente buscan a travs de todas los pasillos en el intento de encontrar el objeto o producto deseado. Ejemplo: (Problema de la mxima suma). Dada una sucesin X de nmeros enteros. Se busca la mxima suma que se puede generar, desde un conjunto de elementos. Por ejemplo, X[0] 31 X[1] -41 X[2] 59 X[3] 26 X[4] -53 X[5] 58 X[6] 97 X[7] -93 X[8] -23 X[9] 84

La suma parcial X[2] + X[3] + X[4] + X[5] + X[6] con los valores (59 + 26 53 + 58 + 97) = 187 soluciona el problema. Diversas otras soluciones existen sobre el particular.
public { /* 1 /* 2 /* 3 /* /* /* /* /* 4 5 6 7 8 static int maxSubsum1(int a[])

/* 9 }

*/ int maxSuma = 0; */ for (int i = 0; i < a.length;i++) */ for (int j = i; j < a.length; j++) { */ int suma = 0; */ for (int k = i; k <= j; k++) */ suma += a[k]; */ if (suma > maxSuma) */ maxSuma = suma; } */ return maxSuma;

El anlisis de tiempo de ejecucin esta dado por b) Dividir para Reinar(DpR)

1 = O( N
i =1 j =i k =i

).

Digamos que un algoritmo recursivo es un algoritmo que contiene un procedimiento recursivo. La recursin es una forma poderosa, elegante y natural de resolver una amplia gama de problemas, aunque en general debemos decir que poseen una deficiencia en los tiempos de ejecucin. En general, problemas de esta naturaleza pueden resolverse mediante una tcnica llamada Dividir para Reinar, en la cual el problema se descompone en subproblemas del mismo tipo que el original, los que a su vez vuelven a particionarse en subproblemas hasta que este proceso genera subproblemas que se pueden resolver de manera directa. Finalmente, las soluciones entregadas por los subproblemas se combinan para obtener una solucin del problema original. Cabe sealar que la tcnica Dividir para Reinar resuelve un problema de tamao n, descomponindolo en varios subproblemas que son similares al problema original, excepto que son de tamao ms pequeo, sin considerar si los subproblemas que se generan tras esta descomposicin son los ms ptimos. Es decir, la condicin de "optimizacin" no es lo trascendente. Por otro lado, cada uno de estos subproblemas son resueltos de manera independiente y a continuacin los resultados obtenidos de la resolucin de cada uno de estos subproblemas se combinan para generar la solucin al problema original. La resolucin de un problema mediante esta tcnica consta fundamentalmente de los siguientes pasos: 1. En primer lugar ha de plantearse el problema de forma que pueda ser descompuesto en k subproblemas del mismo tipo, pero de menor tamao. Es decir, si el tamao de la entrada es n, hemos de conseguir dividir el problema en k subproblemas (donde 1 k n), cada uno con una entrada de tamao nk y donde 0 nk < n. A esta tarea se le conoce como divisin. ____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena
2

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

2. En segundo lugar han de resolverse independientemente todos los subproblemas, bien directamente si son elementales o bien de forma recursiva. El hecho de que el tamao de los subproblemas sea estrictamente menor que el tamao original del problema nos garantiza la convergencia hacia los casos elementales, tambin denominados casos base. 3. Por ltimo, combinar las soluciones obtenidas en el paso anterior para construir la solucin del problema original. As se visualiza una estructura general de la tcnica DpR en forma recursiva se ven as:
Dado un problema P. If P es divisible en problemas ms pequeos then Divide P en 2 o ms partes: P1, P2, ....Pk. //Etapa Dividir Resuelve P1; Resuelve P2; ... ... Resuelve Pk; // Etapa consolidacin para reinar Mezcla las k-sol. parciales en una solucin para P. //combinar else Resuelve P directamente

Cabe mencionar que, en general, la recursividad puede ser simulada con ayuda de iteraciones. Ahora, si bien es cierto que soluciones recursivas son fciles de entender y de implementar, sin embargo, la desventaja ms recurrente para su ejecucin es la necesidad de una gran cantidad de espacio en memoria. La ventaja de los algoritmos de simplificacin es que consiguen reducir el tamao del problema en cada paso, por lo que sus tiempos de ejecucin suelen ser muy buenos (normalmente de orden logartmico o lineal). Adems pueden admitir una mejora adicional, puesto que en ellos suele poder eliminarse fcilmente la recursin mediante el uso de un bucle iterativo, lo que conlleva menores tiempos de ejecucin y menor complejidad espacial al no utilizar la pila de recursin, aunque va en detrimento de la legibilidad del cdigo resultante. Respecto a la complejidad de este tipo de algoritmos sealemos que cuando un algoritmo contiene una llamada a s mismo, el tiempo de ejecucin puede ser descrito por una ecuacin de recurrencia. En general si T(n) es el tiempo de ejecucin de un problema de tamao n, y lo procedemos a particionar en a-subproblemas, cada uno de los cuales es de tamao 1/b del problema original, entonces la recurrencia queda expresada por T(n) = aT(n/b) + D(n) + C(n), en donde D(n) es el tiempo utilizado para dividir el problema en subproblemas y C(n) el tiempo para combinar la solucin de los subproblemas en una solucin al problema original. Ejemplo (Algoritmos de ordenamiento). Bajo esta metodologa se pueden mencionar por ejemplo, InsertionSort donde la particin del archivo de tamao n es escindido en 2 subproblemas uno de tamao n-1 y otro de tamao 1, generando la recurrencia T(n)= T(n-1) + n. Otro algoritmo es QuickSort, o MergeSort
QuickSort(Array A, int p, int r) int q; { if (p < r) { q = Particion(A, p, r); QuickSort(A, p, q); QuickSort(A, q+1, r); } }

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

Recordar que si la etapa de particion de QuickSort toma n -1 elementos y otra con un solo elemento, el tiempo de ejecucin es T(n) = T(n-1) + O(n). Dicho de otro modo si la etapa de particin n -1 comparaciones, entonces la recurrencia para expresar el nmero de comparaciones es C(n) = n -1 + C(a) + C(b), donde a y b son los tamaos de L1 y L2, generalmente satisfacen a+b = n -1. En el peor caso, x es el elemento mnimo en L. Entonces, a = 0 , b = n -1, y la recurrencia queda C(n)=n -1 + C(n -1) = O(n2). As podr ver, que tambin existe un caso peor que lo deja en igualdad de condiciones con InsertionSort. Pero si la particin genera dos regiones de tamao n/2 QuickSort mejora en forma substancial, pues la recurrencia es T(n) = 2T(n/2) + O(n), cuya solucin es O(nlogn). Tambin existen situaciones en donde pueden balancearse, es decir recurrencias del tipo T(n) = T(9n/10) + T(n/10) + n, pero que no generan cambios substanciales respecto a la anterior. Otro algoritmo es en donde la particin del archivo de tamao n se escinde en dos partes iguales, llamado MergeSort
MergeSort(Lista L) { if (longitud(L) < 2) return L else { particiona L en listas L1 y L2, con n/2 elementos L1 = mergeSort(L1) L2 = mergeSort(L2) return merge(L1,L2) } }

la que genera una recurrencia del tipo T(n)=T( piso(n/2) + T(techo(n/2)), cuya resolucin se interpreta como la complejidad del algoritmo. Ejemplo: (Problema de la mxima suma). Retomamos el problema de la suma mxima, usando esta vez una estrategia DpR que representa una solucin algortmica general. En efecto, Dividir: Particionar el problema de tamao N en (al menos) 2 subproblemas, cuando N > 1 , sino solucionar directamente el problema de tamao 1. Reinar: Solucionar los subproblemas de la misma forma. Combinar: Combinar las soluciones parciales. a[0] 4 1. Mitad a[1] -3 a[2] 5 a[3] -2 a[4] -1 2. Mitad a[5] 2 a[6] 6 a[7] -2

La suma ms grande en la primera mitad es 6 (a[0] + a[1] + a[2]), la suma ms grande en la segunda mitad es 8 (a[5] + a[6]). La suma maximal en la 1. Mitad, en la cual es considerado el ltimo elemento de la 1. Mitad inclusive, es decir (a[0] + a[1] + a[2] + a[3]) es 4. La suma maximal en la 2. Mitad, en la cual se considera el primer elemento de la 2. Mitad es 7. La suma maximal que abarca ambas mitades es 4 + 7 = 11.
private static int maxSucSum(int a[], int izq, int der) { /* 1 */ if (izq == der) /* 2 */ if (a[izq] > 0) // Caso que este elemento sea positivo

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

Diseo y Anlisis de Algoritmos con Java


/* /* /* /* /* /* 3 4 5 6 7 8 */ */ */ */ */ */

Dr.Eric Jeltsch F.

return a[izq]; // luego este es el max.Suma parcial else return 0; int mitad = (izq + der) / 2; int maxIzqSuma = maxSucSum(a, izq, mitad); int maxDerSuma = maxSucSum(a, mitad + 1, der); int maxLimiteIzqSuma = 0, limiteIzqSuma = 0; for (int i = mitad; i >= izq; i--) { limiteIzqSuma += a[i]; if (limiteIzqSuma > maxLimiteIzqSuma) maxLimiteIzqSuma = limiteIzqSuma; } int maxLimiteDerSuma = 0, limiteDerSuma = 0; for (int i = mitad + 1; i <= der; i++) { limiteDerSuma += a[i]; if (limiteDerSuma > maxLimiteDerSuma) maxLimiteDerSuma = limiteDerSuma; } if (maxIzqSuma > maxDerSuma) if (maxIzqSuma > (maxLimiteDerSuma + maxLimiteIzqSuma)) return maxIzqSuma; else return (maxLimiteDerSuma + maxLimiteIzqSuma); else if (maxDerSuma > (maxLimiteDerSuma + maxLimiteIzqSuma) ) return maxDerSuma; else return (maxLimiteDerSuma + maxLimiteIzqSuma);

/* 9 */ /*10 */ /*11 */ /*12 */ /*13 */ /*14 */ /*15 */ /*16 */ /*17 */ /*18 */

public static int maxSucSum3(int a[]) { return maxSucSum(a, 0, a.length - 1); }

La implementacin toma un tiempo O(NlogN). Ejemplo: (Bsqueda Binaria). Dado un nmero X y una sucesin de nmeros enteros A0, A1, A2, ... , AN-1 en memoria. Se pide hallar la posicin i tal que Ai=X , y por otro lado devuelve i=-1 si X no fue encontrado.
public static int Bbin(Comparable a[], Comparable x) { /* 1 */ int izq = 0, der = a.length - 1; /* 2 */ while (izq < der) { /* 3 */ int mitad = (izq + der) / 2; /* 4 */ if (a[mitad].compareTo(x) < 0) /* 5 */ izq = mitad + 1; /* 6 */ else if (a[mitad].compareTo(x) > 0) /* 7 */ der = mitad - 1; else /* 8 */ return mitad; // hallado } /* 9 */ return -1; // No hallado }

Podemos ver que la lnea 2 es decisiva en la bsqueda del elemento. Comienza con (der izq) = N-1 y termina con (der izq) = -1. Junto a cada pasada por el ciclo se debe particionar en mitad(der izq). Si (der izq) = 128, luego los valores maximales segn cada iteracin es: 64, 32, 16, 8, 4, 2, 1, 0, -1. El tiempo de ejecucun es O(logN), como se puede ver tras la resolucin de la ecuacin de recurrencia 5 ____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

Diseo y Anlisis de Algoritmos con Java T(n) = 1 # subproblemas T( n/2) + tamao del subproblema

Dr.Eric Jeltsch F. (1) dividir & combinar

La bsqueda Binaria es la implementacin de un Algoritmo para una estructura de datos (de almacenamiento secuencial Lista, Array). Para la bsqueda de algn elemento necesita de tiempo O(logN). Toda otra operacin (p.e. Agregar) necesita tiempo O(N). Ejemplo: Podemos plantearnos tambin disear un algoritmo de bsqueda ternaria, que primero compara con el elemento en posicin n/3 del vector, si ste es menor que el elemento x a buscar entonces compara con el elemento en posicin 2n/3, y si no coincide con x busca recursivamente en el correspondiente subvector de tamao 1/3 del original. Conseguimos as un algoritmo mejor que el de bsqueda binaria?. Ejemplo: (Exponenciacin). Retomando el problema, pero con un enfoque recursivo alternativo a Pot1(), en tal caso se piensa si xn puede ser calculado obteniendo el cuadrado de x n/2 y a continuacin multiplicando el resultado por x si n es impar. Es decir: xn = x n/2 x n/2 . x , si n impar, xn = x n/2 x n/2 , si n par. Si en particular se usa Pot1() para calcular x n/2 se tendrn n/2 multiplicaciones. Luego para completar el clculo de xn, se requiere o bien una, o bien dos multiplicaciones adicionales dependiendo si n es par o impar. De este modo, simplemente calculando n/2 y usando este resultado para calcular xn, tenemos un medio efectivo para reducir aproximadamente a la mitad el nmero de multiplicaciones requeridas para calcular xn. Sin embargo, utilizando esta misma alternativa podra ser posible de calcular x n/2 . En concreto, Pot1() puede usarse para calcular x n/2 ,

2

y este resultado se eleva al cuadrado (y se multiplica por x si n/2 es impar) para formar x , y as sucesivamente generando el algoritmo.
Pot2 ( real x, entero positivo n) if n = 1 then return x y = Pot2 (x, n/2) if n es impar then return y y x else return y y

n/2

Sabemos que en el mejor caso, cuando n = 2k , Pot2( ) realiza kmultiplicaciones, de donde k = log2(n). En general, (log n) - nmero de multiplicaciones. Ejemplo.(multiplicacin de enteros grandes). Considerar el producto de dos enteros X e Y de n-bits. Recordar que el algoritmo que Ud. conoce de la escuela, bien podra considerarse de fuerza bruta, dado que el costo es O(n2), si se toman las multiplicaciones y las adiciones de un solo bit o dgito como un paso. Una estrategia DpR considera dividir X e Y en dos enteros de n/2 bits cada uno. X = B + A2n/2 Y = D + C2n/2 A lo que el producto XY puede escribirse como XY = BD + AC2n + (AD + BC)2n/2

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

Realizando 4-multiplicaciones de enteros de n/2 - bits (AC, AD, BC, BD), 3-sumas de enteros con un mximo de 2n-bits. Generando una recurrencia T(1) = 1. T(n) = 4T(n/2) + cn, cuya resolucin es del orden O(n2) no mejor a la que aprendi en la escuela. Sin embargo si ahora se hacen los cambios XY = BD + AC2n + [(A - B)(D - C) + AC + BD]2n/2 Aunque parezca ms complicado, slo requiere 3-multiplicaciones de enteros con n/2-bits, AC, BD y (A - B)(D - C), generando la recurrencia T(1) = 1. T(n) = 3T(n/2) + cn, cuya solucin es aproximadamente O(n1.5849625), mejorando substancialmente la performance de lo aprendido en la escuela. Por ejemplo, con X= 31785=124* 256 + 41, donde a=124, b=41 Y= 29846=116*256 + 150, donde c=116, d=150. Obteniendo el siguiente producto ac= 14384, bd= 6150, (a + b)(c + d) ac bd = 23356. Ejemplo(Multiplicacin de Matrices). Multiplicar A por B genera una matriz C todas ellas de orden n x n. De donde el tiempo para computar C es del orden n3. En donde n2 son los coeficientes cij de C y n para calcular todos los cij. Este es el algoritmo bsico que Ud. conoce. Sin embargo la matriz anterior se puede pensar como una matriz n x n = 2 x 2 matrices de n/2 x n/2 submatrices a saber C = A x B, es decir r s = t u con esto podemos decir que r = ae + bf, s = ag + bh, t = ce + df, u = cg + dh de donde una multiplicacin en una matriz n x n es "reducida" a 4-ecuaciones, de las cuales se necesitan 2-multiplicaciones de dos n/2 x n/2 matrices, 1- suma de dos n/2 x n/2 matrices, dando en total 8-Multiplicaciones y 4- adiciones. T(n) = 8 # subproblemas T( n/2) + tamao del subproblema ( n2 ) dividir & combinar c d a b x f h e g

no logrndose una mejora, pues la solucin es de orden n3. Sin embargo, Strassen propuso la idea de multiplicar 2 x 2 matrices con solamente 7- multiplicaciones( en vez de 8), lo hizo considerando P1 = a ( g - h ) P2 = ( a + b ) h P3 = ( c + d ) e P4 = d ( f - e ) ____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena
7

Diseo y Anlisis de Algoritmos con Java P5 = ( a + d ) ( e + h) P6 = ( b - d ) ( f + h) P7 = ( a - c ) ( e + g), en donde r = P5 + P4 - P2 + P6 s = P1 + P2 t = P3 + P4 u = P5 + P1 - P3 - P7.

Dr.Eric Jeltsch F.

Segn este principio se necesitan 7-multiplicaciones y 18 adiciones o restas. T(n) = 7 # multiplicaciones T( n/2) + tamao del subproblema ( n2 ) dividir & combinar

cuya solucin es ( n2.81).(teo. Maestro), de manera que (n2.81) v/s ( n3) gana para n>=50 en la practica. c) Programacin Dinmica Un algoritmo de programacin dinmica almacena los resultados, o soluciones de subproblemas ms pequeos y posteriormente los consulta en lugar de volver a calcularlos, para cuando los necesita para resolver subproblemas ms grandes. De manera que PD es idnea para problemas en los que un algoritmo recursivoresolvera muchos de los subproblemas varias veces. En general la idea es convertir una solucin recursiva en un algoritmo de programacin dinmica. La esencia de los algoritmos de programacin dinmica es que cambian espacio por rapidez almacenando soluciones a subproblemas en lugar de volver a resolverlos. En general, la programacion dinamica debe su nombre al siguiente tipo de problema. Un proceso es examinado al principio de cada uno de N periodos sucesivos. Del examen resulta un valor x que es una (o ms) variable que sirve para juzgar la situacion del proceso. Sea (k,x) el dato correspondiente al periodo k. En base a esta informacion elegimos una accion correctiva d tomada de un conjunto D(k,x). Esto provoca una transicion a (k+1,x ) donde x = T(k,x,d) con un costo de la trancisin c(k,x,d). Se plantea el problema de determinar las acciones correctivas en cada una de los N periodos de forma que la suma de los costos de los N periodos sea mnimo. Algunas observaciones que debe tener en cuenta son: Atacar el problema de arriba hacia abajo como si fuera a desarrollar un algoritmo recursivo; determinamos cmo resolver un problema grande suponiendo que conocemos soluciones para problemas ms pequeos. Guardando los resultados de los problemas pequeos podremos evitar clculos repetidos. Decidir que estructura de dato es la ms apropiada, en muchos casos lo mejor es un array uni o bidimensional. Determinar finalmente como obtener la solucin del problema a partir de los datos Los coeficientes binomiales se prestan en forma ideal para aplicar lo antes visto. Sabemos que los coeficientes binomiales se pueden definir con la ecuacin de recurrencia
C(n, k)= C(n -1, k -1) + C(n 1, k) , para n > 0 y k > 0. C(n, 0)= 1, si n 0. C(0, k) = 0, si k > 0.

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

En donde, C(n, k) tambin se puede interpretar como el nmero de formas en que podemos escoger k objetos distintos de un conjunto de n objetos. En este primer programa se muestra la funcin sugerida por la relacin de recurrencia dada para C(n,k).
/* Calculo de Binomio(m, n) usando una metodologa dividir para Reinar. */ public class BinomialDpR { static int[][] llamadas; static int Binom(int m, int n) { if (n == 0) return 1; else if (m == 0) return 0; else return Binom(m-1, n) + Binom(m-1, n-1); } public static void main (String[] args) { llamadas = new int[31][16]; System.out.println(Binom(30, 15)); System.out.println(llamadas[0][7]); for (int i = 0; i<1 ; i++) { for (int j = 0; j<11 ; j++) System.out.print(llamadas[i][j] + " "); System.out.println(); } } }

En este programa BinomCache muestra la implementacin de C(n,k) como un algoritmo de programacin dinmica.
public class BinomCache { static int[][] cache; private static int escogerCache (int m, int n) { if (cache[m][n] == -1) { int resp; if (n == 0) resp = 1; else if (m == 0) resp = 0; else resp = escogerCache(m-1, n) + escogerCache(m-1, n-1); cache[m][n] = resp; } return cache[m][n]; } public static int escoger (int m, int n) { cache = new int[m+1][n+1]; for (int i=0; i<m+1; i++) for (int j=0; j<n+1; j++) cache[i][j] = -1; return escogerCache(m, n); }

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

Diseo y Anlisis de Algoritmos con Java


public static void main (String[] args) { System.out.println(escoger(30, 15)); }

Dr.Eric Jeltsch F.

Ud. podr ver a simple vista la rapidez de este ltimo al compararlo con el primero. Otro ejemplo clasico es el problema del orden de multiplicacin de matrices, que es uno de los ejemplos clsicos dentro de la Programacin dinmica. El propsito no es tan solo mostrar la forma de resolver el problema del orden de la multiplicacin de matrices, sino ms bien como aplicar los principios de desarrollo de un algoritmo de Programacin Dinmica. Comencemos por definir una Matriz A de orden n m, la que est compuesta por elementos a i, j , dispuestos en un arreglo, con n-filas y m-columnas

a 1, 1 a 2, 1 A= a 3, 1

a 1, 2 a 2, 2 a 3, 2

....... .......

a 1, m a 2, m a 3, m

....... ... . .. .........

a n,1

a n,2

a n,m

Dada una matriz A de orden p q , y una matriz B de orden q r. La multiplicacin A B es una matriz C de orden p r, dada por c i, j = q a i, k b k,j k=0 para 1 i p, y 1 j r. Observaciones. Si AB esta definida puede que BA no este definida, puede ocurrir que AB BA, Recursivamente definimos el producto A1 A2 A3 A4 A5 ..... As-1 As como A1 (A2 (A3 (A4 (A5 ..... (As-1 As))). La multiplicacin de matrices es asociativa, es decir, A1 A2 A3 = (A1 A2 )A3 = A1 (A2 A3 ). La complejidad de la multiplicacin de matrices es del orden O(pqr). Notar que C tiene pr entradas y toda entrada toma tiempo O(q). Dada A de orden p q , B de orden q r, y una matriz C de orden r s, entonces D puede ser calculada en dos sentidos, A(BC), o (AB)C. Entonces el nmero de multiplicaciones necesarias es : Mult [(AB)C] = p q r + p r s, Mult [A(BC)] = q r s + p q s, si ahora p= 5, q=4, r= 6 y s=2, entonces Mult [(AB)C] = 180, Mult [A(BC)] = 88, muestran , segn sea el caso una gran diferencia. Por lo tanto la sucesin de multiplicaciones es de suma importancia. ____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena
10

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

Problema: Dada una sucesin de matrices A1 , A2 , A3 , A4 .. An y dimensiones p0, p1, p2, p3,...., pn , en donde Ai es de dimensin pi-1 pi, donde 1 i n. Determinar la sucesin de multiplicaciones que minimiza el nmero de multiplicaciones. Es decir, en otras palabras, Cmo deberemos calcular A1 A2 A3 A4 .. An y cul es el costo mnimo de hacerlo?. Notar que si se tienen matrices M= A1 A2 A3 A4 de orden 10 20, 20 50, 50 1, 1 100, evaluando M en el orden A1 (A2 (A3 A4 )) requiere 125.000 operaciones, mientras que (A1 (A2 A3)) A4 requiere solamente 2.200 operaciones, de manera que la optimizacin de la sucesin de parentizacin es de suma importancia y bien vale considerarla. Sea T(n) las formas distintas de parentizar n-matrices, en el caso para M se tena T(4)=5, en donde se mostraron dos de las 5-posibilidades. En general se tiene T(n) = T(i) T(n i), para n > 1. i=1 En efecto, sean las matrices A1 , A2 , A3 , A4 .. An y la ltima multiplicacin realizada es T(i)= (A1 A2 A3 A4 .. Ai ) (Ai+1Ai+2 Ai+3 .. An ) = T(n-i) , de manera que existen T(i) T(n i) formas de calcular el producto para cada i posible. La solucin de esta recurrencia esta dada por T(n-1) = 1/(n-1)Comb(2n, n), que es el nmero de Catalan cuyo resultado es de orden exponencial. Desarrollando una propuesta en Programacin Dinmica. Etapa 1: Determinar la estructura de una solucin optimal, (en este caso una parentizacin optimal). Descomponer el problema en subproblemas: Para cada par 1 i j n, determinar la sucesin de multiplicaciones para A i...j = AiAi+1 .. Aj que minimiza el nmero de multiplicaciones. Claramente Ai...j =AiAi+1 .. Aj es una matriz pi-1 pj, Problema Original: Determinar la sucesin de multiplicaciones para A1....n. Parentizacin de alto nivel para Ai...j, para cualquier sucesin de multiplicaciones optimal se encontrara que en la ltima etapa se debe realizar el producto de dos matrices Ai...k y Ak+1...j, para algn k esto es: Ai .. j = (AiAi+1 .. Ak ) (Ak+1 .. Aj ) = (Ai .. k ) (Ak+1.. j ) esto se interpreta en forma particular como A3 .. 6 = (A3 (A4 A5 )) (A6 ) = (A3 .. 5 ) (A6.. 6 ), donde k=5 entonces el problema de determinar la sucesin de multiplicaciones optimal es cuestionada en trminos a considerar Cmo decido donde particionar la cadena o sucesin, es decir quin es k?. (debemos buscar entre todos los posibles....valores de k.) Cmo parentizar las subcadenas (Ai .. k ) (Ak+1.. j )? (debemos aplicar el mismo procedimiento en forma recursiva). Principio de Optimizacin ____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena
11
n -2

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

Notar que este problema satisface el principio de Optimizacin, pues si queremos encontrar la sucesin de multiplicaciones optimal Ai .. j debemos usar la sucesin optimal para (A i .. k ) y para (A k+1.. j ), en otras palabras los subproblemas deben ser resueltos en forma optimal para que el problema global sea resuelto en forma optimal. Etapa2: Recursivamente definimos el valor de una solucin optimal. En este sentido, almacenamos las soluciones de los subproblemas en una tabla, en donde para cada par 1 i j n, m[i, j] denota el nmero mnimo de multiplicaciones necesarias para computar Ai .. j. El costo optimal puede ser expresado por la siguiente definicin recursiva. Recursivamente definimos el valor de una solucin optimal, en donde m[i, j] = 0, si i=j m[i, j] = Minimo 1 k < j ( m[i, k] + m[k+1, j] + pi-1 pk pj ) , si i < j. Algoritmo Para computar el costo mnimo en multiplicar n-matrices A1A2 A3A4 .. An . Input: p0, p1, p2, p3,...., pn , donde pi-1 y pi son las dimensiones de Ai. Output: Costo mnimo de multiplicar las matrices Ai asumiendo que p1p2p3 operaciones se necesitan para multiplicar una matriz p1p2, por una matriz p2p3.
Cadena_Matriz(p, n) { for i=1 to n do Ai i=0; for l = 2 to n do { for i=1 to n - l + 1 do { j = i + l 1; m[ i, j ] = ; for k = i to j 1 do { q = m[ i, k ] + m[ k+1, j ] + pi-1 pk pj ; if ( q < m[i, j] ) { m[i, j] = q; s[i, j] = k; } } } } return m y s ; (Optimo en m[1, n]) }

La complejidad del algoritmo a raz de los tres loops anidados, con valores de los indices menores que n, origina que sea de complejidad O(n3), mientras que el espacio de complejidad es (n 2). Ejemplo: Dada una cadena n = 4 matrices A1 , A2 , A3 , A4 con p0 = 5, p1 = 4, p2 = 6, p3 = 2 y p4 = 7. Hallar m[1, 4]. Inicializacin aparece representada por la Fig. 1

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

12

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

Fig. 1 En Fig. 2, computamos m[1, 2], que por definicin est dado por: m[1, 2] = Mnimo 1 k < 2 ( m[1, k] + m[k+1, 2] + p0 pk p2 ) , si i < j. = m[1, 1] + m[2, 2] + p0 p1 p2 = 120.

Fig. 2

En Fig. 3, se obtiene m[2, 3], y en Fig. 4, se obtiene m[3, 4], por los clculos.

Fig. 3

Fig. 4

m[2, 3] = Mnimo 2 k < 3 ( m[2, k] + m[k+1, 3] + p1 pk p3 ) , si i < j. = m[2,2] + m[3, 3] + p1 p2 p3 = 48. m[3, 4] = Mnimo 3 k < 4 ( m[3, k] + m[k+1, 4] + p2 pk p4 ) , si i < j. = m[3, 3] + m[4, 4] + p2 p3 p4 = 84. Anlogamente se van completando los elementos faltantes de la Tabla.

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

13

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

m[1, 3] = Mnimo 1 k < 3 ( m[1, k] + m[k+1, 3] + p0 pk p3 ) , si i < j. = Mnimo entre m[1, 1] + m[2, 3] + p0 p1 p3 y m[1, 2] + m[3, 3] + p0 p2 p3 = 88. m[2, 4] = Mnimo 2 k < 4 ( m[2, k] + m[k+1, 4] + p1 pk p4 ) , si i < j. = Mnimo entre m[2,3] + m[4,4] + p1 p3 p4 m[2, 2] + m[3, 4] + p1 p2 p4 y =104. Tal como lo muestran las Fig. 5 y 6 respectivamente.

Fig. 5

Fig. 6

Obteniendo finalmente la Fig. 7, la que termina de calcular m[1,4]. El cual se obtiene, a travs de los siguientes clculos: m[1, 4] = Mnimo 1 k < 4 ( m[1, k] + m[k+1, 4] + p0 pk p4 ) , si i < j. = Mnimo entre m[1,1] + m[2, 4] + p0 p1 p4 m[1,2] + m[3,4] + p0 p2 p4 m[1,3] + m[4,4] + p0 p3 p4 =158.

Fig. 7 ____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena


14

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

La idea es mantener un array s[1.. n, 1.. n], donde s[ i, j ] denota k para la particin optima en los clculos de Ai .. j = Ai .. k Ak+1.. j . El array s[1.. n, 1.. n], puede ser usado recursivamente para recubrir la sucesin de multiplicaciones. Cmo recubrir la sucesin de multiplicaciones.? s[1, n] corresponde (A1 .. As[1,n] ) (As[1,n]+1 .. An ) s[1, s[1, n]] corresponde (A1 .. As[1, s[1,n]] ) (As[1, s[1,n]] +1 .. As[1,n] ) s[s[1, n] + 1, n]..... y as sucesivamente. Se construye una solucin optimal a partir de la informacin calculada. El cdigo actual de multiplicacin usa el valor s[i, j] para determinar como particionar o escindir la sucesin actual. Asumamos que las matrices son almacenadas en un array de matrices A[1..n], y que s[i, j] es global para este procedimiento recursivo. El siguiente procedimiento recursivo entrega la cadena de un producto matricial A i .. j dada por las matrices A1 , A2 , A3 , A4 .. An , la tabla s computada por Cadena_Matriz(p, n) y los indices i, j. La llamada inicial a este procedimiento se hace a travs de Mult(A, s, 1, n).
Mult(A, s, i, j) if j > i then X= Mult(A, s, i, s[i, j] ) // X = A i .. k donde k = s[i, j] Y= Mult(A, s, s[i, j] + 1, j) // Y = A k+1 .. j return Mult_Matrices_Usual(X, Y) else return Ai

Veamos ahora cmo encontrar la sucesin de multiplicaciones optimal. Supongamos que las tablas m y s ya fueron computadas por el procedimiento Cadena_Matriz(p, n) para n = 6 y las siguientes dimensiones. A1 de dimensin 30 35, A2 de dimensin 35 15, A3 de dimensin 15 5, A4 de dimensin 5 10, A5 de dimensin 10 20 y A6 de dimensin 20 25. Tal como se muestra en Fig. 8.

Fig. 8 Consideremos n=6, y computemos A 1 .. 6 , asumiendo que el array computado es s[1..6,1..6] y de donde la sucesin de multiplicaciones corresponden a Mult(A, s, 1, 6 ) s[1, 6] = 3 y que corresponde a (A1 A 2 A3 ) (A4 A 5 A6 ) Mult(A, s, 1, 3 ) s[1, 3] = 1 (A1 (A 2 A3 )) Mult(A, s, 4, 6 ) s[4, 6] = 5 ((A4 A 5 )A6 ) Mult(A, s, 2, 3 ) s[2, 3] = 2 (A 2 )(A3) Mult(A, s, 4, 5 ) s[4, 5] = 4 (A 4 ) (A5) de aqu entonces que la sucesin de multiplicaciones final es (A1 ((A 2 ) ( A3 ))) (((A4 ) (A 5 ))A6 ) ____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena
15

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

Otra forma de representarlo es a travs de un Arbol de expresin aritmtica, tal como se visualiza a continuacin, en donde cada nodo se identifica con un subproblema y representa una matriz o bien una multiplicacin a efectuar. Cada invocacin de Mult(A, s, i, j), visita un nuevo nodo del rbol de expresin, que posee 2n 1 nodos, as que existen 2n 1 invocaciones, y Mult(A, s, i, j), tarda un tiempo (n). (1,6) * (1,3) * (2,3) A1 A2 Ejemplo Matriz dimension A1 10*20 A2 20*3 A3 3*5 A4 5*30 1 2 3 4 1 0 600 750 1950 2 0 300 2250 3 0 450 4 0 Construccin de la solucin optimal desde la informacin computada Ejemplo Matriz dimension A1 10*20 A2 20*3 A3 3*5 A4 5*30 r 0 1 2 3 4 pr 10 20 3 5 30 r 0 1 2 3 4 pr 10 20 3 5 30 * A3 A4 (4,5) * A5 * A6 (4,6)

1 2 3 4 1 A1 A1A2 (A1A2)A3 (A1A2)(A3A4) 2 A2 A2A3 A2(A3A4) 3 A3 A3A4 4 A4

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

16

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

Implementacin Archivo MultMatPD.java /* Esta es una solucin programacin dinmica para determinar el orden optimal de una sucesin de matrices. Los argumentos en la lnea de comando son p0, p1, p2,..., pn, donde las matrices que deseamos multiplicar y tienen dimensiones (p0 x p1), (p1 x p2),..., (p(n-1) x pn) y en este orden. Ejemplo de uso: >java MultMatPD 30 35 15 5 10 20 25 A1 (30 * 35) A2 (35 * 15) A3 (15 * 5) A4 (5 * 10) A5 (10 * 20) A6 (20 * 25) Izq_a_Der costo:40.500 Producto Minimo costo: 15.125 Parentizacin Optimal: ( (A1 )( (A 2 )(A3 )))(((A4 )(A 5 ))(A6)) */ public class MultMatPD { public static void main(String args[]) { int n; // numero de matrices int[] dim; // n-sima matriz (n>0)tiene dim[n-1] filas y dim[n] columnas int[][] opt_cost; // opt_cost[i,j] es el costo optimal de multiplicar // la i-sima con la j-sima matriz (inclusive) int[][] partic; // partic[i,j] = k cuando la multiplicacin final en un //producto optimal de Ai...Aj es (Ai...Ak)(A(k+1)...Aj) int length, partida_pos, fin_pos, cost, actual_min, izqder_costo, i; // crear arrays y leer las dimensiones de las matrices n = args.length - 1; dim = new int[n+1]; opt_cost = new int[n+1][n+1]; partic = new int[n+1][n+1]; //ingreso por teclado for (i = 0; i<= n; i++) dim[i] = Integer.parseInt(args[i]); // no existe costo para multiplicar una matriz single for (i=1; i <= n; i++) opt_cost[i][i] = 0; // computa los costos optimales para substrings de longitud 2,3,...,n for (length=2; length <= n; length++) { { for (partida_pos=1; partida_pos <= n - length + 1; partida_pos++) fin_pos = partida_pos + length - 1;

// chequear todas los posibles puntos de particiones en el substring for (i=partida_pos; i<fin_pos; i++) { cost = opt_cost[partida_pos][i] + opt_cost[i+1][fin_pos] + dim[partida_pos - 1] * dim[i] * dim[fin_pos]; actual_min = opt_cost[partida_pos][fin_pos];

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

17

Diseo y Anlisis de Algoritmos con Java


// // // //

Dr.Eric Jeltsch F.

if (actual_min == 0 || cost < actual_min) { ya que el costo es siempre estrictamente positivo, sabemos que si opt_cost[i,j]==0, no ha sido asignado por ahora. Asi, testeamos para cero en vez de necesitar un valor de "infinito". opt_cost[partida_pos][fin_pos] = cost; partic[partida_pos][fin_pos] = i; } } }

// print la lista de las matrices y sus dimensiones for (i= 1; i <= n; i++) System.out.println("A"+i+"\t"+"("+dim[i-1]+","+dim[i]+")"); // print los costos de calcular el producto // mult. los costos de izq_a_der p0.p1.p2 + p0.p2.p3 +...+ p0.p(n-1).pn izqder_costo = 0; for (i=1; i<n; i++) { izqder_costo += dim[i]*dim[i+1]; } izqder_costo *= dim[0]; System.out.println ("Izq_a_Der, costo : " + izqder_costo); System.out.println ("Minimo producto, costo: "+opt_cost[1][n]); //print en parentesis, el producto optimal System.out.print ("Parentizacion Optimal : "); printParentizar(1, n, partic); System.out.println();

private static void printParentizar(int partida_pos, int fin_pos, int[][] particion) { int partic_pos; if (partida_pos == fin_pos) System.out.print("A"+partida_pos); else { partic_pos = particion[partida_pos][fin_pos]; System.out.print("("); printParentizar(partida_pos, partic_pos, particion); System.out.print(")("); printParentizar(partic_pos + 1, fin_pos, particion); System.out.print(")"); } }

Aqui aparece con un MVC asociado.

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

18

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

String Matching (bsqueda en texto) Otro ejemplo de aplicacin de la Programacin Dinmica es String Matching. El problema string matching se formaliza como sigue. Consideremos que un texto es un array T[1..n] de longitud n y que los pattern o formas es un array P[1..m] de longitud m. Se supone que los elementos de P y T son caracteres extrados de un alfabeto finito. El array de caracteres de P y T son a menudos llamados string de caracteres. Algoritmos String Matching son frecuentemente utilizados para la bsqueda de formas particulares en las sucesiones de ADN. Ejemplo: El objetivo es encontrar todas las ocurrencias del pattern P = abaa en el texto T = abcabaabcabac. Tal como se ve en Fig. 1 el pattern ocurre solamente una vez en el texto. En este caso todo carcter del pattern esta conectado por una lnea vertical y ensombrecido para caracterizar los efectos del matching.

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

19

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

Una implementacin naive es la que se propone, pero muy simple de implementar, la cual se basa en encontrar todos los shifts vlidos usando un loop que va chequeando las condiciones P[1 ..m] = T[s+1..s+m] para toda de las n m + 1 posibles valores de s.
Naive-String-Matcher(T, P) n=long[T]; m= long[P] for s=0 to n-m do if P[1..m] = T[s+1..s+m] then print pattern ocurri en la posicin s.

Implementacin del algoritmo y ejecucin del ejemplo, considerando que el texto T= abcabaabcabac y el pattern P= abaa, entregando como resultado posicin=3
public class KMP_Naive { static int n = 1; private static int[] iniSig (String pattern) { int[] siguiente = new int [pattern.length ()]; int i = 0, j = -1; siguiente[0] = -1; while (i < pattern.length () - 1) { while (j >= 0 && pattern.charAt (i) != pattern.charAt (j)) j = siguiente[j]; i++; j++; siguiente[i] = j; } return siguiente; } public static int kmp_Busca(String texto, String pattern) { int[] siguiente = iniSig (pattern); int i = 0, j = 0; n = 1; while (i < texto.length ()) { while (j >= 0 && pattern.charAt (j) != texto.charAt (i)) { j = siguiente[j]; } i++; j++; if (j == pattern.length ()) return i - pattern.length (); } return -1; } public static void main (String[] args) { String p = "abaa";//pattern P int[] siguiente = iniSig(p); for (int i = 0; i < siguiente.length; i++) System.out.println ("siguiente[" + i + "] = " + siguiente[i]); System.out.println ("posicion = " + kmp_Busca("abcabaabcabac", p)); }

Ahora si el pattern P es relativamente grande y el alfabeto de donde se extraen los caracteres es razonablemente grande entonces el algoritmo de Boyer-Moore es ms eficiente para string matching. ____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena
20

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

Dentro de este contexto se encuentran una serie de problemas, entre ellos el hallar la subsecuencia comn ms larga entre 2 string, conocido como Longest Common Subsequence, en adelante (LCS). Este es otro ejemplo clsico de la tcnica de programacin dinmica. Antes de que definamos el problema general de la subsecuencia comn ms largo, empezamos con un pequeo ejemplo. Suponga que se da un string (como modelo) y un string ms largo (como texto). La idea es saber si los caracteres del modelo aparecen en orden (pero posiblemente separado) dentro del texto. Si ello ocurre, decimos que el modelo es una subsecuencia del texto, y si es la subsecuencia ms grande por ejemplo "nomi" es una subsecuencia de "conocimiento" como solucin al problema. Formalmente, digamos que una sucesin X = < x1 , x2 , ....., xm > y otra sucesin Z =< z1 , z2 , ....., zk > es una subsucesin de X si existe una sucesin de ndices estrictamente creciente i1 , i2 , ....., ik de ndices de k, tal que para todo j=1, 2, 3..., k, se tiene x i j = z j. Por ejemplo, Z=<B, C, D, B> es una subsucesin de X= <A, B, C, B, D, A, B>, con subndices <2, 3, 5, 7>. Dadas dos secuencias X e Y, se dice que una sucesin Z es una subsucesin comn de X e Y si Z es una subsucesin de ambas , es decir de X e Y. Por ejemplo, si X= <A, B, C, B, D, A, B> , Y= < B, D, C, A, B, A> entonces la sucesin <B, C, A> es una subsucesin comn de X e Y. Observar que <B, C, A> no es la ms larga de X e Y, pues <B, C, B, A> es tambin comn a ambas y tiene longitud 4. Notar que tampoco es nica, pues la sucesin <B, D, A, B> tambin lo es. Sin embargo, no existe LCS de longitud 5 o mayor. Por qu podramos querer nosotros resolver el problema de la subsecuencia comn ms larga? Hay varias aplicaciones que motivan este estudio. * En la biologa molecular. Sucesiones de ADN (genes) pueden representarse como sucesiones de cuatro elementos bsicos ACGT que se corresponden a las cuatro submoleculas que forman el ADN. Cuando los bilogos encuentran una nueva sucesin, ellos quieren saber qu otras sucesiones son similares a ella. De all que la computacin de las dos sucesiones similares pasan por encontrar la longitud de su subsecuencia comn ms largo. * Comparacin de archivos. En Unix-Linux comandos "diff" se usan para comparar dos versiones diferentes del mismo archivo, para determinar qu cambios se han hecho al archivo. Funciona encontrando una subsecuencia comn ms larga de las lneas de los dos archivos; en donde cualquier lnea en la subsecuencia no se ha cambiado. En este caso debemos pensar que cada lnea de un archivo es un carcter dentro de un string. Un approach fuerza bruta para resolver LCS es enumerar todas las subsecuencias de X y chequearlas todas ellas para ver si es tambin subsecuencia de Y, hasta encontrar la subsecuencia ms larga. Estas observaciones nos dan un algoritmo recursivo altamente ineficiente, pues recordar que toda subsecuencia de X corresponde a un subconjunto de los ndices {1, 2, 3,..., m} de X, existiendo 2m subsucesiones de X, de manera que el tiempo es exponencial, hacindolo impracticable para sucesiones ms largas. Basado en Teorema 16.1 pg. 315 (CLR) podemos admitir una subestructura optimal de un LCS, y en este contexto al igual que en el problema de Parentizacin de Matrices se establece una recurrencia para el costo de una solucin optimal. Se define c[i,j] como la longitud de un LCS de secuencias Xi , Yj. Si i=0 o j=0 una de las sucesiones tiene longitud 0, as LCS tiene longitud 0. La subestructura optimal de LCS est dada por la formula recursiva:

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

21

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

0 i=0 o j=0 c[i, j ] = c[i 1, j 1] + 1 i, j > 0 y xi = yi max(c[i, j 1], c[i 1, j ]) i, j > 0 y x y i i Basada en estas ecuaciones podramos escribir un procedimiento recursivo para computar la longitud de un LCS de 2 secuencia, pues existen solamente O(mn problemas distintos.
int lcs_long(char * A, char * B) { if (*A == ' \0 ' || *B == ' \0 ') return 0; else if (*A == *B) return 1 + lcs_long(A+1, B+1); else return max(lcs_long(A+1,B), lcs_long(A,B+1)); }

Sin embargo, el objetivo es usar Programacin Dinmica. El procedimiento considera 2 sucesiones X = < x1 , x2 , ....., xm > e Y =< y1 , y2 , ....., yn > como entrada, se procede a almacenar los valores c[i,j], en una tabla c[0..m, 0..n] cuyas entradas son computadas primera fila de c es llenada de izq. a der., luego la segunda fila, y as sucesivamente. Al mismo tiempo se mantiene una tabla b[1..m, 1..n] para simplificar la construccin de una solucin optimal.
LCS-Long(X, Y) m= long[X] n= long[Y] for i=1 to m do c[i,0]=0 for j=1 to n do c[0, j]=0 for i=1 to m do for j=1 to n do if xi = yj then c[i, j]= c[i -1,j -1] + 1 b[i, j] = if else c[i -1, j ] c[i , j -1 ] then c[i, j]= c[i -1,j ] b[i, j] = else c[i, j]= c[i,j - 1] b[i, j] = return c y b.

La fig. 2 muestra la tabla que se genera por el procedimiento anterior sobre la secuencia X= <A, B, C, B, D, A, B> e Y= < B, D, C, A, B, A>. El tiempo de ejecucin del procedimiento es claramente O(mn).

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

22

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

El siguiente procedimiento recursivo imprime la salida de LCS de X e Y. La llamada inicial es printLCS(b, X, long[X], long[Y]).
printLCS(b, X, i, j). if i=0 o j=0 then return if b[i, j] = then printLCS(b, X, i -1, j -1) print xi else if b[i, j] = then printLCS(b, X, i -1, j ) else printLCS(b, X, i , j 1 )

Para la tabla b en Fig. 2 este procedimiento imprimeBCBA. Implementacin /*


Hallar la Longest Common Subsequence, LCS de 2 secuencias como entrada, usando Programacin Dinmica. Considerar que el programa lee los argumentos de la lnea de comando. El primer argumento es la cadena X de caracteres o string, en este caso se consideran solo enteros(int) mientras que el segundo argumento es la cadena Y de caracteres o string, tambin enteros(int). la linea de comando es: x1 x2 ... xm . y1 y2 ... yn Para notar el termino de una cadena con respecto al inicio de la otra se considera un punto "." como separador. Ejemplo de uso >java LCS 1 0 0 1 0 1 0 1 . 0 1 0 1 1 0 1 1 0 1 0 0 1 0 1 0 1 0 1 0 1 1 0 1 1 0 LCS tiene longitud: 6 1 0 0 1 1 0 */ public class LCS { public static void main(String[] args) { int[] x, y; // las 2 secuencia int n, m; // x's van de x1 a xn; y's van de y1 a ym int[][] longit; // longit[i][j] es la longitud de la subsucesion //comun mas grande de la long-i subsucesion de los x's, y la long-j //subsucesion de los y's. int[][] reconstruir;//es la variable "b" , //util para simplificar la construccion de una solucion optimal. int i, j, k, xs, ys; // lee la linea de comando con los argumentos k = args.length; i=0; while (! args[i].equals(".")) i++; n = i; m = k - i-1; x = new int[n+1]; // parten los indices en 1 y = new int[m+1];

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

23

Diseo y Anlisis de Algoritmos con Java


for (j=0; j<i; j++) x[j+1] = Integer.parseInt(args[j]); for (j=0; j<k-i-1; j++) y[j+1] = Integer.parseInt(args[i+1+j]); longit = new int[n+1][m+1]; reconstruir = new int[n+1][m+1]; System.out.println(); for (j=1; j <= n; j++) System.out.print(x[j]+" "); System.out.println(); for (j=1; j<= m; j++) System.out.print(y[j]+" "); System.out.println(); //Si uno del par de sucesiones //largo de su LCS es tambin 0. for (xs = 1; xs <= n; xs++) longit[xs][0] = 0; for (ys = 1; ys <= m; ys++) longit[0][ys] = 0; tiene longitud

Dr.Eric Jeltsch F.

0,

entonces

el

// Ahora calculamos LCS que sucesivamente compara prefijos de x // con prefijos de y for (xs = 1; xs <= n; xs++) { for (ys = 1; ys <= m; ys++) { if (x[xs] == y[ys]) { longit[xs][ys] = longit[xs - 1][ys - 1] + 1; reconstruir[xs][ys] = 3; // "3" es la flecha diagonal. //Indica los elementos que han sido seleccionados // (vea CLR pag. 317) } else if (longit[xs - 1][ys] >= longit[xs][ys-1]) { longit[xs][ys] = longit[xs-1][ys]; reconstruir[xs][ys] = 1; // "1" es la flecha horizontal } else { longit[xs][ys] = longit[xs][ys-1]; reconstruir[xs][ys] = 2; // "2" es la flecha vertical }

System.out.println("LCS tiene longitud: " + longit[n][m]); printLCS(reconstruir, x, n, m); System.out.println(); } private static void printLCS(int[][] reconstruir, int[] x, int n, int m) { if (n==0 || m==0) { System.out.println(); return; } else if (reconstruir[n][m] == 3) {

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

24

Diseo y Anlisis de Algoritmos con Java


} else if (reconstruir[n][m] == 2) printLCS(reconstruir, x, n, m-1); else printLCS(reconstruir, x, n-1, m); printLCS(reconstruir, x, n-1, m-1); System.out.print(" " + x[n]);

Dr.Eric Jeltsch F.

Aqui aparece con un MVC asociado.

Bajo un esquema similar es posible presentar la siguiente aplicacin: Dada una sucesin de enteros X = < x1 , x2 , ....., xm >, los cuales no contienen elementos duplicados, entonces es posible disear un algoritmo para encontrar la subsucesin creciente ms grande xi1 < xi2 < .... < xik, donde i1 < i2 < .... < ik que puede encontrarse en X. Tambin conocido como Longest increasing subsequence. (LIS). Por ejemplo, la subsucesin ms grande de {3, 1, 4, 5, 9, 2, 6, 0, 7, 8, 12, 10} es {3, 4, 5, 6, 7, 8, 12} y otra es {1, 4, 5, 6, 7, 8, 10}. Implementacin:
/* Observacin: Esta es una solucion para encontrar una ( de varias posibles) subsucesin creciente ms larga que esta insertada en una sucesin de simbolos. Ejemplo de uso: $ java LIS 3 5 6 8 9 12 23 4

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

25

Diseo y Anlisis de Algoritmos con Java


3 5 6 8 9 12 23 4 ^ ^ ^ ^ ^ ^ ^ Una LIS tiene longitud 7.

Dr.Eric Jeltsch F.

*/ public class LIS {

public static void main(String[] args) { int n; // la longitud de la suc de entrada int[] x; // la sucesion de entrada int[] lis; // lis[i] es la longitud de la LIS(longest increasing //subsequence), cuyo miembro final es x[i]. int[] previo; /* si x[i] pertenece a una LIS determinada por el algoritmo, previo[i] es el indice de los predecesor x[i]'s en la LIS. El miembro inicial de la LIS tiene previo[i] = n. */ boolean[] en_LIS; // en_LIS[i] es true ssi x[i] esta en la LIS. int i, j, k, lis_valor, lis_max, k_max, i_max, fin_de_longest; // inicializar variables y leer la sucesin de entrada, desde la lnea //de comando. n = args.length; x = new int[n]; lis = new int[n]; previo = new int[n]; en_LIS = new boolean[n]; for (i=0; i < n; i++) x[i] = Integer.parseInt(args[i]); // hallar una LIS, sin contar la existencia del total de ellas. lis[0] = 1; previo[0] = n; i_max = 0; fin_de_longest = 0; for (i=1; i<n; i++) { lis_max = 0; k_max = 0; for (k=0; k < i; k++) { lis_valor = (x[k] < x[i] ? 1 : 0) * lis[k]; if (lis_valor > lis_max) { lis_max = lis_valor; k_max = k; } } lis[i] = lis_max + 1; if (lis_max == 0) previo[i] = n; else previo[i] = k_max; if (lis[i] > i_max) { i_max = lis[i]; fin_de_longest = i; }

//marca todos los elementos que estan en la LIS j = fin_de_longest; en_LIS[j] = true; while (previo[j] != n) {

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

26

Diseo y Anlisis de Algoritmos con Java


} j = previo[j]; en_LIS[j] = true;

Dr.Eric Jeltsch F.

//print la sucesin de entrada System.out.println(); for (i=0; i<n; i++) System.out.print(x[i] + " "); System.out.println(); //marca los elementos que estan en la LIS for (i=0; i<n; i++) { if (en_LIS[i]) System.out.print("^" + rellenar(x[i]) + " "); else System.out.print(" " + rellenar(x[i]) + " "); } System.out.println(); System.out.println("Una LIS tiene longitud" + lis[fin_de_longest]); } //return el nmero de digitos en n private static int digitos(int n) { int m = n / 10; if (m == 0) return 1; else return( 1 + digitos(m)); } /*return un string de whitespace, de longitud una menor que el nmero de dgitos en el argumenmto de entrada.*/ private static String rellenar(int n) { int d = digitos(n) - 1; StringBuffer rellenar = new StringBuffer(); for (int i = 0; i < d; i++) rellenar.append(" "); return (rellenar.toString());

} }

Arboles de Bsqueda Binario Optimal El objetivo del ABBO es minimizar el tiempo medio de bsqueda, dadas las frecuencias de acceso de cada uno de los datos. Una estrategia en los arboles binarios de bsqueda seria colocar los elementos que se acceden con mas frecuencia cerca de la raz del rbol. Adems en estos tipos de arboles siempre se debe respetar la condicin de ABB (sub- rbol izquierdo < raz < sub- rbol derecho). Una estrategia para lograr este objetivo es la Programacin Dinmica. La tcnica que esta detrs del llenado de la matriz obedece al clculo de los costos de los posibles sub -rboles optimales dentro de un rango especificado por la ecuacin: R C(L,R) = min { C( L , i 1 ) + C( i + 1 , R ) + Pj }
L iR j=L

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

27

Diseo y Anlisis de Algoritmos con Java Ejemplo

Dr.Eric Jeltsch F.

Dada la siguiente tabla de palabras junto con sus frecuencias de acceso, se muestran algunos de los clculos correspondientes a los valores que sern almacenados en las matrices de costos C[i,j] y de races R[i,j].
i 1 2 3 4 5 Ki A Bien Con De El Pi 20 10 30 15 25

C( 1,2 ) = min { ( C(1,0) + C(2,2) + P1+ P2 ), C(1,1) +C(3,2) +P1 +P2 } i =1 i =2 C(1,2 ) = min { 40 , 50 } = > C(1,2) = 40 ; R(1,2) =1
Cij 1 2 3 4 5 1 20 0 0 0 0 2 40 10 0 0 0 3 30 0 0 4 5 Rij 1 2 3 4 5 1 1 0 0 0 0 2 1 2 0 0 0 3 3 0 0 4 5

15 0

25

4 0

C( 2,3 ) = min { ( C(2,1) + C(3,3) + P2+ P3 ), C(2,2) +C(4,3) +P2 +P3 } i =2 i =3 C(2,3 ) = min { 70 , 50 } = > C(2,3) = 50 ; R(2,3) =3
Cij 1 2 3 4 5 1 20 0 0 0 0 2 40 10 0 0 0 3 50 30 0 0 4 5 Rij 1 2 3 4 5 1 1 0 0 0 0 2 1 2 0 0 0 3 3 3 0 0 4 5

15 0

25

4 0

C( 3,4 ) = min { ( C(3,1) + C(3,3) + P3+ P4 ), C(3,2) +C(4,3) +P3 +P4} i =3 i =4 C(3,4 ) = min { 60 , 75 } = > C(3,4) = 60 ; R(3,4) =3 Cij 1 2 3 4 5 1 20 0 0 0 0 2 40 10 0 0 0 3 50 30 0 0 4 60 15 0 5 Rij 1 2 3 4 5 1 1 0 0 0 0 2 1 2 0 0 0 3 3 3 0 0 4 3 4 0 5

25

C(4,5 ) = min { 65 , 55 } = > C(4,5) = 55 ; R(4,5) =5


Cij 1 1 20 2 40 3 4 5 Rij 1 1 1 2 1 3 4 5 28

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

Diseo y Anlisis de Algoritmos con Java


2 3 4 5 0 0 0 0 10 0 0 0 50 30 0 0 60 15 0 2 3 4 5 0 0 0 0 2 0 0 0 3 3 0 0 3 4 0

Dr.Eric Jeltsch F.

55 25

5 5

C(1,3 ) = min { 110 , 110 , 100 } = > C(1,3) = 100 ; R(1,3) = 3


Cij 1 2 3 4 5 1 20 0 0 0 0 2 40 10 0 0 0 3 100 50 30 0 0 4 60 15 0 5 Rij 1 2 3 4 5 1 1 0 0 0 0 2 1 2 0 0 0 3 3 3 3 0 0 4 3 4 0 5

55 25

5 5

C(2,4 ) = min { 115 , 80 , 105 } = > C(2,4) = 80 ; R(2,4) = 3


Cij 1 2 3 4 5 1 20 0 0 0 0 2 40 10 0 0 0 3 100 50 30 0 0 4 80 60 15 0 5 Rij 1 2 3 4 5 1 1 0 0 0 0 2 1 2 0 0 0 3 3 3 3 0 0 4 3 3 4 0 5

55 25

5 5

C(3,5 ) = min { 125 , 125 , 130 } = > C(3,5) = 125 ; R(3,5) = 4 En caso de que dos costos se repitan y sean mnimos, se escoge aquel que esta asociado al subndice i mayor.
Cij 1 2 3 4 5 1 20 0 0 0 0 2 40 10 0 0 0 3 100 50 30 0 0 4 80 60 15 0 5 125 55 25 Rij 1 2 3 4 5 1 1 0 0 0 0 2 1 2 0 0 0 3 3 3 3 0 0 4 3 3 4 0 5 4 5 5

C(1,4 ) = min { 155 , 155 , 130 , 175 } = > C(1,4) = 130 ; R(1,4) = 3
Cij 1 2 3 4 5 1 20 0 0 0 0 2 40 10 0 0 0 3 100 50 30 0 0 4 130 80 60 15 0 5 125 55 25 Rij 1 2 3 4 5 1 1 0 0 0 0 2 1 2 0 0 0 3 3 3 3 0 0 4 3 3 3 4 0 5 4 5 5

C(2,5 ) = min { 205 , 145 , 155 , 160 } = > C(2,5) = 145 ; R(2,5) = 3
Cij 1 2 3 4 5 1 20 0 0 0 0 2 40 10 0 0 0 3 100 50 30 0 0 4 130 80 60 15 0 5 145 125 55 25 Rij 1 2 3 4 5 1 1 0 0 0 0 2 1 2 0 0 0 3 3 3 3 0 0 4 3 3 3 4 0 5 3 4 5 5

C(1,5) = min {245, 245, 195, 225, 230} = > C(1,5) = 195; R(1,5) = 3

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

29

Diseo y Anlisis de Algoritmos con Java


Cij 1 2 3 4 5 1 20 0 0 0 0 2 40 10 0 0 0 3 100 50 30 0 0 4 130 80 60 15 0 5 195 145 125 55 25 Rij 1 2 3 4 5 1 1 0 0 0 0 2 1 2 0 0 0 3 3 3 3 0 0 4 3 3 3 4 0 5 3 3 4 5 5

Dr.Eric Jeltsch F.

La construccin del rbol es un proceso recursivo, el cual consiste en tomar los sub ndices del primer arreglo y localizarlos en la matriz de races. Se asigna el elemento R[1,n] de esa matriz y pasa a ser la raz del ABBO subdividiendo el arreglo en una mitad que representa al sub rbol izquierdo y otra al sub rbol derecho, repitindose este proceso hasta encontrar una matriz de dimensin 1x1, que representara una hoja en el rbol final.
Rij 1 2 3 4 5 A 1 Rij 1 2 3 4 5 1 1 0 0 0 0 Bien 2 1 1 0 0 0 0 2 1 2 0 0 0 Con 3 2 1 2 0 0 0 3 3 3 3 0 0 De 4 3 3 3 3 0 0 4 3 3 3 4 0 El 5 4 3 3 3 4 0 5 3 3 4 5 5 A 5 3 3 4 5 5

Con

A 1

Bien 2

De 4

El 5

Con

El

Bien 2

De 4

Las sub-matrices restantes son de 1x 1 entonces se localizan en el rbol cumpliendo la condicin de ABB, quedando as el siguiente ABBO.

Con Rij 1 2 3 4 5 1 1 0 0 0 0 2 1 2 0 0 0 3 3 3 3 0 0 4 3 3 3 4 0 5 3 3 4 5 5

El

Bien

De

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

30

Diseo y Anlisis de Algoritmos con Java d) Problemas de optimizacin y algoritmos avaros (greedy)

Dr.Eric Jeltsch F.

En esta seccin estudiaremos varios problemas de optimizacin que se pueden resolver empleando algoritmo codiciosos o estrategia Greedy. Es comn que en los problemas de optimizacin el algoritmo tenga que tomar una serie de decisiones cuyo efecto general es reducir al mnimo el costo total, o aumentar al mximo el beneficio total, de algn sistema. El mtodo codicioso consiste en tomar sucesivamente, las decisiones de modo que cada decisin individual sea la mejor de acuerdo con algn criterio limitado "a corto plazo" cuya evaluacin no sea demasiado costosa. Una vez tomada una decisin, no se podr revertir, ni siquiera si ms adelante se hace obvio que no fue una buena decisin. Por esta razn, los mtodos codiciosos no necesariamente hallan la solucin ptima exacta de muchos problemas. No obstante, en el caso de los problemas que se estudiaran en este captulo, es posible demostrar que la estrategia codiciosa apropiada produce soluciones ptimas. Tambin se abordaran problemas con los que estrategias codiciosas muy similares fracasan. En general la estrategia Greedy trabaja en fases. En cada fase, se realiza una decisin que aparentemente es la mejor, sin considerar o lamentar futuras consecuencias. Generalmente, esta idea es pensada como escoger algn ptimo local. Si se resume la estrategia como "toma lo que puedas obtener ahora" podr entender el porque del nombre de esta clase de algoritmos. Cuando el algoritmo termina tendremos la esperanza que el ptimo local es igual al ptimo global. Si este es el caso, entonces el algoritmo es correcto, de otra manera el algoritmo a generado una solucin suboptimal. Si la "mejor" respuesta no es requerida entonces los algoritmos greedy son a menudo usados para generar respuestas aproximadas, en vez de usar algoritmos ms complicados que requieren de otra estrategia para generar "la" respuesta correcta. En general la estrategia Greedy trabaja en etapas "Top-Down" realizando una eleccin greedy despus de la otra. En resumen, la estrategia Greedy consiste de dos partes: 1. Subestructura Optimal 2. Partir con eleccin de ptimos locales y continuar haciendo elecciones localmente optimales, hasta que la solucin es encontrada. Ejemplos: Problema Arbol Expandido Minimal ( Minimum Spanning Trees) Dado un grafo no-dirigido y conexo G = <V,E>, donde cada arco tiene asociado una "longitud" o "peso", se desea un subconjunto T, de arcos E, tal que el grafo restante sea conexo si solamente los arcos en T son usados, y la suma de los pesos de los arcos es el ms pequeo posible. Tal subconjunto debe formar un subgrafo que es un rbol, al cual se le llama Minimum Spanning Tree. Otro problema: En el proceso de cambio de monedas, por ejemplo $15.530 puede expresarse en $15.530 = 1 * $10.000 + 1 * $ 5.000 + 1 * $ 500 + 3 * ($10). En total se requieren 5 elementos. Al hacer esto estamos seguro de que el nmero de billetes y monedas es el mnimo. Los problemas de trfico son tpicos casos en que las elecciones ptimas locales no siempre funcionan. Si duda, pregunte en Santiago sobre el resultado de las vas exclusivas que luego pasaron a ser vas ..y ahora el transantiago. (Problema de Planificacin) Dado los trabajos t 1, t 2, ... t n, con tiempos t1, t2, ... tn y un solo procesador. Cul es la mejor forma de planificar esos trabajos a fin de minimizar el tiempo medio de terminacin?

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

31

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

Compresin de archivos. (Cdigo de Huffman) Un cdigo de longitud fija de un alfabeto A= { a1 , a2 , ..., am } con m-letras es una expresin en donde cada letra ai = ci,1 ci,2 ...ci,n , en donde i= 1, ...m, y donde cada ci,j fes un elemento de algn conjunto de smbolos. El conjunto C= { ci,1 ci,2 ...ci,n , tal que, i= 1, ...m,} es llamado cdigo de longitud fija de longitud n, y cada ci,1 ci,2 ...ci,,n es llamado cdigo. Ejemplo: Sea A= { a, b, c, d }, entonces { a= 00, b= 01, c=10, d=11} es un cdigo de longitud fija para A y { 00, 01, 10, 11} es el cdigo binario de longitud fija de tamao n, donde los smbolos del conjunto es {0, 1]. Ejemplo: Cdigo ASCII es un cdigo binario de longitud fija de longitud 8. Toda letra o carcter es codificado con string de 8-bit. Por otra parte, un cdigo de longitud variable de un alfabeto A= { a1,a2 , ..., am } con m-letras es una expresin en donde cada letra ai = ci,1 ci,2 ...ci, li , en donde i= 1, ...m, y donde cada ci,j fes un elemento de algn conjunto de smbolos. El conjunto C= { ci,1 ci,2 ...ci,,li , tal que, i= 1, ...m,} es llamado cdigo de longitud variable, y cada ci,1 ci,2 ...ci, li es llamado cdigo. Ejemplo: Sea A= { a, b, c, d }, entonces { a= 0, b= 110, c=10, d=111} es un cdigo de longitud variable para A y { 0, 110, 10, 111} es el cdigo binario de longitud variable, donde los smbolos del conjunto son de {0, 1]. Se dice, que un cdigo es prefijo si ningn cdigo es un prefijo de algn otro. Ejemplo: Sea A= { a, b, c, d }, entonces { a= 0, b= 110, c=10, d=111} es un cdigo prefijo, pues al recibir el mensaje 1100111 tiene una nica decodificacin bad. Por otra parte, si { a= 1, b= 110, c=10, d=111}, entonces { 1,110, 10, 111}, no es un cdigo prefijo, pues al recibir el mensaje 110 podra ser decodificado en b o ac. El problema es, suponga que tenemos un archivo de dato con 100.000-caracteres que se desea compactificar, observamos que los caracteres que ocurren con sus respectivas frecuencias son: 6 caracteres, a, b, c, d, e y f con frecuencias 45, 13, 12, 16, 9 y 5, respectivamente (en miles). Cul es el cdigo de longitud fija y variable que usa el menor espacio?. Una propuesta es: Caracteres a Frecuencia 45 Cdigo long. Fija 000 Cdigo long. Var. 0 b 13 001 101 c 12 010 100 d e 16 9 011 100 111 1101 f 5 101 1100

Para almacenar 100 de estos caracteres, cdigo de long. Fija requiere 100*3 = 300 bits, mientras que cdigo de long. variable requiere 45*1+13*3+12*3+16*3+9*4+5*4=224, ahorrando casi un 25%, de manera que podemos en principio afirmar que cdigos de longitud variable son mejores. Dado un alfabeto A con frecuencia de distribucin { f(a), a A}, el cdigo Huffman construye un rbol binario T, donde los nodos consisten de algunas letras de A y algunas nuevas letras junto con su frecuencia, las hojas consisten de solamente letras de A, todo nodo tiene 2 hijos, hijo izquierdo, rotulado con 0 e hijo derecho rotulado con1. El cdigo para cada letra es la sucesin de bits rotulados sobre el nico camino desde la raz a la hoja. ____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena
32

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

Etapa1: Tomar 2 letras con la frecuencia ms pequea de ocurrencia y crear un subrbol que tiene estos 2 caracteres como hojas. Etapa2: Ahora imaginemos que este subrbol representa un nuevo carcter cuya frecuencia es igual a la suma de las frecuencias de las 2 letras, de manera que se obtiene un nuevo alfabeto con una letra menos. Se repite este proceso, llamada merge, con el nuevo alfabeto hasta que el alfabeto tiene una letra. Ejemplo: Sea A= {f/5, e/9, c/12, b/13, d/16, a/45}, el alfabeto con sus frecuencias de distribucin. El cdigo se obtiene como sigue:
c/12 b/13 n1/14 d/16 a/45

0
f/5

1
e/9

El nuevo alfabeto es A1 = {c/12, b/13, n1/14, d/16, a/45},

n1/14

d/16

n2/25

a/45

0
f/5

1
e/9

0
c/12

1
b/13

El nuevo alfabeto es A2 = {n1/14, d/16, n2/25, a/45},


n2/25 n3/30 a/45

0
c/12

1
b/13

0
n1/14

1
d/16

0
f/5

1
e/9

El nuevo alfabeto es A 3 = {n2/25, n3/30, a/45}, continuando se obtiene,


a/45 n4/55

0
n2/25

1
n3/30

0
c/12

1
b/13

0
n1/14

1
d/16

0
f/5

1
e/9 33

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

Diseo y Anlisis de Algoritmos con Java El nuevo alfabeto es A4 = {n4/55, a/45}, y finalmente se tiene
n5/100

Dr.Eric Jeltsch F.

1
n4/55

a/45

0
n2/25

1
n3/30

0
c/12

1
b/13

0
n1/14

1
d/16

0
f/5

1
e/9

El nuevo alfabeto es A5 = {n5/100}. El cdigo Huffman es obtenido desde el rbol binario rotulado. En efecto, a=0, b=101, c= 100, d=111 , e=1101, f= 1100. Algoritmo: Dado un alfabeto A con distribucin de frecuencia { f(a), a A}, una prioridad Q, claves sobre f, que es usada para identificar las 2 letras con menor frecuencia. Se tiene,
Huffman(A) { n=|A|; Q=A; for i=1 to n-1 { z=localizar() x= Izq[z] = Extraer_Min(Q); y= Der[z] = Extraer_Min(Q); f[z]= f[x] + f[y]; Insertar(Q, z); } return Extraer_Min(Q)//raz del rbol binario

El tiempo de ejecucin es O(nlogn), ya que cada operacin prioridad toma tiempo O(logn). Este algoritmo es para construir el rbol de Huffman, y luego a partir de l construir el cdigo de Huffman. Se demuestra que el algoritmo Huffman genera un rbol cdigo prefijo optimal. Desde el punto de vista de su implementacin el cdigo Huffman usa "priority_queue" , que es una aplicacin usual de los Heaps, la cola de prioridad es una estructura de datos con slo 2 operaciones, Insertar un elemento y eliminar el elemento que tenga la clave ms grande (o pequea). Si los elementos tienen claves iguales, la regla habitual establece que el primer elemento introducido deber ser el primero en elimninarse. Por ejemplo, en un sistema de computo de tiempo compartido, un extenso nmero de tareas pude estar esperando para hacer uso de la CPU. En donde algunas de ellas tendrn mayor prioridad que otras, de ah que el conjunto de las que esperan forme una cola de prioridad. Propiedades: El elemento que posea la clave mayor est en la cima, y puede extraerse en tiempo constante. Sin embargo, tardar tiempo O(log(n)) en restablecer la propiedad de heap ____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena
34

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

para el resto de las claves. Ahora si otro elemento ha de insertarse de inmediato, parte de ese tiempo puede combinarse con el tiempo O(log(n)) necesario para insertar el nuevo elemento. De esta manera, la representacin de una cola de prioridades como heap resulta ventajoso par un n-grande, ya que se representa eficientemente en el almacenamiento contiguo y se garantiza que slo requerir tiempo algortmico para las inserciones y las eliminaciones. Como ya se vi el objetivo es buscar un codigo variable binario apropiado, cuya frecuencia de los datos de entrada sea considerada. El algoritmo de Huffman realiza la codificacin binaria con ayuda de los rboles binarios, que se v de la siguiente manera: Cada nodo tiene un peso, cuyo lugar en el rbol de Codificacin de Huffman esta predeterminado, lo que se logra a travs de "priority_queue" , en donde segn la frecuencia de los smbolos se van disponiendo. Luego, son los nodos de ms baja frecuencia eliminados, para ser reemplazados por un nodo interno que contiene la suma de los nodos internos eliminados. El nuevo nodo es vuelto a poner en la priority_queue , este proceso se repite hasta que solamente quede un solo nodo en la la priority_queue como raz del nuevo rbol. Los rtulos de los arcos en el rbol binario desde la izquierda de su nodo padre se rotula con 0, arcos a la derecha con 1. El rtulo de sus hojas es consecuencia de los rotulos de sus arcos sobre el camino desde la raz a la hoja (como se puede ver 0000, 001, 1001, etc). Implementacin /*
Observacin: Esta es una greedy solucion para minimizar el largo del camino externo ponderado de un arbol. Como ejemplo se muestra la implementacin de compresin de huffman. Esta solucin usa arboles y priority queue. Ejemplo de uso: $ java Cod_Huffman d=111 e=1101 f=1100 b=101 c=100 a=0 */ public class Cod_Huffman { a 45 b 13 c 12 d 16 e 9 f 5

public static void main (String[] arg) { if (arg.length == 0) { System.out.println ( "Use: java Cod_Huffman simbolo frecuencia [simbolo peso ...]"); return; } //formateando la entrada de datos int tamao = arg.length / 2; // numero de items PQ queue = new PQ(); int peso; char simbolo; Arbol_Peso hoja; // incorporando el peso en la priority queue for (int item=0; item < tamao; item++) { peso = Integer.parseInt(arg[item*2 + 1]); simbolo = arg[item*2].charAt(0);

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

35

Diseo y Anlisis de Algoritmos con Java


} hoja = new Arbol_Peso (peso,simbolo); queue.insertar(hoja);

Dr.Eric Jeltsch F.

// combina repetidamente los pesos ms pequeos de los rboles, // el rotulo "1" esta a la derecha Arbol_Peso chico; Arbol_Peso sig_chico; Arbol_Peso combina; while (! queue.isSingleton()) { chico = queue.getMin(); sig_chico = queue.getMin(); combina = new Arbol_Peso(chico, sig_chico); queue.insertar(combina); } // print los resultados printTabla (queue.getMin(),"");

public static void printTabla (Arbol_Peso arbol, String prefijo) { if (arbol.izq == null) { // una hoja System.out.println(arbol.simbolo()+"="+prefijo); } else { printTabla (arbol.der,prefijo+"1"); printTabla (arbol.izq, prefijo+"0"); } }

//------------------------------------------------------------// Arboles ponderados para representar cdigo de huffman // inicializacin a travs del constructor Arbol_Peso() class Arbol_Peso { private int peso; private char caracter; public Arbol_Peso izq; public Arbol_Peso der; public Arbol_Peso (int peso, char caracter) { this.peso = peso; this.caracter = caracter; this.izq = null; this.der = null; } public Arbol_Peso(Arbol_Peso izq, Arbol_Peso der) { this.izq = izq; this.der = der; this.peso = izq.peso + der.peso; } public int valor () { return this.peso; } public char simbolo () { return this.caracter; }

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

36

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

//---------------------------------------------------------------// Una Priority Queue (PQ)(para Arbol_Peso) // usa un loop en vez de una llamada recursiva para mayor rapidez. class PQ { private PQnodo primer=null; public boolean isSingleton() { return (primer.sig == null); } public Arbol_Peso getMin () { Arbol_Peso minArbol = primer.arbol; primer = primer.sig; return (minArbol); } public void insertar(Arbol_Peso newArbol) { primer = insertNodo(newArbol, primer); } private PQnodo insertNodo (Arbol_Peso newArbol, PQnodo nodo) { if ((nodo==null) || (newArbol.valor()<nodo.arbol.valor())) return (new PQnodo (newArbol, nodo)); else { nodo.sig = insertNodo (newArbol, nodo.sig); return (nodo); } }

class PQnodo { public Arbol_Peso arbol; public PQnodo sig; public PQnodo (Arbol_Peso arbol, PQnodo sig) { this.arbol = arbol; this.sig = sig; }

Aqui aparece con un MVC asociado.

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

37

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

Problema de la mochila Dado n-objetos y una mochila de capacidad M, en donde el objeto i tiene un peso wi, Se deja un porcentaje xi, (0 xi, 1) del objeto i en la mochila, para obtener una ganancia o profit pi xi. El objetivo es entonces completar la carga de la mochila con la mxima ganancia o profit que pueda soportar, y que no exceda la capacidad M. Formalmente se desea maximizar (1) p i x i , con la condicin (2) w i x i M, y i =1 i =1 (0 x i 1), p i > 0, w i > 0, e (0 i n). De manera que cada conjunto de partes (x1, x2, x3,.... xn) que satisfagan las condiciones (2) y (3) es una posible solucin. Una solucin optimal es una solucin posible, para lo cual la condicin (1) es un mximo. Existe otra versin del problema llamada versin 0-1 del problema de la mochila, pues no existen fracciones de las especies, es decir o se toma o se deja. Sin embargo, se puede demostrar que tienen subestructuras optimal, esto es que si eliminamos j- item de las especies optimales, el resto debe seguir siendo optimal para W - w j. Por ejemplo, para el caso en que n=3 (nmero de objetos), M=20 capacidad de la Mochila, y los valores (p1 , p2 , p3) = (25, 24, 15) y (w1 , w2 , w3) = (18, 15, 10), se obtienen 4-posibles soluciones 38 ____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena
n n

Diseo y Anlisis de Algoritmos con Java (x1 , x2 , x3) (i) (ii) (iii) (iv) (1/2, 1/3, ) (1, 2/15, 0) (0, 2/3, 1) (0, 1, ) suma (w i xi ) 16.5 20 20 20 suma(pi xi ) 24.25 28.2 31 31.5

Dr.Eric Jeltsch F.

es claro que de estas 4-soluciones la (iv) alcanza su mximo profit. Tras este algoritmo se muestra un problema de Optimizacin, en el sentido de hallar la utilidad total ms grande que cualquier subconjunto de los objetos que quepa en la mochila(y hallar un subconjunto que produzca la utilidad mxima). As como un problema de Decisin, pues dado k, existe un subconjunto de los objetos que quepa en la mochila y tenga una utilidad total de por lo menos k?. El problema de la mochila tiene diversas aplicaciones en planificacin econmica y en problemas de carga o empaque. Por ejemplo, podra describirse un problema de toma de decisiones de inversin en el que el tamao de una inversin es la cantidad de dinero requerida, M es el capital total que se puede invertir y la utilidad de una inversin es el rendimiento esperado. Cabe sealar que el problema de la mochila es de maximizacin. Implementacin: /*
Archivo MochilaGreedy.java Esta es una solucin greedy para el problema de la mochila fraccionario. Los argumentos en la lnea de comando son p1, w1, p2, w2, ..., pn, wn. donde p1 w1 corresponde al valor y peso del objeto1 respectivamente, y as sucesivamente. Ejemplo de uso: >java MochilaGreedy Profit Peso fit 24 15 1.0 15 10 0.5 25 18 0.0 Total Profit:31.5 */ public class MochilaGreedy { private static int maxPeso = 20; // lo que la mochila puede soportar 25 18 24 15 15 10

public static void main (String[] argumentos) { if (argumentos.length == 0) { System.out.println ( "Use: java MochilaGreedy profit peso [valor peso ...]"); return; } int tam = argumentos.length / 2; // numero de objetos

Mochila_objetos [] objs = new Mochila_objetos[tam]; float [] fit = new float[tam]; // ingreso para los objetos en la Mochila // ingresa los datos valor y peso en el objeto array int profit; int peso; for (int obj=0; obj < tam; obj++) { profit = Integer.parseInt(argumentos[2*obj]);

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

39

Diseo y Anlisis de Algoritmos con Java


peso = Integer.parseInt(argumentos[2*obj+1]);

Dr.Eric Jeltsch F.

objs[obj] = new Mochila_objetos(profit, peso);// creacion de un objeto fit[obj] = 0; // ningun objeto en la mochila inicialmente } //ordena los objetos en orden decreciente por mtrica //ordenar (objs); // hace el manejo (GREEDY) float espacioIzq = maxPeso; int obj=0; while ((espacioIzq>0) && (obj<tam)) { peso = objs[obj].getPeso(); if (peso > espacioIzq) { fit[obj] = espacioIzq / peso; espacioIzq = 0; } else { fit[obj] = (float) 1.0; espacioIzq = espacioIzq - peso; } obj++; } } displayObjetos(objs, fit);

// --------------------------------------------------------------// display el listado de los datos del objeto { public static void displayObjetos (Mochila_objetos[] objs, float[] fit) // print los datos en este formato System.out.println ("\nProfit\tPeso\tfit"); float totalProfit=0; int profit; int peso; float suma; for (int pos=0; pos < objs.length; pos++) { profit= objs[pos].getProfit(); peso= objs[pos].getPeso(); suma= fit[pos]; totalProfit = (profit*suma) + totalProfit; System.out.println(" "+profit+"\t "+peso+"\t"+fit[pos]); } System.out.println("\nTotal Profit: "+totalProfit);

//--------------------------------------------------------------// sort los objetos en orden decreciente basado en la metrica public static void ordenar (Mochila_objetos[] array) { int ult = array.length - 1; Mochila_objetos temp; for (int actual=0; actual < ult; actual++) { for (int obj=actual+1; obj <= ult; obj++) { if (array[actual].getMetric() < array[obj].getMetric()) { temp = array[obj]; array [obj] = array[actual]; array [actual] = temp; } }

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

40

Diseo y Anlisis de Algoritmos con Java


} } }

Dr.Eric Jeltsch F.

//------------------------------------------------------------// objetos para representar los objetos de la mochila. Cada uno tiene //un profit y un peso como metrica - maximizamos esto en cada etapa del //algoritmo Greedy class Mochila_objetos { private int profit; private int peso; private float metrica;

// la metrica greedy

public Mochila_objetos(int profit, int peso) { this.profit = profit; this.peso = peso; } this.metrica = (float) profit / (float) peso;

public int getProfit () {return this.profit;} public int getPeso () {return this.peso;} public float getMetric () {return this.metrica;} }

Existe otra versin considerando que el objeto o se toma o se deja, de ah el considerar 0 o 1, conocido como Knapsack 0-1. Una descripcin formal es: Dados los vectores de valores < v1, v2, v3 .. vn >, y pesos < w1, w2, w3 .. wn >, correspondiente a n-objetos, tal que M > 0. Se pide determinar el conjunto T {1, 2, 3, 4,...n}, (de objetos a tomar) que logra maximizar (1) vi , sujeta a la condicin (2) w i M. iT iT La idea que se trabaja en este contexto es la de Programacin dinmica, recordemos que la programacin dinmica es un approach para resolver problemas de optimizacin, y es por eso que ve en este contexto. En este contexto se computan las soluciones a los subproblemas una vez y se almacenan en una tabla de manera que ellos puedan ser reutilizados ms tarde. Etapa1: Caracterizar la estructura de una solucin optimal(Estructura), es decir descomponer el problema en subproblemas ms pequeos y encontrando en ellos una relacin entre las estructuras de la solucin optimal al problema original con los problemas ms pequeos. Etapa2: Recursivamente se definen los valores de una solucin optimal (Principio de Optimabilidad), es decir expresar la solucin del problema original en trminos de soluciones optimales de los problemas ms pequeos. Etapa3: Computar el valor de una solucin optimal en la forma bottom-up usando la estructura de la tabla. (Computacin Bottom-up ) Etapa4: Construccin de una solucin optimal desde la informacin computada. (Construccin de la solucin optimal). Etapas3 y 4 pueden ser combinadas, sin embargo las etapas 1-3 forman la base a la solucin de problemas va programacin dinmica. Etapa4 puede ser omitida si solamente los valores ____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena
41
n

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

de una solucin optimal son requeridos. En general, la programacin en programacin dinmica se refiere al mtodo de la tabla. Ejemplo para el caso 0-1, consideremos la fig. XX en donde la fila i representa los objetos, la fila vi los valores y wi los pesos. La matriz V[i, w] es la matriz o tabla en donde se registran

los clculos, al ver la tabla Ud. notar que la solucin se obtiene en la posicin V[4, 10]= 90 que es la solucin optima, cuyos subconjuntos son T ={2, 4}={(4, 40), (3, 50)}. Observacin: Basado en el ejemplo anterior y la propuesta siguiente Objetos 1 2 3 Valor 60 100 120 Peso 10 20 30 Valor/Peso 6 5 4

podr constatar que siendo la capacidad igual a M=50, la solucin greedy seleccionara 1, cuyo capacidad mxima es 60, con un resto de 40 Kg. A este punto, si escogemos los objetos 1 y 2 el valor total es 160, y si escogemos los objetos 1 y 3 el valor total es 180. Sin embargo, la solucin optimal se logra al escoger los objetos 2 y 3. De donde el valor total de esta solucin es 220. Implementacin /* En general se tiene una mochila que puede soportar un peso total M, as como un juego de N objetos, en que cada uno tiene un wi de peso y un pi de valor. Los objetos son nicos, en el sentido de incluirlos en la mochila o dejarlos, es decir no tiene sentido en este contexto tomar una porcin, porcentaje, parte o mltiplos de la inclusin de un objeto. Se desea empacar en nuestra mochila los objetos de mayor valor, cuya capacidad total <= W. Considerar que el programa lee los argumentos de la lnea de comando. El primer argumento es la capacidad maxima admisible, y los siguientes valores son pares asociados a (peso, valor) para cada objeto.

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

42

Diseo y Anlisis de Algoritmos con Java


Ejemplo de uso >java Mochila01 10 5 10 4 40 6 30 3 50 Capacidad Maxima: 10 (5, 10)(4, 40)(6, 30)(3, 50) Subconj -Valores (4, 40) 40 (3, 50) 50 */ public class Mochila01 {

Dr.Eric Jeltsch F.

public static void main(String[] args) { int max_capacidad, num_objetos, objs, capacidad, i, longitud, w, cap; int valor0, valor1, max_valor, j; int[] peso, valor; int[][] subsoluciones; int[][][] carga; // Lee los argumentos de la lnea de comando. El primer //argumento es capacidad maxima los siguientes son pares asociados a //(peso, valor) para cada objeto. longitud = args.length; max_capacidad = Integer.parseInt(args[0]); num_objetos = (longitud - 1) / 2; peso = new int[num_objetos]; valor = new int[num_objetos]; subsoluciones = new int[num_objetos][max_capacidad+1]; carga = new int[num_objetos][max_capacidad+1][num_objetos]; for (i=0; i<num_objetos; i++) { peso[i] = Integer.parseInt(args[1+2*i]); valor[i] = Integer.parseInt(args[2+2*i]); } System.out.println("Capacidad Maxima: " + max_capacidad); for (i=0; i < num_objetos; i++) System.out.print("("+peso[i]+", "+valor[i]+") "); System.out.println(); // Completamos la primera fila de la matriz de subsoluciones. for (w = 0; w <= max_capacidad; w++) if (peso[0] <= w) { subsoluciones[0][w] = valor[0]; carga[0][w][0] = 1; } else subsoluciones[0][w] = 0; // y se construye la primera columna for (i=0; i<num_objetos; i++) subsoluciones[i][0] = 0; // Ahora completamos las filas restantes de la matriz. En cada //nueva fila consideramos de izquierda a derecha, los datos calculados. for (objs = 1; objs < num_objetos; objs++) { for (cap = 1; cap <= max_capacidad; cap++) { for (j=0; j<objs; j++)

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

43

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

carga[objs][cap][j] = carga[objs-1][cap][j]; if (peso[objs] > cap) subsoluciones[objs][cap] = subsoluciones[objs-1][cap]; else { valor0 = subsoluciones[objs-1][cap]; valor1 = subsoluciones[objs-1][cap - peso[objs]] + valor[objs]; if (valor0 > valor1) { subsoluciones[objs][cap] = valor0; } else { subsoluciones[objs][cap] = valor1; for (j=0; j<objs; j++) carga[objs][cap][j] = carga[objs-1][cap-peso[objs]][j]; carga[objs][cap][objs] = 1; } } } } System.out.println(); max_valor = subsoluciones[num_objetos - 1][max_capacidad]; System.out.println("Subconj\t-Valores"); for (j=0; j<num_objetos; j++) { if (carga[num_objetos-1][max_capacidad][j] == 1) System.out.println("("+peso[j]+","+valor[j]+")" + "\t" + valor[j]); } }

e) BACKTRACKING Backtracking (o bsqueda hacia atrs) es una tcnica de programacin para hacer bsqueda sistemtica a travs de todas las configuraciones posibles dentro de un espacio de bsqueda. Para lograr esto, los algoritmos de tipo backtracking construyen posibles soluciones candidatas de manera sistemtica. En general, dada una solucin candidata S, se procede a 1. Verifican si S es solucin. Si lo es, hacen algo con ella (depende del problema). 2. Construyen todas las posibles extensiones de S, e invocan recursivamente al algoritmo con todas ellas. A veces los algoritmos de tipo backtracking se usan para encontrar una solucin, pero otras veces interesa que las revisen todas (por ejemplo, para encontrar la ms corta).

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

44

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

EJEMPLO 1: Un problema clsico es el problema del vendedor viajero: Dadas n ciudades conectadas todas con todas, encontrar el camino que recorra todas las ciudades con mnimo costo. Se parte de una ciudad cualquiera, y se obtiene el siguiente espacio de soluciones: (1) (1,2) (1,3) .......... ....... (1,n) (1,n,2) ......

(1,2,3) (1,2,n) .................. (1,2,3,4,...,n)

En este nivel se tienen todos los posibles caminos junto con su costo asociado.

Criterios de poda: cuando se encuentra el primer camino se guarda el camino y su costo como posible solucin. Si se encuentra uno mejor se redefine la solucin. Si por una rama se detecta que el costo parcial es mayor al costo de la solucin alcanzada hasta el momento, entonces podo ese subrbol (pero no a sus hermanos). EJEMPLO 2: Dada una secuencia de n enteros, se desea saber si existe una particin en dos subconjuntos S1 y S2 disjuntos, tal que la suma de sus elementos sea la misma, es decir: Dado S= {s1,s2,...,sn} Se desea encontrar dos subconjuntos S1 y S2 tal que: S1S2 = y sj = si jS1 iS2 Esta particin podra existir como no. Analicemos todo el espacio de soluciones para un ejemplo concreto: S={2, 3, 4, 5, 9, 6, 7 } S1= {2, 3, 4, 9} S2= {5, 6, 7} S S1= {2} S2= {3,4,5,9,6,7} {3} ... {2,4,5,9,6,7} {7} {2,3,4,5,9,6}

{2,3} {2,4} {4,5,9,6,7} {3,5,9,6,7} ... {2,3,4} ...

...

{2,7} ... {3,4,5,9,6} ... ...

{7,2} ... {3,4,5,9,6} ...

{7,6} {2,3,4,5,9} {5,9,6,7}


45

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

Diseo y Anlisis de Algoritmos con Java 1 nivel 2 nivel ....

Dr.Eric Jeltsch F.

1 n .... n(n-1) ... n! O(nn) Sobre esto pueden hacerse mejoras: - Evitar generar los mismos subconjuntos: Se puede observar en el rbol que algunas particiones estn repetidas, por ejemplo {2,7}{3,4,5,9,6} y {7,2}{3,4,5,9,6}. Para evitar esto se podran ordenar los elementos de menor a mayor antes de empezar la bsqueda, y luego si se tiene {7,2}y vemos que 7 >2, esta particin ya fue hecha y seguir en esta rama sera en vano, por lo que se abandona la bsqueda en esa rama, y de esta manera se va podando el rbol. EJEMPLO 3: Otro tipo de aplicaciones de Backtracking son los juegos. Considere juegos tales como el ajedrez, damas, donde hay dos jugadores. Los jugadores alternan sus movimientos, y el estado del juego puede ser representado por la posicin del tablero. Se asume que hay un nmero finito de posiciones en el tablero y que el juego tiene alguna regla que asegure su terminacin. Con cada juego asociamos un rbol llamado rbol del juego. Cada nodo en el rbol representa una posicin en el tablero. A la raz le asociamos la posicin de comienzo. Si la posicin x est asociada con el nodo n, luego los hijos de n corresponden al conjunto de movidas permitidas desde la posicin x, y con cada hijo es asociada la posicin del tablero resultante. Por ejemplo la siguiente figura muestra parte del rbol del tic tac toe:

juega A
0

...

. . .
A A A B B 1 A A A A B B B 1 A B A A A B B B 0 A B A A A B B A B 0 A A A A B B B -1 A A A A B B B B -1 A B

...

juega A

A B 0

juega B

A A B A B

juega A

B A A B A B 0

A A B A B B A B 1 A A A B A B B A B 1 46

A B A A A B B A B 0

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

Las hojas de un rbol de juego corresponden a las posiciones del tablero donde no hay ms movimientos, porque uno de los jugadores ha ganado o todos los casilleros estn llenos y a ocurrido un empate. Se asocia un valor a cada nodo del rbol. Primero se asignan valores a las hojas planteado desde el punto de vista de uno de los jugadores, tomemos A. Los valores sern -1, 0 o 1, si A pierde, gana o empata respectivamente. Los valores se propagan hacia arriba en el rbol de acuerdo a la siguiente regla: Si un nodo corresponde a la posicin del tablero donde le toca jugar a A, luego el valor con el que se rotura este nodo es el mximo de los valores de los hijos de este nodo. Es decir, se asume que A har el movimiento ms favorable para s, el cual produce el valor ms alto. Si el nodo corresponde a un movimiento de B, el valor con el que rotulamos es el mnimo de los valores de sus hijos. Es decir, se asume que B mover inteligentemente, tratando de ganarle a A o por lo menos empatar. Al terminar el backtracking, se distinguen tres situaciones: 1. Si la raz tiene un valor 1, el jugador A tiene una estrategia ganadora. Cuando comienza tiene la garanta de que puede seleccionar un movimiento que lo lleva a la posicin del tablero de valor 1. Cuando es el turno del jugador B, ste no tiene mas eleccin que un movimiento que lo conduce a la posicin del tablero de valor 1, una prdida para l. El hecho de que un juego termine, garantiza una eventual ganancia para el primer jugador. 2. Si la raz tiene valor cero, como en el caso del ta-te-ti, luego ninguno de los jugadores tiene una estrategia ganadora, solo puede garantizarse a si mismo, empatar jugando tan bien como sea posible. 3. Si la raz es -1, el jugador B tiene una estrategia ganadora. El siguiente algoritmo determina si existe una estrategia ganadora para A, teniendo en cuenta que: B juega inteligentemente. El estado es el tablero, hay que distinguir si el nivel es mx o mn, segn juegue A o B respectivamente. Hay tres situaciones: A gana (1), A pierde (-1), A empata (0). Un nodo es una hoja si se da una de las situaciones mencionadas anteriormente. Poda: si el estado corresponde al nivel de mximo y en un hijo retorna un 1, ya no puede superarse, se tiene una estrategia ganadora, no se necesita buscar ms (no se necesita recorrer sus hermanos), entonces: se poda. En el ejemplo del rbol del ta-te-ti, el primer hijo retorna un 1, por lo tanto no hay necesidad de generar sus hermanos. De la misma forma, si el estado corresponde al nivel de mnimo, si se obtiene un -1, encuentre lo que encuentre ms adelante el mnimo ser -1, entonces se podan los hermanos. Geometra Computacional La geometra computacional se encarga del diseo y anlisis de algoritmos para resolver problemas geomtricos. Los algoritmos geomtricos eficientes son muy utilizados en campos como la computacin grfica, la estadstica, el procesamiento de imgenes y en el diseo con integracin a muy grande escala, tambin llamado (very-large-scale-integration, VLSI). Algoritmo para calcular la cubierta convexa. Uno de los problemas fundamentales en la geometra computacional es el de calcular los puntos que "acotan" a un conjunto finito de puntos en el plano, formalmente llamado, cubierta convexa. ____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena
47

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

p4

p10

p3 p8 p9 p2

p5 p6

p11 p7

p1 Figura 1 La cubierta convexa tiene aplicaciones en muchas reas, incluyendo la estadstica, pues se puede considerar una muestra de datos que determinen la cubierta convexa, sin embargo es posible dejar estos datos aislados producto de que sern irrelevantes en los clculos finales. Un algoritmo que presenta una solucin a este problema es el realizado por Graham. Definicin Dado un conjunto finito de puntos S del plano, un punto p en S es un punto de la cubierta si existe una lnea (recta) L que pasa por p de modo que todos los puntos de S, excepto p, estn en un lado de L ( y excepto p, ninguno est en L). Ejemplo:

p4

p10

p3 p8 p9 p2

p5 p6

p11 p7 L1

p1 L2 ____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena


48

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

P1 es un punto de la cubierta, pues podemos determinar una lnea L1 que pasa por P, tal que todos los otros puntos estn estrictamente a un lado de L1. P8 no es un punto de la cubierta, la lnea recta que pasa por P8 corta el plano dejando a ambos lados elementos. Definicin La cubierta convexa de un conjunto finito de puntos S del plano es la sucesin p1,p2,....,pn de puntos de la cubierta de S enumerados en el siguiente orden. El punto p1 es el punto con coordenada y mnima. Si existen varios puntos con la misma coordenada y mnima, p1 es el que tiene abscisa mnima(observar que p1 es un punto de la cubierta). Para i>=2 sea i el ngulo que forma la horizontal con el segmento de recta p1, pi (ver fig. 3). Los puntos p2, p3,...pn se ordenan de modo que 2, 3,..., n, sea una sucesin creciente. pi i p1 Figura 3

i es el ngulo que forma la horizontal con el segmento de recta p1, p2.

p4

p10

p3 p8 p2

p9 p5 p11 p7 p6 p1 Mnima 2 3 4 5

Figura 4

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

49

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

P1 tiene la coordenada mnima, de modo que es el primer punto enumerado en la cubierta convexa. Se ve que los ngulos que forman la horizontal con los segmentos de recta P1 ,P2; P1 ,P3; P1 ,P4; P1 ,P5 van creciendo. As, la cubierta convexa del conjunto de puntos de la Figura 4 es P1 ,P2 , P3, P4, P5. Informalmente para calcular la cubierta convexa de un conjunto finito de puntos S del plano. Primero se determina el punto P1 con coordenada y mnima. Si existen varios puntos con la misma coordenada y mnima, se elige el punto con abscisa mnima. A continuacin se ordenan todos los puntos P y S de acuerdo con el ngulo que forma la horizontal con el segmento de recta P1,P. Por ultimo se examinan los puntos en orden, y se descartan aquellos que no estn en la cubierta convexa. Apresto: Debemos descubrir una forma para comparar ngulos, y debemos desarrollar un mtodos para verificar si los puntos estn en la cubierta convexa. El resultado ser la cubierta convexa. Comparacin de Angulos: Supongamos que visitamos los puntos distintos P1 ,P0, P2 en el plano en ese orden. Si despus de salir de P0 nos movemos hacia la izquierda decimos que los puntos P1 ,P0, P2 forman un giro a la izquierda. P2 Giro derecha Menor de 180 Giro izquierda P1 Graham (a) p6 p7 p5 p4 p9 p3 p10 p1 p8 p7 p5 (b) p6 p4 mayor de 180

p9 p8

p10 p1

p3

p2

p2
50

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

(a) El punto p1 tiene la coordenada Y mnima, y entre todos los puntos de este tipo, tiene una abscisa mnima. Los puntos ordenados segn el ngulo de la horizontal con p1, p2, p3 forman un giro hacia la izquierda, de modo que se conserva a p2. (b) Se examinan p2, p3, p4 p2, p3, p4 forman un giro a la izquierda, de modo que se conserva a p2. (c) P6 P7 P4 p9 P5 P3 p7 (d) p6 p4

P9

P10 P1

P8

p10 p1

p8

p5

p3

P2

p2

(c) Se examinan p3, p4, p5 p3, p4, p5 forman un giro hacia la izquierda de manera que se conserva a p4. (d) Se examinan p4, p5, p6 p4, p5, p6 forman un giro hacia la derecha de manera que se descarta a p5. El algoritmo regresa a p3, p4, p6 , de donde p3, p4, p6 forma un giro a la izquierda, de modo que se conserva a p4. (e) p6 (f) p6 p4 p5

p7 p9 p10 p8 p5

p4

p9 p8

p7

p3

p10

p3

p1

p2

p1

p2
51

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

(e) Se examina p4, p6, p7 p4, p6, p7 forman un giro a la izquierda, se conserva p6. (f) Se examina p4, p5, p6 p4, p5, p6. (g) p6 (h) p6 p4 p5

p7 p9 p10 p8 p5

p4

p9 p10 p8

p7

p3

p3

p1 Finalmente en (i) p6 p7

p2

p1

p2

(i) P9

p4

p10 p1

p8

p5

p3

p2

(g) (i) El algoritmo concluye examinando a p6, p9, p10 p6, p9, p10 forman un giro a la izquierda, se conserva p9. De donde la cubierta convexa 1. p1, p2, p3 , p4, p6, p9, p10

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

52

Diseo y Anlisis de Algoritmos con Java ALGORITMO DE GRAHAM

Dr.Eric Jeltsch F.

Este algoritmo calcula la cubierta de los puntos p1,,pn del plano. Las coordenadas x y y del punto p se denotan px y py, respectivamente.
Entrada: p1,,pn y n Salida: p1,,pk (la cubierta convexa de p1,pn) y k

Procedure graham_scan (p, n, k) //caso trivial if n = 11 then begin k := 1 return end // determinar el punto con coordenada y mnima min :=1 for i : = 2 to n do if pi y < pmin y then min : = 1 // Entre todos estos puntos, determinar aqul con abscisa mnima // coordenada x for i : = 2 to n do if pi y = pmin y and pi x < pmin x then min : = i swap (p1, pmin) //ordenar segn el ngulo de la horizontal a p1, pi sort p2,, pn //p0 es un punto adicional, agregado con el fin de evitar que // el algoritmo se repita indefinidamente p0:=pn // descartar los puntos que no estn en la cubierta convexa k : = 2 for i : = 3 to n do begin while pk 1, pk, pi no giran a la izquierda do // descartar pk k := k-1 k:=k+1 swap (pp pk) end end graham_scan

EJERCICIO: Utilice Graham para determinar la cubierta convexa de los puntos: (10,1), (7,7), (3,13), (6,10), (16,4), (10,5), (7,13), (13,8), (4,4), (2,2), (1,8), (10,13), (7,1), (4,8), (12,3), (16,10), (14,5), (10,9). Acotacin de problemas Hemos visto que en ocasiones para resolver un problema es posible transformarlo en otro equivalente y as resolviendo ste, queda resuelto el problema inicial. Esta tcnica es til en problemas para los cuales se buscan soluciones algortmicas. Metodologa: Suponga que tenemos planteados dos problemas A y B y que para el segundo de ellos se dispone de un algoritmo que lo resuelve. Si somos capaces de transformar los datos de entrada del problema A en una entrada para el algoritmo que resuelve el problema B y la respuesta de ____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena
53

Diseo y Anlisis de Algoritmos con Java

Dr.Eric Jeltsch F.

ste algoritmo sabemos transformarla en respuesta para el caso particular del problema A de partida, resulta entonces que hemos encontrado un algoritmo para resolver el problema A.

Bibliografa: - Introduction to Algorithms, Cormen, Leiserson and Rivest. - Fundamentals of Computers Algorithms, Horowitz - Sahni. - Estructuras de Datos y Algoritmos, Aho - Hopcroft - Ullman. - Algorithms in C, Sedgewick.

____________________________________________________________________ Ingeniera en Computacin, Universidad de La Serena

54

You might also like