Probleme – La multiplication chaînée de matrices
Exercice · niveau 3 (difficile) · informatique (MP2I/MPI), chapitre 17 — Programmation dynamique
Énoncé
Multiplier , où est de taille . Le produit est associatif : le résultat ne dépend pas du parenthésage, mais le coût si.
- Sur , comparer les deux parenthésages possibles.
- Définir le sous-problème, écrire la récurrence, et dire dans quel ordre remplir la table.
- Programmer, et reconstruire le parenthésage optimal.
- Quelle est la complexité ? Combien de parenthésages existe-t-il ?
Corrigé
1. Le coût dépend du parenthésage. Multiplier une matrice par une coûte multiplications scalaires. Avec :
Un facteur dix, pour trois matrices. Sur six matrices, l'écart se compte en ordres de grandeur.
2. Le sous-problème. = coût minimal pour calculer . Le dernier produit effectué coupe la chaîne en un point :
La sous-structure optimale se voit ici très bien : si le parenthésage optimal coupe en , alors les deux moitiés doivent elles-mêmes être parenthésées de façon optimale — sinon on améliorerait le tout en les remplaçant, sans toucher au reste.
L'ordre de remplissage n'est ni « par ligne » ni « par colonne » : dépend de cases dont l'intervalle est plus court. On remplit donc par longueur d'intervalle croissante : d'abord tous les , puis tous les intervalles de longueur , etc. C'est la première fois du chapitre où l'ordre de remplissage n'est pas l'ordre naturel des indices, et c'est là que se trompent les mises en œuvre.
3. Le programme.
/* Cout minimal du produit A_1 x ... x A_n, ou A_i est d[i-1] x d[i].
Remplit aussi coupe[i][j] : le point de coupure optimal.
Preconditions : n >= 1, d de taille n+1, d[i] >= 1.
Complexite : Theta(n^3) en temps, Theta(n^2) en memoire. */
int chaine(const int d[], int n, int cout[][N_MAX + 1], int coupe[][N_MAX + 1]) {
assert(n >= 1);
for (int i = 1; i <= n; i = i + 1) { cout[i][i] = 0; }
/* INVARIANT : au debut du tour l, tous les cout[i][j] avec j-i+1 < l
sont definitifs. */
for (int l = 2; l <= n; l = l + 1) {
for (int i = 1; i + l - 1 <= n; i = i + 1) {
int j = i + l - 1;
cout[i][j] = INT_MAX;
for (int k = i; k < j; k = k + 1) {
int q = cout[i][k] + cout[k + 1][j] + d[i - 1] * d[k] * d[j];
if (q < cout[i][j]) { cout[i][j] = q; coupe[i][j] = k; }
}
}
}
return cout[1][n];
}
/* Affiche le parenthesage optimal de A_i x ... x A_j.
Precondition : coupe a ete rempli par chaine(). */
void afficher(int coupe[][N_MAX + 1], int i, int j) {
if (i == j) { printf("A%d", i); return; }
printf("(");
afficher(coupe, i, coupe[i][j]);
afficher(coupe, coupe[i][j] + 1, j);
printf(")");
}
Mesure sur l'instance classique , six matrices :
ligne i=1 : 0 15750 7875 9375 11875 15125
ligne i=2 : - 0 2625 4375 7125 10500
ligne i=3 : - - 0 750 2500 5375
ligne i=4 : - - - 0 1000 3500
ligne i=5 : - - - - 0 5000
ligne i=6 : - - - - - 0
optimum = 15125 parenthesage = ((A1(A2A3))((A4A5)A6))
Une énumération exhaustive des parenthésages confirme et le même arbre.
4. Les complexités. Il y a sous-problèmes, chacun coûtant à résoudre (la boucle sur ) : en temps, en mémoire. On ne peut pas réduire la mémoire ici, puisque la reconstruction lit la table coupe.
Le nombre de parenthésages de matrices est le -ième nombre de Catalan,
soit pour et , environ , pour . L'énumération est hors de portée dès , la table est instantanée jusqu'à : c'est le gain de la programmation dynamique, chiffré.
Les autres exercices de ce chapitre Le cours du chapitre
Un blocage sur cet exercice ? Le tuteur d'Adloun guide par questions, sans donner la réponse.