Skip to main content

Unit - 2

TITLE

Divide & Conquer Algorithms​

1. Introduction​

1.1 Overview​

  • When faced with a large problem without an immediate overall solution, a common approach is to take a part of it and solve it.
  • If the partial solution works, it can be applied to the remaining parts of the larger problem.
  • Example: To plant a 10,00010,000 sq. ft farm, if one person can plant 250250 sq. ft/hr, you need 1010 people to complete the entire farm within 44 hours.

2. Recurrence Equations​

2.1 Definition​

  • Many algorithms, particularly Divide and Conquer, are recursive in nature.
  • When analyzing their time complexity, we derive a recurrence relation.
  • A recurrence relation expresses the running time as a function of the input size nn in terms of the running time on inputs of smaller sizes.
  • It is a recursive description of a function.

3. Methods to Solve Recurrences​

3.1 Common Methods​

  • Substitution Method
  • Homogeneous (characteristic equation)
  • Inhomogeneous
  • Master Method
  • Recurrence Tree Method
  • Intelligent guesswork
  • Change of variable
  • Range transformations

4. Substitution Method​

4.1 Concept​

  • Make a guess for the solution and then use mathematical induction to prove whether the guess is correct or incorrect.
  • Example: T(n)=T(n−1)+nT(n) = T(n-1) + n
    • Replacing nn with n−1n-1 and n−2n-2:
      • T(n−1)=T(n−2)+n−1T(n-1) = T(n-2) + n - 1
      • T(n−2)=T(n−3)+n−2T(n-2) = T(n-3) + n - 2
    • Substituting back:
      • T(n)=T(n−3)+n−2+n−1+nT(n) = T(n-3) + n - 2 + n - 1 + n
    • Generalizing for kk steps:
      • T(n)=T(n−k)+(n−k+1)+(n−k+2)+⋯+nT(n) = T(n-k) + (n-k+1) + (n-k+2) + \dots + n
    • If we take k=nk = n:
      • T(n)=T(0)+1+2+⋯+nT(n) = T(0) + 1 + 2 + \dots + n
      • T(n)=n(n+1)2=O(n2)T(n) = \frac{n(n+1)}{2} = O(n^2)

5. Master Method​

5.1 Overview​

  • The Master Method is a "cookbook" method for solving recurrences of the form: T(n)=aT(n/b)+f(n)T(n) = aT(n/b) + f(n)
  • aa: Number of sub-problems.
  • n/bn/b: Size of each sub-problem.
  • f(n)f(n): Time required to divide the problem and recombine the results.

5.2 Three Cases​

  1. Case 1: If f(n)≤nlog⁡baf(n) \le n^{\log_b a} (specifically f(n)f(n) is in O(nlog⁡ba−ϵ)O(n^{\log_b a - \epsilon}))
    • Then T(n)=Θ(nlog⁡ba)T(n) = \Theta(n^{\log_b a})
  2. Case 2: If f(n)=nlog⁡baf(n) = n^{\log_b a} (specifically f(n)f(n) is in Θ(nlog⁡ba)\Theta(n^{\log_b a}))
    • Then T(n)=Θ(nlog⁡balog⁡n)T(n) = \Theta(n^{\log_b a} \log n)
  3. Case 3: If f(n)≥nlog⁡baf(n) \ge n^{\log_b a} (specifically f(n)f(n) is in Ω(nlog⁡ba+ϵ)\Omega(n^{\log_b a + \epsilon}))
    • Then T(n)=Θ(f(n))T(n) = \Theta(f(n))

5.3 Examples​

  • Merge Sort: T(n)=2T(n/2)+Θ(n)T(n) = 2T(n/2) + \Theta(n)
    • a=2,b=2  ⟹  nlog⁡22=n1=na=2, b=2 \implies n^{\log_2 2} = n^1 = n. Here f(n)=Θ(n)f(n) = \Theta(n).
    • Case 2 applies: T(n)=Θ(nlog⁡n)T(n) = \Theta(n \log n)
  • Binary Search: T(n)=T(n/2)+Θ(1)T(n) = T(n/2) + \Theta(1)
    • a=1,b=2  ⟹  nlog⁡21=n0=1a=1, b=2 \implies n^{\log_2 1} = n^0 = 1. Here f(n)=Θ(1)f(n) = \Theta(1).
    • Case 2 applies: T(n)=Θ(log⁡n)T(n) = \Theta(\log n)

