Programación dinámica
5.1. Método general.
5.2. Análisis de tiempos de ejecución.
5.3. Ejemplos de aplicación.
5.3.1. Problema del cambio de monedas.
5.3.2. Problema de la mochila 0/1.
5.3.3. Multiplicación encadenada de matrices.
1
Método general
• La programación dinámica se suele utilizar en problemas
de optimización, donde una solución está formada por una
serie de decisiones.
• Igual que la técnica divide y vencerás, resuelve el problema
original combinando las soluciones para subproblemas más
pequeños.
• Sin embargo, la programación dinámica no utiliza
recursividad, sino que almacena los resultados de los
subproblemas en una tabla, calculando primero las
soluciones para los problemas pequeños.
• Con esto se pretende evitar la repetición de cálculos para
problemas más pequeños.
2
Método general
• Ejemplo. Cálculo de los números de Fibonacci.
• Con método recursivo
Fibonacci (n: integer)
Si n<2 Devolver 1
Sino Devolver Fibonacci (n-1) + Fibonacci (n-2)
– Problema: Muchos cálculos están repetidos, tiempo de ejec.
exponencial.
– Solución: Calcular los valores de menor a mayor empezando por
0, e ir guardando los resultados en una tabla.
• Con programación dinámica.
Fibonacci (n: integer)
T[0] = 0; T[1] = 1
para i = 2,3, ...,n
T[i] = T[i-1] + T[i-2]
Devolver T[n]
– Se utiliza la misma fórmula que en la versión anterior, pero de forma
más inteligente. El tiempo de ejecución es (n).
3
Método general
• Los algoritmos divide y vencerás están dentro de los
métodos descendentes.
– Empezar con el problema original y descomponer en
pasos sucesivos en problemas de menor tamaño.
– Partiendo del problema grande, descendemos hacia
problemas más sencillos.
• La programación dinámica, por el contrario, es un método
ascendente:
– Resolvemos primero los problemas pequeños
(guardando las soluciones en una tabla) y después
vamos combinando para resolver los problemas más
grandes.
4
Método general
• La programación dinámica se basa en el Principio de
Optimalidad de Bellman: cualquier subsecuencia de una
secuencia óptima debe ser, a su vez, una secuencia óptima.
• Para cada problema deberíamos comprobar si es aplicable
el principio de optimalidad.
• Ejemplo.
2
B
9
A
D
3
C
7
Camino óptimo de A a D: A-C-D, de longitud 10
Camino óptimo de A al siguiente nivel: A-B, de longitud 2, y
después B-D de longitud 9. Total: A-B-D, de longitud 11
¿Cumple el Principio de Optimalidad?
5
Método general
• Aspectos a definir en un algoritmo de programación
dinámica:
– Ecuación recurrente, para calcular la solución de los
problemas grandes en función de los problemas más
pequeños.
– Determinar los casos base.
– Definir las tablas utilizadas por el algoritmo, y cómo son
rellenadas.
– Cómo se recompone la solución global a partir de los
valores de las tablas.
6
Análisis de tiempos de ejecución
• El tiempo de ejecución depende de las características
concretas del problema a resolver.
• En general, será de la forma:
Tamaño de la tabla*Tiempo de rellenar cada elemento de
la tabla.
• Un aspecto importante de los algoritmos de programación
dinámica es que necesitan una tabla para almacenar los
resultados parciales, que puede ocupar mucha memoria.
• Además, algunos de estos cálculos pueden ser
innecesarios.
7
Problema del cambio de monedas
• Problema: Dado un conjunto de n tipos de monedas, cada una
con valor ci, y dada una cantidad P, encontrar el número mínimo
de monedas que tenemos que usar para obtener esa cantidad.
• El algoritmo voraz es muy eficiente, pero sólo funciona en un
número limitado de casos.
• Utilizando programación dinámica:
– Definimos el problema en función de problemas más
pequeños.
– Determinar los valores de los casos base.
– Definimos las tablas necesarias para almacenar los
resultados de los subproblemas.
– Establecemos una forma de rellenar las tablas y de
obtener el resultado.
8
Problema del cambio de monedas
Definición de la ecuación recurrente:
• Cambio (i, Q), el problema de calcular el número mínimo de
monedas necesario para devolver una cantidad Q, usando los i
primeros tipos de monedas (es decir los tipos 1...i).
• La solución de Cambio(i, Q) puede que utilice k monedas de tipo i
o puede que no utilice ninguna.
– Si no usa ninguna moneda de ese tipo: Cambio(i, Q) =
Cambio(i - 1, Q)
– Si usa k monedas de tipo i: Cambio(i, Q) = Cambio(i, Q – k*ci) +
k
• En cualquier caso, el valor será el mínimo:
Cambio(i, Q) = mink=0,1,...,Q/ci {Cambio(i-1, Q-k* ci)+k}
Casos base: Cambio(i, Q)
• Si (i0) ó (Q<0) entonces no existe ninguna solución al problema,
y Cambio(i, Q) = +.
9
• En otro caso para cualquier i>0, Cambio(i, 0) = 0.
Problema del cambio de monedas
Definición de las tablas utilizadas:
• Necesitamos almacenar los resultados de todos los
subproblemas.
• El problema a resolver será: Cambio (n, P).
• Por lo tanto, necesitamos una tabla de nxP, de enteros, que
llamaremos D, siendo D[i, j ]= Cambio(i, j).
• Ejemplo. n= 3, P= 8, c= (1, 4, 6)
D
M onedas
C a n tid a d a d e v o lv e r
0
1
2
3
4
5
6
7
8
C 1= 1
C2 = 4
C3 = 6
Forma de rellenar las tablas:
• De arriba hacia abajo y de izquierda a derecha, aplicar la
ecuación de recurrencia:
D[i, j] = mink=0,1,...,Q/ci {D(i-1, Q-k* ci)+k}
10
Problema del cambio de monedas
• Algoritmo.
Devolver-cambio (P: int; C: array [1..n] of int; var D: array [1..n, 0..P] of int);
para i = 1,2,...,n
D[i, 0] = 0
para i = 1,2,...,n
para j = 1,2,...,P
{Tener en cuenta si el valor }
D[i, j] = mink=0,1,...,Q/ci {D(i-1, Q-k* ci)+k} { cae fuera de la tabla}
• Ejemplo. n= 3, P= 8, c= (1, 4, 6)
0
1
2
3
4
5
6
7
8
C 1= 1
0
1
2
3
4
5
6
7
8
C2 = 4
0
1
2
3
1
2
3
4
2
C3 = 6
0
1
2
3
1
2
1
2
2
• ¿Cuál es el tiempo de ejecución del algoritmo?
• ¿Cómo es en comparación con el algoritmo voraz?
• D[n, P]: número mínimo de monedas que hay que usar para
devolver la cantidad P.
11
Problema del cambio de monedas
• ¿Cómo calcular cuántas monedas de cada tipo deben usarse, es decir la
solución (x1, x2, ..., xn)?
• Se usa otra tabla de decisiones tomadas:
Aux
C1= 1
C2 = 4
C3 = 6
0
1
2
3
4
5
6
7
8
0
0
0
1
0
0
2
0
0
3
0
0
4
1
0
5
1
0
6
1
1
7
1
1
8
2
0
• Algoritmo para obtener una solución:
para i=n,n-1,...,1
xi=Aux[i,P]
P=P-xi*ci
• ¿Cuál es el tiempo de ejecución del algoritmo para obtener la solución?
• ¿Es aplicable el principio de optimalidad?
12
Problema de la mochila 0/1
• Igual que en el tema anterior, pero los objetos no se pueden
fragmentar en trozos más pequeños.
• Problema: Tenemos n objetos, cada uno con un peso (wi) y un
beneficio (vi), y una mochila en la que podemos meter objetos,
con una capacidad de peso máximo M. El objetivo es maximizar
el beneficio de los objetos transportados, donde cada objeto se
puede coger entero (xi=1) o nada (xi=0).
Definición de la ecuación recurrente:
• Sea Mochila (i, m) el problema de la mochila, considerando sólo los i
primeros objetos (de los n originales) con una capacidad de peso m.
Supondremos que devuelve el valor de beneficio total:

