Les problèmes d'optimisation dynamique ont des applications importantes
aussi bien dans l'industrie qu'en gestion. Il s'agit de minimiser le
coût d'une trajectoire dans un espace d'états. On dispose d'une loi
d'évolution, qui détermine l'état suivant à partir de l'état courant et
d'une << commande >> ; les trajectoires sont construites à partir d'un
état initial et d'une suite de commandes, suivant cette loi d'évolution
; on se donne également une fonction d'objectif, définie sur les
trajectoires, qu'il s'agit de minimiser. La programmation
dynamique est une méthode de résolution, pour les problèmes qui
satisfont au principe d'optimalité de Bellman (1955) : une
sous-trajectoire d'une trajectoire optimale est elle-même optimale pour
la fonction d'objectif restreinte aux trajectoires ayant pour origine
celle de cette sous-trajectoire. Ce principe permet une méthode de
résolution ascendante, qui détermine une solution optimale
d'un problème à partir des solutions de tous les sous-problèmes.
Un exemple simple, mais classique, est celui du calcul des plus courts chemins dans un graphe, par l'algorithme de Floyd (1962) ; on doit plutôt parler de chemins à moindre coût, ce problème n'ayant aucune signification métrique.
Représentons un graphe à N sommets comme un tableau c d'entiers
positifs de taille ; l'élément cij est le coût de l'arc
; si l'arc n'existe pas, on pose
, de sorte
qu'on travaille avec un graphe complet. Le coût d'un chemin est la somme
des coûts des arcs de ce chemin. Considérons un chemin de coût minimal
entre i et j et
un sommet intermédiaire sur ce chemin :
les sous-chemins de ce chemin, de i à k et de k à j sont aussi
de coût minimal (sinon, en remplaçant un de ces sous-chemins par un
chemin de moindre coût de mêmes extrémités, on diminuerait le coût du
chemin de i à j, ce qui est impossible). Ce problème satisfait donc
au principe d'optimalité.
L'algorithme de Floyd est une méthode ascendante, qui calcule
successivement, pour tous les couples de sommets i, j, les chemins
minimaux de i à j parmi ceux ne contenant aucun sommet
intermédiaire, puis parmi ceux contenant 1, puis ceux contenant 1 et 2,
etc. Notons dijk le coût d'un chemin de i à j minimal parmi
les k-chemins, c'est-à-dire ceux ayant comme sommets intermédiaires
. On doit calculer dijn à partir de dij0 =
cij. Considérons un k-chemin de coût minimum. S'il ne contient pas
k, c'est un (k-1)-chemin de coût minimum ; s'il contient k, on
peut le diviser en deux sous-chemins de i à k et de k à j, et
par le principe d'optimalité, chacun de ces sous-chemins est un
(k-1)-chemin de coût minimal ; comme l'un des deux cas se produit, on
a la relation :
On peut interpréter cette formule récurrente comme une fonction récursive en i,j,k ; une exécution récursive conduirait à des invocations multiples, comme dans le cas de la définition récursive de la suite de Fibonacci. L'algorithme de Floyd procède au contraire par une évaluation ascendante. Pour le programmer, on a utilisé la représentation unidimensionnelle des matrices. On l'appliquera par exemple à c pour calculer les coûts minimaux dans d:
#define M INT_MAX #define N 5 int c[] = { 0, 3, 8, M, -4, M, 0, M, 1, 7, M, 4, 0, M, M, 2, M, -5, 0, M, M, M, M, 6, 0 }; int d[N*N];
La fonction floyd implémente cet algorithme :
void floyd(int n, int c[], int d[]) { int i, j, k; for (i=0; i<n; i++) { for (j=0; j<n; j++) { d[i*n+j] = c[i*n+j]; } } for (k=0; k<n; k++) { for (i=0; i<n; i++) { for (j=0; j<n; j++) { int u = d[i*n+k] + d[k*n+j]; if (u < d[i*n+j]) { d[i*n+j] = u; } } } } }
Il est facile de modifier cet algorithme pour être capable de construire les chemins minimaux : il suffit de conserver dans un autre tableau le k optimal.