Adloun

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.

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.