6. Recurrence Tree Method​

6.1 Concept​

  • Each node in the tree represents the cost of a single sub-problem.
  • We sum the costs across each level of the tree to obtain per-level costs.
  • Finally, sum all per-level costs to determine the total cost of the recursion.
  • Example for T(n)=2T(n/2)+nT(n) = 2T(n/2) + n:
    • Level 0 cost: nn
    • Level 1 cost: n/2+n/2=nn/2 + n/2 = n
    • Total levels: log⁡2n\log_2 n
    • Total cost: ∑i=0log⁡2n−1n+2log⁡2nT(1)=nlog⁡n+n=O(nlog⁡n)\sum_{i=0}^{\log_2 n - 1} n + 2^{\log_2 n} T(1) = n \log n + n = O(n \log n)

7. Divide & Conquer (D&C) Technique​

7.1 Three Steps​

  1. Divide: Break the problem into several smaller sub-problems similar to the original problem.
  2. Conquer: Solve the sub-problems recursively. If they are small enough, solve them in a straightforward (base case) manner.
  3. Combine: Merge the sub-problem solutions to create the solution for the original problem.

7.2 Running Time Analysis​

  • The total time T(n)T(n) is generally T(n)=aT(n/b)+f(n)T(n) = a T(n/b) + f(n).

8. Multiplying Large Integers​

8.1 Problem Statement​

  • Multiplying two nn-digit large integers using divide and conquer.
  • Example: 981×1234981 \times 1234
  • Splitting into halves:
    • w=09,x=81,y=12,z=34w=09, x=81, y=12, z=34
    • 981=102w+x981 = 10^2 w + x
    • 1234=102y+z1234 = 10^2 y + z

8.2 Standard vs. Optimized​

  • Standard approach requires 4 multiplications: w⋅yw \cdot y, w⋅zw \cdot z, x⋅yx \cdot y, x⋅zx \cdot z.
  • Karatsuba optimization reduces it to 3 multiplications:
    • p=w⋅yp = w \cdot y
    • q=x⋅zq = x \cdot z
    • r=(w+x)(y+z)r = (w+x)(y+z)
    • The middle term w⋅z+x⋅y=r−p−qw \cdot z + x \cdot y = r - p - q.
  • Time Complexity: Reduces from O(n2)O(n^2) to O(nlog⁡23)≈O(n1.585)O(n^{\log_2 3}) \approx O(n^{1.585}).

9.1 Concept​

  • Finds an element xx in a sorted array T[1…n]T[1 \dots n].
  • Compares xx with the midpoint. If x<midx < \text{mid}, search the left half. If x>midx > \text{mid}, search the right half.

9.2 Step-by-Step Explanation​

  1. Initialize two pointers, left at the start (0) and right at the end (n−1n-1) of the array.
  2. Loop while left is less than or equal to right.
  3. Calculate Midpoint: Find the middle index mid = left + (right - left) / 2.
  4. Compare:
    • If the element at mid equals xx, return mid.
    • If the element at mid is less than xx, xx must be in the right half, so set left = mid + 1.
    • If the element at mid is greater than xx, xx must be in the left half, so set right = mid - 1.
  5. Not Found: If the loop ends without returning, xx is not in the array. Return -1.

9.3 Algorithm Complexity​

  • Recurrence: T(n)=T(n/2)+Θ(1)T(n) = T(n/2) + \Theta(1)
  • Time Complexity:
    • Best Case: O(1)O(1)
    • Average & Worst Case: O(log⁡n)O(\log n)
  • Space Complexity: O(1)O(1) for iterative, O(log⁡n)O(\log n) for recursive due to call stack.

9.4 Algorithm​

💡 The Big Idea in Plain English​

Think of looking up a word in a dictionary: you don't read page-by-page from the start. You open right in the middle!

  • If your word comes after the middle page, you throw away the left half and check the middle of the right half.
  • If your word comes before, you throw away the right half and check the left half.
  • You repeat this until you land on your word! Because the search space is cut in half every time, even searching 1,000,000 items takes at most 20 checks!

