Problème des multiplications matricielles enchaînées

En informatique, un algorithme de multiplication de matrices enchaînées est un algorithme d'optimisation qui sert à trouver un ordre dans lequel calculer un produit de plusieurs matrices A 1 A k {\displaystyle A_{1}\cdot \dots \cdot A_{k}} de façon à minimiser le nombre de multiplications scalaires à effectuer.

Exemple élémentaire

On considère A = ( a 1 a 2 a 3 ) {\displaystyle A={\begin{pmatrix}a_{1}&a_{2}&a_{3}\\\end{pmatrix}}} , B = ( b 1 b 2 b 3 ) {\displaystyle B={\begin{pmatrix}b_{1}\\b_{2}\\b_{3}\\\end{pmatrix}}} et C = ( c 1 c 2 c 3 ) {\displaystyle C={\begin{pmatrix}c_{1}&c_{2}&c_{3}\\\end{pmatrix}}} . Comme le produit matriciel est associatif on a ( A B ) C {\displaystyle (AB)C} = A ( B C ) {\displaystyle A(BC)} mais le calcul de ( A B ) C {\displaystyle (AB)C} nécessite 6 multiplications scalaires tandis que celui de A ( B C ) {\displaystyle A(BC)} en nécessite 18. Il est donc préférable de calculer d'abord A B {\displaystyle AB} puis ( A B ) C {\displaystyle (AB)C} plutôt que d'abord B C {\displaystyle BC} puis A ( B C ) {\displaystyle A(BC)} .

Énoncé du problème

