programación dinámica 2
TRANSCRIPT
PROGRAMACIÓN DINÁMICA 2
Introducción § Programación dinámica es una técnica de algoritmo.
§ Fue inventado por Richard Bellman en 1950.
§ No tiene nada que ver con la programación
§ Divide el problema en sub-problemas (como Divide y
Vencerás)
§ Retiene en memoria las soluciones de los sub-
problemas, para evitar cálculos repetidos (memoizing)
§ Devuelve la solución óptima (principio de Optimalidad de
Bellman)
Introducción
La solución de problemas mediante esta técnica se basa en el llamado principio “de óptimo” enunciado por Bellman en 1957 y que dice: “En una secuencia de decisiones óptima toda subsecuencia ha de ser también óptima”.
Programación Dinámica VS Divide y Vencerás
Ambos combinan soluciones de subproblemas, pero § DyV se aplica cuando los subproblemas son independientes § PD se aplica cuando los subproblemas se solapan
DyV repetiría muchos cálculos. PD los mantiene en memoria § DyV utiliza recurrencias (+ tiempo, - memoria) § PD intenta evitar recurrencias, utiliza memoria para iteración
(- tiempo, + memoria) § En ambos casos se obtiene la solución óptima
Pasos de un algoritmo de Programación Dinámica
§ Planteamiento de la solución como una sucesión de
decisiones y verificación de que ésta cumple el
principio de Bellman.
§ Definición recursiva de la solución.
§ Cálculo del valor de la solución óptima mediante una
tabla en donde se almacenan soluciones a problemas
parciales para reutilizar los cálculos.
§ Construcción de la solución óptima haciendo uso de la
información contenida en la tabla anterior.
Ejemplo 1: Cálculo de los
Números Fibonacci
Antes de abordar problemas mas complejos veamos un primer
ejemplo en el cual va a quedar reflejada toda esta problemática.
Se trata del cálculo de los términos de la sucesión de números
Fibonacci. Dicha sucesión podemos expresarla recursivamente
en términos matemáticos de la siguiente manera: Fib(n)
Cálculo de los Números de Fibonacci
1 si n=0,1 Fib(n-1) + Fib(n-2) si n >1
Por tanto, la forma mas natural de calcular los términos de esa sucesión es mediante un Programa recursivo: PROCEDURE FibRec ( n : CARDINAL) : CARDINAL; BEGIN IF n<=1 THEN RETURN 1 ELSE RETURN FibRec ( n-1 ) + FibRec ( n-2 ) END END FibRec;
Cálculo de los Números de Fibonacci
El inconveniente es que el algoritmo resultante es poco
eficiente ya que su tiempo de ejecución es de orden
exponencial.
Como podemos observar, la falta de eficiencia del algoritmo
se debe a que se producen llamadas recursivas repetidas
para calcular valores de la sucesión, que habiéndose
calculado previamente, no se conserva el resultando y por
tanto es necesario volver a calcular cada vez.
Cálculo de los Números de Fibonacci
Para este problema es posible diseñar un algoritmo que en tiempo
lineal lo resuelva mediante la construcción de una tabla que permita
ir almacenando los cálculos realizados hasta el momento , para
poder reutilizarlos:
Fib(0) Fib(1) Fib(2) … Fib(n)
Cálculo de los Números de Fibonacci
El algoritmo iterativo que calcula la sucesión de Fibonacci utilizando tal tabla es: TYPE TABLA = ARRAY [ 0..N] OF CARDINAL PROCEDURE FibIter ( VAR T: TABLA; n : CARDINAL) : CARDINAL; VAR i : CARDINAL; BEGIN IF n<=1 THEN RETURN 1 ELSE T[0] : =1; T[1] : =1; FOR i :=2 TO n DO T[i] :=T[i-1] + T[i-2] END RETURN T[n] END END FibIter;
Cálculo de los Números de Fibonacci
Existe aún otra mejora a este algoritmo, que aparece al fijarnos que únicamente son necesarios los dos últimos valores calculados para determinar cada término, lo que permite eliminar la tabla entera y quedarnos solamente con dos variables para almacenar los dos últimos términos:
PROCEDURE FibIter2(n: CARDINAL):CARDINAL; VAR i,suma,x,y:CARDINAL; (* x e y son los 2 últimos términos *)
BEGIN IF n<=1 THEN RETURN 1 ELSE
x:=1; y:=1; FOR i:=2 TO n DO
suma:=x+y; y:=x; x:=suma; END; RETURN suma
END END FibIter2;
Cálculo de los Números de Fibonacci
Aunque esta función sea de la misma complejidad temporal que la anterior (lineal), consigue una complejidad espacial menor, pues de ser de orden O(n) pasa a ser O(1) ya que hemos eliminado la tabla. El uso de estructuras (vectores o tablas) para eliminar la repetición de los cálculos, pieza clave de los algoritmos de Programación Dinámica, hace que en este capítulo nos fijemos no sólo en la complejidad temporal de los algoritmos estudiados, sino también en su complejidad espacial. En general, los algoritmos obtenidos mediante la aplicación de esta técnica consiguen tener complejidades (espacio y tiempo) bastante razonables, pero debemos evitar que el tratar de obtener una complejidad temporal de orden polinómico conduzca a una complejidad espacial demasiado elevada
Cálculo de los Números de Fibonacci
Ejemplo 2: Cálculo de los
Coeficientes Binomiales
En la resolución de un problema, una vez encontrada la expresión
recursiva que define su solución, muchas veces la dificultad estriba
en la creación del vector o la tabla que ha de conservar los
resultados parciales. Así en este segundo ejemplo, aunque
también sencillo, observamos que vamos a necesitar una tabla
bidimensional algo más compleja. Se trata del cálculo de los
coeficientes binomiales, definidos como:
Cálculo de los Coeficientes Binomiales
El algoritmo recursivo que los calcula resulta ser de complejidad exponencial por la repetición de los cálculos que realiza. No obstante, es posible diseñar un algoritmo con un tiempo de ejecución de orden O(nk) basado en la idea del Triángulo de Pascal. Para ello es necesario la creación de una tabla bidimensional en la que ir almacenando los valores intermedios que se utilizan posteriormente:
Cálculo de los Coeficientes Binomiales
Iremos construyendo esta tabla por filas de arriba hacia abajo y de izquierda a derecha mediante el siguiente algoritmo de complejidad polinómica:
PROCEDURE CoefIter(n,k: CARDINAL):CARDINAL; VAR i,j: CARDINAL; C: T ABLA; BEGIN FOR i:=0 TO n DO C[i,0] :=1 END; FOR i:=1 TO n DO C[i,1] :=i END; FOR i:=2 TO k DO C[i,i] :=1 END; FOR i:=3 TO n DO FOR j:=2 TO i-1 DO IF j<=k THEN C[i,j]:=C[i-1,j-1]+C[i-1,j] END END END; RETURN C[n,k] END CoefIter.
Cálculo de los Coeficientes Binomiales
Ejemplo 3: Coin Row Problem
Problema de las monedas.)
Problema de las monedas
Hay una fila de n monedas cuyos valores son algunos números
enteros positivos, pero no son necesariamente distintos. El
problema se resuelve con programación dinámica, encontrando la
cantidad máxima de dinero con la restricción de que no se pueden
tomar dos monedas adyacentes.
Pseudocódigo Una recurrencia de F(n) se obtiene dividiendo todas las selecciones de
monedas permitidos en dos grupos:
§ El primero es para las que incluyen la última moneda y el segundo
está compuesto por los que no.
§ La relación de recurrencia resultante es F (n) = max {c n + F (n-2), F
(n-1)} para n> 1, F (0) = 0, F (1) = c1.
§ Un bucle for se utiliza para iterar a través de la gama de valores de
la moneda mientras se utiliza el máximo para emitir la mayor
cantidad de dinero que puede ser recogido.
§ Para evitar repetir los mismos cálculos durante el backtracing, la
información acerca de cuál de los dos términos de la recurrencia era
más grande se puede guardar en una matriz cuando se calculan los
valores de F.
Algoritmo del problema de las monedas (C [1..n]).
// Se aplica la fórmula (F (n) = max {c n + F (n-2), F (n-1)} // para n> 1, F (0) = 0, F (1) = c1) inferior hasta encontrar la cantidad máxima //de dinero que puede ser recogido de una fila de monedas sin recoger dos //monedas adyacentes //Entrada: Array C [1..n] de enteros positivos que indica los valores de la //moneda // Salida: El dinero máximo para f que puede ser recogido F[0] ß 0; F[1] ß C[1] For i ß 2 to n do F[i] ß max(C[i] + F[i – 2], F[i – 1]) Return F[n]
Problema Fila de monedas con PD
§ La cantidad más grande que podemos obtener del primer grupo
es igual a Cn + F(n-2). § El valor de la moneda enésima más la cantidad que podemos recoger de
los primeros n-2 monedas.
§ El importe máximo que podemos obtener del segundo grupo es
igual a F(n-1). § Por la definición de F(n) (Uno antes como en Fibonacci).
F (n) = max { C n + F (n-2), F (n-1) } Grupo 1 Grupo 2
Ejemplo:
La aplicación del algoritmo para las monedas de: 5,1,2,10,6,2 es:
Index 0 1 2 3 4 5 6 C 5 1 2 10 6 2 F 0 5
F[0] = 0, F[1] = C1 = 5
Index 0 1 2 3 4 5 6 C 5 1 2 10 6 2 F 0 5 5
F[2] = max{1 + 0, 5} = 5
Ejemplo: Index 0 1 2 3 4 5 6
C 5 1 2 10 6 2 F 0 5 5 7
F[3] = max{2 + 5, 5} = 7
Index 0 1 2 3 4 5 6 C 5 1 2 10 6 2 F 0 5 5 7 15
F[4] = max{10 + 5, 7} = 15
Index 0 1 2 3 4 5 6 C 5 1 2 10 6 2 F 0 5 5 7 15 15
F[5] = max{6 + 7, 15} = 15
Ejemplo:
Index 0 1 2 3 4 5 6 C 5 1 2 10 6 2 F 0 5 5 7 15 15 17
F[6] = max{2 + 15, 15} = 17
La solución del problema de monedas con programación dinámica para la fila de monedas: 5, 1, 2, 10, 6, 2. es igual a 17.
Ejemplo 4: Change Making Problem (Cambio de monedas.)
Change Making Problem Dar cambio por una cantidad n, usando el mínimo número de monedas disponibles. d1<d2<…dm. En este ejemplo se asume el caso general en donde: d1= 1 Y que F(n)= El número mínimo de monedas y que la suma de este sea = n; Es conveniente definir que F(0) = 0
iz
Change Making Problem La cantidad de n sólo se puede obtener de la suma de la denominación de la moneda dj a la cantidad de n-dj Para j=1,2…m tal que n ≥ dj Por lo tanto podemos considerar todas las denominaciones tal que al escogerlas sea el número mínimo de monedas así que F(n-dj) + 1 Tomando en cuenta que el uno por ser constante se puede primero encontrar a F(n-dj) y luego agregar al 1. Por lo tanto se puede considerar lo siguiente F(n) = min { F (n – dj ) } + 1 para n > 0 , F(0) = 0
Change Making Problem Algorithm Change Making (D[1…m], n) // Encuentra el número mínimo de monedas para dar cambio // d1<d2<…<dm donde d1= 1 y que al sumar las dj = n // Entrada: Enteros Positivos indicando los valores del arreglo [1…m] // Donde [1]= 1 y que sumando esos valores = n // Salida = número mínimo de monedas a entregar que sumadas nos den n F[0] = ← 0 For i ← 1 to n do Temp ← ∞; j ← 1 while j ≤ m and i ≥ D [j] do temp ← min (F[i – D[j]], temp) j ← j + 1 F[i] ← temp + 1 Return F [n]
Change Making Problem Ejemplo n= 6 Monedas con denominaciones de: 1, 3, 4
n 0 1 2 3 4 5 6 f 0 F[0]= 0
n 0 1 2 3 4 5 6 f 0 1 F[1] = min {F[1-1]+1 = 1
n 0 1 2 3 4 5 6 f 0 1 2 F[2] = min {F[2-1]+1 = 2
n 0 1 2 3 4 5 6 f 0 1 2 1 F[3] = min {F[3-1], F[3-3]}+1 = 1
Change Making Problem Ejemplo n= 6 Monedas con denominaciones de: 1, 3, 4
n 0 1 2 3 4 5 6 f 0 1 2 1 1
n 0 1 2 3 4 5 6 f 0 1 2 1 1 2
n 0 1 2 3 4 5 6 f 0 1 2 1 1 2 2
F[4] = min {F[4-1], F[4-3], F[4-4]}+1 = 1
F[5] = min {F[5-1], F[5-3], F[5-4]}+1 = 2
F[6] = min {F[6-1], F[6-3], F[6-4]}+1 = 2
Ejemplo 4: Coin Collecting Problem
(Recolección de Monedas)
Coin Collecting Problem
Varias monedas se colocan en las celdas de un tablero de n * m, una
sola moneda por celda.
Un robot, que se encuentra en la celda superior izquierda del tablero,
tiene que recoger la mayor cantidad de monedas como sea posible y
llevarlos a la celda inferior derecha.
En cada paso, el robot puede moverse ya sea una celda a la derecha
o una celda abajo desde su ubicación actual. Cuando el robot visita
una celda con una moneda, siempre recoge esa moneda.
Coin Collecting Problem solución:
Sea F (i, j) es el más grande número de monedas que el robot puede
recoger y llevar a la celda (i, j) en la fila i y j columna del tablero. Se
puede llegar a esta celda, ya sea de la celda adyacente (i-1, j) o la
celda (i, j-1).
Recibimos la siguiente fórmula:
F (i, j) = max {F (i-1, j), F (i, j-1)} + C [i, j] for 1 <= i <= n, 1 <= j <= m
F(0,j) = 0 for 1 <= j <= m 1 y F(i,0) = 0 for 1<= i <= n,
Coin Collecting Problem Algorithm // Aplica programación dinámica para contar el número más grande de //monedas // que un robot puede recolectar en un tablero de n * m, iniciando en la //posición (1,1) // Y moviéndose hacia la derecha o hacia abajo // Entrada: Matriz C[1…n, 1…m] y sus elementos son uno o cero, // Para celdas con o sin moneda respectivamente. // Salida = El máximo número de monedas que el robot puede recolectar a la //celda (n.m) F[1,1] = ← C[1,1]; for j ← 2 to m do F[1,j] ← F[1,j-1]+ C[1,j] For i ← 2 to n do
F[1,j] ← F[i-1 ,1]+ C[i,1] For j ← 2 to m do
F (i, j) ← max (F [i-1, j], F [i, j-1]) + C [i, j] Return F[n,m]
Coin Collecting Ejemplo (a) monedas para recolectar (b) Resultado, utilizando programación dinámica .
(c) camino para recolectar el máximo número de monedas