CMSC  435 Algorithm Analysis & Design

 

Divide and Conquer

 

Example 1.  Multiplication of polynomials of the same degree

 

Let P(x) and Q(x) be two polynomials of degree n - 1:

 

            P(x) = an-1xn-1 + an-2xn-2 + … + a1x + a0     and

 

            Q(x) = bn-1xn-1 + bn-2xn-2 + … + b1x + b0

 

Let d = én/2ù

 

We can write the two polynomials as

 

            P(x) = xdP2(x) + P1(x)

            Q(x) = xdQ2(x) + Q1(x)

 

where

            P2(x) = an-1xn-d-1 + an-2xn-d-2 + … + ad+1x + ad                 and

 

            P1(x) = ad-1xd-1 + … + a1x + a0

 

and similarly for Q1 and Q2

 

a straightforward application of the distributive law of multiplication yields:

 

            P(x)Q(x) = x2dP2(x)Q2(x) + xd(P2(x)Q1(x)) + P1(x)Q2(x)) + P1(x)Q1(x)

 

There are four products:  P2Q2, P2Q1, P1Q2, and P1Q1

 

each of these products is an n/2 polynomial x  n/2 polynomial = n2/4 products of the coefficients.  The total number of multiplications is thus 4(n2/4) + Q(n).

 

But this is the same number of multiplications that was needed to multiply the two polynomials without dividing them into two parts each.  We have divided, but we have not conquered!

 

However, there is a less intuitive, but more insightful way in which we can combine the split polynomials that uses only three multiplications instead of four.

 

            P(x)Q(x) = x2dP2(x)Q2(x) + xd(P1(x) + P2(x))(Q1(x) + Q2(x))

                               - P1(x)Q1(x) - P2(x)Q2(x)) + P1(x)Q1(x)

 

Only three products of polynomials are needed:

            P1(x)Q1(x), P2(x)Q2(x), and (P1(x) + P2(x))(Q1(x) + Q2(x))

 

We can then write the (pseudocode) divide and conquer algorithm as

 

            function polyMult(P(x), Q(x), n) {

                if (n = 1)

                    return a0 * b0;

                else {

                    d = n/2 + n%2;

                    split(P(x), P2(x), P1(x));     //split P into two halves-- P1 and P2

                    split(Q(x), Q2(x), Q1(x));  //split Q into two halves

                    R(x) = polyMult(P2(x), Q2(x), d);

                    S(x) = polyMult(P1(x) + P2(x), Q1(x) + Q2(x), d);

                    T(x) = polyMult(P1(x), Q1(x), d);

                    return x2d * R(x) + xd (S(x) - R(x) - T(x)) + T(x);

                }

            }

 

            Let T(n) = the number of coefficient multiplications and additions.  Then:

 

                        T(n) = 3T(n/2)  + cn2    if n > 1 and  T(n) = 1    if n = 1

 

            from the Master Theorem we obtain: T(n) = Q(nlg(3))

 

To illustrate this algorithm, consider the following problem:

 

            P(x) = x3 + 2x2 + 3x + 4

            Q(x) = 5x3 - 4x2 - 2x + 1

 

Multiplying these two polynomials in the usual way we obtain:

 

            P(x)Q(x) = 5x6 + 6x5 + 5x4 + 5x3 - 20x2 - 5x + 4

 

Now let us apply the algorithm to the same problem.

 

            product = polyMult([1,2,3,4],[5,-4,-2,1],4)

 

This first call to polyMult leads to the following sequence of recursive calls:

 

            R1(x) = polyMult([1,2],[5,-4],2)

            S1(x) = polyMult([1+3, 2+4], [5-2, -4+1],2) = polyMult([4,6], [3, -3],2)

            T1(x) = polyMult([3,4],[-2,1],2)

 

Each of these calls to polyMult produces the following:

   To find R1(x), where the subscript indicates this is the value of R(x) for the original call to polyMult, we have:

 

            R2(x) = polyMult([1],[5],1) = 5 // from the if n == 1 block

            S2(x) = polyMult([1+2],[5-4],1) = 3

            T2(x) = polyMult([2],[-4],1) = -8

 

            R1ß return R2(x)x2 + (S2(x) - R2(x) - T2(x))x + T2(x) = 5x2 + 6x - 8

 

Similarly, to find S1(x) we have  polyMult([4,6],[3,-3],2)

 

            R2(x) = polyMult([4],[3],1) = 12           // from the if n == 1 block

            S2(x) = polyMult([4+6],[3-3],1) = 0

            T2(x) = polyMult([6],[-3],1) = -18

 

            S1ß return R2(x)x2 + (S2(x) - R2(x) - T2(x))x + T2(x) = 12x2  + 6x - 18

 

