# Divide and Conquer Case Study

Nov. 19, 2017
1 of 35

### Divide and Conquer Case Study

• 1.   ADA - Case Study  Divide & Conquer    Submitted to:  Mr. Neeraj Garg
• 2.       Submitted By:    Sahil Malik Kushagra Chadha  01214807216, 5C-12 00914807216, 5C-12    Yash Goel Anshuman Raina  01414807216, 5C-12 01396402715, 5C-12    Abhishek Aman Singhal  00596402715, 5C-12 01096402715, 5C-12          1
• 3.     Contents  1. Introduction 3  2. Understanding Approach 3  Divide/Break 3  Conquer/Solve 4  Merge/Combine 4  3. Advantages of Divide and Conquer Approach 5  Solving diﬃcult problems 5  Algorithm eﬃciency 5  Parallelism 5  Memory access 5  Round Oﬀ control 6  4. Disadvantages of Divide and Conquer Approach 6  Recursion 6  Explicit stack 6  Stack size 7  Choosing the base cases 7  Sharing repeated subproblems 7  5. D&C Algorithms 8  5.1 Binary Search 8  5.2 La Russe Method for Multiplication 11  5.3 Sorting Algorithms 14  5.3.1 Merge Sort 14  5.3.2 Quicksort 17  5.4 Finding Maximum and Minimum of a sequence of Numbers 21  5.5 Closest Pair of points problem 25  5.5.1 Naive method/ Brute force method 25  5.5.2 Divide and Conquer method 27  5.5.3 Comparison of methods 28  5.6 Strassen’s Multiplication Algorithm 29  5.6.1 Naive method/ Brute force method 29  5.6.2 Divide And Conquer Method 30  5.6.3 Strassen's Multiplication Algorithm 31  Conclusion 34  2
• 4.     1. Introduction  Most of the algorithms are recursive by nature, for solution of any given problem, they try  to break it into smaller parts and solve them individually and at last building solution from  these subsolutions.  In computer science, divide and conquer is an algorithm design paradigm based on  multi-branched recursion. A divide and conquer algorithm works by recursively breaking  down a problem into two or more subproblems of the same or related type, until these  become simple enough to be solved directly. The solutions to the subproblems are then  combined to give a solution to the original problem.  This divide and conquer technique is the basis of eﬃcient algorithms for all kinds of  problems, such as sorting (e.g., quicksort, merge sort), multiplying large numbers (e.g. the  Karatsuba algorithm), ﬁnding the closest pair of points, syntactic analysis (e.g., top-down  parsers), and computing the discrete Fourier transform (FFTs).  Understanding and designing divide and conquer algorithms is a complex skill that  requires a good understanding of the nature of the underlying problem to be solved. As  when proving a theorem by induction, it is often necessary to replace the original  problem with a more general or complicated problem in order to initialize the recursion,  and there is no systematic method for ﬁnding the proper generalization[clariﬁcation  needed]. These divide and conquer complications are seen when optimizing the  calculation of a Fibonacci number with eﬃcient double recursion.  The correctness of a divide and conquer algorithm is usually proved by mathematical  induction, and its computational cost is often determined by solving recurrence relations.  2. Understanding Approach  Broadly, we can understand divide-and-conquer approach in a three-step process.  Divide/Break  This step involves breaking the problem into smaller sub-problems. Sub-problems should                      represent a part of the original problem. This step generally takes a recursive approach to divide                                3
• 5.     the problem until no sub-problem is further divisible. At this stage, subproblems become atomic                            in nature but still represent some part of the actual problem.  Conquer/Solve  This step receives a lot of smaller sub-problems to be solved. Generally, at this level, the                                problems are considered 'solved' on their own.  Merge/Combine  When the smaller subproblems are solved, this stage recursively combines them until they                          formulate a solution of the original problem. This algorithmic approach works recursively and                          conquer & merge steps works so close that they appear as one.  Examples  The following computer algorithms are based on divide-and-conquer programming approach −  ● Merge Sort  ● Quick Sort  ● Binary Search  ● Strassen's Matrix Multiplication  ● Closest pair (points)  There are various ways available to solve any computer problem, but the mentioned are a good                                example of divide and conquer approach.      4
• 6.     3. Advantages of Divide and Conquer Approach  - Solving diﬃcult problems  Divide and conquer is a powerful tool for solving conceptually diﬃcult problems: all it  requires is a way of breaking the problem into subproblems, of solving the trivial cases  and of combining sub-problems to the original problem. Similarly, divide and conquer  only requires reducing the problem to a single smaller problem, such as the classic  Tower of Hanoi puzzle, which reduces moving a tower of height n to moving a tower of  height n − 1.  - Algorithm eﬃciency  The divide-and-conquer paradigm often helps in the discovery of eﬃcient algorithms. It  was the key, for example, to Karatsuba's fast multiplication method, the quicksort and  mergesort algorithms, the Strassen algorithm for matrix multiplication, and fast Fourier  transforms.  In all these examples, the D&C approach led to an improvement in the asymptotic cost  of the solution.  - Parallelism  Divide and conquer algorithms are naturally adapted for execution in multi-processor  machines, especially shared-memory systems where the communication of data  between processors does not need to be planned in advance, because distinct  sub-problems can be executed on diﬀerent processors.  - Memory access  Divide-and-conquer algorithms naturally tend to make eﬃcient use of memory  caches. The reason is that once a sub-problem is small enough, it and all its  sub-problems can, in principle, be solved within the cache, without accessing the  slower main memory. An algorithm designed to exploit the cache in this way is called  cache-oblivious, because it does not contain the cache size as an explicit  parameter.Moreover, D&C algorithms can be designed for important algorithms (e.g.,  5
• 7.     sorting, FFTs, and matrix multiplication) to be optimal cache-oblivious algorithms–they  use the cache in a probably optimal way, in an asymptotic sense, regardless of the  cache size. In contrast, the traditional approach to exploiting the cache is blocking, as  in loop nest optimization, where the problem is explicitly divided into chunks of the  appropriate size—this can also use the cache optimally, but only when the algorithm  is tuned for the speciﬁc cache size(s) of a particular machine.  - Round Oﬀ control  In computations with rounded arithmetic, e.g. with ﬂoating point numbers, a  divide-and-conquer algorithm may yield more accurate results than a superﬁcially  equivalent iterative method. For example, one can add N numbers either by a simple  loop that adds each datum to a single variable, or by a D&C algorithm called pairwise  summation that breaks the data set into two halves, recursively computes the sum of  each half, and then adds the two sums. While the second method performs the same  number of additions as the ﬁrst, and pays the overhead of the recursive calls, it is  usually more accurate.  4. Disadvantages of Divide and Conquer Approach  Like any other approach, D&C too have some diﬃculties. Most of these are related with  implementation.  - Recursion  Divide-and-conquer algorithms are naturally implemented as recursive procedures. In that case, the  partial sub-problems leading to the one currently being solved are automatically stored in the  procedure call stack. A recursive function is a function that calls itself within its deﬁnition.  - Explicit stack  Divide and conquer algorithms can also be implemented by a non-recursive program that stores the  partial sub-problems in some explicit data structure, such as a stack, queue, or priority queue. This  approach allows more freedom in the choice of the sub-problem that is to be solved next, a feature  that is important in some applications — e.g. in breadth-ﬁrst recursion and the branch and bound  6
• 8.     method for function optimization. This approach is also the standard solution in programming  languages that do not provide support for recursive procedures.  - Stack size  In recursive implementations of D&C algorithms, one must make sure that there is suﬃcient memory  allocated for the recursion stack, otherwise the execution may fail because of stack overﬂow.  Fortunately, D&C algorithms that are time-eﬃcient often have relatively small recursion depth. For  example, the quicksort algorithm can be implemented so that it never requires more than log2 n  nested recursive calls to sort n items.  - Choosing the base cases  In any recursive algorithm, there is considerable freedom in the choice of the base cases, the small  subproblems that are solved directly in order to terminate the recursion.  Choosing the smallest or simplest possible base cases is more elegant and usually leads to simpler  programs, because there are fewer cases to consider and they are easier to solve.  On the other hand, eﬃciency often improves if the recursion is stopped at relatively large base cases,  and these are solved non-recursively, resulting in a hybrid algorithm.  The generalized version of this idea is known as recursion "unrolling" or "coarsening" and various  techniques have been proposed for automating the procedure of enlarging the base case.  - Sharing repeated subproblems  For some problems, the branched recursion may end up evaluating the same sub-problem  many times over. In such cases it may be worth identifying and saving the solutions to these  overlapping subproblems, a technique commonly known as memoization. Followed to the  limit, it leads to bottom-up divide-and-conquer algorithms such as dynamic programming and  chart parsing.        7
• 9.     5. D&C Algorithms  5.1 Binary Search  Binary search is a fast search algorithm with run-time complexity of Ο(log n). This search  algorithm works on the principle of divide and conquer. For this algorithm to work properly, the  data collection should be in the sorted form    Algorithm if(l=i) then  {  if(x=a[i]) return i;  else return 0;  }  else  {  //reduce to smaller subproblems  mid=(mid+1)/2;  if(x=a[mid]) then return mid;  else if(x<a[mid]) then return BinSearch(a,i,mid-1,x)  else return BinSearch(a,mid+1,l,x)  }      8
• 10.       Complexity  Time complexity of binary search algorithm is O(log2(N)).  At a glance the complexity table is like this -  Worst case performance : O(log2 n)  Best case performance : O(1)  Average case performance: O(log2 n)  Worst case space complexity: O(1)  But that is not the fact, the fact is why it is log2(N) ?  Here is a mathematical proof which describe why the complexity is log2(N).  The question is, how many times can you divide N by 2 until you have 1? This is essentially  saying, do a binary search (half the elements) until you found it.   In a formula this would be this:  1 = N / 2x  multiply by 2x:  2x = N  now do the log2:  log2(2x) = log2 N  x * log2(2) = log2 N  x * 1 = log2 N  This means you can divide log N times until you have everything divided. Which means you have  to divide log N ("do the binary search step") until you found your element.      9
• 11.     Example  # Python Program for recursive binary search.   # Returns index of x in arr if present, else -1.  def binarySearch (arr, l, r, x):     # Check base case  if r >= l:     mid = l + (r - l)/2     # If element is present at the middle itself  if arr[mid] == x:  return mid      # If element is smaller than mid, then it can only  # be present in left subarray  elif arr[mid] > x:  return binarySearch(arr, l, mid-1, x)  # Else the element can only be present in right subarray  else:  return binarySearch(arr, mid+1, r, x)     else:  # Element is not present in the array  return -1   # Test array  arr = [ 2, 3, 4, 10, 40 ]  x = 10     # Function call  result = binarySearch(arr, 0, len(arr)-1, x)   if result != -1:  print "Element is present at index %d" % result  else:  print "Element is not present in array"    Output    10
• 12.     5.2 La Russe Method for Multiplication  La Russe Method follows a Divide and Conquer Approach for multiplication of two 2 or  more digit numbers. It works on following underlying principle:  Let a and b be two integers of minimum 2 digit length. The value of a*b is same as  (a*2)*(b/2) if b is even, otherwise the value is same as ((a*2)*(b/2) + a). In the while loop,  we keep multiplying ‘a’ with 2 and keep dividing ‘b’ by 2. If ‘b’ becomes odd in loop, we  add ‘a’ to ‘res’. When value of ‘b’ becomes 1, the value of ‘res’ + ‘a’, gives us the result.  Note: that when ‘b’ is a power of 2, the ‘res’ would remain 0 and ‘a’ would have the  multiplication.  So, in simple terms, Given two integers, write a function to multiply them without using  multiplication operator.One interesting method is the Russian peasant algorithm. The  idea is to double the ﬁrst number and halve the second number repeatedly till the  second number doesn’t become 1. In the process, whenever the second number become  odd, we add the ﬁrst number to result (result is initialized as 0)    Algorithm:   1. Let the two given numbers be 'a' and 'b'  2. Initialize result 'res' as 0.  3. Do following while 'b' is greater than 0  a. If 'b' is odd, add 'a' to 'res'  b. Double 'a' and halve 'b'  4. return res   Rules:   ● Write each number at the head of a column.  ● Double the number in the ﬁrst column, and halve the number in the second column  ● If the number in the second column is odd, divide it by two and drop the remainder.  ● If the number in the second column is even, cross out that entire row.  ● Keep doubling, halving, and crossing out until the number in the second column is 1.  ● Add up the remaining numbers in the ﬁrst column. The total is the product of your original  number.    11
• 13.     Multiply 57 by 86 as an example:  Write each number at the head of a column.  57  86  Double the number in the ﬁrst column, and halve the number in the second column.  57  86  114  43  If the number in the second column is even, cross out that entire row.  57  86  114  43  Keep doubling, halving, and crossing out until the number in the second column is 1.  57  86  114  43  228  21  456  10  912  5  1824  2  3648  1  Add up the remaining numbers in the ﬁrst column.  57 86  114  43  228  21  456  10  912  5  1824  2  + 3648 1  4902          12
• 14.     Source code:  import java.io.*;  class laRusseMultAlgo  {  static int russianPeasant(int a, int b)  {  int res = 0;    while (b > 0)  {  if ((b & 1) != 0)  res = res + a;  a = a << 1;  b = b >> 1;  }  return res;  }  public static void main (String[] args)  {  System.out.println(russianPeasant(18, 1));  System.out.println(russianPeasant(20, 12));  }  }  Output:   18  240      13
• 15.       5.3 Sorting Algorithms  There are a few algorithms for sorting which follow Divide & Conquer Approach. We will  study two major ones, Merge Sort and Quicksort.  5.3.1 Merge Sort  Merge Sort is a Divide and Conquer algorithm. It divides input array in two halves, calls  itself for the two halves and then merges the two sorted halves. The merge() function is  used for merging two halves. The merge(arr, l, m, r) is key process that assumes that  arr[l..m] and arr[m+1..r] are sorted and merges the two sorted sub-arrays into one.   Merge Sort Approach  •Divide  –Divide the n-element sequence to be sorted into two subsequences of n/2 elements  each  •Conquer  –Sort the subsequences recursively using merge sort  –When the size of the sequences is 1 there is nothing more to do  •Combine  –Merge the two sorted subsequences  Algorithm  MERGE-SORT(A, p, r)  if p < r Check for base case  then q ← [(p + r)/2] Divide  MERGE-SORT(A, p, q) Conquer  MERGE-SORT(A, q + 1, r) Conquer  MERGE(A, p, q, r) Combine    14
• 16.     MERGE(A, p, q, r)  ➔ Create copies of the subarrays L ← A[p..q] and M ← A[q+1..r].  ➔ Create three pointers i,j and k  ◆ i maintains current index of L, starting at 1  ◆ j maintains current index of M, starting at 1  ◆ k maintains current index of A[p..q], starting at p  ➔ Until we reach the end of either L or M, pick the larger among the elements from L  and M and place them in the correct position at A[p..q]  ➔ When we run out of elements in either L or M, pick up the remaining elements and  put in A[p..q]        MERGE-SORT Running Time  •Divide:  –compute q as the average of p and r: D(n) = θ (1)  •Conquer:  –recursively solve 2 subproblems, each of size n/2 => 2T (n/2)  •Combine:  –MERGE on an n-element subarray takes θ (n) time C(n) = θ (n)    θ (1) if n =1  T(n) =  2T(n/2) + θ (n) if n > 1  Use Master’s Theorem:  Compare n with f(n) = cn  Case 2: T(n) = Θ(n log n)        15
• 18.     5.3.2 Quicksort  QuickSort is a Divide and Conquer algorithm. It picks an element as pivot and partitions  the given array around the picked pivot. There are many diﬀerent versions of quickSort  that pick pivot in diﬀerent ways.  Quicksort Approach  •Divide  –Partition the array A into 2 subarrays A[p..q] and A[q+1..r], such that each element  of A[p..q] is smaller than or equal to each element in A[q+1..r]  –Need to ﬁnd index q to partition the array  •Conquer  –Recursively sort A[p..q] and A[q+1..r] using Quicksort  •Combine  –Trivial: the arrays are sorted in place  –No additional work is required to combine them  –The entire array is now sorted.    Algorithm  QUICKSORT(A, p, r)  if p < r  then q ← PARTITION(A, p, r)  QUICKSORT (A, p, q)  QUICKSORT (A, q+1, r)        17
• 19.     PARTITION (A, p, r)  x ← A[p]  i ← p – 1  j ← r + 1  while TRUE    do repeat j ← j – 1    until A[j] ≤ x  do repeat i ← i + 1    until A[i] ≥ x    if i < j    then exchange A[i] ↔ A[j]  else return j     QUICK-SORT Running Time    For Worst-case partitioning  –One region has one element and the other has n – 1 elements  –Maximally unbalanced      T(n) = T(1) + T(n – 1) + n,   T(1) = Q(1)  18
• 20.     T(n) = T(n – 1) + n  = n + ( =)∑ n k=1 k − 1 (n) Θ(n ) Θ(n )Θ + 2 = 2     Best Case Partitioning  –Partitioning produces two regions of size n/2    T(n) = 2T(n/2) + Θ(n)  Use Master’s Theorem:  Compare n with f(n) = cn  Case 2: T(n) = Θ(nlgn)    19
• 22.     5.4 Finding Maximum and Minimum of a sequence of Numbers  This method is also known as Tournament method.  This method is used, as the name suggests, for ﬁnding the maximum and the minimum  numbers present in any given sequence of numbers. This works on negative as well as  on decimal numbers, which makes it applicable on all types of numeric data types.  Algorithm  Let MaxMin be any function which takes in an Array of numbers and size of array, and it  returns the pair of numbers which are maximum and minimum in array  MaxMin(array, array_size)  if array_size = 1  return element as both max and min  else if arry_size = 2  one comparison to determine max and min  return that pair  else array_size > 2  recur for max and min of left half  recur for max and min of right half  one comparison determines true max of the two candidates  one comparison determines true min of the two candidates  return the pair of max and min      21
• 23.     Complexity  Consider n=8 elements in an array {1,4,5,8,3,2,7,9}  Let’s make a tournament bracket for them, where at each stage the winner is the  minimum element between the two.      As we can see, number of comparisons  being done = n-1 = 7     Similarly, to ﬁnd the maximum element you again will need n-1 comparisons!  So total no of comparisons to ﬁnd min and max=2(n-1)  There is one optimisation to it !!    The last level in the tree is making n/2 comparisons(4 in this case) and these are  being repeated while ﬁnding the minimum and maximum!     So doing the last level comparisons only once, we do n/2 comparisons less  Hence 2(n-1) - n/2 = 2n-2 - n/2 = (3n/2) - 2.    And when compared to normal iterative method of using loops, loop will run n times and  there will be two comparisons, which gives 2n.  So Tournament Method is faster.      22
• 24.     Example  Consider following example of Tournament Method in Java:    class pair{  public int max, min;  }  public class HelloWorld  {  public static void main(String[] args)  {  ﬂoat[] arr = {-34, 43,45,2,46};  pair p = getMinMax(arr, 0, arr.length-1);  System.out.println(p.max+" "+p.min);  }      public static pair getMinMax(ﬂoat[] arr, int start, int end){  pair minmax =new pair(), mml=new pair(), mmr=new pair();    int mid;      if (start == end){  minmax.max = arr[start];  minmax.min = arr[start];    return minmax;  }        if (end == start+ 1){    if (arr[start] > arr[end]) {  minmax.max = arr[start];  minmax.min = arr[end];  }    else{  minmax.max = arr[end];  minmax.min = arr[start];  }    return minmax;  }      mid = (start + end)/2;    mml = getMinMax(arr, start, mid);  23
• 25.     mmr = getMinMax(arr, mid+1, end);        if (mml.min < mmr.min)  minmax.min = mml.min;  else  minmax.min = mmr.min;       if (mml.max > mmr.max)  minmax.max = mml.max;  else  minmax.max = mmr.max;        return minmax;  }  }    Output:          24
• 26.     5.5 Closest Pair of points problem    Given a ﬁnite set of n points in the plane, the goal is to ﬁnd  the closest pair, that is, the distance between the two closest  points. Formally, we want to ﬁnd a distance δ such that there  exists a pair of points (ai , aj) at distance δ of each other, and  such that any other two points are separated by a distance  greater than or equal to δ. The above stated can be achieved  by two approaches-            5.5.1 Naive method/ Brute force method  Given the set of points we iterate over each and every point in the plain calculating the  distances between them and storing them. Then the minimum distance is chosen which  gets us the solution to the problem.  Algorithm  minDist = inﬁnity  for i = 1 to length(P) - 1  for j = i + 1 to length(P)  let p = P[i], q = P[j]  if dist(p, q) < minDist:  minDist = dist(p, q)  closestPair = (p, q)  return closestPair  25
• 27.     Example  Consider the following python code demonstrating the naive method-  import math  def calculateDistance(x1, y1, x2, y2):  dist = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)  return dist  def closest(x,y):  result=[0,0]  min=999999  for i in range(len(x)):  for j in range(len(x)):  if i==j:  pass  else:  dist=calculateDistance(x[i],y[i],x[j],y[j]);  if dist<=min:  min=dist;  result[0]=i  result[1]=j  print("Closest distance is between point "+str(result[0]+1)+" and "+str(result[1]+1)+"  and distance is-"+str(min))  x=[5,-6,8,9,-10,0,20,6,7,8,9]  y=[0,-6,7,2,-1,5,-3,6,3,8,-9]  closest(x,y)      26
• 28.     Complexity  As we can see from the above example using the naive method for 11 points as input we require  to calculate distance of each 11 point with other points giving the total 121 comparisons.   Therefore if we use this method for every n points n^2 comparisons are required.   The time complexity of brute force method is O(n^2),    5.5.2 Divide and Conquer method  The problem can be solved using the recursive divide and conquer approach. In this approach  we divide the one dimensional plane into smaller plane (generally by dividing it from between).  Then we recursively solve these two planes in order to achieve the solution from them,  conquering each plane. We combine all these solutions to ﬁnd the points separated by the least  distance.   Divide and Conquer approach for Closest pair of points problem follows the following steps-  1. Sort points according to their x-coordinates.  2. Split the set of points into two equal-sized subsets by a vertical line x=xmid.  3. Solve the problem recursively in the left and right subsets. This yields the left-side and  right-side minimum distances Lmin and Rmin, respectively. (picture 1)  4. Find the minimal distance LRmin among the set of pairs of points in which one point lies  on the left of the dividing vertical and the other point lies to the right. (picture 2)  5. The ﬁnal answer is the minimum among Lmin, Rmin, and LRmin.  Now the approach for 4th step is that for each point say p lined near to mid point of the plane we  consider only the points that are less than LRmin away from the point p on right side.                27
• 29.     Complexity  Divide and Conquer approach to the closest point problem helps divide the  problem into subproblems conquer each subproblem and then merge solutions to  ﬁnd the ﬁnal solution. The time complexity for divide and conquer can be found as  follows-  Complexity for dividing the set into subset=O(n)  Complexity for sorting the distances=O(nlogn)  Complexity for ﬁnding the distances=O(n)  As the problem is recursively divided into 2 subproblems (left and right)  T(n)=2T(n/2)+complexity of dividing+complexity of sorting+complexity of solving  T(n)=2T(n/2)+O(n)+O(nlogn)+O(n)  Therefore time complexity of divide and conquer is O(nlogn)    5.5.3 Comparison of methods  We were able to get deﬁned time complexity for both the  methods. We use that complexity as a metric to compare  the two methods.   By seeing both the complexities we can deduce that  Divide and Conquer approach is better approach for  closest pair of point problem.            28
• 30.       5.6 Strassen’s Multiplication Algorithm    Problem Statement  Considering two matrices X and Y, we need to calculate the resultant product Matrix Z of  X and Y.    5.6.1 Naive method/ Brute force method  First, we will discuss naïve method and its complexity. Here, we are calculating Z = X × Y.  Using Naïve method, two matrices (X and Y) can be multiplied if the order of these  matrices are p × q and q × r. The resultant product matrix Z will have the order of p × r.  Algorithm : ( Brute Force )    Analysis:  In the above Algo, we see that i runs from 1 to p . Then j runs from 1 to q . Inside the  nested loops, the last loop runs from 1 to r . Thus the complexity for such a looping  Algorithm is O(n3 ).  Naive Method can be quite lengthy, thus we try to reduce its size by using Divide And  Conquer.    29
• 31.     5.6.2 Divide And Conquer Method    Following is simple Divide and Conquer method to multiply two square matrices.  1) Divide matrices A and B in 4 sub-matrices of size N/2 x N/2 as shown in the                                    below diagram.  2) Calculate following values recursively. ae + bg, af + bh, ce + dg and cf + dh.    MULTIPLICATIONS : 8  ADDITIONS : 4  Addition of two matrices using this has the following equation.  T( N ) = 8T( N/2 ) + O( N2 )  Evaluating this gives us the Time Complexity O( N3 ).  Now, our Objective is to reduce the number of multiplications to seven, The idea of  Strassen’s method is to reduce the number of recursive calls to 7.      30
• 32.     5.6.3 Strassen's Multiplication Algorithm  As said earlier, Strassen’s Algorithm is similar to DIvide and Conquer Algorithm.  Strassen’s method is similar to above simple divide and conquer method in the sense  that this method also divide matrices to sub-matrices of size N/2 x N/2 .Only here  emphasis is on reducing the time complexity so as to gain the resultant matrix in the best  possible time complexity.  He deﬁned P1, P2, P3, P4, P5, P6 and P7 as deﬁned on the image below.     Complexity  As I mentioned above the Strassen’s algorithm is slightly faster than the general matrix  multiplication algorithm. The general algorithm’s time complexity is O(n^3), while the  Strassen’s algorithm is O( n^2.80 ) = O( n log27 ) .    31
• 33.       You can see on the chart below how slightly faster is this even for large n    Fig. Comparison of time complexities between Strassen and Naive Algorithm.  Application    Although this algorithm seems to be more close to pure mathematics than to computer  practically everywhere we use NxN arrays we can beneﬁt from matrix multiplication.  In the other hand the algorithm of Strassen is not much faster than the general n^3 matrix  multiplication algorithm. That’s very important because for small n (usually n < 45) the  general algorithm is practically a better choice. However as you can see from the chart  above for n > 100 the diﬀerence can be very big.    32
• 34.     Time Complexity of Strassen’s Method        3.6.4 Application In Real World  Generally Strassen’s Method is not preferred for practical applications for following  reasons :  ● The constants used in Strassen’s method are high and for a typical application  Naive method works better.  ● For Sparse matrices, there are better methods especially designed for them.  The submatrices in recursion take extra space.  ● Because of the limited precision of computer arithmetic on noninteger values,  larger errors accumulate in Strassen’s algorithm than in Naive Method      33
• 35.     Conclusion  The Divide-and-Conquer paradigm can be described in this way:  Given an instance of the problem to be solved, split this into several, smaller,  sub-instances (of the same problem) independently solve each of the sub-instances and  then combine the sub-instance solutions so as to yield a solution for the original instance.  It gives very eﬃcient solutions. It is the choice when the following is true:  1. the problem can be described recursively - in terms of smaller instances of  the same problem.  2. the problem-solving process can be described recursively, i.e. we can  combine the solutions of the smaller instances to obtain the solution of the  original problem  Note that we assume that the problem is split in at least two parts of equal size, not  simply decreased by a constant factor.    34