👣 4 Easy Steps to Remember​

  1. Set Boundaries: Put two pointers at the ends — low at the start (00), high at the end (n−1n-1).
  2. Find the Middle: Calculate midpoint index: mid=low+(high−low)/2\text{mid} = \text{low} + (\text{high} - \text{low}) / 2.
  3. Check the Middle Element:
    • Match! If A[mid]==xA[\text{mid}] == x, return mid\text{mid} immediately.
    • Target is Bigger (A[mid]<xA[\text{mid}] < x): The answer must be in the right half →\to move low=mid+1\text{low} = \text{mid} + 1.
    • Target is Smaller (A[mid]>xA[\text{mid}] > x): The answer must be in the left half →\to move high=mid−1\text{high} = \text{mid} - 1.
  4. Stop Condition: Loop while low≤high\text{low} \le \text{high}. If low>high\text{low} > \text{high}, the item does not exist →\to return −1-1.

🧠 Memory Trick: Array must be sorted →\to check middle →\to target bigger? go right (low = mid + 1) →\to target smaller? go left (high = mid - 1).

10. Merge Sort​

10.1 Concept​

  • Divides the unsorted list into nn sub-lists of 11 element each.
  • Repeatedly merges adjacent sub-lists to produce new sorted sub-lists until only 11 sorted list remains.

10.2 Step-by-Step Explanation​

  1. Divide: Check if the array has more than 1 element. If so, find the middle point to divide the array into two halves, L (left) and R (right).
  2. Conquer: Recursively call mergeSort on the first half L and then on the second half R.
  3. Combine (Merge):
    • Initialize three pointers: ii (for L), jj (for R), and kk (for the original array).
    • Compare elements L[i] and R[j]. Place the smaller element into the original array at index kk and increment the respective pointer.
    • Once either L or R is exhausted, copy the remaining elements of the other half into the original array.

10.3 Algorithm Complexity​

  • Separating takes linear time; merging takes linear time.
  • Recurrence: T(n)=2T(n/2)+Θ(n)T(n) = 2T(n/2) + \Theta(n)
  • Time Complexity: Θ(nlog⁡n)\Theta(n \log n) for all cases (Best, Average, Worst).
  • Space Complexity: O(n)O(n) because it requires an auxiliary array for merging.

10.4 Algorithm​

💡 The Big Idea in Plain English​

Think of sorting a messy pile of numbered cards: Instead of sorting everything at once, you chop the pile in half again and again until you hold single cards (a single card is already sorted!). Then, you zipper merge pairs of sorted piles: compare their top cards, place the smaller one down first, and repeat. You keep merging upward until you have one perfectly sorted pile!

👣 3 Easy Steps to Remember​

  1. Divide (Split in Half): If the subarray has more than 1 element (low<high\text{low} < \text{high}), calculate mid=low+(high−low)/2\text{mid} = \text{low} + (\text{high} - \text{low}) / 2.
  2. Conquer (Sort Each Half):
    • Recursively sort the left half: MergeSort(A, low, mid).
    • Recursively sort the right half: MergeSort(A, mid + 1, high).
  3. Combine (Zipper Merge):
    • Look at the front of both sorted halves.
    • Take whichever number is smaller and place it into the main array.
    • Once one half runs out, copy over any leftover numbers from the other half.

🧠 Memory Trick: Split down to 1 →\to Zipper merge back up! Compare the tops of both piles, take the smaller one, and advance. Always runs in Θ(nlog⁡n)\Theta(n \log n) time.

11. Quick Sort​

11.1 Concept​

  • Chooses a pivot element.
  • Partitions the array so elements less than the pivot move to the left, and elements greater move to the right.
  • Recursively applies the same logic to the left and right partitions.

11.2 Step-by-Step Explanation​

  1. Choose Pivot: Select an element from the array to act as the pivot (often the last element).
  2. Partition: Rearrange the array so that all elements smaller than the pivot are on its left, and all elements greater are on its right. The pivot is now in its final sorted position.
    • Maintain a pointer ii to track the boundary of elements smaller than the pivot.
    • Iterate through the array with jj; if an element is smaller than the pivot, increment ii and swap elements at ii and jj.
    • Finally, swap the pivot with the element at i+1i+1.
  3. Recursion: Recursively apply the above steps to the sub-array of elements smaller than the pivot, and the sub-array of elements greater than the pivot.

11.3 Algorithm Complexity​

  • Worst Case: The partition produces one sub-array of n−1n-1 elements and one of 00 elements (e.g., array is already sorted).
    • Recurrence: T(n)=T(n−1)+Θ(n)T(n) = T(n-1) + \Theta(n)
    • Time Complexity: Θ(n2)\Theta(n^2)
  • Best / Average Case: Partition produces two roughly equal halves.
    • Recurrence: T(n)=2T(n/2)+Θ(n)T(n) = 2T(n/2) + \Theta(n)
    • Time Complexity: Θ(nlog⁡n)\Theta(n \log n)
  • Space Complexity: O(log⁡n)O(\log n) due to the recursive call stack (if tail-call optimized), worst-case O(n)O(n) if heavily unbalanced.

