12 PROGRAMACION DINAMICA

45
ALGORITMICA III Programación Dinámica Docente: Carlos A.Ruiz De La Cruz Melo Correo: [email protected]

Transcript of 12 PROGRAMACION DINAMICA

ALGORITMICA III

Programación Dinámica

Docente: Carlos A.Ruiz De La Cruz MeloCorreo: [email protected]

Existe una serie de problemas cuyas soluciones pueden ser expresadas recursivamente en términos matemáticos, y posiblemente la manera más natural de resolverlos es mediante un algoritmo recursivo.

Sin embargo, el tiempo de ejecución de la solución recursiva, normalmente de orden exponencial y por tanto impracticable, puede mejorarse substancialmente mediante la Programación Dinámica.

INTRODUCCION

En el diseño Divide y Vencerás nosotros veíamos cómo para resolver un problema lo dividíamos en subproblemas independientes, los cuales se resolvían de manera recursiva para combinar finalmente las soluciones y así resolver el problema original.

El inconveniente se presenta cuando los subproblemas obtenidos no son independientes sino que existe solapamiento entre ellos; entonces es cuando una solución recursiva no resulta eficiente por la repetición de cálculos que conlleva.

INCONVENIENTES EN DIVIDE Y VENCERÁS

SIMILITUD:– Descomposición del problema.– Se obtiene aplicando un razonamiento

inductivo.

DIFERENCIA:• Divide y vencerás: aplicar directamente

la fórmula recursiva.• Programación dinámica: resolver los

problemas más pequeños, guardando los resultados en una tabla (programa iterativo).

MÉTODOS Divide y vencerás utiliza el método

descendente La programación dinámica usa el

método ascendente

DIVIDE Y VENCERAS VS PROGRAMACION DINAMICA

La Programación Dinámica nos puede ofrecer una solución aceptable a los inconvenientes del Divide y Vencerás. La eficiencia de esta técnica consiste en resolver los subproblemas una sola vez, guardando sus soluciones en una tabla para su futura utilización.

La Programación Dinámica no sólo tiene sentido aplicarla por razones de eficiencia, sino porque además presenta un método capaz de resolver de manera eficiente problemas cuya solución ha sido abordada por otras técnicas y ha fracasado.

RAZON DELA PROGRAMACION DINAMICA (D.N.)

DEFINICION:

En informática, la programación dinámica es un método para reducir el tiempo de ejecución de un algoritmo mediante la utilización de subproblemas superpuestos y subestructuras óptimas.

Para que un problema pueda ser abordado por esta técnica ha de cumplir dos condiciones:

1. La solución al problema ha de ser alcanzada a través de una secuencia de decisiones, una en cada etapa.

2. Dicha secuencia de decisiones ha de cumplir el principio de óptimo.

CONDICIONES DE LA P.D.

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:

PRINCIPIO DE ÓPTIMO

“En una secuencia de decisiones óptima toda subsecuencia ha de ser también óptima”.

Hemos de observar que aunque este principio parece evidente no siempre es aplicable y por tanto es necesario verificar que se cumple para el problema en cuestión. Un ejemplo claro para el que no se verifica este principio aparece al tratar de encontrar el camino de coste máximo entre dos vértices de un grafo ponderad

En grandes líneas, el diseño de un algoritmo de Programación Dinámica consta de los siguientes pasos:

DISEÑO DE ALGORITMOS DE P.D.

1. Planteamiento de la solución como una sucesión de decisiones y verificación de que ésta cumple el principio de óptimo.

2. Definición recursiva de la solución.

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

4. Construcción de la solución óptima haciendo uso de la información contenida en la tabla anterior.

Una subestructura óptima significa que se pueden usar soluciones óptimas de subproblemas para encontrar la solución óptima del problema en su conjunto. En general, se pueden resolver problemas con subestructuras óptimas siguiendo estos tres pasos:

SUBESTRUCTURA ÓPTIMA

1. Dividir el problema (proceso inductivo)

2. Resolver estos subproblemas

3. Recomponer la solución final

En resumen, la programación hace uso de: Subproblemas superpuestos

Decir que un problema tiene subproblemas superpuestos es decir que se usa un mismo subproblema para resolver diferentes problemas mayores.