a=1..i
xa·va
• Podemos definir el problema de forma recurrente, en función de que se
use o no el objeto i.
13
Problema de la mochila 0/1
Definición de la ecuación recurrente:
• Si no se usa el objeto i: Mochila (i, m) = Mochila (i - 1, m)
• Si se usa: Mochila (i, m) = vi + Mochila (i - 1, m - wi)
• Valor óptimo:
Mochila (i, m) = max (Mochila (i-1, m), vi + Mochila (i-1, m - wi))
Casos base:
• Si (i<0) o (m<0) entonces no hay solución: Mochila (i, m) = -
• En otro caso, si (i=0) ó (m=0) la solución es no incluir ningún objeto:
Mochila (i, m) = 0
Definición de las tablas:
• La solución del problema original será Mochila (n, M).
• Por lo tanto necesitamos una tabla: V: array [0..n, 0..M] of integer.
• V[i, j] = Beneficio máximo usando los i primeros objetos y peso j.
14
Problema de la mochila 0/1
Forma de rellenar las tablas:
• Inicializar los casos base.
• Para todo i, desde 1 hasta n, y j desde 1 hasta M, aplicar la ecuación
de recurrencia:
V[i, j] = max (V[i - 1, j] , V[i - 1, j - wi] + vi)
• Si j es negativo, entonces V[i, j] = -, y el máximo será el otro término.
• Ejemplo. n= 3, M= 6, w= (2, 3, 4), v= (1, 2, 5)
j
0
1
2
3
4
5
6
0
0
0
0
0
0
0
0
1
0
0
1
1
1
1
1
2
0
0
1
2
2
3
3
3
0
0
1
2
5
5
6
i
• Tiempo de ejecución: (nM).
15
Problema de la mochila 0/1
• Se puede tener una tabla auxiliar de 0/1 para almacenar las
decisiones parciales y recomponer la solución, o
• A partir de la tabla V obtener la solución (x1, x2, ..., xn): partir de
la posición V[n, M] y analizar las decisiones que se tomaron para
cada objeto i.
– Si (V[i, j] = V[i-1, j]) entonces la solución no usa el objeto i, xi= 0.
– Si (V[i, j] = V[i-1, j-wi] + vi) entonces sí se usa el objeto i, xi= 1.
– Si (V[i, j] = V[i-1, j-wi] + vi) y (V[i, j] = V[i-1, j]) entonces podemos usar
el objeto i o no (existe más de una solución óptima).
– Acabar cuando lleguemos a un i=0 ó j=0.
• ¿Cuál será el tiempo de recomponer la solución?
• ¿Se cumple el principio de optimalidad?
16
Multiplicación encadenada de matrices
• Supongamos que tenemos las matrices M1, M2, ..., Mn, que
queremos multiplicar:
M = M1 x M2 x ... x Mn
• Puesto que el producto es asociativo, habrá muchas formas
de realizar las multiplicaciones. Cada colocación de los
paréntesis indica un orden en el que se realizan las
operaciones.
• Según el orden de las multiplicaciones, el número de total
de multiplicaciones escalares necesarias puede variar
considerablemente.
• Sea una matriz A de dimensión pxq y B de qxr, entonces el
producto AxB requiere p·q·r multiplicaciones escalares
(método clásico).
17
Multiplicación encadenada de matrices
• Ejemplo. Sean las matrices A, B, C y D, de dimensiones: A=
13x5, B= 5x89, C= 89x3 y D= 3x34. Podemos multiplicarlas de 5
formas:
–
–
–
–
–
((AB)C)D
(AB)(CD)
(A(BC))D
A((BC)D)
A(B(CD))
Requiere 10.582 = 13·5·89 + 13·89·3 + 13·3·34
“
54.201
“
2.856 = 5·89·3 + 13·5·3 + 13·3·34
“
4.055
“
26.418
• Objetivo: obtener un orden de multiplicación que minimice el
número de multiplicaciones escalares.
• Solución sencilla: estimar el número de multiplicaciones
necesarias para todas las ordenaciones posibles. Quedarse con
la que tenga menor valor.
• ¿Cuál es el número de ordenaciones posibles, T(n)?
18
Multiplicación encadenada de matrices
• Si n=1 ó n=2, T(n) = 1.
• Si n>2, entonces podemos realizar la primera multiplicación por n-1
sitios distintos:
(M1M2 ... Mi)(Mi+1Mi+2... Mn)
• T(n) =