11.4 Algorithm​

💡 The Big Idea in Plain English​

Think of organizing students by height: You pick one student to be the pivot (say, the last student in line). Then you instruct: "Anyone shorter than the pivot, step to the left! Anyone taller, step to the right!" Now, the pivot student is standing in their exact, permanent sorted spot! You then simply repeat the same process on the left group and the right group until everyone is sorted.

👣 3 Easy Steps to Remember​

  1. Pick a Pivot: Choose an element (standard choice: the last element pivot = A[high]).
  2. Partition (The Traffic Cop):
    • Walk through the array from left to right.
    • Whenever an element is smaller than the pivot, swap it into the left section.
    • Once finished, swap the pivot right between the smaller and larger numbers.
    • The pivot index pipi is now locked in its final position!
  3. Repeat on Both Sides:
    • Recursively sort the left side: QuickSort(A, low, pi - 1).
    • Recursively sort the right side: QuickSort(A, pi + 1, high).

🧠 Memory Trick: Pick pivot →\to Smaller to left, larger to right →\to Pivot locked →\to Repeat for both sides! Average time: Θ(nlog⁡n)\Theta(n \log n), worst-case: O(n2)O(n^2) (if already sorted).

12. Matrix Multiplication (Strassen's Algorithm)​

12.1 Concept​

  • Multiplying two 2×22 \times 2 matrices traditionally requires 8 scalar multiplications.
  • For n×nn \times n matrices, classic complexity is Θ(n3)\Theta(n^3).
  • Strassen's Algorithm uses algebraic tricks to compute the product using only 7 scalar multiplications for a 2×22 \times 2 block.

12.2 Step-by-Step Explanation​

  1. Divide: Split the input matrices AA and BB into 4 sub-matrices of size (n/2×n/2)(n/2 \times n/2).
  2. Compute 7 Products: Using specific algebraic combinations of these sub-matrices (involving additions and subtractions), compute 7 intermediate matrix products (P1P_1 to P7P_7) instead of the usual 8.
  3. Combine: Use additions and subtractions of the P1…P7P_1 \dots P_7 matrices to form the 4 sub-matrices of the final result matrix CC.
  4. Recursion: If the sub-matrices are larger than 1×11 \times 1, recursively apply Strassen's algorithm to compute the 7 products.

12.3 Algorithm Complexity​

  • Recurrence: T(n)=7T(n/2)+Θ(n2)T(n) = 7T(n/2) + \Theta(n^2)
  • Using Master Method (Case 3): T(n)=Θ(nlog⁡27)≈Θ(n2.81)T(n) = \Theta(n^{\log_2 7}) \approx \Theta(n^{2.81})

13. Exponentiation​

13.1 Sequential Approach​

  • Compute x=anx = a^n by multiplying aa by itself n−1n-1 times.
  • Loop executes n−1n-1 times: r = a * r
  • Total time (considering large numbers): T(m,n)∈Θ(m2n2)T(m,n) \in \Theta(m^2 n^2) where mm is the size of the operand.

13.2 Divide & Conquer Approach​

  • an=(an/2)2a^n = (a^{n/2})^2 if nn is even.
  • an=a×an−1a^n = a \times a^{n-1} if nn is odd.
  • Recurrence: N(n)=N(n/2)+1N(n) = N(n/2) + 1 (if even)
  • Total time (considering large numbers): T(m,n)∈Θ(mlog⁡23nlog⁡23)T(m,n) \in \Theta(m^{\log_2 3} n^{\log_2 3})

13.3 Step-by-Step Explanation (D&C)​

  1. Base Case: If n=0n = 0, return 1. If n=1n = 1, return aa.
  2. Divide: Calculate the exponent for half the power, recursively calling the function for an/2a^{n/2}.
  3. Combine:
    • If nn is even: Square the result of an/2a^{n/2} (i.e., return an/2×an/2a^{n/2} \times a^{n/2}).
    • If nn is odd: Square the result of an/2a^{n/2} and multiply by an extra aa (i.e., return a×an/2×an/2a \times a^{n/2} \times a^{n/2}).