Subestructuras óptimas

soluciones óptimas de subproblemas para encontrar la solución óptima del problema en su conjunto.

Memorización

Se guarda las soluciones que ya se han calculado

PROGRAMACION DINAMICA

ENFOQUES DE LA P.D.

Top-down:

El problema se divide en subproblemas, y estos se resuelven recordando las soluciones por si fueran necesarias nuevamente. Es una combinación de memorización y recursión.

Bottom-up:

Todos los problemas que puedan ser necesarios se resuelven de antemano y después se usan para resolver las soluciones a problemas mayores.

1)2()1(

1,01)(

nsinFibnFib

nsinFib

CALCULO DE LOS NUMEROS FIBONACCI

La sucesión Fibonacci podemos expresarla recursivamente en términos matemáticos de la siguiente manera

funcion Fib(n):entero si n ≤ 1 entonces retornar 1 sino retornar Fib(n-1) + Fib(n-2) finsi finFib

Por tanto, la forma más natural de calcular los

términos de esa sucesión es mediante un programa

recursivo:

Muchos cálculos están repetidos. No se conservan los resultados previos y se tienen que volver a calcular

ARBOL FIBONACCI

Fib(4)

Fib(3) + Fib(2)

Fib(2) + Fib(1) Fib(1) + Fib(0)

Fib(1) + Fib(0) Fib(0) + Fib(-1) Fib(0) + Fib(-1)

OPERACIONF REPITENCIAS

Fib(2) 2

Fib(1) 3

Fib(0) 4

Fib(-1) 2

Podemos resolver el problema mediante programación dinámica, y en particular, utilizando el enfoque de memorización (guardar los valores que ya han sido calculados para utilizarlos posteriormente). Llenamos una tabla con los resultados de los distintos subproblemas. (BOTTOM-UP)

Procedimiento Fibo (n) arreglo: tabla i : natural si n ≤ 1 entonces retornar 1 sino tabla[0] ← 0 tabla[1] ← 1 para i ← 2 hasta n hacer tabla[i] ← tabla[i-1] + tabla[i-2] finpara retornar tabla[n] finsi finfibo

SOLUCION FIBONACCI ITERATIVO

COEFICIENTE BINOMIAL

Los coeficientes binomiales, números combinatorios o combinaciones son números estudiados en combinatoria que corresponden al número de formas en que se pueden extraer subconjuntos a partir de un conjunto dado.

El número de formas de escoger k elementos a partir de un conjunto de n, puede denotarse de varias formas:

COEFICIENTE BINOMIAL

Se tiene un conjunto con 6 objetos diferentes {A,B,C,D,E,F}, de los cuales se desea escoger 2 (sin importar el orden de elección). Existen 15 formas de efectuar tal elección:

{ (A,B), (A,C) (A,D), (A,E), (A,F), (B,C), (B,D), (B,E), (B,F), (C,D), (C,E), (C,F), (D,E), (D,F), (E,F) }

6

215

TRIANGULO DE PASCAL

Se tiene una cuadrícula rectangular en la cual se escribe el número 1 en las casillas del borde superior y el borde izquierdo:

1 1 1 1 …

1

1

1

En cada casilla se escribe la suma de los valores de las dos casillas contiguas situadas a su izquierda y en la parte superior:

1 1 1 1 …

1 2 3 4

1 3 6 10

1 4 10 20

TRIANGULO DE PASCAL

Es común, sin embargo, presentar el arreglo en forma triangular, con los bordes inclinados, tal como se ilustra en la figura.

1 1 1 1 …

1 2 3 4

1 3 6 10

1 4 10 20

TRIANGULO DE PASCAL

Se observa que para cualquier entero n, se cumple que sólo hay una forma de escoger cero o todos los elementos de un conjunto dado.

n0

= 1 = nn

1 1 1 1 …

1

1

1

C(0,0) C(1,1) C(2,2) C(3,3) …

C(1,0)

C(2,0)

C(3,0)

TRIANGULO DE PASCAL

Se observa también que las demás casillas del arreglo son coeficientes binomiales:

1 1 1 1 …

1 2 3 4 …

1 3 6 10 …

1 4 10 20 …

… … … … …

C(0,0) C(1,1) C(2,2) C(3,3) …

C(1,0) C(2,1) C(3,2) C(4,3) …C(2,0) C(3,1) C(4,2) C(5,3) …C(3,0) C(4,1) C(5,2) C(6,3) …

… … … … …

3)!23(!2

!3

10

,01

1

1

n

nnnksi

k

n

k

n

k

n

COEFICIENTES BINOMIALES

La afirmación de que las entradas del triangulo de Pascal son precisamente los coeficientes binomiales, se basa en la siguiente identidad, conocida ahora como identidad de Pascal o teorema de pascal.

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:

COEFICIENTES BINOMIALES

COEFICIENTES BINOMIALES MAS EFICIENTE

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

#include<iostream.h>#include<conio.h>int factorial(int n){ if(n<2) return 1; else return n * factorial(n-1);} int combinacion(int n, int r){ if(r==1)return n; else{ if(n==r) return 1; else return factorial(n)/(factorial(r)*factorial(n - r)); }} int main(){ for(int i=0; i<=6; i++) { for(int ii=0; ii<=i; ii++) cout << combinacion(i, ii) << " "; cout << endl; } getch(); return 0;}

PROGRAMA 1: TRIANGULO DE PASCAL

11 11 2 11 3 3 11 4 6 4 11 5 10 10 5 11 6 15 20 15 6 1

PROGRAMA 2: TRIANGULO DE PASCAL

#include<conio.h>#include<stdio.h>main(){ printf("\t\t triangulo de pascal\n"); int trian[100][100]={0}, h, n, i, j; do{ printf("Ingrese el Alto del Triangulo[1-25]= "); scanf("%d",&h); }while(h<1 || h>100); n=(2*h)-1; trian[0][h-1]=1; for (i=1; i<=h; i++){ for (j=h+1-i; j <= i-h+n ; j++){ trian[i][j]= trian[i-1][j-1] + trian[i-1][j+1]; } } for (i=1;i<=h;i++){ for (j=1;j<=n;j++){ if (trian[i][j]!=0){ printf("%2d",trian[i][j]); } else{ printf(" "); } } printf("\n"); } getch();}

triangulo de pascalIngrese el Alto del Triangulo[1-25]= 7 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 1 6 15 20 15 6 1

Sea G = {N, A} un grafo dirigido, N conjunto de nodos y A conjunto de aristas.

Toda arista tiene una longitud no negativa.

Se desea calcular la longitud del camino más corto entre cada par de nodos.

Por ejemplo:

ALGORITMO DE CAMINOS MÍNIMOS

Para k desde 1 hasta n hacerPara i desde 1 hasta n hacer

Para j desde 1 hasta n hacer D[i, j] minimo((D[i, k]+ D[k, j]) , D[i, j])

Fin ParaFin Para

Fin Para

Para calcular el menor de los caminos mínimos entre dos vértices cualesquiera del grafo podemos aplicar el algoritmo de Floyd.

ALGORITMO DE CAMINOS MÍNIMOS

ALGORITMO DE CAMINOS MÍNIMOS

1 2 3 4

1 0 5

2 50 0 15 5

3 30 0 15

4 15 5 0L = D0 =

Dada la matriz L de adyacencia del grafo G, se calcula una matriz D con la longitud del camino mínimo que une cada par de vértices.

Matriz de adyacencia

Nos planteamos si tal algoritmo puede ser considerado o no de Programación Dinámica, es decir, si reune las características esenciales de ese tipo de algoritmos.

Este algoritmo puede ser considerado de Programación Dinámica ya que es aplicable el principio de óptimo, que puede enunciarse para este problema de la siguiente forma:

si en el camino mínimo de vi a vj, vk es un vértice intermedio, los caminos de vi a vk y de vk a vj han de ser a su vez caminos mínimos

ALGORITMO DE CAMINOS MÍNIMOS

ALGORITMO DE CAMINOS MÍNIMOS

)},(),,({),( 111

jkDkiDMinjiD kkk

k

Siguiendo el esquema de la Programación Dinámica, utiliza una matriz para evitar la repetición de los cálculos.

Con ello consigue que su complejidad temporal sea de orden O(n3) debido al triple bucle anidado en cuyo interior hay tan sólo operaciones constantes

Por lo tanto, puede plantearse la relación en recurrencia que resuelve el problema como:

• Para k=1

1 2 3 4

1 0 5

2 50 0 15 5

3 30 35 0 15

4 15 20 5 0

• Para k=2

1 2 3 4

1 0 5 20 10

2 50 0 15 5

3 30 35 0 15

4 15 20 5 0

D[2,1]=min(D[2,1]+D[1,1] , D[2,1]) 50 =min( 50+ 0 , 50 )

D[2,1]=min(D[2,2]+D[2,1] , D[2,1]) 50 =min( 0+ 50 , 50 )

ALGORITMO DE CAMINOS MÍNIMOS

)},(),,({),( 111

jkDkiDMinjiD kkk

k

1 2 3 4

1 0 5

2 50 0 15 5

3 30 0 15

4 15 5 0

• Para k=3

• Para k=4

1 2 3 4

1 0 5 20 10

2 45 0 15 5

3 30 35 0 15

4 15 20 5 0

1 2 3 4

1 0 5 15 10

2 20 0 10 5

3 30 35 0 15

4 15 20 5 0

D[2,1]=min(D[2,3]+D[3,1] , D[2,1]) 45 =min( 50 + 30 , 50 )

D[2,1]=min(D[2,4]+D[4,1] , D[2,1]) 45 =min( 5 + 15 , 45 )

ALGORITMO DE CAMINOS MÍNIMOS

)},(),,({),( 111

jkDkiDMinjiD kkk

k

DEVOLUCION DE MONEDAS

Para el problema de las monedas con programación dinámica se necesita crear un algoritmo que permita a una máquina expendedora devolver el cambio mediante el menor número de monedas posible.

El denominado “problema de la moneda" es el siguiente: dados diferentes tipos de monedas, cada uno con un valor diferente, y una cantidad C, ¿cuál es el menor número de monedas necesarias para devolver el cambio C?

1. Coge una moneda con el valor V más

grande tal que V <= C 2. Repite desde 1 con el valor C - V hasta

que C = 0

DEVOLUCION DE MONEDAS

Estrategia básica para resolver el problema

Sin embargo, esta estrategia no funciona para valores aleatorios de las monedas. Por ejemplo, si las monedas tienen valores 1, 4 y 6, el número mínimo de monedas para hacer un cambio de 8 es 2 monedas de 4 (cogiendo una moneda de 6 necesitamos como mínimo 3 monedas en total).

DEVOLUCION DE MONEDAS

Supongamos que se tienen monedas de valor 1, 4 y 6 y que se debe devolver una cantidad correspondiente al valor 8. Siguiendo el método de la programación dinámica, se rellenará una tabla con las filas correspondientes a cada valor para las monedas y las columnas con valores desde el 1 hasta el 8.

Cada posición (i, j) de la tabla nos indica el número mínimo de monedas requeridas para devolver la cantidad j con monedas con valor menor o igual al de i:

1 2 3 4 5 6 7 8

m1=1 1 2 3 4 5 6 7 8

m2=4 1 2 3 1 2 3 4 2

m3=6 1 2 3 1 2 1 2 2

para la posición i = 2 y j = 7, se requiere una moneda tipo 2 con valor 4 y tres monedas de tipo 1 con valor uno, por lo tanto en la tabla el número de monedas en la posición (2,7) será 1 + 3 = 4.

DEVOLUCION DE MONEDAS

Ejemplo

1 2 3 4 5 6 7 8

m1=1 1 2 3 4 5 6 7 8

m2=4 1 2 3 1 2 3 4 2

m3=6 1 2 3 1 2 1 2 2

Cada posición (i, j) de la tabla nos indica el número mínimo de monedas requeridas para devolver la cantidad j con monedas con valor menor o igual al de i:

1. Para cada casilla de la tabla hacer:2. Si el valor de la moneda actual es mayor

que la cantidad, se paga con el resto de monedas, es decir, se toma el resultado de la casilla superior.

3. Si el valor de la moneda actual es menor o igual que la cantidad, se toma el mínimo entre:

1. Pagar con el resto de monedas, tomando el resultado de la casilla superior.

2. Pagar con una moneda del tipo actual y el resto con el resultado que se hubiera obtenido al pagar la cantidad actual a la que se le ha restado el valor de la moneda actual.

4. Tomar como resultado el valor de la última celda.

DEVOLUCION DE MONEDAS

Algoritmo 1 2 3 4 5 6 7 8

m1=1 1 2 3 4 5 6 7 8

m2=4 1 2 3 1 2 3 4 2

m3=6 1 2 3 1 2 1 2 2

#include <stdio.h>#include <stdlib.h>#define MONEDAS 8 int monedas[] = {200,100,50,20,10,5,2,1}; int solucion[MONEDAS]; int main (){ int i, devolucion; printf("devolucion: "); scanf ("%d",&devolucion); //inicializacion de vector solucion for (i = 0; i < MONEDAS; i++) solucion[i] =0; //-- //-- bucle voraz for (i = 0; i < MONEDAS; i++) while (devolucion >= monedas[i]){ solucion[i]++; devolucion-= monedas[i]; } //-- fin del bucle voraz if (devolucion) // !0 printf ("No hay monedas para devolver\n"); else //mostramos la solucion for (i = 0; i < MONEDAS; i++) if (solucion[i]) printf ("%d modenas de %d\n", solucion[i], monedas[i]); system ("pause"); return 0; }

SOLUCION VORAZ

SALIDA DEL PROGRAMA

devolucion: 671 modenas de 501 modenas de 101 modenas de 51 modenas de 2Presione una tecla para continuar . . .

DEVOLUCION DE MONEDAS

En general, para resolver el problema de la moneda tenemos que probar todas las posibles combinaciones de coger monedas.

Para la cantidad C, las posibilidades son: coger una moneda del primer tipo, coger una moneda del segundo tipo, etc. En cada caso se genera un subproblema del mismo tipo por resolver. Por ejemplo, si cogemos una moneda de valor 1, recursivamente tenemos que encontrar la mejor manera de devolver el cambio C-1, y sumarle 1 para obtener una posible solución para C.

La solución para C es la mejor entre todas estas posibles maneras de coger la primera moneda.

Si denominamos A(C) el subproblema para la cantidad C, una definición resursiva para el problema es:

donde k es el número total de monedas y m_i es el valor de la moneda i. Ahora podemos escribir una solución basada en la programación dinámica:

A(0) = 0 (si la cantidad es 0 no necesitamos ninguna moneda)A(C) = min(1 + A(C-m_1), 1 + A(C-m_2), ..., 1 + A(C-m_k))

DEVOLUCION DE MONEDAS

SOLUCION DINAMICA

int main() { int M[3] = {1, 4, 9}; int res[101]; res[0] = 0; for (int c = 1; c <= 100; ++c) { res[c] = c; for (int i = 0; i < 3; ++i) if (M[i] <= c && 1 + res[c - M[i]] < res[c])

res[c] = 1 + res[c - M[i]]; }

cout << res[1] << endl; cout << res[12] << endl; cout << res[20] << endl; cout << res[22] << endl; getch();}

Después del bucle el array res contendrá el resultado para cada C en el rango [0, 100].

SALIDA DEL PROGRAMA

1343

1 4 9

MONEDAS

SOLUCION DINAMICA

1 4 9

MONEDAS

SALIDA DEL PROGRAMA

1343

La cantidad C=1 se paga con 1 monedaLa cantidad C=12 se paga con 3 monedasLa cantidad C=20 se paga con 4 monedasLa cantidad C=22 se paga con 3 monedas

2 de 91 de 4

EN RESUMEN

Contemplar un problema como una secuencia de decisiones equivale a dividirlo en problemas más pequeños y por lo tanto más fáciles de resolver como hacemos en Divide y Vencerás, técnica similar a la de programación dinámica. La programación dinámica se aplica cuando la subdivisión de un problema conduce a:

Una enorme cantidad de problemas.

Problemas cuyas soluciones parciales se solapan.

Grupos de problemas de muy distinta complejidad

CONCLUSIONES

Una función recursiva gasta un poco de memoria y tiempo en cada llamada recursiva. Por esta razón, la programación dinámica prescinde de la recursividad y resuelve el problema exclusivamente con la iteración.