Docsity
Docsity

Prepara tus exámenes
Prepara tus exámenes

Prepara tus exámenes y mejora tus resultados gracias a la gran cantidad de recursos disponibles en Docsity


Consigue puntos base para descargar
Consigue puntos base para descargar

Gana puntos ayudando a otros estudiantes o consíguelos activando un Plan Premium


Orientación Universidad
Orientación Universidad


Programación Dinámica: Soluciones Recursivas a Problemas, Monografías, Ensayos de Economía I

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

2018/2019

Subido el 01/12/2022

manuela-sanchez-33
manuela-sanchez-33 🇨🇴

3 documentos

1 / 21

Toggle sidebar

Esta página no es visible en la vista previa

¡No te pierdas las partes importantes!

bg1
Programación Dinámica
Agustín Santiago Gutiérrez
Facultad de Ciencias Exactas y Naturales
Universidad de Buenos Aires
Septiembre 2017
Agustín Gutiérrez (UBA-FCEN) Programación Dinámica Septiembre 2017 1 / 21
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15

Vista previa parcial del texto

¡Descarga Programación Dinámica: Soluciones Recursivas a Problemas y más Monografías, Ensayos en PDF de Economía I solo en Docsity!

Programación Dinámica

Agustín Santiago Gutiérrez

Facultad de Ciencias Exactas y Naturales Universidad de Buenos Aires

Septiembre 2017

Soluciones recursivas a problemas

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)

¿En qué consiste programación dinámica? (2)

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

Problemas de optimización

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

Problema “de las monedas”

(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.

Estructura de una solución

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.

Estructura de una solución (cont...)

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?

Estructura de una solución (cont.........)

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...

Estructura de una solución (resumiendo)

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

Fórmula recursiva

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

Algoritmo top-down

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.

Algoritmo bottom-up

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?

Obtener la solución S

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

Obtener la solución S: ejemplo

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 }