and, to find T1(x) we have  polyMult([3,4],[-2,1],2)

 

            R2(x) = polyMult([3],[-2],1) = -6          // from the if n == 1 block

            S2(x) = polyMult([3+4],[-2+1],1) = -7

            T2(x) = polyMult([4],[1],1) = 4

 

            T1ß return R2(x)x2 + (S2(x) - R2(x) - T2(x))x + T2(x) = -6x2  - 5x  + 4

 

Now given R1, S1, and T1, we are ready to return the product.

 

            PQ = R1(x)x4 + (S1(x) - R1(x) - T1(x))x2 + T1(x)

 

                  = (5x2 +6x-8)x4 + (12x2+6x-18)x2 -(5x2 +6x-8)x2 -(-6x2-5x+4)x2 - 6x2 - 5x + 4

 

                  = 5x6 +6x5 + (-8 + 12 - 5 +6)x4 + (6 - 6 +5)x3 + (-18 + 8 - 4 -6)x2 - 5x + 4

 

                  = 5x6 + 6x5 + 5x4 + 5x3 - 20x2 - 5x + 4

 

Here is an implementation of the previous pseudocode algorithm.  The polynomials are represented by vectors of the coefficients.  The coefficients of the higher power terms are in the lower indices of the array.  The powers of x are implicit.

 

            public double [ ] polyMult(double [ ] p, double [ ] q, int n) {

                if (n == 1) {

                    double [ ] temp = {p[0] * q[0]};

                    return temp;

                }

                int d = n/2 + n%2;

                //split p and q

                double [ ] p2 = new double[d];

                double [ ] q2 = new double [d];

                for (int i = 0; i < d; i++) {

                    p2[i] = p[i];

                    q2[i] = q[i];

                }

                double [ ] p1 = new double[d-n%2];

                double [ ] q1 = new double[d-n%2];

                for (int i = 0; i < d; i++) {

                    p1[i] = p[i+d];

                    q1[i] = q[i+d];

                }

                double [ ] r = polyMult(p2,q2,d);

                double [ ] s = polyMult(p2+p1, q2+q1,d);

                double [ ] t = polyMult(p1,q1,d);

                double [ ] prod = new double[2 * n];

                //remember, the lowest index holds the coefficient of the highest power term

                //multiplying by xd involves shifting d places to the left which is equivalent

                //to shifting the terms multiplied by a lower power d places to the right

                for (int i = 0; i < d; i++) {

                    prod[i] = r[i];

                    prod[i+d] += s[i] - r[i] - t[i];

                    prod[i+2*d] += t[i];

                }

                return prod;

            }

               

Suppose Q(x) is of degree n-1 and P(x) of degree m-1 where m < n.

 

Assume for convenience that n = km for some positive integer k.

 

            Q(x) = Qk(x)xm(k-1) + Qk-1(x)xm(k-2) + … + Q2(x) + Q1(x)

 

It follows then that

 

            P(x)Q(x) = P(x) Qk(x)xm(k-1) + P(x) Qk-1(x)xm(k-2) + … + P(x)Q1(x)

 

            function polyMultUnequal(P(x), Q(x), n, m) {

                prodPoly(x) = 0;

                for (int i = 1; i <= k; i++)

                    Qi(x) = bi(m-1)xm-1 + bi(m-1)xm-2 + … + b(i-1)m;

                for (int i = 1; i <= k; i++)

                    prodPoly(x) += x(i-1)m polyMult(P(x), Qi(x), m);

                return prodPoly(x);

            }

 

The complexity of this Algorithm is Q(kmlg3) = Q((n/m) mlg3) = Q(n mlg(3/2))

 

Example 2 -- Strassen's Algorithm for matrix Multiplication

 

Let A and B be two NxN matrices.

 

Divide A and B into 4 (N/2 x N/2) submatrices

 

                        |A11      A12|                              |B11      B12|

            A =      |                |                  B =      |                |

                        |A21        A22|                              |B21      B22|

 

The usual matrix multiplication requires 8 products of the submatrices:

 

            A11B11              A12B21              A11B12              A12B22

 

            A21B11              A22B21              A21B12              A22B22

 

Strassen found a clever, non-intuitive way to carry out matrix multiplication using only 7 matrix multiplications.

 

            M1 = (A11 + A22)(B11 + B22)

            M2 = (A21 + A22)B11    

            M3 = A11(B12 - B22)

            M4 = A22(B21 - B11)

            M5 = (A11 + A12)B22    

            M6 = (A21 - A11)(B11 + B12)

            M7 = (A12 - A22)(B21 + B22)

 

and

 

                        |M1 + M4 - M5 + M7                 M3 + M5                      |

            AB =    |                                                                                   |

                        |M2 + M4                                  M1 + M3 - M2 + M6     |

 

Let T(n) = the number of multiplications for the product of two n by n matrices.

