Matrix Chain Multiplication is a classic optimization problem that finds the most efficient way to multiply a chain of matrices together. Given a sequence of matrices, the goal is to determine the optimal parenthesization that minimizes the total number of scalar multiplications required.
Key Point: Matrix multiplication is associative, meaning A(BC) = (AB)C, but the order of multiplication significantly affects the computational cost.
Consider n matrices A, A, ..., A where matrix A has dimensions p p. The product of these matrices can be computed in many different ways because matrix multiplication is associative, meaning (AA)A = A(AA). However, the number of scalar multiplications required can vary significantly based on the parenthesization.
For example, multiplying a 10100 matrix by a 1005 matrix by a 550 matrix can be done in two ways:
The first approach is 10 times more efficient!
The computational complexity of multiplying matrices is O(n) for two n n matrices. For multiple matrices, the difference in computational cost based on parenthesization can be dramatic, as shown in the example above. This problem has significant practical implications in:
A naive approach would be to try all possible parenthesizations and compute the cost of each, then select the minimum. However, the number of ways to parenthesize the product of n matrices grows rapidly with n. It is given by the Catalan number C(n-1) = (1/n) bin(2n-2, n-1), which is exponential in n.
For a chain of 4 matrices, we have 5 possible parenthesizations. For 10 matrices, there are 4,862 ways. For 20 matrices, the number grows to 17,672,631,900! This exponential growth makes the brute-force approach impractical for anything but very small values of n.
A more efficient solution uses dynamic programming. The key insight is that subproblems overlap to find the optimal solution for multiplying matrices i through j, we consider all possible ways to split the chain between i and j, and use the optimal solution for each subproblem.
Let m[i, j] be the minimum number of scalar multiplications needed to compute the product A...A. Then:
We fill the table m[][] in a bottom-up manner, first solving for chains of length 2, then chains of length 3, and so on until we solve for the entire chain.
function MatrixChainOrder(p): n = length(p) - 1 // number of matrices m[1..n, 1..n] // minimum costs s[1..n-1, 2..n] // split points for optimal parenthesization // Initialize the diagonal elements for i = 1 to n: m[i, i] = 0 // Process chains of increasing length for l = 2 to n: // l is chain length for i = 1 to n - l + 1: j = i + l - 1 m[i, j] = for k = i to j - 1: cost = m[i, k] + m[k+1, j] + p[i-1] * p[k] * p[j] if cost < m[i, j]: m[i, j] = cost s[i, j] = k return m and s O(n)
O(n)
Although a time complexity of O(n) might seem expensive, it's exponentially better than the brute-force approach with exponential complexity. This makes the dynamic programming approach practical for reasonably sized matrix chains (up to a few hundred matrices).
Let's consider four matrices with dimensions: A is 54, A is 46, A is 62, and A is 27. We want to find the optimal way to compute A A A A.
For this example, p = [5, 4, 6, 2, 7], so we have n = 4 matrices.
Step 1: Initialize the DP table and calculate cost for single matrices (diagonal elements) as 0.
Step 2: Consider chains of length 2:
Step 3: For chains of length 3:
Step 4: Finally, for the complete chain of length 4:
The optimal parenthesization is (A (A A)) A, requiring 158 scalar multiplications, showing a potential saving of 60% compared to a naive left-to-right multiplication.
Matrix chain multiplication has applications in various domains:
The matrix chain multiplication problem is related to several other optimization problems:
