User:Gkhan/Matrix Multiplication DP version

Matrix Chain Multiplication edit

Defining the problem edit

Say that you need to multiply a series of n matrices together and form a product P.

 

What is the fastest way we can form this product? Even though matrix-multiplication is not commutative it is associative. I.e.

 
 

This means we can decide in what way to parenthesize a matrix product but we cannot change the order which we multiply them. Since you can only multiply two matrices at a time the product   can be paranthesized in these ways:

 
 
 
 
 

Two matrixes   and   can be multiplied if the number of coulums in   equals the number of rows in  . The number of rows in their product will equal the number rows in   and the number of columns will equal the number of columns in  . I.e if the dimensions of   is   and   has dimensions   their product will have dimensions  

Two multiply two matrices with eachother we will use a function called MatrixMultiply which takes two matrices and returns their product. We will leave implementation of this function alone for the moment as it is not the focus of this chapter (how to multiply two matrices in the fastest way has been under intensive study for several years and really merits it's own chapter). The time this function takes two multiply two matrices of size   and   is essentially proportional to the number of scalar multiplications which is proportional to  . This means that the paranthezation matters. Say that we have three matrices  ,   and  .   has dimensions  ,   has dimensions   and   has dimensions  . Let's parantezise them in the two possible ways and see which way requires the least amount of multiplications. The two ways are

 
 

To form the product in the first way requires 75000 scalar multiplications (5*100*100=50000 to form product   and another 5*100*50=25000 for the last multiplications.) This might seem like alot, but in comparison to the 525000 scalar multiplications required by the second parenthesization (50*100*100=500000 plus 5*50*100=25000) it is miniscule! One understands why such an algorithm is important, imagine what would happen if we needed to multiply 50 matrices!

Forming a recursive solution edit

Note that in this discussion we will concentrate on finding a how many scalar multiplications are needed instead of the actual order. This is because once we have found a working algorithm to find the amount it is trivial to create an algorithm for the actual paranthezation. It will, however, be discussed in the end.

So how would an algorithm for the optimum paranthezation look? By the chapter title you might expect that a dynamic programming method is in order (not to give the answer away or anything). So how would a dynamic programming method work? Since dynamic programming algorithms are based on optimal substructure, what would the optimal substructure in this problem be?

Suppose that the optimal way parenthesize

 

splits the product at k, i.e

 

Then the optimal solution contains the optimal solutions to the two subproblems

 
 

I.e, just in accordance with the fundamental principle of dynamic programming, the solution to the problem depends on the solution of smaller sub-problems.

Let's say that it takes   number of scalar multiplications to multiply matrices   and   and   is number of scalar multiplications to be performed in an optimal paranthesation of the matrices  . The definition or   is the first step towards a solution.

When  , the formulation is trivial, it is just  . But what is it when the distance is larger? Using the observation above we can derive a formulation. Suppose an optimal solution to the problem divides the matrices at matrices k and k+1 (i.e  ) then the number of scalar multiplications are.

 

That is, the amount of time to form the first product, the amount of time it takes to form the second product and the amount of time it takes to multiply them together. But what is this optimal value k? The answer is, ofcourse, the value that makes the above formula assume the minimum value. We can thus form the complete definition for the function:

 

A straight-forward recursive solution to this would look something like this (the language is Wikicode):

function f(m, n) {
    if m-n = 1
        return c(m)

    minCost =  

    for k from m + 1 to n - 1 {
        v = f(m, k) + f(k + 1, n) + c(k)
        if v < minCost
            minCost = v
    }
}

This rather simple solution is, unfortunatly, not a very good one. It spends mountains of time recomputing data and its running time is exponential.

[TODO: write an analysis of the straight-forward-recursion method]

Using Memoization edit

It is straightforward to convert the recursive solution into one that uses memoization. You just have to modify the code so that results are stored so no values need to be recomputed.

int[,] x;

function f(m, n) {
    if x[m][n] != null
        return x[m][n]
    
    if m == n
        x[m][n] = 0
        return 0

    if m-n = 1 {
        x[m][n] = c(m)
        return c(m)
    }

    minCost =  

    for k from m to n - 1 {
        v = f(m, k) + f(k + 1, n) + c(k)
        if v < minCost
            minCost = v
    }

    x[m][n] = minCost;

    return minCost
}

As you can see, there is very little modification needed to the original code. This is a huge optimization, down to a polynomial-time algorithm.

[What is the exact runtime? My qualified guess would be O(n^2*log n) but i haven't analyzed it properly.]

Using Dynamic Programming edit

So how do we make it into a full-fledged bottom-up dynamic progragramming algorithm? To illustrate that point, lets use an example. Suppose we have 6 matrices with the following dimensions:

 
 
 
 
 
 

This will make the cost-function c(n) assume the following values:

 

The matrix which will hold the optimum values (called x in the memozation example) can be seen like this:

File:Matrix-chain-1.png

This is only half of the entire array, but since the values m>n are never used it would be redundant to display them. The reason that it is tilted to the side is that it conveinently shows the how the process "builds" the result up from the bottom. So, what can we do to this matrix. If we look at the definition of f(m, n) it says that for all the values which m=n the value in the array is zero. Let's fill in those values:

File:Matrix-chain-2.png

Allright! Let's boldly continue. The row just above the one we filled in contains all the values with adjacent indexes (i.e 1,2 2,3 3,4 4,5 and 5,6). Those values are just the values of the cost-function, so let's fill in those too.

File:Matrix-chain-3.png

Now we continue to next row and just use the definition to fill in the blanks. What, for instance should be in 1,3? Just use the definition: