













Prepara tus exámenes y mejora tus resultados gracias a la gran cantidad de recursos disponibles en Docsity
Gana puntos ayudando a otros estudiantes o consíguelos activando un Plan Premium
Prepara tus exámenes
Prepara tus exámenes y mejora tus resultados gracias a la gran cantidad de recursos disponibles en Docsity
Prepara tus exámenes con los documentos que comparten otros estudiantes como tú en Docsity
Encuentra los documentos específicos para los exámenes de tu universidad
Estudia con lecciones y exámenes resueltos basados en los programas académicos de las mejores universidades
Responde a preguntas de exámenes reales y pon a prueba tu preparación
Consigue puntos base para descargar
Gana puntos ayudando a otros estudiantes o consíguelos activando un Plan Premium
Comunidad
Pide ayuda a la comunidad y resuelve tus dudas de estudio
Ebooks gratuitos
Descarga nuestras guías gratuitas sobre técnicas de estudio, métodos para controlar la ansiedad y consejos para la tesis preparadas por los tutores de Docsity
La técnica de programación dinámica, una metodología recursiva para resolver problemas donde los subproblemas no son independientes entre sí. Se utiliza para optimizar soluciones y encontrar la cantidad mínima de monedas necesarias para dar un vuelto dado un conjunto de monedas. El documento incluye ejemplos y una comparación con la técnica Divide and Conquer.
Tipo: Monografías, Ensayos
1 / 21
Esta página no es visible en la vista previa
¡No te pierdas las partes importantes!














Agustín Santiago Gutiérrez
Facultad de Ciencias Exactas y Naturales Universidad de Buenos Aires
Septiembre 2017
Muchos algoritmos de utilidad son recursivos: para resolver un problema, se utilizan las soluciones a subproblemas fuertemente relacionados. En estos algoritmos: Se divide el problema en “varios” subproblemas 0 (casos base) 1 o más (casos recursivos) Estos se resuelven utilizando el mismo algoritmo Se utilizan esas subsoluciones para resolver el problema original Ejemplo: Divide and Conquer (Algoritmos II)
Solución: usar programación dinámica. PD propone almacenar las soluciones a subproblemas ya calculados Almacenar las soluciones permite calcularlas una sola vez , y luego leer el valor ya calculado cuando se lo vuelve a necesitar. PD = D&C + caching
Uno de los usos más importantes es en problemas de optimización : Se busca una solución que maximice un cierto puntaje u objetivo, en un espacio de soluciones posibles Para poder aplicar D&C o PD (técnicas recursivas) debe cumplirse el principio del óptimo : Las partes de una solución óptima a un problema, deben ser soluciones óptimas de los correspondientes subproblemas Permite obtener una solución óptima al problema original a partir de soluciones óptimas de los subproblemas Ejemplo donde SI se cumple: Ordenar con quicksort Ejemplo donde NO se cumple: Calcular un camino máximo simple entre u y v en un grafo
(Parte principal del ejercicio 3.10, práctica 3):
Dadas varias monedas, de valores posiblemente distintos, dar un cierto vuelto utilizando la mínima cantidad de ellas posible.
Más formalmente: Dadas n monedas, cuyos valores están dados por v∑ 1 , v 2 , · · · , vn ∈ Z> 0 , y un T ∈ Z> 0 , se debe dar un I ⊆ { 1 , 2 , 3 , · · · , n} tal que i∈I vi^ =^ T^ , y^ |I|^ sea mínimo.
Nuestro objetivo es llegar a un planteo recursivo del problema. Antes de escribir la fórmula de la solución, tenemos que relacionar la estructura que tiene la solución a un problema, con las soluciones a problemas más chicos. Algunas ideas generales para este paso: Cuando hay muchos elementos “disponibles” en el problema (ej: monedas), a veces conviene enfocar en uno particular Es muy común realizar una división en casos, que abarquen todas las opciones posibles (ej: o usamos esa moneda, o no la usamos) En este paso, muchas veces ayuda hacer dibujos y ejemplos.
Si n ∈ S... Por ejemplo, S tendría la pinta del dibujo:
Es decir, S contiene algún subconjunto de monedas, que sabemos que contiene la moneda de valor vn = 20 ¿Qué podemos decir sobre las demás monedas en S?
Si n ∈ S... S = {n} ∪ S′, con ∑ i∈S′^ vi^ =^ 38,^ S′^ ⊆ {^1 ,^2 ,^3 ,^ · · ·^ ,^ n^ −^1 }
De manera similar a lo que ocurría antes, de todas las maneras de formar 38 con {v 1 , v 2 , v 3 , · · · , vn− 1 }, la cantidad mínima de monedas posibles tendrá que ser |S′| Si no fuera así, habría un∑ S′′^ ⊆ { 1 , 2 , 3 , · · · , n − 1 } con i∈S′′^ vi^ =^ 38 y^ |S ′′| < |S′| Pero entonces, cambiando S′^ por S′′^ en nuestra solución...
Como resultado del análisis anterior, hemos caracterizado una solución óptima S para el problema original: Si n ∈/ S, entonces la mejor solución es S = S 1 , siendo S 1 solución óptima para {v 1 , v 2 , v 3 , · · · , vn− 1 } dando el mismo vuelto T Si n ∈ S, entonces la mejor solución es S = {n} ∪ S 2 , siendo S 2 solución óptima para {v 1 , v 2 , v 3 , · · · , vn− 1 } dando un vuelto T − vn Por lo tanto, como esas son las únicas dos posibilidades, S deberá ser la que use menos monedas entre S 1 y {n} ∪ S 2 Usando esta caracterización , podremos elaborar una recursión que calcule la cantidad óptima de monedas necesarias
Definimos: f (i, t) =
“mínima cantidad de monedas necesarias para dar un vuelto t con las monedas { 1 , 2 , · · · , i}, o +∞ si es imposible” Es muy importante escribir esta definición de f “en palabras”: Si no sabemos qué queremos calcular con f , es imposible saber si la recursión que estamos dando es correcta o no. Como nuestro objetivo final es resolver el problema, también es importante decir cómo se usa f para obtener la respuesta al problema. En este caso, la cantidad óptima de monedas necesarias para dar el vuelto pedido es simplemente f (n, T ): Por la definición de f , esa es la cantidad mínima necesaria para dar un vuelto T usando las monedas { 1 , 2 , · · · , n} Como esas son todas las monedas disponibles, f (n, T ) resulta ser simplemente la mínima cantidad de monedas necesarias, sin restringir cuáles
1 // d: Diccionario global de soluciones ya calculadas 2 monedas(i, t): 3 IF (i,t) not in d THEN 4 IF i=0 and t=0 THEN 5 d[i,t] = 0 6 IF i=0 and t!=0 THEN 7 d[i,t] = +INF 8 IF i>0 and v[i]>t THEN 9 d[i,t] = monedas(i-1, t) 10 IF i>0 and v[i]<=t THEN 11 d[i,t] = min(monedas(i-1,t), 1 + monedas(i-1, t-v[i])) 12 return d[i,t]
El diccionario d podría ser una simple matriz (con un valor especial como -1 para indicar un elemento no calculado), por lo que tiene acceso O( 1 ). Notar que gracias a que guardamos las respuestas, nunca se calcula un mismo subproblema más de una vez.
1 monedas(v, n, T): // v indexado desde 1 2 f <- matrizDeEnteros[0..n , 0..T] 3 f[0,0] = 0 4 FOR t <- 1 TO T DO 5 f[0, t] = +INF 6 FOR i <- 1 TO n DO 7 FOR t <- 0 TO T DO 8 IF v[i] > t THEN 9 f[i,t] = f[i-1, t] 10 ELSE 11 f[i,t] = min(f[i-1,t], 1 + f[i-1, t - v[i]) 12 RETURN f[n, T]
La complejidad del algoritmo resultante es O(nT ), tanto espacial como temporal. Se puede bajar la complejidad espacial a O(T ) (guardando sólo dos filas de la matriz f : cada una depende sólo de la anterior) ¿Cuál es la complejidad de la versión top-down?
Lo anterior es suficiente si solamente necesitamos calcular la cantidad mínima de monedas necesarias. ¿Pero cómo obtenemos la solución S eficientemente? Para cada subproblema f (i, t), la solución óptima usa la moneda i o no la usa, según con cuál de las dos opciones se alcance el mínimo. Podemos almacenar junto a cada valor f (i, t), un booleano que indique con cuál de las dos opciones se obtuvo el mínimo (o bien deducirlo a partir de los valores f (i, t) almacenados) En este caso, ya no podemos usar la misma técnica para bajar la complejidad espacial del problema, pues necesitamos mantener la matriz en memoria para poder obtener la solución
T = 58, n = 8, v = { 10 , 25 , 10 , 10 , 5 , 3 , 5 , 20 } f ( 8 , 58 ) = 1 + f ( 7 , 38 ), así que usamos la moneda v 8 = 20 f ( 7 , 38 ) = f ( 6 , 38 ) f ( 6 , 38 ) = 1 + f ( 5 , 35 ), así que usamos la moneda v 6 = 3 f ( 5 , 35 ) = f ( 4 , 35 ) = f ( 3 , 35 ) f ( 3 , 35 ) = 1 + f ( 2 , 25 ), así que usamos la moneda v 3 = 10 f ( 2 , 25 ) = 1 + f ( 1 , 0 ), así que usamos la moneda v 2 = 25 f ( 1 , 0 ) = f ( 0 , 0 ) = 0 De todo lo anterior, resultó S = { 2 , 3 , 6 , 8 }