Tutorial Playlist
Matrix chain multiplication is a fundamental topic in computer science that plays a key role in several disciplines, including optimization, numerical analysis, and graph theory. The objective is to efficiently multiply a chain of matrices while reducing the amount of scalar multiplications necessary. This tutorial will introduce you to the dynamic programming solution, which considerably decreases the time complexity for bigger matrices.
Finding the most effective technique to multiply the number of matrices together is a computer science problem known as matrix chain multiplication. The objective is to reduce the overall number of scalar multiplications needed.
The problem statement for the Matrix Chain Multiplication problem is to determine the most efficient technique to multiply a given collection of matrices. The purpose is to determine the procedure in which the matrices should be multiplied to decrease the number of math steps required to compute the final result.
Here is an example to show the problem:
Suppose we have three matrices: A with dimensions 10Ă—20, B with dimensions 20Ă—30, and C with dimensions 30Ă—40. There are various methods to parenthesize the product of these matrices. However, the number of operations necessary might vary depending on the multiplication sequence.
Option 1: (AB)C
The product of A and B is a matrix with a size of 10Ă—30.
The product of the resultant matrix and C is a matrix with a size of 10Ă—40.
Total amount of operations: (10Ă—20Ă—30) + (10Ă—30Ă—40) = 6000 + 12000 = 18000 operations.
Option 2: A(BC)
The product of B and C is a matrix with a size of 20Ă—40.
The product of A and the resultant matrix is a matrix of size 10Ă—40.
Total amount of operations: (20Ă—30Ă—40) + (10Ă—20Ă—40) = 24000 + 8000 = 32000 operations.
In this case, Option 1 involves fewer operations than Option 2. Hence it is the most efficient option to multiply the matrices.
The Matrix Chain Multiplication issue may be addressed using dynamic programming. By evaluating all conceivable methods to partition the series of matrices into subchains and computing the lowest number of operations for each subchain, we may build up a solution for the complete sequence.
The recursive solution to the Matrix Chain Multiplication issue includes breaking down the problem into subproblems and solving them recursively. Here's an explanation of the recursive solution with an example:
Let's explore an example using a succession of matrices: A, B, C, D. The dimensions of the matrices are as follows:
A: 2x3
B: 3x4
C: 4x2
D: 2x5
To obtain the least number of scalar multiplications required to multiply these matrices, we may follow these steps:
Code:
#include <iostream>
#include <vector>
#include <climits>
// Function to calculate the minimum number of scalar multiplications
// needed to multiply a sequence of matrices
int matrixChainMultiplication(const std::vector<int>& dimensions) {
  int n = dimensions.size() - 1;
  std::vector<std::vector<int>> dp(n, std::vector<int>(n, 0));
  // Initialize the diagonal elements with 0
  for (int i = 0; i < n; ++i) {
    dp[i][i] = 0;
  }
  // Loop to fill the remaining elements in the dp table
  for (int len = 2; len <= n; ++len) {
    for (int i = 0; i <= n - len + 1; ++i) {
      int j = i + len - 1;
      dp[i][j] = INT_MAX;
      for (int k = i; k < j; ++k) {
        int cost = dp[i][k] + dp[k + 1][j] + dimensions[i] * dimensions[k + 1] * dimensions[j + 1];
        dp[i][j] = std::min(dp[i][j], cost);
      }
    }
  }
  return dp[0][n - 1];
}
int main() {
  std::vector<int> dimensions = {10, 30, 5, 60};
  int minScalarMultiplications = matrixChainMultiplication(dimensions);
  std::cout << "Minimum scalar multiplications: " << minScalarMultiplications << std::endl;
  return 0;
}
In the above example, we're given the dimensions of a sequence of matrices to be multiplied. The matrixChainMultiplication function calculates the minimum number of scalar multiplications needed to multiply the matrices in an optimal order. The dp table is used to store the optimal solutions for subproblems. The dynamic programming approach avoids redundant calculations and optimizes the matrix multiplication order.
Note that the dimensions vector should represent the dimensions of matrices in the sequence. For example, if you have matrices A with dimensions 10x30, B with dimensions 30x5, and C with dimensions 5x60, the dimensions vector would be {10, 30, 5, 60}. The output will provide the minimum number of scalar multiplications needed for optimal matrix chain multiplication.
Dynamic Programming (DP) with memoization is a technique for increasing the performance of recursive algorithms by preserving and reusing the results of expensive function calls. This can significantly reduce the method's temporal complexity.
The algorithm for DP utilizing memoization may be stated as follows:
Here is the pseudocode for DP with memoization:
Memo = {} Â // Memoization table to store computed results
function dpMemoization(arguments):
  if arguments are in Memo:
    return Memo[arguments]
  if base case:
    result = compute base case value
  else:
    result = recursive call(s) with dpMemoization
  Memo[arguments] = result
  return result
