G. Tremblay
Automne 1998
Intuition (descendante) de la programmation dynamique:
PROCEDURE fib( n: nat ): nat DEBUT SI n = 0 || n = 1 ALORS RETOURNER 1 SINON RETOURNER fib(n-1) + fib(n-2) FIN
Complexité: O(2n)
PROCEDURE fib( n: nat ): nat DEBUT dict <- new Dictionary() dict.insertItem( 0, 1 ) dict.insertItem( 1, 1 ) RETOURNER fib'( n, dict ) FIN
PROCEDURE fib'( n: nat, dict: Dictionary ): nat DEBUT r <- dict.findElement( n ) SI r != NO_SUCH_KEY ALORS // L'appel fib(n) a d'ej`a 'et'e calcul'e RETOURNER r FINComplexité (temps):// Premier appel `a fib(n) r1 <- fib'( n-1, dict ) // dict peut ^etre modifi'e r2 <- fib'( n-2, dict ) // idem r <- r1 + r2 dict.insertItem( n, r ) RETOURNER r FIN
Intuition
PROCEDURE fib( n: nat ): nat DEBUT a <- new nat[n+1] // Table pour les solutions interm'ediaires a[0] <- 1 // Initialisation pour les probl`emes de base a[1] <- 1 // Idem. POUR i <- 2 A n FAIRE // Construction ascendante de la table a[i] <- a[i-1] + a[i-2] FIN RETOURNER a[n] FINComplexité (temps): O(n)
L'algorithme qui suit est une version récursive utilisant la méthode diviser-pour-régner pour le problème du minimisation du nombre d'opérations pour la multiplication d'une chaîne de matrices (cf. section 12.3.1 du manuel).
Pour résoudre le problème de trouver le nombre optimal de
multiplications requises pour multiplier les matrices A0, ...,
An-1, on va calculer, pour tous les k possibles entre et
n-1, le nombre optimal de multiplications pour faire le produit
// Combinaison: on connait le nombre de multiplications requises pour les
// deux sous-probl`emes. On les combine (avec +) et on ajoute
// le nombre d'op'erations requises pour multiplier
// les deux matrices qui r'esultent de cette d'ecomposition
nbMulikj <- nbMulik + nbMulkj + d[i] * d[k+1] * d[j+1]
// On v'erifie si ce nouveau r'esultat est le minimum
SI nbMulikj < minMulij ALORS
minMulij <- nbMulikj
FIN
FIN
// On a fait la division-combinaison pour tous les k possibles
// et on a trouve le minimum: on retourne ce minimum comme resultat global
RETOURNER minMulij
FIN
. On choisira ensuite le k qui
minimise le nombre total de multiplications (= applications
répétées de la stratégie diviser-pour-régner puis recherche
de la solution minimale parmi les diverses solutions obtenues).
PROCEDURE MatrixChain( d: sequence{nat} ): nat
ARGUMENTS
Les matrices `a multiplier sont A0, ..., An-1 (non requises)
La taille de la matrice Ai est donn'ee par d
di+1
PRE
d = d0, d1, ..., dn-1, dn
DEBUT
n <- length(d)-1
RETOURNER MatrixChain'( d, 0, n-1 )
FIN
PROCEDURE MatrixChain'( d: sequence{nat}, i, j: nat ): nat
PRE
i < j && j+1 < length(d)
DEBUT
minMulij <- +INFINITY // On va chercher le nombre minimum requis d'ops
POUR k <- i A j-1 FAIRE
// Diviser: on d'ecompose en 2 sous-probl`emes qu'on r'esout r'ecursivement
nbMulik <- MatrixChain'( d, i, k ) // Sous-probl`eme r'ecursif
nbMulkj <- MatrixChain'( d, k+1, j ) // Autre sous-probl`eme r'ecursif
Supposons qu'on désire multiplier les matrices A0, A1, A2, A3, A4. Avec la méthode diviser-pour-régner na"ive indiquée plus haut, les appels récursifs à MatrixChain' seraient ceux indiqués à la figure 1. On voit clairement que certains de ces appels vont inévitablement se répéter (comme fibonacci), rendant l'algorithme inefficace.
Note: Le nombre de ``>'' devant une expression ``k = ?'' indique le niveau de la récursion. Les appels MC' sous une telle expression sont ceux (les deux appels) faits pour cette valeur de k donnee.
L'algorithme qui suit est une version récursive descendante avec mémorisation pour la solution avec programmation dynamique au problème du minimisation du nombre d'opérations pour la multiplication d'une chaîne de matrices (cf. section 12.3.1 du livre).
PROCEDURE MatrixChain( d: sequence{nat} ): nat
ARGUMENTS
Les matrices `a multiplier sont A0, ..., An-1
La taille de la matrice Ai est d
di+1
PRE
d = d0, d1, ..., dn-1, dn
DEBUT
n <- length(d)-1
dict <- new Dictionary()
POUR i <- 0 A n-1 FAIRE
// On 'etablit les cas de base de la r'ecursion
dict.insertItem( (i, i), 0 )
FIN
RETOURNER MatrixChain'( d, 0, n-1, dict )
FIN
PROCEDURE MatrixChain'(
d: sequence{nat},
i, j: nat,
dict: Dictionary
): nat
DEBUT
r <- dict.findElement( (i, j) )
SI r != NO_SUCH_KEY ALORS
RETOURNER r
FIN
// ASSERT: i < j
minMulij <- +INFINITY
POUR k <- i A j-1 FAIRE
nbMulik <- MatrixChain'( d, i, k, dict )
nbMulkj <- MatrixChain'( d, k+1, j, dict )
nbMulikj <- nbMulik + nbMulkj + d[i] * d[k+1] * d[j+1]
SI nbMulikj < minMulij ALORS
minMulij <- nbMulikj
FIN
FIN
dict.insertItem( (i, j), minMulij )
RETOURNER minMulij
FIN
Le problème du sac à dos est de maximiser le ``bénéfice'' pouvant être obtenu en remplissant un sac avec des items. À chaque item est associé un bénéfice (nombre réel) et un poids (une quantité entière). On doit remplir le sac à dos pour maximiser le bénéfice, mais sans dépasser un certain poids (= poids pouvant être conservé au maximum par le sac).
Dans le problème fractionnaire, on peut prendre une partie d'un item. Dans ce cas, on peut utiliser un algorithme vorace. On a vu en classe un exemple où l'approche vorace ne conduit PAS à un résultat optimal dans le cas où on doit prendre tout un item ou rien du tout (pas de fraction d'un item, d'où le nom "0-1": 0 = rien, 1 = tout).
Pour le problème 0-1, on doit donc utiliser une approche différente. Soit W le poids maximum pouvant être contenu par le sac. Soit n le nombre total d'items (on suppose que les items,, et les poids et bénéfices associés, sont numérotés 1, 2, ..., n).
La stratégie pour obtenir la solution maximale est de résoudre les sous-problèmes suivants:
Si on peut résoudre tous ces sous-problèmes, alors la solution optimale au problème initiale sera B[n, W]. Notons que ceci signifie qu'il faut, pour un poids W donné, calculer toutes les possibilités pour w = 1, 2, 3, ..., W!
De facon récursive descendante, diviser-pour-régner (non
efficace!), on peut résoudre ce problème (grosso modo) comme suit
(ici, on retourne simplement le bénéfice total resultant):
// 1: On n'inclut pas l'item k: w restant ne change donc pas
b1 <- BS'(k-1, w, poids, benefs)
// 2: On inclut l'item k: le poids restant est donc diminue
b2 <- BS'(k-1, w - poids[k], poids, benefs) + benefs[k]
RETOURNER max{b1, b2}
FIN
FIN
PROCEDURE BeneficeSac( W: nat, poids: sequence{nat}, benefs: sequence{real} ): real
PRE
length(poids) = length(benefs) = n
DEBUT
RETOURNER BS'( n, W, poids, benefs )
FIN
PROCEDURE BS'(
k: nat,
w: nat,
poids: sequence{nat},
benefs: sequence{real}
): real
// Pour simplifier et refl'eter ce qui est dans le livre, on suppose
// que les s'equences sont index'ees `a partir de 1 plutot que 0.
DEBUT
SI w = 0 ALORS
RETOURNER 0.0
FIN
SI poids[k] > w ALORS
// Pour trouver la solution optimale pour un poids de w avec les
// items 1 `a k, on ne doit pas inclure l'item k pour un poids
// restant w car son poids depasse w
RETOURNER BS'( k-1, w, poids, benefs )
SINON
// ASSERT: poids[k] <= w
// On peut donc inclure l'item k ... SI le b'en'efice en vaut la peine
// On examine deux sous-probl`emes et on choisit celui qui
// apporte le meilleur b'en'efice (inclure ou pas l'item k)
This document was generated using the LaTeX2HTML translator Version 97.1 (release) (July 13th, 1997)
Copyright © 1993, 1994, 1995, 1996, 1997, Nikos Drakos, Computer Based Learning Unit, University of Leeds.
The command line arguments were:
latex2html -split +0 -auto_navigation prog-dynamique.tex.
The translation was initiated by Tremblay Guy on 10/1/1999