On se donne une suite de matrices rectangulaires A 1 , A k {\displaystyle \langle A_{1}\dots ,A_{k}\rangle } et on souhaite en calculer le produit A 1 , k = A 1 A k {\displaystyle A_{1,k}=A_{1}\dots A_{k}} (on suppose que toutes les matrices ont une taille compatible, c'est-à-dire que le produit est bien défini). Le produit matriciel étant associatif, n'importe quel parenthésage du produit donnera le même résultat. On cherche à déterminer avant d'effectuer tout calcul quel parenthésage nécessitera le moins de multiplications scalaires.

Un parenthésage optimal pour un produit matriciel naïf ayant une complexité en O ( k 3 ) {\displaystyle O(k^{3})} est a priori également optimal pour les algorithmes de produit optimisé comme l'algorithme de Strassen[réf. nécessaire] ; on supposera donc par la suite que le parenthésage à trouver concerne un produit matriciel classique.

Détermination du nombre d'opérations associées à un parenthésage particulier

Lorsque l'on multiplie deux matrices rectangulaires A {\displaystyle A} et B {\displaystyle B} ayant respectivement p {\displaystyle p} lignes et q {\displaystyle q} colonnes, et q {\displaystyle q} lignes et r {\displaystyle r} colonnes, la matrice A B {\displaystyle AB} obtenue a p {\displaystyle p} lignes et r {\displaystyle r} colonnes. Le calcul de chacun de ses p r {\displaystyle pr} coefficients nécessite q {\displaystyle q} multiplications scalaires d'après la définition du produit matriciel ; le calcul de A B {\displaystyle AB} requiert donc p q r {\displaystyle pqr} multiplications scalaires[1].

On peut donc calculer de proche en proche le nombre de multiplications scalaires associées à un parenthésage particulier. Cette détermination a une complexité O ( k ) {\displaystyle O(k)} k {\displaystyle k} est le nombre de matrices à multiplier.

Algorithme naïf

Une solution possible est de procéder par force brute en énumérant tous les parenthésage possibles pour retenir le meilleur. Ce n'est pas envisageable car pour un produit de k {\displaystyle k} facteurs, le nombre de parenthésages possibles est égal au k 1 {\displaystyle k-1} -ième nombre de Catalan, dont la suite a une croissance exponentielle[1].

Algorithme utilisant la programmation dynamique

Le problème a une structure telle qu'un sous-parenthésage d'un parenthésage optimal est lui-même optimal. De plus un même sous-parenthésage peut intervenir dans plusieurs parenthésages différents. Ces deux conditions rendent possible la mise en œuvre de techniques de programmation dynamique.

Structure d'un parenthésage optimal

On remarque que pour un parenthésage optimal du produit A i , j = A i A j {\displaystyle A_{i,j}=A_{i}\dots A_{j}} (où on a 1 i < j k {\displaystyle 1\leq i<j\leq k} ), si le dernier produit matriciel calculé est ( A i A l ) ( A l + 1 A j ) {\displaystyle (A_{i}\dots A_{l})\cdot (A_{l+1}\dots A_{j})} alors les parenthésages utilisés pour le calcul de A i A l {\displaystyle A_{i}\dots A_{l}} et A l + 1 A j {\displaystyle A_{l+1}\dots A_{j}} sont eux aussi optimaux. En effet si ce n'était pas le cas on pourrait les remplacer par un meilleur parenthésage et donc avoir un meilleur parenthésage pour le produit A i A j {\displaystyle A_{i}\dots A_{j}} , ce qui est contradictoire avec l'hypothèse d'optimalité que l'on a faite.

La même hypothèse d'optimalité peut être faite pour tous les parenthésages de tous les produits intermédiaires au calcul de A i , j {\displaystyle A_{i,j}} et donc pour tous ceux du calcul de A 1 , k {\displaystyle A_{1,k}} . Cela permet une résolution grâce à la programmation dynamique[1].

Calcul du coût des sous-parenthésages

Pour tout i [ 1 , k ] {\displaystyle i\in [1,k]} on note p i 1 {\displaystyle p_{i-1}} le nombre de lignes de A i {\displaystyle A_{i}} et p i {\displaystyle p_{i}} son nombre de colonnes[2].

On définit les tableaux à deux dimensions m [ 1 k ] [ 1 k ] {\displaystyle m[1\dots k][1\dots k]} et l [ 1 k ] [ 1 k ] {\displaystyle l[1\dots k][1\dots k]} tels que pour tout couple d'indices ( i , j ) {\displaystyle (i,j)} tel que i j {\displaystyle i\leq j} , la case m [ i ] [ j ] {\displaystyle m[i][j]} contient le nombre minimal de multiplications scalaires nécessaire au calcul de A i , j {\displaystyle A_{i,j}} et l [ i ] [ j ] {\displaystyle l[i][j]} contient un indice tel que le parenthésage optimal du produit soit A i , j = A i , l A l + 1 , j {\displaystyle A_{i,j}=A_{i,l}\cdot A_{l+1,j}} on obtient[1]:

m [ i ] [ j ] = { 0  si  i = j , min i l < j { m [ i ] [ l ] + m [ l + 1 ] [ j ] + p i 1 p l p j }  si  i < j , NIL  si  i > j . {\displaystyle m[i][j]={\begin{cases}0&{\text{ si }}i=j,\\\min _{i\leq l<j}\{m[i][l]+m[l+1][j]+p_{i-1}p_{l}p_{j}\}&{\text{ si }}i<j,\\{\text{NIL}}&{\text{ si }}i>j.\end{cases}}}

et

l [ i ] [ j ] = { s  tel que  min i l < j { m [ i ] [ l ] + m [ l + 1 ] [ j ] + p i 1 p l p j } = m [ i ] [ s ] + m [ s + 1 ] [ i ] + p i 1 p s p j  si  i < j , NIL  sinon . {\displaystyle l[i][j]={\begin{cases}s{\text{ tel que }}\min _{i\leq l<j}\{m[i][l]+m[l+1][j]+p_{i-1}p_{l}p_{j}\}=m[i][s]+m[s+1][i]+p_{i-1}p_{s}p_{j}&{\text{ si }}i<j,\\{\text{NIL}}&{\text{ sinon}}.\end{cases}}}

On peut calculer le contenu des cases de m {\displaystyle m} et de s {\displaystyle s} simultanément de proche en proche.

Reconstitution d'un parenthésage optimal

Étant donné le tableau l {\displaystyle l} , on peut utiliser l'algorithme récursif suivant pour étant donnés deux indices i {\displaystyle i} et j {\displaystyle j} déterminer un parenthésage optimal du produit A i , j {\displaystyle A_{i,j}} [1]:

Affichage-Parenthésage-Minimal(l,i,j)
si i=j
  afficher "A_i"
sinon afficher "("
  Affichage-Parenthésage-Minimal(l,i,l[i][j])
  Affichage-Parenthésage-Minimal(l,l[i][j]+1,j)
  afficher ")"

On obtient alors un parenthésage optimal en exécutant :

Affichage-Parenthésage-Minimal(l,1,k)


Calcul de la complexité

Comme il y a O ( k 2 ) {\displaystyle O(k^{2})} cases dans le tableau et que le coût d'un sous-parenthésage peut être calculé en O ( k ) {\displaystyle O(k)} , il s'ensuit que l'on peut calculer les coûts de l'ensemble des sous-parenthésages optimaux en O ( k 3 ) {\displaystyle O(k^{3})} avec une capacité de stockage mémoire Θ ( k 2 ) {\displaystyle \Theta (k^{2})} [1]. On peut également montrer que la complexité en temps du calcul de m {\displaystyle m} est Ω ( k 3 ) {\displaystyle \Omega (k^{3})}  : la complexité est cubique même dans le meilleur des cas[1].

Une fois déterminés m {\displaystyle m} et s {\displaystyle s} , l'algorithme affichant le parenthésage a une complexité O ( k ) {\displaystyle O(k)} [1].

Exemple d'exécution

Soit M 5 , 10 , M 10 , 6 , M 6 , 30 , M 30 , 4 , M 4 , 12 , M 12 , 16 {\displaystyle M_{5,10},M_{10,6},M_{6,30},M_{30,4},M_{4,12},M_{12,16}} six matrices rectangulaires (pour chaque matrice, le premier indice indique son nombre de lignes et le second son nombre de colonnes). On cherche un parenthésage optimal pour calculer le produit M = M 5 , 10 M 10 , 6 M 6 , 30 M 30 , 4 M 4 , 12 M 12 , 16 {\displaystyle M=M_{5,10}\cdot M_{10,6}\cdot M_{6,30}\cdot M_{30,4}\cdot M_{4,12}\cdot M_{12,16}} . Si l'on souhaite procéder par recherche exhaustive il y a C 5 = 42 {\displaystyle C_{5}=42} parenthésages à tester, on opte donc pour l'algorithme par programmation dynamique.

On remplit les cases ( i , j ) {\displaystyle (i,j)} des tableaux c [ 1..6 ] [ 1..6 ] {\displaystyle c[1..6][1..6]} et l [ 1..6 ] [ 1..6 ] {\displaystyle l[1..6][1..6]} en suivant l'ordre :

Première diagonale ( 1 , 2 ) ( 2 , 3 ) ( 3 , 4 ) ( 4 , 5 ) ( 5 , 6 ) {\displaystyle (1,2)-(2,3)-(3,4)-(4,5)-(5,6)}
Deuxième diagonale ( 1 , 3 ) ( 2 , 4 ) ( 3 , 5 ) ( 4 , 6 ) {\displaystyle (1,3)-(2,4)-(3,5)-(4,6)}
Troisième diagonale ( 1 , 4 ) ( 2 , 5 ) ( 3 , 6 ) {\displaystyle (1,4)-(2,5)-(3,6)}
Quatrième diagonale ( 1 , 5 ) ( 2 , 6 ) {\displaystyle (1,5)-(2,6)}
Cinquième diagonale ( 1 , 6 ) {\displaystyle (1,6)}


On obtient alors les tableaux suivants :


Tableau c {\displaystyle c} des coûts des sous-parenthésages.
1 2 3 4 5 6
1 0 300 1 200 1 140 1 380 2 228
2 NIL 0 1 800 960 1 440 2 368
3 NIL NIL 0 720 1 008 1 872
4 NIL NIL NIL 0 1 440 2 688
5 NIL NIL NIL NIL 0 768
6 NIL NIL NIL NIL NIL 0

Tableau l {\displaystyle l} d'indices de séparation optimaux.

1 2 3 4 5 6
1 NIL 1 2 2 4 4
2 NIL NIL 2 2 4 4
3 NIL NIL NIL 3 4 4
4 NIL NIL NIL NIL 4 4
5 NIL NIL NIL NIL NIL 5
6 NIL NIL NIL NIL NIL NIL


D'où l'on déduit qu'un parenthésage optimal est M = ( ( M 5 , 10 M 10 , 6 ) ( M 6 , 30 M 30 , 4 ) ) ( M 4 , 12 M 12 , 16 ) {\displaystyle M=((M_{5,10}\cdot M_{10,6})\cdot (M_{6,30}\cdot M_{30,4}))\cdot (M_{4,12}\cdot M_{12,16})} qui permet un calcul de M {\displaystyle M} avec 2 228 multiplications scalaires. À titre de comparaison, le tableau suivant présente les coûts de différents parenthésages.

Parenthésage Nombre de multiplications
M = ( ( M 5 , 10 M 10 , 6 ) ( M 6 , 30 M 30 , 4 ) ) ( M 4 , 12 M 12 , 16 ) {\displaystyle M=((M_{5,10}\cdot M_{10,6})\cdot (M_{6,30}\cdot M_{30,4}))\cdot (M_{4,12}\cdot M_{12,16})} 2 228
M = M 5 , 10 ( M 10 , 6 ( M 6 , 30 ( M 30 , 4 ( M 4 , 12 M 12 , 16 ) ) ) ) {\displaystyle M=M_{5,10}\cdot (M_{10,6}\cdot (M_{6,30}\cdot (M_{30,4}\cdot (M_{4,12}\cdot M_{12,16}))))} 3 000
M = ( ( ( ( M 5 , 10 M 10 , 6 ) M 6 , 30 ) M 30 , 4 ) M 4 , 12 ) M 12 , 16 {\displaystyle M=((((M_{5,10}\cdot M_{10,6})\cdot M_{6,30})\cdot M_{30,4})\cdot M_{4,12})\cdot M_{12,16}} 7 328
M = ( M 5 , 10 ( M 10 , 6 M 6 , 30 ) ) ( ( M 30 , 4 M 4 , 12 ) M 12 , 16 ) {\displaystyle M=(M_{5,10}\cdot (M_{10,6}\cdot M_{6,30}))\cdot ((M_{30,4}\cdot M_{4,12})\cdot M_{12,16})} 12 900

Algorithme quasi linéaire

Cette section est vide, insuffisamment détaillée ou incomplète. Votre aide est la bienvenue ! Comment faire ?

Un algorithme a été proposé en 1981 dont la complexité est O ( k log k ) {\displaystyle O(k\log k)} [3].

Applications

Dans la pratique, la taille des matrices à multiplier excède souvent le nombre de facteurs du produit matriciel. Ainsi même si la complexité de l'algorithme d'optimisation est O ( k 3 ) {\displaystyle O(k^{3})} , l'appliquer pour minimiser le nombre de multiplications scalaires à effectuer dans le produit proprement dit représente un gain de temps[1].

Bibliographie

Ana Bušić, « Programmation Dynamique appliquée à l'algorithmique Numérique: Multiplications matricielles enchaînées (Cours Licence 3) »,

Références

  1. a b c d e f g h et i (en) Thomas H. Cormen, Charles E. Leiserson et Ronald L. Rivest, Introduction à l'algorithmique, Paris, Dunod, , xxix+1146 (ISBN 978-2-10-003922-7, SUDOC 068254024), chap. 15 (« Programmation dynamique »).
  2. La notation est cohérente car pour que le produit de deux matrices soit défini, le nombre de colonnes de celle de gauche doit être égal au nombre de lignes de celle de droite.
  3. T. C. Hu et M.T. Shing, « Computation of Matrix Chain Products. Part I, Part II », Technical Reports of Stanford University, Université de Stanford,‎ (lire en ligne, consulté le )
  • icône décorative Portail des mathématiques
  • icône décorative Portail de l'informatique théorique