Strassen's Algorithm - A Faster Way to Multiply Matrices with Divide and Conquer
Hey everyone, and welcome back to the blog! Multiplying matrices is a fundamental operation in many fields, from computer graphics and scientific computing to machine learning and data analysis. The standard "schoolbook" method for multiplying two matrices involves scalar multiplications, giving it an O(n³) time complexity. This is perfectly fine for small matrices, but for large matrices, this cubic complexity can become a significant performance bottleneck.
Enter Strassen's Algorithm! Developed by Volker Strassen in 1969, this ingenious algorithm was one of the first to demonstrate that matrix multiplication could be done faster than O(n³). It uses a divide and conquer approach to reduce the number of required multiplications, achieving a better asymptotic complexity. Let's explore how it works its magic.
The Problem: Speeding Up Matrix Multiplication
Given two square matrices, and , both of size , our goal is to compute their product, , also an matrix. The challenge is to do this with fewer than the scalar multiplications required by the standard algorithm, especially when is large.
Strassen's algorithm achieves this by cleverly reducing the multiplication of two matrices to seven multiplications of submatrices, along with a fixed number of matrix additions and subtractions (which are operations). This reduction from eight (which the naive divide and conquer approach would use) to seven recursive multiplications is the key to its improved time complexity.
The Strassen Algorithm: A Divide and Conquer Approach
Let's assume, for simplicity, that is a power of 2, so we can always divide matrices evenly. (If not, padding can be used, though this adds some complexity in practice).
-
Divide (Partitioning the Matrices): Divide the input matrices and (and the result matrix ) into four equal-sized submatrices:
, ,
The standard multiplication would then be: This involves 8 recursive multiplications of matrices.
-
Conquer (The 7 Clever Products - Strassen's Steps): Strassen defined 10 intermediate sum/difference matrices ( to ) involving only additions and subtractions of the submatrices of and . Then, 7 products ( to ) are computed recursively using these matrices or the original submatrices.
Let's define the 10 sums/differences:
Now, compute the 7 products recursively:
-
Combine (Calculating Submatrices of C): Finally, the submatrices of the result are calculated using additions and subtractions of these 7 products:
The resulting matrix is then formed by combining .
The base case for the recursion is when the matrix size becomes 1 (or some small threshold where standard multiplication is faster). If , is just a simple scalar multiplication.
Pseudocode for Strassen's Algorithm
Here's a conceptual pseudocode reflecting these steps:
// A, B: Input square matrices of size n x n
function strassen(A, B):
n = number of rows in A // Assuming square matrices, A.rows == A.cols
// Base Case: If matrices are 1x1 (or small enough for standard multiplication)
if n == 1:
C = new matrix of size 1x1
C[0][0] = A[0][0] * B[0][0]
return C
// If n is not 1, proceed with partitioning (assuming n is a power of 2 for simplicity)
// Split A and B into four n/2 x n/2 submatrices
A11, A12, A21, A22 = split_matrix(A) // Helper function to get submatrices
B11, B12, B21, B22 = split_matrix(B)
// Calculate the 10 intermediate S matrices (using matrix addition/subtraction)
S1 = B12 - B22
S2 = A11 + A12
S3 = A21 + A22
S4 = B21 - B11
S5 = A11 + A22
S6 = B11 + B22
S7 = A12 - A22
S8 = B21 + B22
S9 = A11 - A21
S10 = B11 + B12
// Recursively compute the 7 products P1 through P7
P1 = strassen(A11, S1)
P2 = strassen(S2, B22)
P3 = strassen(S3, B11)
P4 = strassen(A22, S4)
P5 = strassen(S5, S6)
P6 = strassen(S7, S8)
P7 = strassen(S9, S10)
// Calculate the submatrices of the result C
C11 = P5 + P4 - P2 + P6
C12 = P1 + P2
C21 = P3 + P4
C22 = P5 + P1 - P3 - P7
// Combine C11, C12, C21, C22 into the final result matrix C
C = combine_submatrices(C11, C12, C21, C22)
return C
// Helper functions like split_matrix, matrix_add, matrix_subtract, combine_submatrices
// would need to be defined.Explanation of the Logic:
The strassen function is the core recursive function.
- It first checks for the base case: if the input matrices are very small (e.g., 1x1), it performs standard scalar multiplication and returns.
- Divide: If the matrices are larger, it splits matrix
AintoA11, A12, A21, A22and matrixBintoB11, B12, B21, B22. Each of these is an matrix. - Compute S matrices: It calculates the 10 intermediate matrices through using matrix additions and subtractions on the submatrices from step 2. These are operations.
- Conquer (Recursive Calls): It then makes seven recursive calls to the
strassenfunction to compute the products through . This is the heart of the algorithm's efficiency gain over the naive 8-multiplication divide and conquer. - Combine: Finally, it computes the four submatrices of the result matrix using a specific set of additions and subtractions involving the products. These are also operations. These submatrices are then combined to form the final result matrix .
Proof of Correctness (Intuition)
The correctness of Strassen's algorithm hinges on the algebraic validity of the specific formulas used to compute and from the seven products . These formulas were carefully derived by Strassen to match the results of the standard matrix multiplication but using one fewer recursive matrix multiplication.
- Base Case: When (or the chosen small threshold), the algorithm correctly returns the product by direct multiplication.
- Inductive Step: Assuming Strassen's algorithm correctly multiplies matrices of size , the formulas for combining through ensure that are correctly computed as if derived from the standard 8-multiplication approach. This can be verified by expanding out Strassen's formulas for and showing they equate to the standard definitions (e.g., , which is the standard formula).
Complexity Analysis
-
Time Complexity: The recurrence relation for Strassen's algorithm is: where:
- : Seven recursive calls on matrices of size .
- : The cost of matrix additions and subtractions at each step (18 additions/subtractions of matrices, each taking time).
Using the Master Theorem, this recurrence relation solves to O(), which is approximately O(). This is asymptotically faster than the standard O(n³) algorithm.
-
Space Complexity:
The recursive calls create a call stack. The depth of this recursion is O(log n). At each level of recursion, new temporary matrices are created for the and subproblems.
Applications of Strassen's Algorithm
While Strassen's algorithm offers better asymptotic complexity, it has some practical considerations:
- The constant factor hidden in the O-notation is larger than for the naive algorithm. This means for small , the naive algorithm might actually be faster. Implementations of Strassen's usually switch to standard multiplication for matrices smaller than a certain threshold (e.g., or ).
- It involves more additions and subtractions, which can be a factor.
- It can be less numerically stable than the standard algorithm for certain types of matrices.
- Implementing it with good memory management to actually achieve better performance can be complex.
Despite these, Strassen's algorithm (and its more advanced successors like Coppersmith–Winograd, though those are even more complex) is significant because:
- It proved that O(n³) was not the theoretical lower bound for matrix multiplication.
- It is useful in practice for multiplying very large matrices where the behavior eventually outpaces the behavior despite larger constant factors.
- It has applications in areas requiring high-performance linear algebra computations on large matrices.
Key Takeaways
- Strassen's Algorithm is a divide and conquer algorithm for matrix multiplication that is asymptotically faster than the standard O(n³) method.
- It reduces the multiplication of two matrices to 7 recursive multiplications of submatrices, plus a fixed number of matrix additions/subtractions.
- Its time complexity is approximately O().
- While theoretically faster, practical benefits are typically seen for large matrices due to higher constant factors and implementation complexity.
Strassen's algorithm is a landmark in algorithmic design, demonstrating how clever restructuring of a problem can lead to surprising breakthroughs in efficiency!