Algoritmos en Programación

Anuncio
PARTE II: ALGORÍTMICA
Tema 5. 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.
A.E.D. II
Tema 5. Programación Dinámica.
1
5.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 el problema de divide y vencerás de calcular
varias veces la solución para problemas pequeños.
• Ejemplo. Cálculo de los números de Fibonacci, con divide y vencerás.
Fibonacci (n: integer)
Si n<2 Devolver n
Sino Devolver Fibonacci (n-1) + Fibonacci (n-2)
Fibonacci (6) = Fib(5) + Fib (4) = (Fib(4) + Fib(3)) + (Fib(3) + Fib(2)) =
= (Fib(3) + Fib(2)) + (Fib(2) + Fib(1)) + (Fib(2) + Fib(1)) + (Fib(1) + Fib(0))
Esta es una mala forma de calcular los números de Fibonacci, con tiempo de
ejecución exponencial. Problema: Muchos cálculos están repetidos. Solución:
calcular los valores de menor a mayor empezando por 0, e ir guardando los
resultados en una tabla.
A.E.D. II
2
Tema 5. Programación Dinámica.
5.1. Método general.
• Ejemplo. Cálculo de los números de Fibonacci, con programación
dinámica.
Fibonacci (n: integer)
T[0]:= 0; T[1]:= 1;
for i:= 2 to n do
T[i]:= T[i-1] + T[i-2];
Devolver T[n];
Se utiliza la misma fórmula que en la versión anterior del algoritmo, pero de
forma más inteligente. El tiempo de ejecución es O(n).
• Los algoritmos divide y vencerás están dentro de los llamados
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.
A.E.D. II
Tema 5. Programación Dinámica.
3
5.1. Método general.
• Ejemplo. Algoritmo de Floyd, para el cálculo de los caminos mínimos
entre cualquier par de nodos en un grafo.
– Partimos de la matriz de costos asociado al grafo.
– Calcular los caminos mínimos, pudiendo pasar por 1 nodo intermedio (el
nodo 1), luego pudiendo pasar por los 2 primeros, ...
– Repetir hasta tener los caminos mínimos pudiendo pasar por cualquier nodo.
– En este caso, los resultados para los problemas pequeños se pueden
guardar en la misma matriz de costes.
• 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.
•
•
Por ejemplo, si tenemos un camino mínimo de A a B pasando por C,
entonces los trozos de camino de A a C, y de C a B deben ser también
mínimos.
Sin embargo, este principio no es aplicable en todos los problemas. Por
ejemplo, si el camino simple más largo de A a B pasa por C, los trozos de A
a C y de C a B no tienen por qué ser soluciones óptimas.
• Para cada problema deberíamos comprobar si es aplicable el principio
A.E.D. II
4
de optimalidad.
Tema 5. Programación Dinámica.
5.1. Método general.
• Los aspectos a definir en un algoritmo de programación dinámica son:
– Ecuación recurrente, para calcular la solución de los problemas grandes
en función de los problemas más pequeños.
– 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.
• Ejemplo. ¿Cómo están definidos para el cálculo de los números de
Fibonacci y para el algoritmo de Floyd?
5.2. 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.
A.E.D. II
5
Tema 5. Programación Dinámica.
5.3. Ejemplos de aplicación.
5.3.1. 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.
• Definición de la ecuación recurrente:
– Podemos definir Cambio (i, Q), como 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).
A.E.D. II
Tema 5. Programación Dinámica.
6
5.3.1. Problema del cambio de monedas.
• Definición de la ecuación recurrente:
– La solución de Cambio(i, Q) puede que utilice al menos una moneda 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 una moneda (al menos) de tipo i: Cambio(i, Q) = Cambio(i, Q - ci) + 1
– En cualquier caso, el valor será el mínimo entre ambas opciones:
Cambio(i, Q) = min(Cambio(i-1, Q), Cambio(i, Q - ci)+1) Ec. de recurrencia
• Definición de las tablas utilizadas:
–
–
–
–
Necesitamos almacenar los resultados de los subproblemas.
El problema original será: Cambio (n, P).
Por lo tanto, necesitamos una tabla de nxP, de enteros, que llamaremos D.
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
A.E.D. II
Tema 5. Programación Dinámica.
7
5.3.1. Problema del cambio de monedas.
• Casos base:
– Si (i0) ó (j<0) (el valor cae fuera de la tabla) supondremos que D[i, j]
vale +, no existe ninguna solución al problema.
– En otro caso para cualquier i, D[i, 0] = 0. Inicializar la columna 0 a valor 0.
• Forma de rellenar las tablas:
– De arriba hacia abajo (variable i) y de izquierda a derecha (variable j), aplicar
la ecuación de recurrencia:
D[i, j] = min (D[i - 1, j] , D[i, j - ci] + 1)
Devolver-cambio (P: integer; C: array [1..n] of integer; var D: array [1..n, 0..P] of
integer);
for i:= 1 to n do
D[i, 0]:= 0;
for i:= 1 to n do
for j:= 1 to P do
{ Tener en cuenta si el valor }
D[i, j]:= min ( D[i-1, j] , D[i, j - C[i]] + 1 );
{ 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
A.E.D. II
Tema 5. Programación Dinámica.
8
5.3.1. Problema del cambio de monedas.
• ¿Cuál es el tiempo de ejecución del algoritmo? ¿Cómo es en
comparación con el algoritmo voraz?
• Tenemos en D[n, P] el número mínimo de monedas que hay que usar
para devolver la cantidad P. ¿Cómo calcular cuántas monedas de cada
tipo deben usarse, es decir la solución (x1, x2, ..., xn)?
• Debemos analizar las decisiones que se tomaron en cada celda D[i, j]:
– Si el mínimo fue D[i-1, j], entonces no se utilizó ninguna moneda de tipo i.
– Si fue D[i, j - C[i]] + 1, entonces se utilizó una moneda de tipo i.
• Algoritmo para obtener una solución:
1. Empezar con i= n, j=P y con una solución (x1=0, x2=0, ..., xn=0)
2. Si D[i, j] = D[i-1, j] entonces i:= i - 1
En otro caso, xi:= xi + 1; j:= j - C[i];
3. Si i=0 y j=0 entonces acabar. Sino volver al paso 2.
• ¿Cuál es el tiempo de ejecución del algoritmo para obtener la
solución?
• ¿Es correcto el algoritmo? ¿Es aplicable el principio de optimalidad?
A.E.D. II
Tema 5. Programación Dinámica.
9
5.3.2. Problema de la mochila 0/1.
• Igual que el problema de la mochila del tema anterior, pero los objetos no
se pueden fragmentar en trozos más pequeños.
• 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).
• Para aplicar programación dinámica, seguimos los mismos pasos que en el
ejemplo anterior.
• 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.
– 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))
A.E.D. II
Tema 5. Programación Dinámica.
10
5.3.2. Problema de la mochila 0/1.
• Casos base:
– Si (i<0) ó (j<0) entonces no hay solución: Mochila (i, j) = -
– En otro caso, si (i=0) ó (j=0) la solución es no incluir ningún objeto:
Mochila (i, j) = 0
• Definición de las tablas utilizadas:
– La solución del problema original será Mochila (n, M).
– Por lo tanto necesitamos una tabla: V: array [0..n, 0..M] of integer.
• 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
A.E.D. II
Tema 5. Programación Dinámica.
11
5.3.2. Problema de la mochila 0/1.
• Tiempo de ejecución: O(nM).
• La tabla V almacena el valor óptimo de beneficio que se puede obtener
para el problema.
• A partir de la misma es posible obtener la solución (x1, x2, ..., xn),
partiendo de la posición V[n, M] y analizando 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?
A.E.D. II
Tema 5. Programación Dinámica.
12
5.3.3. Multiplicación encadenada de matrices.
• Supongamos que tenemos una serie de matrices M1, M2, ..., Mn, que
queremos multiplicar:
M = M1 x M2 x ... x Mn
• Puesto que la operación es asociativa, habrán muchas formas de
realizar las multiplicaciones. Cada colocación de los paréntesis indicará
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 (utilizamos el método
clásico).
• Ejemplo. Sean las matrices A, B, C y D, de dimensiones: A= 13x5, B=
5x89, C= 89x3 y C= 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
A.E.D. II
Tema 5. Programación Dinámica.
13
5.3.3. Multiplicación encadenada de matrices.
• El objetivo es obtener un orden de multiplicación que minimice el número
de multiplicaciones escalares necesarias.
• Solución sencilla: estimar el número de multiplicaciones necesarias
para todas las ordenaciones posibles. Quedarse con la que tenga menor
valor.
• ¿Cuál será el número de ordenaciones posibles, T(n)?
• 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
• Para cada ordenación necesitaríamos un tiempo de (n).
• El valor de T(n) está en (4n/n2). Por lo tanto, esta solución requeriría un
tiempo con una cota inferior de (4n/n).
• Supongamos 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].
A.E.D. II
Tema 5. Programación Dinámica.
14
5.3.3. Multiplicación encadenada de matrices.
Solución utilizando programación dinámica.
Definimos NMulti (i, j) el problema de calcular 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
• 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.
A.E.D. II
Tema 5. Programación Dinámica.
15
5.3.3. 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
0
X
X
X
3
0
X
X
2
0
X
1
0
0
i= 1
2
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
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
4
0
A.E.D. II
Tema 5. Programación Dinámica.
16
5.3.3. Multiplicación encadenada de matrices.
• ¿Cuál es el orden de complejidad de este algoritmo? ¿Se puede aplicar
la fórmula: Número de elementos de la tabla*Tiempo de cada elemento?
• 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.
• Para ello, podemos empezar en la posición M[1, n] y analizar las
decisiones que se tomaron en cada paso.
• Otra posibilidad: Usar una matriz 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 cual 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
-
A.E.D. II
Tema 5. Programación Dinámica.
17
5.3.3. Multiplicación encadenada de matrices.
• Para obtener el resultado podemos utilizar un procedimiento divide y
vencerás. Supongamos que “x” es el producto de dos matrices.
Multiplica (i, j: entero): matriz
si i = j entonces
Devuelve Mi
sino si i = j-1 entonces
Devuelve Mi x Mj
sino
Devuelve Multiplica(i, Mejork[i, j]) x Multiplica(Mejork[i, j]+1, j)
• Ejemplo:
Multiplica (1, 4) = Multiplica (1, 3) x Multiplica (4, 4) =
= (Multiplica(1, 1) x Multiplica (2, 3)) x M4 = (M1 x (M2 x M3)) x M4
• ¿Cuál es el orden de este algoritmo?
A.E.D. II
Tema 5. Programación Dinámica.
18
Descargar