Code:
#include <iostream>
#include <unordered_map>
std::unordered_map<int, int> memo; // Memoization table
int fibonacci(int n) {
  if (memo.find(n) != memo.end()) {
    return memo[n];
  }
  int result;
  if (n <= 1) {
    result = n;
  } else {
    result = fibonacci(n - 1) + fibonacci(n - 2);
  }
  memo[n] = result;
  return result;
}
int main() {
  int n = 10;
  std::cout << "Fibonacci(" << n << ") = " << fibonacci(n) << std::endl;
  return 0;
}
In this implementation, we calculate the nth Fibonacci number using dynamic programming with memoization. We use an unordered map memo to store precomputed results for different values of n. Before computing a Fibonacci value, we check if it exists in the memo map. If it does, we return the precomputed value. Otherwise, we calculate it recursively and store it in the memo map.
Code:
import java.util.HashMap;
import java.util.Map;
public class DPMemoization {
  static Map<Integer, Integer> memo = new HashMap<>(); // Memoization table
  static int fibonacci(int n) {
    if (memo.containsKey(n)) {
      return memo.get(n);
    }
    int result;
    if (n <= 1) {
      result = n;
    } else {
      result = fibonacci(n - 1) + fibonacci(n - 2);
    }
    memo.put(n, result);
    return result;
  }
  public static void main(String[] args) {
    int n = 10;
    System.out.println("Fibonacci(" + n + ") = " + fibonacci(n));
  }
}
This is the same implementation as the C++ version, but in Java. We use a HashMap memo to store precomputed results and avoid redundant calculations.
In the bottom-up DP approach, you start by solving the smallest subproblems and gradually build up to the desired solution by solving larger subproblems. You avoid the recursion and instead use iterative loops to fill up a table (often an array) that stores solutions to subproblems. This approach is often more intuitive for some people and can be more memory-efficient than memoization.
The algorithm for DP utilizing the bottom-up approach may be stated as follows:
Here is the pseudocode for DP with bottom-up approach:
function bottomUpDP(arguments):
  table = createEmptyTable()
  // Initialize base cases
  table[0] = baseCaseValue0
  table[1] = baseCaseValue1
  // Fill the table iteratively
  for i = 2 to n:
    table[i] = computeSolutionFrom(table[i-1], table[i-2], ...)
  return table[n]
Code:
#include <iostream>
#include <vector>
int bottomUpFibonacci(int n) {
  if (n <= 1) {
    return n;
  }
  std::vector<int> dp(n + 1);
  dp[0] = 0;
  dp[1] = 1;
  for (int i = 2; i <= n; ++i) {
    dp[i] = dp[i - 1] + dp[i - 2];
  }
  return dp[n];
}
int main() {
  int n = 10;
  std::cout << "Fibonacci(" << n << ") = " << bottomUpFibonacci(n) << std::endl;
  return 0;
}
In this implementation, we calculate the nth Fibonacci number using a bottom-up dynamic programming approach. We use an array dp to store the solutions to subproblems, starting from the base cases (dp[0] = 0 and dp[1] = 1). We then iteratively fill the array using previously computed solutions.
Code:
public class BottomUpDP {
  static int bottomUpFibonacci(int n) {
    if (n <= 1) {
      return n;
    }
    int[] dp = new int[n + 1];
    dp[0] = 0;
    dp[1] = 1;
    for (int i = 2; i <= n; ++i) {
      dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n];
  }
  public static void main(String[] args) {
    int n = 10;
    System.out.println("Fibonacci(" + n + ") = " + bottomUpFibonacci(n));
  }
}
This is the same implementation as the C++ version, but in Java. We use an array dp to store the solutions to subproblems and fill it using an iterative loop.
Understanding matrix chain multiplication is critical for improving the efficiency of matrix calculations in various applications. We may reduce the total computation cost and improve the efficiency of algorithms that incorporate sequences of matrix multiplications by grasping the underlying ideas and employing dynamic programming techniques. This skill benefits anybody working in fields where matrix operations are essential, such as computer graphics, optimization, and scientific computing.
There are various matrix chain multiplication calculators available online. One example is the calculator offered by CalculatorSoup, which allows you to enter the dimensions of the matrices and estimate the best multiplication sequence and the total number of scalar multiplications necessary.
Dynamic programming is one method for performing matrix chain multiplication in C. You may create a 2D array to track how many scalar multiplications are required to multiply each subsequence of matrices. The array may then be filled up, and the best multiplication order is found using nested loops.
Assume we have matrices A, B, C, and D that are 10x20, 20x30, 30x40, and 40x30 in size. We may use the following series of multiplications to multiply these matrices: (A(BC))D. This requires 10,800 scalar multiplications in all.
Dynamic programming has an O(n3) temporal complexity for matrix chain multiplication, where n is the number of matrices in the series. This is due to the approach requiring O(n3) time to compute the best multiplication sequence for every feasible sub-sequence of a matrix.
PAVAN VADAPALLI
Popular
Talk to our experts. We’re available 24/7.
Indian Nationals
1800 210 2020
Foreign Nationals
+918045604032
upGrad does not grant credit; credits are granted, accepted or transferred at the sole discretion of the relevant educational institution offering the diploma or degree. We advise you to enquire further regarding the suitability of this program for your academic, professional requirements and job prospects before enrolling. upGrad does not make any representations regarding the recognition or equivalence of the credits or credentials awarded, unless otherwise expressly stated. Success depends on individual qualifications, experience, and efforts in seeking employment.
upGrad does not grant credit; credits are granted, accepted or transferred at the sole discretion of the relevant educational institution offering the diploma or degree. We advise you to enquire further regarding the suitability of this program for your academic, professional requirements and job prospects before enr...