T(i)·T(n-i)
i=1..n-1
• El valor de T(n) está en (4n/n2)
• Para cada ordenación necesita un tiempo de (n).
• Esta solución requeriría un tiempo con una cota inferior de (4n/n).
Solución utilizando programación dinámica
Definimos NMulti (i, j): el número mínimo de productos escalares
necesarios para realizar la multiplicación entre la matriz i y la j
(con i  j), es decir: Mi x Mi+1 x ... x Mj
• Suponemos que las dimensiones se almacenan en un array d[0..n],
donde la matriz Mi será de dimensión d[i-1] x d[i].
19
Multiplicación encadenada de matrices
Casos base:
• Si i = j, entonces NMulti(i, j) = 0. No necesitamos realizar ninguna
operación.
• Si i = j-1, entonces NMulti(i, j) = d[i-1]·d[i]·d[i+1]. Sólo existe una forma
de hacer el producto.
Ecuación de recurrencia:
• Si no se da ninguno de los casos anteriores, entonces podemos hacer
la primera multiplicación por una posición k, con ik<j:
(Mi x ... x Mk)x(Mk+1 x ... X Mj)
• El resultado será el valor mínimo:
NMulti(i, j) = min (NMulti(i, k) + NMulti(k+1, j) + d[i-1]·d[k]·d[j])
ik<j
Tablas usadas por el algoritmo:
• El resultado será NMulti(1, n).
• Necesitamos una posición para cada i, j, con 1  i  j  n.
20
Multiplicación encadenada de matrices
• Tablas usadas por el algoritmo.
– Sea M una matriz [1..n, 1..n] de enteros. El algoritmo usará la mitad
de la matriz.
j= 1
2
3
4
i= 1
0
2
X
X
X
3
0
X
X
2
0
X
1
0
0
3
4
• Forma de rellenar la tabla.
– Inicializar la matriz. Para todo i, desde 1 hasta n. M[i, i] = 0
– Aplicar la ecuación de recurrencia por diagonales.
M[i, j] = min (M[i, k] + M[k+1, j] + d[i-1]·d[k]·d[j])
ik<j
• Ejemplo. n= 4, d = (10, 20, 50, 1, 100)
i= 1
2
3
4
j= 1
2
3
4
0
1 0 .0 0 0
1 .2 0 0
2 .2 0 0
0
1 .0 0 0
3 .0 0 0
0
5 .0 0 0
0
21
Multiplicación encadenada de matrices
• ¿Cuál es el orden de complejidad de este algoritmo?
• En la posición M[1, n] tenemos almacenado el número mínimo de
multiplicaciones escalares necesario (para la ordenación que es
óptima). Necesitamos calcular cuál es esta ordenación óptima.
• Usar una matriz auxiliar Mejork [1..n, 1..n] en la que se almacene
el mejor valor de k encontrado en cada paso durante el cálculo de
M (indica cuál fue el mínimo en cada celda).
• En el ejemplo anterior.
M e jo rk
j= 1
2
3
4
i= 1
-
1
1
3
-
2
3
-
3
2
3
4
-
22
Descargar

Programación dinámica