Then

                        1                                  if n = 1

            T(n) =

                        7T(n/2) + 18n2             if n > 1

 

From the Master Theorem, T(n) = Q(nlg(7)) = Q(n2.81)

 

Example 3.  Tiling With Triominoes

 

A tromino is an object composed of three 1 x 1 squares.  Trominoes can appear in four different orientations:

These orientations are labeled UL -- upper left; UR -- upper right; LL -- lower left; and   LR -- lower right.

 

The problem we are to solve is to tile with trominoes an N x N board with one deficiency, where N is 2m for m > 1.  A deficiency is where one of the unit squares in the board has been "removed".

To show that such a tiling is feasible, we first need to prove that the number of non-deficient unit squares is divisible by three.  (Since a tromino covers a set of three squares.)

 

Exercise:  Prove by induction that the number of non-deficient squares is divisible by three.

Hint:    The number of non-deficient square = N2 - 1 = 22m -1 = 4m - 1

            This can be written as 4(4m-1) - 4 + 3 = 4(4m-1 - 1) + 3

            This last term is divisible if (4m-1 - 1) is divisible.  Now develop the inductive

            proof.

 

We will employ a divide and conquer strategy to solve this problem.

Divide the board into four quadrants.  The deficiency will be located in one of the quadrants.

Our approach is to divide the board into four quadrants, locate the quadrant with the deficiency, place a tromino with the appropriate orientation at the corner of the quadrant with the deficiency, mark the three squares covered by the tromino in the three remaining quadrants as deficient, and recursively tile the four quadrants.

 

In the following algorithm we will locate the board to be tiled and the deficient square by the coordinates of their lower left corner.

 

            public void  tile(int n, Pt pb, Pt, pd) {

                if (n == 2) {

                    String str;              //identify the orientation of the tromino

                    if (pb.equals(pd) )

                         str = "UR";

                    else if (pb.getY( ) == pd.getY( ))

                         str = "UL";

                    else if (pb.getX( ) == pd.getX( ))

                         str = "LR";

                    else

                         str = "LL";

                    place(str, pb.getX( ) + 1, pb.getY( ) + 1);

                }

                else {

                    //partition into 4 quadrants and locate the quadrant with the deficiency

                    Pt p = new Pt(pb.getX( ) + n/2, pb.getY( ) + n/2);

                    String str;              //determine the orientation of the tromino

                    if (pd.getX( ) <= p.getX( ) && pd.getY( ) >= p.getY( ))  {

                         //deficiency is in upper left quadrant

                        str = "LR";

                        tile(n/2, new Pt(pb.getX( ), p.getY( )), new Pt(pd)); //upper left

                        tile(n/2, new Pt(p), new Pt(p));             //upper right

                        tile(n/2, new Pt(pb), new Pt(p.getX( ) - 1, p.getY( ) -1));  //lower left

                        tile(n/2, new Pt(p.getX( ), pb.getY( )), new Pt(p.getX( ), p.getY( ) - 1);

                    }

                    else if (pd.getX( ) >= p.getX( ) && pd.getY( ) >= p.getY( )) {

                        //deficiency is in upper right quadrant

                        str = "LL";

                        //upper left

                        tile(n/2, new Pt(pb.getX( ), p.getY( )), new Pt(p.getX( )-1, p.getY( ));

                        tile(n/2, new Pt(p), new Pt(pd));           //upper right

                        tile(n/2, new Pt(pb), new Pt(p.getX( ) - 1, p.getY( ) -1));  //lower left

                        tile(n/2, new Pt(p.getX( ), pb.getY( )), new Pt(p.getX( ), p.getY( ) - 1);

                    }

                    else if (pd.getX( ) <= p.getX( ) && pd.getY( ) <= p.getY( ))

                        //deficiency is in lower left quadrant

                        str = "UR";

                        //upper left

                        tile(n/2, new Pt(pb.getX( ), p.getY( )), new Pt(p.getX( )-1, p.getY( ));

                        tile(n/2, new Pt(p), new Pt(p));             //upper right

                        tile(n/2, new Pt(pb), new Pt(pd));  //lower left

                        tile(n/2, new Pt(p.getX( ), pb.getY( )), new Pt(p.getX( ), p.getY( ) - 1);

                    }

                    else

                        //deficiency is in lower right quadrant

                        str = "UL";

                        //upper left

                        tile(n/2, new Pt(pb.getX( ), p.getY( )), new Pt(p.getX( )-1, p.getY( ));

                        tile(n/2, new Pt(p), new Pt(p));             //upper right

                        tile(n/2, new Pt(pb), new Pt(p.getX( ) - 1, p.getY( ) -1));  //lower left

                        tile(n/2, new Pt(p.getX( ), pb.getY( )), new Pt(pd);

                    }

                    place(str, p.getX( ), p.getY( ));

                }

            }