Vous êtes sur la page 1sur 4

Programmation dynamique

39 langues
 Article
 Discussion
 Lire
 Modifier
 Modifier le code
 Voir l’historique

Cet article traite d'un paradigme algorithmique. Pour une classe particulière de
langages de programmation, voir langage de programmation dynamique
En informatique, la programmation dynamique est une
méthode algorithmique pour résoudre des problèmes d'optimisation. Le concept a
été introduit au début des années 1950 par Richard Bellman1. À l'époque, le terme
« programmation » signifie planification et ordonnancement1. La programmation
dynamique consiste à résoudre un problème en le décomposant en sous-problèmes,
puis à résoudre les sous-problèmes, des plus petits aux plus grands en stockant les
résultats intermédiaires. Elle a d'emblée connu un grand succès, car de nombreuses
fonctions économiques de l'industrie étaient de ce type, comme la conduite et
l'optimisation de procédés chimiques, ou la gestion de stocks1.

Principe[modifier | modifier le code]

Le graphe de dépendance des sous-problèmes pour le calcul de F5, le 5ème terme de la suite de Fibonacci.

Illustrons la programmation dynamique sur le calcul du nème terme de la suite de


Fibonacci, parfois utilisé comme exemple introductif dans un cours sur la
programmation dynamique2. La suite de Fibonacci est définie par F0 = 0, F1 = 1 et
Fn = Fn-1 + Fn-2. La programmation dynamique s'appuie sur le principe d'optimalité de
Bellman : une solution optimale d'un problème s'obtient en combinant des solutions
optimales à des sous-problèmes. Sur l'exemple de la suite de Fibonacci, la solution
Fn s'obtient en additionnant Fn-1 et Fn-2.
La méthode de programmation dynamique, comme la méthode diviser pour régner,
résout des problèmes en combinant des solutions de sous-problèmes. Les
algorithmes diviser-pour-régner partitionnent le problème en sous-problèmes
indépendants qu’ils résolvent récursivement, puis combinent leurs solutions pour
résoudre le problème initial. La méthode diviser-pour-régner est inefficace si on doit
résoudre plusieurs fois le même sous-problème. Par exemple, l'algorithme suivant
est inefficace :
fonction fibonacci(n)
si n = 0 ou n = 1
retourner n
sinon
retourner fibonacci(n-1) + fibonacci(n-2)

Le graphe de dépendance montré sur la droite n'est pas un arbre : cela illustre que
les sous-problèmes se chevauchent. Par exemple, pour calculer F5, nous avons
besoin de F3 et F4. Mais pour calculer F4, nous avons besoin de F2 et F3. Ainsi, F3 est
utile à la fois pour le calcul de F4 et pour le calcul de F5. La programmation
dynamique consiste alors à stocker les valeurs des sous-problèmes pour éviter les
recalculs3. Il existe alors deux méthodes pour calculer effectivement une solution : la
méthode ascendante et la méthode descendante. Dans la méthode ascendante, on
commence par calculer des solutions pour les sous-problèmes les plus petits, puis,
de proche en proche, on calcule les solutions des problèmes en utilisant le principe
d'optimalité et on mémorise les résultats dans un tableau F[.]. Par exemple :

fonction fibonacci(n)
F[0] = 0
F[1] = 1
pour tout i de 2 à n
F[i] := F[i-1] + F[i-2]
retourner F[n]

Dans la méthode descendante, on écrit un algorithme récursif mais on utilise


la mémoïsation pour ne pas résoudre plusieurs fois le même problème, c'est-à-dire
que l'on stocke dans un tableau F[.] les résultats des appels récursifs :

fonction fibonacci(n)
si F[n] n'est pas défini
si n = 0 ou n = 1
F[n] := n
sinon
F[n] := fibonacci(n-1) + fibonacci(n-2)
retourner F[n]

La programmation dynamique est utilisée pour résoudre des problèmes


d'optimisation. Les sections suivantes en donnent quelques exemples. La conception
d’un algorithme de programmation dynamique est typiquement découpée en quatre
étapes3.

1. Caractériser la structure d’une solution optimale.


2. Définir (souvent de manière récursive) la valeur d’une solution
optimale.
3. Calculer la valeur d’une solution optimale.
4. Construire une solution optimale à partir des informations calculées.
La dernière étape est utile pour calculer une solution optimale, et pas seulement la
valeur optimale. Un problème d'optimisation peut avoir de nombreuses solutions.
Chaque solution a une valeur, et on souhaite trouver une solution ayant la valeur
optimale. Une telle solution optimale au problème n'est pas forcément unique, c'est
sa valeur qui l'est.

Exemples[modifier | modifier le code]


Pyramide de nombres[modifier | modifier le code]
Dans une pyramide de nombres, on cherche, en partant du sommet de la pyramide,
et en se dirigeant vers le bas à chaque étape, à maximiser le total des nombres
traversés4. Dans l'exemple ci-dessous, le maximum est obtenu pour le chemin en
gras (3+7+4+9 = 23).

3
7 4
2 4 6
9 5 9 3

Un algorithme naïf, sur l'exemple, consiste à examiner les 8 chemins possibles, et


choisir celui qui a le plus grand total. En général, quand la pyramide a n niveaux, il y
a chemins et calculs à effectuer. Donc l'algorithme naïf est en temps exponentiel en
n.
Le paradigme de la programmation dynamique permet d'obtenir un algorithme
efficace en définissant des sous-problèmes, en écrivant une relation de récurrence,
puis en donnant un algorithme (avec méthode ascendante ou descendante).
Pour toute position dans la pyramide, notons le nombre écrit à cette position et le
total des nombres traversés dans un chemin maximal issu . Les sous-problèmes
consistent à calculer les valeurs de pour tout . Le problème initial consiste à
calculer lorsque est le sommet de la pyramide.
Donnons maintenant une définition récursive de :
pour toute position situé au rez-de chaussée de la pyramide
pour toute autre position , où et sont les positions inférieurs gauche et droite
sous la position .
Si on cherche à calculer directement par la définition récursive, on évalue
plusieurs fois la même valeur; dans l'exemple ci-dessus, la valeur 4 de la
troisième ligne est calculée deux fois en venant de la ligne supérieure (une
fois depuis le 7, une fois depuis le 4). Le paradigme de la programmation
dynamique consiste à calculer les valeurs soit, à l'aide d'un algorithme itératif
ascendant en stockant les valeurs déjà calculées dans un tableau, soit à
l'aide d'un algorithme récursif descendant avec mémoïsation. L'important est
de conserver dans un tableau les valeurs intermédiaires. La séquence des
calculs est indiquée en gras :

3 23
7 4 20 19 20 19
2 4 6 11 13 15 11 13 15
9 5 9 3 9 5 9 3

Le nombre de calculs est seulement .


Calcul d'un produit de matrices[modifier | modifier le code]
Article détaillé : Algorithme de multiplication de matrices enchaînées.
On se donne des matrices , et on veut calculer la matrice produit 5,3. Les
matrices ne sont pas forcément carrées, et l'ordre dans lequel on effectue les
multiplications peut influencer le coût. Ainsi, le produit des matrices exige un
nombre de multiplications différent suivant que l'on débute par , avec 320
multiplications, ou par , avec 300 multiplications. La seconde façon est
optimale par rapport au nombre de multiplications.
Si chaque matrice a lignes et colonnes, le produit demande opérations. On
note le nombre minimum d'opérations pour calculer le produit . On a alors
.
Pour obtenir la meilleure valeur pour le produit , on le découpe en et et
on choisit le meilleur . Ceci conduit à la formule :
.
Le calcul des se fait dans un tableau, diagonale par diagonale, en
temps quadratique en le nombre de matrices.
Dans cet exemple aussi, il est important de garder le résultat des
calculs dans un tableau pour ne pas les recalculer.

Histoire[modifier | modifier le code]


Le terme programmation dynamique était utilisé dans les années
1940 par Richard Bellman pour décrire le processus de résolution de
problèmes où on trouve les meilleures décisions les unes après les
autres. En 1953, il en donne la définition moderne, où les décisions à
prendre sont ordonnées par sous-problèmes6 et le domaine a alors été
reconnu par IEEE comme un sujet d'analyse de systèmes et
d’ingénierie. La contribution de Bellman est connu sous le nom
d'équation de Bellman, qui présente un problème d'optimisation sous
forme récursive.

Vous aimerez peut-être aussi